Skip to main content

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, 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        // TODO
42    }
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        /// The balancerd image to run.
68        pub balancerd_image_ref: String,
69        /// The name of an externally managed ConfigMap in this namespace containing
70        /// dynamic configuration as a JSON object in `config.json`.
71        pub configmap_name: Option<String>,
72        // Resource requirements for the balancerd pod
73        pub resource_requirements: Option<ResourceRequirements>,
74        // Number of balancerd pods to create
75        pub replicas: Option<i32>,
76        // The configuration for generating an x509 certificate using cert-manager for balancerd
77        // to present to incoming connections.
78        // The dns_names and issuer_ref fields are required.
79        pub external_certificate_spec: Option<MaterializeCertSpec>,
80        // The configuration for generating an x509 certificate using cert-manager for balancerd
81        // to use to communicate with environmentd.
82        // The dns_names and issuer_ref fields are required.
83        pub internal_certificate_spec: Option<MaterializeCertSpec>,
84        // Annotations to apply to the pods
85        pub pod_annotations: Option<BTreeMap<String, String>>,
86        // Labels to apply to the pods
87        pub pod_labels: Option<BTreeMap<String, String>>,
88
89        // Configuration for statically routing traffic
90        pub static_routing: Option<StaticRoutingConfig>,
91        // Configuration for routing traffic via Frontegg
92        pub frontegg_routing: Option<FronteggRoutingConfig>,
93
94        // This can be set to override the randomly chosen resource id
95        pub resource_id: Option<String>,
96    }
97
98    impl Balancer {
99        pub fn name_prefixed(&self, suffix: &str) -> String {
100            format!("mz{}-{}", self.resource_id(), suffix)
101        }
102
103        pub fn resource_id(&self) -> &str {
104            &self.status.as_ref().unwrap().resource_id
105        }
106
107        pub fn namespace(&self) -> String {
108            self.meta().namespace.clone().unwrap()
109        }
110
111        pub fn deployment_name(&self) -> String {
112            self.name_prefixed("balancerd")
113        }
114
115        pub fn replicas(&self) -> i32 {
116            self.spec.replicas.unwrap_or(2)
117        }
118
119        pub fn app_name(&self) -> String {
120            "balancerd".to_owned()
121        }
122
123        pub fn service_name(&self) -> String {
124            self.name_prefixed("balancerd")
125        }
126
127        pub fn external_certificate_name(&self) -> String {
128            self.name_prefixed("balancerd-external")
129        }
130
131        pub fn external_certificate_secret_name(&self) -> String {
132            self.name_prefixed("balancerd-external-tls")
133        }
134
135        pub fn routing(&self) -> anyhow::Result<Routing<'_>> {
136            match (&self.spec.static_routing, &self.spec.frontegg_routing) {
137                (Some(config), None) => Ok(Routing::Static(config)),
138                (None, Some(config)) => Ok(Routing::Frontegg(config)),
139                (None, None) => bail!("no routing configuration present"),
140                _ => bail!("multiple routing configurations present"),
141            }
142        }
143
144        pub fn status(&self) -> BalancerStatus {
145            self.status.clone().unwrap_or_else(|| BalancerStatus {
146                resource_id: self
147                    .spec
148                    .resource_id
149                    .clone()
150                    .unwrap_or_else(new_resource_id),
151                conditions: vec![],
152            })
153        }
154    }
155
156    #[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
157    #[serde(rename_all = "camelCase")]
158    pub struct BalancerStatus {
159        /// Resource identifier used as a name prefix to avoid pod name collisions.
160        pub resource_id: String,
161
162        pub conditions: Vec<Condition>,
163    }
164
165    impl ManagedResource for Balancer {
166        fn default_labels(&self) -> BTreeMap<String, String> {
167            BTreeMap::from_iter([
168                (
169                    "materialize.cloud/organization-name".to_owned(),
170                    self.name_unchecked(),
171                ),
172                (
173                    "materialize.cloud/organization-namespace".to_owned(),
174                    self.namespace(),
175                ),
176                (
177                    "materialize.cloud/mz-resource-id".to_owned(),
178                    self.resource_id().to_owned(),
179                ),
180                ("materialize.cloud/app".to_owned(), "balancerd".to_owned()),
181            ])
182        }
183
184        fn app_name(&self) -> Option<&str> {
185            Some("balancerd")
186        }
187    }
188}