1use crate::{
4 error::{ErrorData, Result},
5 import::data::AzureApplicationGatewayForContainersBootstrap,
6 resource::{ResourceDefinition, ResourceOutputsDefinition, ResourceRef},
7 ResourceType,
8};
9use alien_error::AlienError;
10use bon::Builder;
11use serde::{Deserialize, Serialize};
12use std::any::Any;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
17#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
18#[serde(rename_all = "camelCase")]
19pub enum KubernetesClusterProvider {
20 Eks,
21 Gke,
22 Aks,
23 Generic,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
29#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
30#[serde(rename_all = "camelCase")]
31pub enum KubernetesClusterOwnership {
32 Managed,
33 Existing,
34 External,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
40#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
41#[serde(rename_all = "camelCase")]
42pub enum KubernetesHeartbeatMode {
43 KubernetesApi,
44 KubernetesApiAndCloudMetadata,
45 Disabled,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
50#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
51#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
52#[serde(rename_all = "camelCase", deny_unknown_fields)]
53pub struct KubernetesCloudReference {
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub cluster_name: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub cluster_id: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub region: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub account_id: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub project_id: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub subscription_id: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub resource_group: Option<String>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
72#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
73#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
74#[serde(rename_all = "camelCase", deny_unknown_fields)]
75#[builder(start_fn = new)]
76pub struct KubernetesCluster {
77 #[builder(start_fn)]
78 pub id: String,
79 pub provider: KubernetesClusterProvider,
80 pub ownership: KubernetesClusterOwnership,
81 pub namespace: String,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 pub cloud: Option<KubernetesCloudReference>,
84 pub heartbeat_mode: KubernetesHeartbeatMode,
85}
86
87impl KubernetesCluster {
88 pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("kubernetes-cluster");
89
90 pub fn id(&self) -> &str {
91 &self.id
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
98#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
99#[serde(rename_all = "camelCase")]
100pub struct KubernetesClusterOutputs {
101 pub provider: KubernetesClusterProvider,
102 pub ownership: KubernetesClusterOwnership,
103 pub namespace: String,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub cluster_name: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub cluster_id: Option<String>,
108 pub kubernetes_api_reachable: bool,
109 pub namespace_ready: bool,
110 pub rbac_ready: bool,
111 pub agent_ready: bool,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub cloud_metadata_ready: Option<bool>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub azure_application_gateway_for_containers:
116 Option<AzureApplicationGatewayForContainersBootstrap>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub version: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub status_message: Option<String>,
121}
122
123impl ResourceOutputsDefinition for KubernetesClusterOutputs {
124 fn get_resource_type(&self) -> ResourceType {
125 KubernetesCluster::RESOURCE_TYPE
126 }
127
128 fn as_any(&self) -> &dyn Any {
129 self
130 }
131
132 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
133 Box::new(self.clone())
134 }
135
136 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
137 other.as_any().downcast_ref::<KubernetesClusterOutputs>() == Some(self)
138 }
139
140 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
141 serde_json::to_value(self)
142 }
143}
144
145impl ResourceDefinition for KubernetesCluster {
146 fn get_resource_type(&self) -> ResourceType {
147 Self::RESOURCE_TYPE
148 }
149
150 fn id(&self) -> &str {
151 &self.id
152 }
153
154 fn get_dependencies(&self) -> Vec<ResourceRef> {
155 Vec::new()
156 }
157
158 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
159 let new_cluster = new_config
160 .as_any()
161 .downcast_ref::<KubernetesCluster>()
162 .ok_or_else(|| {
163 AlienError::new(ErrorData::UnexpectedResourceType {
164 resource_id: self.id.clone(),
165 expected: Self::RESOURCE_TYPE,
166 actual: new_config.get_resource_type(),
167 })
168 })?;
169
170 if self != new_cluster {
171 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
172 resource_id: self.id.clone(),
173 reason: "KubernetesCluster is a frozen runtime substrate and cannot be changed during runtime updates".to_string(),
174 }));
175 }
176
177 Ok(())
178 }
179
180 fn as_any(&self) -> &dyn Any {
181 self
182 }
183
184 fn as_any_mut(&mut self) -> &mut dyn Any {
185 self
186 }
187
188 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
189 Box::new(self.clone())
190 }
191
192 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
193 other.as_any().downcast_ref::<KubernetesCluster>() == Some(self)
194 }
195
196 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
197 serde_json::to_value(self)
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn generic_external_cluster_serializes_as_resource() {
207 let cluster = KubernetesCluster::new("kubernetes".to_string())
208 .provider(KubernetesClusterProvider::Generic)
209 .ownership(KubernetesClusterOwnership::External)
210 .namespace("default".to_string())
211 .heartbeat_mode(KubernetesHeartbeatMode::KubernetesApi)
212 .build();
213
214 let resource = crate::Resource::new(cluster);
215 let value = serde_json::to_value(&resource).unwrap();
216
217 assert_eq!(value["type"], "kubernetes-cluster");
218 assert_eq!(value["provider"], "generic");
219 assert_eq!(value["ownership"], "external");
220 }
221}