|
| 1 | +use stackable_operator::{ |
| 2 | + builder::{ |
| 3 | + meta::ObjectMetaBuilder, |
| 4 | + pod::{container::ContainerBuilder, resources::ResourceRequirementsBuilder}, |
| 5 | + }, |
| 6 | + k8s_openapi::api::{ |
| 7 | + batch::v1::{Job, JobSpec}, |
| 8 | + core::v1::{ |
| 9 | + PodSecurityContext, PodSpec, PodTemplateSpec, SecretVolumeSource, Volume, VolumeMount, |
| 10 | + }, |
| 11 | + }, |
| 12 | + kube::api::ObjectMeta, |
| 13 | + kvp::{ |
| 14 | + Label, Labels, |
| 15 | + consts::{STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE}, |
| 16 | + }, |
| 17 | +}; |
| 18 | + |
| 19 | +use crate::{ |
| 20 | + controller::{ContextNames, ValidatedCluster}, |
| 21 | + framework::{ |
| 22 | + IsLabelValue, builder::meta::ownerreference_from_resource, role_utils::ResourceNames, |
| 23 | + }, |
| 24 | +}; |
| 25 | + |
| 26 | +const RUN_SECURITYADMIN_CERT_VOLUME_NAME: &str = "tls"; |
| 27 | +const RUN_SECURITYADMIN_CERT_VOLUME_MOUNT: &str = "/stackable/cert"; |
| 28 | +const SECURITY_CONFIG_VOLUME_NAME: &str = "security-config"; |
| 29 | +const SECURITY_CONFIG_VOLUME_MOUNT: &str = "/stackable/opensearch/config/opensearch-security"; |
| 30 | +const RUN_SECURITYADMIN_CONTAINER_NAME: &str = "run-securityadmin"; |
| 31 | + |
| 32 | +pub struct JobBuilder<'a> { |
| 33 | + cluster: ValidatedCluster, |
| 34 | + context_names: &'a ContextNames, |
| 35 | + resource_names: ResourceNames, |
| 36 | +} |
| 37 | + |
| 38 | +impl<'a> JobBuilder<'a> { |
| 39 | + pub fn new(cluster: ValidatedCluster, context_names: &'a ContextNames) -> JobBuilder<'a> { |
| 40 | + JobBuilder { |
| 41 | + cluster: cluster.clone(), |
| 42 | + context_names, |
| 43 | + resource_names: ResourceNames { |
| 44 | + cluster_name: cluster.name.clone(), |
| 45 | + product_name: context_names.product_name.clone(), |
| 46 | + }, |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + pub fn build_run_securityadmin_job(&self) -> Job { |
| 51 | + let product_image = self |
| 52 | + .cluster |
| 53 | + .image |
| 54 | + .resolve("opensearch", crate::built_info::PKG_VERSION); |
| 55 | + // Maybe add a suffix for consecutive |
| 56 | + let metadata = self.common_metadata(format!( |
| 57 | + "{}-run-securityadmin", |
| 58 | + self.resource_names.cluster_name, |
| 59 | + )); |
| 60 | + |
| 61 | + let args = [ |
| 62 | + "plugins/opensearch-security/tools/securityadmin.sh".to_string(), |
| 63 | + "-cacert".to_string(), |
| 64 | + "config/tls-client/ca.crt".to_string(), |
| 65 | + "-cert".to_string(), |
| 66 | + "config/tls-client/tls.crt".to_string(), |
| 67 | + "-key".to_string(), |
| 68 | + "config/tls-client/tls.key".to_string(), |
| 69 | + "--hostname".to_string(), |
| 70 | + self.opensearch_master_fqdn(), |
| 71 | + "--configdir".to_string(), |
| 72 | + "config/opensearch-security/".to_string(), |
| 73 | + ]; |
| 74 | + let mut cb = ContainerBuilder::new(RUN_SECURITYADMIN_CONTAINER_NAME) |
| 75 | + .expect("should be a valid container name"); |
| 76 | + let container = cb |
| 77 | + .image_from_product_image(&product_image) |
| 78 | + .command(vec!["sh".to_string(), "-c".to_string()]) |
| 79 | + .args(vec![args.join(" ")]) |
| 80 | + // The VolumeMount for the secret operator key store certificates |
| 81 | + .add_volume_mounts([ |
| 82 | + VolumeMount { |
| 83 | + mount_path: RUN_SECURITYADMIN_CERT_VOLUME_MOUNT.to_owned(), |
| 84 | + name: RUN_SECURITYADMIN_CERT_VOLUME_NAME.to_owned(), |
| 85 | + ..VolumeMount::default() |
| 86 | + }, |
| 87 | + VolumeMount { |
| 88 | + mount_path: SECURITY_CONFIG_VOLUME_MOUNT.to_owned(), |
| 89 | + name: SECURITY_CONFIG_VOLUME_NAME.to_owned(), |
| 90 | + ..VolumeMount::default() |
| 91 | + }, |
| 92 | + ]) |
| 93 | + .expect("the mount paths are statically defined and there should be no duplicates") |
| 94 | + .resources( |
| 95 | + ResourceRequirementsBuilder::new() |
| 96 | + .with_cpu_request("100m") |
| 97 | + .with_cpu_limit("400m") |
| 98 | + .with_memory_request("128Mi") |
| 99 | + .with_memory_limit("512Mi") |
| 100 | + .build(), |
| 101 | + ) |
| 102 | + .build(); |
| 103 | + |
| 104 | + let pod_template = PodTemplateSpec { |
| 105 | + metadata: Some(metadata.clone()), |
| 106 | + spec: Some(PodSpec { |
| 107 | + containers: vec![container], |
| 108 | + |
| 109 | + security_context: Some(PodSecurityContext { |
| 110 | + fs_group: Some(1000), |
| 111 | + ..PodSecurityContext::default() |
| 112 | + }), |
| 113 | + service_account_name: Some(self.resource_names.service_account_name()), |
| 114 | + volumes: Some(vec![Volume { |
| 115 | + name: SECURITY_CONFIG_VOLUME_NAME.to_owned(), |
| 116 | + secret: Some(SecretVolumeSource { |
| 117 | + secret_name: Some("opensearch-security-config".to_string()), |
| 118 | + ..Default::default() |
| 119 | + }), |
| 120 | + ..Volume::default() |
| 121 | + }]), |
| 122 | + ..PodSpec::default() |
| 123 | + }), |
| 124 | + }; |
| 125 | + |
| 126 | + Job { |
| 127 | + metadata, |
| 128 | + spec: Some(JobSpec { |
| 129 | + backoff_limit: Some(100), |
| 130 | + ttl_seconds_after_finished: Some(120), |
| 131 | + template: pod_template, |
| 132 | + ..JobSpec::default() |
| 133 | + }), |
| 134 | + ..Job::default() |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + fn opensearch_master_fqdn(&self) -> String { |
| 139 | + let cluster_manager_service_name = self.resource_names.discovery_service_name(); |
| 140 | + let namespace = &self.cluster.namespace; |
| 141 | + let cluster_domain = &self.context_names.cluster_domain_name; |
| 142 | + format!("{cluster_manager_service_name}.{namespace}.svc.{cluster_domain}") |
| 143 | + } |
| 144 | + |
| 145 | + fn common_metadata(&self, resource_name: impl Into<String>) -> ObjectMeta { |
| 146 | + ObjectMetaBuilder::new() |
| 147 | + .name(resource_name) |
| 148 | + .namespace(&self.cluster.namespace) |
| 149 | + .ownerreference(ownerreference_from_resource( |
| 150 | + &self.cluster, |
| 151 | + None, |
| 152 | + Some(true), |
| 153 | + )) |
| 154 | + .with_labels(self.labels()) |
| 155 | + .build() |
| 156 | + } |
| 157 | + |
| 158 | + /// Labels on role resources |
| 159 | + fn labels(&self) -> Labels { |
| 160 | + // Well-known Kubernetes labels |
| 161 | + let mut labels = Labels::role_selector( |
| 162 | + &self.cluster, |
| 163 | + &self.context_names.product_name.to_label_value(), |
| 164 | + &ValidatedCluster::role_name().to_label_value(), |
| 165 | + ) |
| 166 | + .unwrap(); |
| 167 | + |
| 168 | + let managed_by = Label::managed_by( |
| 169 | + &self.context_names.operator_name.to_string(), |
| 170 | + &self.context_names.controller_name.to_string(), |
| 171 | + ) |
| 172 | + .unwrap(); |
| 173 | + let version = Label::version(&self.cluster.product_version.to_string()).unwrap(); |
| 174 | + |
| 175 | + labels.insert(managed_by); |
| 176 | + labels.insert(version); |
| 177 | + |
| 178 | + // Stackable-specific labels |
| 179 | + labels |
| 180 | + .parse_insert((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE)) |
| 181 | + .unwrap(); |
| 182 | + |
| 183 | + labels |
| 184 | + } |
| 185 | +} |
0 commit comments