1pub mod apis;
2
3pub mod duration;
4pub use duration::Duration;
5
6#[cfg(test)]
7mod tests {
8 use std::process::Command;
9
10 use anyhow::Error;
11 use hyper_util::client::legacy::Client as HTTPClient;
12 use hyper_util::rt::TokioExecutor;
13 use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, Time};
14 use k8s_openapi::chrono::Utc;
15 use kube::api::{Patch, PatchParams, PostParams};
16 use kube::config::{KubeConfigOptions, Kubeconfig};
17 use kube::core::ObjectMeta;
18 use kube::Client as KubeClient;
19 use kube::{client::ConfigExt, Api, Config, CustomResourceExt};
20 use serde_json::json;
21 use tower::BoxError;
22 use tower::ServiceBuilder;
23 use uuid::Uuid;
24
25 use crate::apis::standard::{
26 constants::{
27 GatewayConditionReason, GatewayConditionType, ListenerConditionReason,
28 ListenerConditionType,
29 },
30 gatewayclasses::{GatewayClass, GatewayClassSpec},
31 gateways::{
32 Gateway, GatewaySpec, GatewayStatus, GatewayStatusAddresses, GatewayStatusListeners,
33 },
34 };
35
36 #[ignore]
41 #[tokio::test]
42 async fn deploy_gateway() -> Result<(), Error> {
43 let (client, cluster) = get_client().await?;
44 let info = client.apiserver_version().await?;
45
46 println!(
47 "kind cluster {} is running, server version: {}",
48 cluster.name, info.git_version
49 );
50
51 let mut gwc = GatewayClass {
52 metadata: ObjectMeta::default(),
53 spec: GatewayClassSpec {
54 controller_name: "test-controller".to_string(),
55 description: None,
56 parameters_ref: None,
57 },
58 status: None,
59 };
60 gwc.metadata.name = Some("test-gateway-class".to_string());
61 gwc = Api::all(client.clone())
62 .create(&PostParams::default(), &gwc)
63 .await?;
64
65 assert!(gwc.metadata.name.is_some());
66 assert!(gwc.metadata.uid.is_some());
67
68 let mut gw = Gateway {
69 metadata: ObjectMeta::default(),
70 spec: GatewaySpec {
71 gateway_class_name: gwc
72 .metadata
73 .name
74 .ok_or(Error::msg("could not find GatewayClass name"))?,
75 ..Default::default()
76 },
77 status: None,
78 };
79 gw.metadata.name = Some("test-gateway".to_string());
80 gw = Api::default_namespaced(client.clone())
81 .create(&PostParams::default(), &gw)
82 .await?;
83
84 assert!(gw.metadata.name.is_some());
85 assert!(gw.metadata.uid.is_some());
86
87 let mut gw_status = GatewayStatus::default();
88 gw_status.addresses = Some(vec![GatewayStatusAddresses::default()]);
89 gw_status.listeners = Some(vec![GatewayStatusListeners {
90 name: "tcp".into(),
91 attached_routes: 0,
92 supported_kinds: vec![],
93 conditions: vec![Condition {
94 last_transition_time: Time(Utc::now()),
95 message: "testing gateway".to_string(),
96 observed_generation: Some(1),
97 reason: ListenerConditionReason::Programmed.to_string(),
98 status: "True".to_string(),
99 type_: ListenerConditionType::Programmed.to_string(),
100 }],
101 }]);
102 gw_status.conditions = Some(vec![Condition {
103 last_transition_time: Time(Utc::now()),
104 message: "testing gateway".to_string(),
105 observed_generation: Some(1),
106 reason: GatewayConditionReason::Programmed.to_string(),
107 status: "True".to_string(),
108 type_: GatewayConditionType::Programmed.to_string(),
109 }]);
110
111 gw = Api::default_namespaced(client)
112 .patch_status(
113 gw.metadata.name.clone().unwrap().as_str(),
114 &PatchParams::default(),
115 &Patch::Merge(json!({
116 "status": Some(gw_status)
117 })),
118 )
119 .await?;
120
121 assert!(gw.status.is_some());
122 assert!(gw.status.clone().unwrap().addresses.is_some());
123 assert!(gw.status.clone().unwrap().listeners.is_some());
124 assert!(gw.status.clone().unwrap().conditions.is_some());
125
126 Ok(())
127 }
128
129 struct Cluster {
134 name: String,
135 }
136
137 impl Drop for Cluster {
138 fn drop(&mut self) {
139 match delete_kind_cluster(&self.name) {
140 Err(err) => panic!("failed to cleanup kind cluster {}: {}", self.name, err),
141 Ok(()) => {}
142 }
143 }
144 }
145
146 async fn get_client() -> Result<(kube::Client, Cluster), Error> {
147 let cluster = create_kind_cluster()?;
148 let kubeconfig_yaml = get_kind_kubeconfig(&cluster.name)?;
149 let kubeconfig = Kubeconfig::from_yaml(&kubeconfig_yaml)?;
150 let config =
151 Config::from_custom_kubeconfig(kubeconfig, &KubeConfigOptions::default()).await?;
152
153 let https = config.rustls_https_connector()?;
154 let http_client = HTTPClient::builder(TokioExecutor::new()).build(https);
155 let service = ServiceBuilder::new()
156 .layer(config.base_uri_layer())
157 .option_layer(config.auth_layer()?)
158 .map_err(BoxError::from)
159 .service(http_client);
160
161 let client = KubeClient::new(service, config.default_namespace);
162
163 deploy_crds(client.clone()).await?;
164
165 Ok((client, cluster))
166 }
167
168 async fn deploy_crds(client: kube::Client) -> Result<(), Error> {
169 let mut gwc_crd = GatewayClass::crd();
170 gwc_crd.metadata.annotations = Some(std::collections::BTreeMap::from([(
171 "api-approved.kubernetes.io".to_string(),
172 "https://github.com/kubernetes/enhancements/pull/1111".to_string(),
173 )]));
174
175 Api::all(client.clone())
176 .create(&PostParams::default(), &gwc_crd)
177 .await?;
178
179 let mut gw_crd = Gateway::crd();
180 gw_crd.metadata.annotations = Some(std::collections::BTreeMap::from([(
181 "api-approved.kubernetes.io".to_string(),
182 "https://github.com/kubernetes/enhancements/pull/1111".to_string(),
183 )]));
184
185 Api::all(client.clone())
186 .create(&PostParams::default(), &gw_crd)
187 .await?;
188
189 Ok(())
190 }
191
192 fn create_kind_cluster() -> Result<Cluster, Error> {
193 let cluster_name = Uuid::new_v4().to_string();
194
195 let output = Command::new("kind")
196 .arg("create")
197 .arg("cluster")
198 .arg("--name")
199 .arg(&cluster_name)
200 .output()?;
201
202 if !output.status.success() {
203 return Err(Error::msg(String::from_utf8(output.stderr)?));
204 }
205
206 Ok(Cluster { name: cluster_name })
207 }
208
209 fn delete_kind_cluster(cluster_name: &str) -> Result<(), Error> {
210 let output = Command::new("kind")
211 .arg("delete")
212 .arg("cluster")
213 .arg("--name")
214 .arg(cluster_name)
215 .output()?;
216
217 if !output.status.success() {
218 return Err(Error::msg(String::from_utf8(output.stderr)?));
219 }
220
221 Ok(())
222 }
223
224 fn get_kind_kubeconfig(cluster_name: &str) -> Result<String, Error> {
225 let output = Command::new("kind")
226 .arg("get")
227 .arg("kubeconfig")
228 .arg("--name")
229 .arg(cluster_name)
230 .output()?;
231
232 if !output.status.success() {
233 return Err(Error::msg(String::from_utf8(output.stderr)?));
234 }
235
236 Ok(String::from_utf8(output.stdout)?)
237 }
238}