1use std::collections::BTreeMap;
11
12use anyhow::bail;
13use k8s_openapi::{
14 api::core::v1::ResourceRequirements, apimachinery::pkg::apis::meta::v1::Condition,
15};
16use kube::{CustomResource, Resource, ResourceExt};
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize};
19
20use crate::crd::{ManagedResource, MaterializeCertSpec, new_resource_id};
21
22pub mod v1alpha1 {
23 use super::*;
24
25 #[derive(Clone, Debug)]
26 pub enum Routing<'a> {
27 Static(&'a StaticRoutingConfig),
28 Frontegg(&'a FronteggRoutingConfig),
29 }
30
31 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)]
32 #[serde(rename_all = "camelCase")]
33 pub struct StaticRoutingConfig {
34 pub environmentd_namespace: String,
35 pub environmentd_service_name: String,
36 }
37
38 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)]
39 #[serde(rename_all = "camelCase")]
40 pub struct FronteggRoutingConfig {
41 }
43
44 #[derive(
45 CustomResource,
46 Clone,
47 Debug,
48 Default,
49 PartialEq,
50 Deserialize,
51 Serialize,
52 JsonSchema
53 )]
54 #[serde(rename_all = "camelCase")]
55 #[kube(
56 namespaced,
57 group = "materialize.cloud",
58 version = "v1alpha1",
59 kind = "Balancer",
60 singular = "balancer",
61 plural = "balancers",
62 status = "BalancerStatus",
63 printcolumn = r#"{"name": "ImageRef", "type": "string", "description": "Reference to the Docker image.", "jsonPath": ".spec.balancerdImageRef", "priority": 1}"#,
64 printcolumn = r#"{"name": "Ready", "type": "string", "description": "Whether the deployment is ready", "jsonPath": ".status.conditions[?(@.type==\"Ready\")].status", "priority": 1}"#
65 )]
66 pub struct BalancerSpec {
67 pub balancerd_image_ref: String,
69 pub configmap_name: Option<String>,
72 pub resource_requirements: Option<ResourceRequirements>,
74 pub replicas: Option<i32>,
76 pub externally_scaled: Option<bool>,
80 pub external_certificate_spec: Option<MaterializeCertSpec>,
84 pub internal_certificate_spec: Option<MaterializeCertSpec>,
88 pub pod_annotations: Option<BTreeMap<String, String>>,
90 pub pod_labels: Option<BTreeMap<String, String>>,
92
93 pub static_routing: Option<StaticRoutingConfig>,
95 pub frontegg_routing: Option<FronteggRoutingConfig>,
97
98 pub resource_id: Option<String>,
100 }
101
102 impl Balancer {
103 pub fn name_prefixed(&self, suffix: &str) -> String {
104 format!("mz{}-{}", self.resource_id(), suffix)
105 }
106
107 pub fn resource_id(&self) -> &str {
108 &self.status.as_ref().unwrap().resource_id
109 }
110
111 pub fn namespace(&self) -> String {
112 self.meta().namespace.clone().unwrap()
113 }
114
115 pub fn deployment_name(&self) -> String {
116 self.name_prefixed("balancerd")
117 }
118
119 pub fn replicas(&self) -> Option<i32> {
121 if self.spec.externally_scaled.unwrap_or(false) {
122 None
123 } else {
124 Some(self.initial_replicas())
125 }
126 }
127
128 pub fn initial_replicas(&self) -> i32 {
131 self.spec.replicas.unwrap_or(2)
132 }
133
134 pub fn app_name(&self) -> String {
135 "balancerd".to_owned()
136 }
137
138 pub fn service_name(&self) -> String {
139 self.name_prefixed("balancerd")
140 }
141
142 pub fn external_certificate_name(&self) -> String {
143 self.name_prefixed("balancerd-external")
144 }
145
146 pub fn external_certificate_secret_name(&self) -> String {
147 self.name_prefixed("balancerd-external-tls")
148 }
149
150 pub fn routing(&self) -> anyhow::Result<Routing<'_>> {
151 match (&self.spec.static_routing, &self.spec.frontegg_routing) {
152 (Some(config), None) => Ok(Routing::Static(config)),
153 (None, Some(config)) => Ok(Routing::Frontegg(config)),
154 (None, None) => bail!("no routing configuration present"),
155 _ => bail!("multiple routing configurations present"),
156 }
157 }
158
159 pub fn status(&self) -> BalancerStatus {
160 self.status.clone().unwrap_or_else(|| BalancerStatus {
161 resource_id: self
162 .spec
163 .resource_id
164 .clone()
165 .unwrap_or_else(new_resource_id),
166 conditions: vec![],
167 })
168 }
169 }
170
171 #[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
172 #[serde(rename_all = "camelCase")]
173 pub struct BalancerStatus {
174 pub resource_id: String,
176
177 pub conditions: Vec<Condition>,
178 }
179
180 impl ManagedResource for Balancer {
181 fn default_labels(&self) -> BTreeMap<String, String> {
182 BTreeMap::from_iter([
183 (
184 "materialize.cloud/organization-name".to_owned(),
185 self.name_unchecked(),
186 ),
187 (
188 "materialize.cloud/organization-namespace".to_owned(),
189 self.namespace(),
190 ),
191 (
192 "materialize.cloud/mz-resource-id".to_owned(),
193 self.resource_id().to_owned(),
194 ),
195 ("materialize.cloud/app".to_owned(), "balancerd".to_owned()),
196 ])
197 }
198
199 fn app_name(&self) -> Option<&str> {
200 Some("balancerd")
201 }
202 }
203}