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