mz_cloud_resources/crd/
balancer.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use 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;
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        // TODO
42    }
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        /// The balancerd image to run.
61        pub balancerd_image_ref: String,
62        // Resource requirements for the balancerd pod
63        pub resource_requirements: Option<ResourceRequirements>,
64        // Number of balancerd pods to create
65        pub replicas: Option<i32>,
66        // The configuration for generating an x509 certificate using cert-manager for balancerd
67        // to present to incoming connections.
68        // The dns_names and issuer_ref fields are required.
69        pub external_certificate_spec: Option<MaterializeCertSpec>,
70        // The configuration for generating an x509 certificate using cert-manager for balancerd
71        // to use to communicate with environmentd.
72        // The dns_names and issuer_ref fields are required.
73        pub internal_certificate_spec: Option<MaterializeCertSpec>,
74        // Annotations to apply to the pods
75        pub pod_annotations: Option<BTreeMap<String, String>>,
76        // Labels to apply to the pods
77        pub pod_labels: Option<BTreeMap<String, String>>,
78
79        // Configuration for statically routing traffic
80        pub static_routing: Option<StaticRoutingConfig>,
81        // Configuration for routing traffic via Frontegg
82        pub frontegg_routing: Option<FronteggRoutingConfig>,
83
84        // This can be set to override the randomly chosen resource id
85        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 deployment_name(&self) -> String {
98            self.name_prefixed("balancerd")
99        }
100
101        pub fn replicas(&self) -> i32 {
102            self.spec.replicas.unwrap_or(2)
103        }
104
105        pub fn app_name(&self) -> String {
106            "balancerd".to_owned()
107        }
108
109        pub fn service_name(&self) -> String {
110            self.name_prefixed("balancerd")
111        }
112
113        pub fn external_certificate_name(&self) -> String {
114            self.name_prefixed("balancerd-external")
115        }
116
117        pub fn external_certificate_secret_name(&self) -> String {
118            self.name_prefixed("balancerd-external-tls")
119        }
120
121        pub fn routing(&self) -> anyhow::Result<Routing<'_>> {
122            match (&self.spec.static_routing, &self.spec.frontegg_routing) {
123                (Some(config), None) => Ok(Routing::Static(config)),
124                (None, Some(config)) => Ok(Routing::Frontegg(config)),
125                (None, None) => bail!("no routing configuration present"),
126                _ => bail!("multiple routing configurations present"),
127            }
128        }
129
130        pub fn status(&self) -> BalancerStatus {
131            self.status.clone().unwrap_or_else(|| BalancerStatus {
132                resource_id: self
133                    .spec
134                    .resource_id
135                    .clone()
136                    .unwrap_or_else(new_resource_id),
137                conditions: vec![],
138            })
139        }
140    }
141
142    #[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
143    #[serde(rename_all = "camelCase")]
144    pub struct BalancerStatus {
145        /// Resource identifier used as a name prefix to avoid pod name collisions.
146        pub resource_id: String,
147
148        pub conditions: Vec<Condition>,
149    }
150
151    impl ManagedResource for Balancer {
152        fn default_labels(&self) -> BTreeMap<String, String> {
153            BTreeMap::from_iter([
154                (
155                    "materialize.cloud/mz-resource-id".to_owned(),
156                    self.resource_id().to_owned(),
157                ),
158                ("materialize.cloud/app".to_owned(), "balancerd".to_owned()),
159            ])
160        }
161    }
162}