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, Clone, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema,
46 )]
47 #[serde(rename_all = "camelCase")]
48 #[kube(
49 namespaced,
50 group = "materialize.cloud",
51 version = "v1alpha1",
52 kind = "Balancer",
53 singular = "balancer",
54 plural = "balancers",
55 status = "BalancerStatus",
56 printcolumn = r#"{"name": "ImageRef", "type": "string", "description": "Reference to the Docker image.", "jsonPath": ".spec.balancerdImageRef", "priority": 1}"#,
57 printcolumn = r#"{"name": "Ready", "type": "string", "description": "Whether the deployment is ready", "jsonPath": ".status.conditions[?(@.type==\"Ready\")].status", "priority": 1}"#
58 )]
59 pub struct BalancerSpec {
60 pub balancerd_image_ref: String,
62 pub resource_requirements: Option<ResourceRequirements>,
64 pub replicas: Option<i32>,
66 pub external_certificate_spec: Option<MaterializeCertSpec>,
70 pub internal_certificate_spec: Option<MaterializeCertSpec>,
74 pub pod_annotations: Option<BTreeMap<String, String>>,
76 pub pod_labels: Option<BTreeMap<String, String>>,
78
79 pub static_routing: Option<StaticRoutingConfig>,
81 pub frontegg_routing: Option<FronteggRoutingConfig>,
83
84 pub resource_id: Option<String>,
86 }
87
88 impl Balancer {
89 pub fn name_prefixed(&self, suffix: &str) -> String {
90 format!("mz{}-{}", self.resource_id(), suffix)
91 }
92
93 pub fn resource_id(&self) -> &str {
94 &self.status.as_ref().unwrap().resource_id
95 }
96
97 pub fn namespace(&self) -> String {
98 self.meta().namespace.clone().unwrap()
99 }
100
101 pub fn deployment_name(&self) -> String {
102 self.name_prefixed("balancerd")
103 }
104
105 pub fn replicas(&self) -> i32 {
106 self.spec.replicas.unwrap_or(2)
107 }
108
109 pub fn app_name(&self) -> String {
110 "balancerd".to_owned()
111 }
112
113 pub fn service_name(&self) -> String {
114 self.name_prefixed("balancerd")
115 }
116
117 pub fn external_certificate_name(&self) -> String {
118 self.name_prefixed("balancerd-external")
119 }
120
121 pub fn external_certificate_secret_name(&self) -> String {
122 self.name_prefixed("balancerd-external-tls")
123 }
124
125 pub fn routing(&self) -> anyhow::Result<Routing<'_>> {
126 match (&self.spec.static_routing, &self.spec.frontegg_routing) {
127 (Some(config), None) => Ok(Routing::Static(config)),
128 (None, Some(config)) => Ok(Routing::Frontegg(config)),
129 (None, None) => bail!("no routing configuration present"),
130 _ => bail!("multiple routing configurations present"),
131 }
132 }
133
134 pub fn status(&self) -> BalancerStatus {
135 self.status.clone().unwrap_or_else(|| BalancerStatus {
136 resource_id: self
137 .spec
138 .resource_id
139 .clone()
140 .unwrap_or_else(new_resource_id),
141 conditions: vec![],
142 })
143 }
144 }
145
146 #[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
147 #[serde(rename_all = "camelCase")]
148 pub struct BalancerStatus {
149 pub resource_id: String,
151
152 pub conditions: Vec<Condition>,
153 }
154
155 impl ManagedResource for Balancer {
156 fn default_labels(&self) -> BTreeMap<String, String> {
157 BTreeMap::from_iter([
158 (
159 "materialize.cloud/organization-name".to_owned(),
160 self.name_unchecked(),
161 ),
162 (
163 "materialize.cloud/organization-namespace".to_owned(),
164 self.namespace(),
165 ),
166 (
167 "materialize.cloud/mz-resource-id".to_owned(),
168 self.resource_id().to_owned(),
169 ),
170 ("materialize.cloud/app".to_owned(), "balancerd".to_owned()),
171 ])
172 }
173 }
174}