From a3031dfd1c698b51b4a9a2c681344b1fbf3041e3 Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Fri, 22 Dec 2023 06:59:11 +0000 Subject: [PATCH 1/9] feat(upgrade-job): add support for yq yaml obj update Signed-off-by: Niladri Halder --- .../src/bin/upgrade-job/common/constants.rs | 3 + .../src/bin/upgrade-job/common/error.rs | 13 ++++ .../src/bin/upgrade-job/helm/values.rs | 24 +++++++- .../src/bin/upgrade-job/helm/yaml/yq.rs | 61 ++++++++++++++++--- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/k8s/upgrade/src/bin/upgrade-job/common/constants.rs b/k8s/upgrade/src/bin/upgrade-job/common/constants.rs index 881a93081..fa8e3eef1 100644 --- a/k8s/upgrade/src/bin/upgrade-job/common/constants.rs +++ b/k8s/upgrade/src/bin/upgrade-job/common/constants.rs @@ -44,3 +44,6 @@ pub(crate) const TWO_DOT_FOUR: &str = "2.4.0"; /// Version value for the earliest possible 2.5 release. pub(crate) const TWO_DOT_FIVE: &str = "2.5.0"; + +/// Version value for the earliest possible 2.6 release. +pub(crate) const TWO_DOT_SIX: &str = "2.6.0"; diff --git a/k8s/upgrade/src/bin/upgrade-job/common/error.rs b/k8s/upgrade/src/bin/upgrade-job/common/error.rs index 8e41e7972..5ed8d9286 100644 --- a/k8s/upgrade/src/bin/upgrade-job/common/error.rs +++ b/k8s/upgrade/src/bin/upgrade-job/common/error.rs @@ -601,6 +601,19 @@ pub(crate) enum Error { std_err: String, }, + /// Error for when the yq command to update a yaml object returns an error. + #[snafu(display( + "`yq` set-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", + command, + args, + std_err, + ))] + YqSetObjCommand { + command: String, + args: Vec, + std_err: String, + }, + /// Error for when we fail to read the entries of a directory. #[snafu(display("Failed to read the contents of directory {}: {}", path.display(), source))] ReadingDirectoryContents { diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs index 04acd0589..52c47e1bd 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs @@ -1,6 +1,8 @@ use crate::{ common::{ - constants::{TWO_DOT_FIVE, TWO_DOT_FOUR, TWO_DOT_ONE, TWO_DOT_O_RC_ONE, TWO_DOT_THREE}, + constants::{ + TWO_DOT_FIVE, TWO_DOT_FOUR, TWO_DOT_ONE, TWO_DOT_O_RC_ONE, TWO_DOT_SIX, TWO_DOT_THREE, + }, error::{Result, SemverParse}, file::write_to_tempfile, }, @@ -57,7 +59,8 @@ where // Resultant values yaml for helm upgrade command. // Merge the source values with the target values. let yq = YqV4::new()?; - let upgrade_values_yaml = yq.merge_files(source_values_file.path(), target_values_filepath)?; + let upgrade_values_yaml = + yq.merge_files(source_values_file.path(), target_values_filepath.as_ref())?; let upgrade_values_file: TempFile = write_to_tempfile(Some(workdir), upgrade_values_yaml.as_slice())?; @@ -162,6 +165,23 @@ where } } + // Special-case values for 2.6.x. + let two_dot_six = Version::parse(TWO_DOT_SIX).context(SemverParse { + version_string: TWO_DOT_SIX.to_string(), + })?; + if source_version.ge(&two_dot_o_rc_zero) && source_version.lt(&two_dot_six) { + yq.set_obj( + YamlKey::try_from(".loki-stack.loki")?, + target_values_filepath.as_ref(), + upgrade_values_file.path(), + )?; + yq.set_obj( + YamlKey::try_from(".loki-stack.promtail")?, + target_values_filepath.as_ref(), + upgrade_values_file.path(), + )?; + } + // Default options. // Image tag is set because the high_priority file is the user's source options file. // The target's image tag needs to be set for PRODUCT upgrade. diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs index fcc138a1f..c22db44a5 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs @@ -1,7 +1,7 @@ use crate::{ common::error::{ NotAValidYamlKeyForStringValue, NotYqV4, RegexCompile, Result, U8VectorToString, - YqCommandExec, YqMergeCommand, YqSetCommand, YqVersionCommand, + YqCommandExec, YqMergeCommand, YqSetCommand, YqSetObjCommand, YqVersionCommand, }, vec_to_strings, }; @@ -109,16 +109,24 @@ impl YqV4 { /// preferred over those of the other file's. In case there are values absent in the latter one /// which exist in the other file, the values of the other file are taken. The 'latter' file in /// this function is the one called 'high_priority' and the other file is the 'low_priority' - /// one. The output of the command is stripped of the 'COMPUTED VALUES:' suffix. + /// one. /// E.g: - /// high_priority file: low_priority file: - /// =================== ================== - /// foo: foo: - /// bar: "foobar" bar: "foobaz" - /// baz: baz: - /// - "alpha" - "gamma" - /// - "beta" - "delta" friend: "ferris" + /// high_priority file: + /// =================== + /// foo: + /// bar: "foobar" + /// baz: + /// - "alpha" + /// - "beta" /// + /// low_priority file: + /// ================== + /// foo: + /// bar: "foobaz" + /// baz: + /// - "gamma" + /// - "delta" + /// friend: "ferris" /// /// result: /// ======= @@ -225,6 +233,41 @@ impl YqV4 { Ok(()) } + /// This sets yaml objects to a file from the same yaml object in another file. + pub(crate) fn set_obj(&self, key: YamlKey, obj_source: &Path, filepath: &Path) -> Result<()> { + let yq_set_obj_args = vec_to_strings![ + "-i", + format!( + r#"{} = load("{}"){}"#, + key.as_str(), + obj_source.to_string_lossy(), + key.as_str() + ), + filepath.to_string_lossy() + ]; + let yq_set_obj_output = self + .command() + .args(yq_set_obj_args.clone()) + .output() + .context(YqCommandExec { + command: self.command_as_str().to_string(), + args: yq_set_obj_args.clone(), + })?; + + ensure!( + yq_set_obj_output.status.success(), + YqSetObjCommand { + command: self.command_as_str().to_string(), + args: yq_set_obj_args, + std_err: str::from_utf8(yq_set_obj_output.stderr.as_slice()) + .context(U8VectorToString)? + .to_string() + } + ); + + Ok(()) + } + /// Returns an std::process::Command using the command_as_str member's value. fn command(&self) -> Command { Command::new(self.command_name.clone()) From c15a800ec943c7cc203bd6ddc2d06950955371fe Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Fri, 22 Dec 2023 07:03:25 +0000 Subject: [PATCH 2/9] refactor(upgrade-job): replace &Path with AsRef Signed-off-by: Niladri Halder --- k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs index c22db44a5..9869d826a 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs @@ -177,9 +177,10 @@ impl YqV4 { } /// This sets in-place yaml values in yaml files. - pub(crate) fn set_value(&self, key: YamlKey, value: V, filepath: &Path) -> Result<()> + pub(crate) fn set_value(&self, key: YamlKey, value: V, filepath: P) -> Result<()> where V: Display + Sized, + P: AsRef, { // Command for use during yq file update let mut command = self.command(); @@ -209,7 +210,7 @@ impl YqV4 { let yq_set_args = vec_to_strings![ "-i", format!(r#"{} = {value}"#, key.as_str()), - filepath.to_string_lossy() + filepath.as_ref().to_string_lossy() ]; let yq_set_output = command .args(yq_set_args.clone()) @@ -234,16 +235,20 @@ impl YqV4 { } /// This sets yaml objects to a file from the same yaml object in another file. - pub(crate) fn set_obj(&self, key: YamlKey, obj_source: &Path, filepath: &Path) -> Result<()> { + pub(crate) fn set_obj(&self, key: YamlKey, obj_source: P, filepath: Q) -> Result<()> + where + P: AsRef, + Q: AsRef, + { let yq_set_obj_args = vec_to_strings![ "-i", format!( r#"{} = load("{}"){}"#, key.as_str(), - obj_source.to_string_lossy(), + obj_source.as_ref().to_string_lossy(), key.as_str() ), - filepath.to_string_lossy() + filepath.as_ref().to_string_lossy() ]; let yq_set_obj_output = self .command() From c3704ddf35d66b12e8f1fbfa9b2ab6bf8a30f6da Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Sun, 24 Dec 2023 09:22:36 +0000 Subject: [PATCH 3/9] feat(upgrade-job): add migration for loki-stack - removes .loki-stack.loki.config.ingester.lifecycler.ring.kvstore as it does not exist in loki-stack v2.9.11 - adds set value for the grafana/loki container image tag - removes .loki-stack.promtail.config.snippets.extraClientConfigs as it does not exist in loki-stack v2.9.11 - removes .loki-stack.promtail.initContainer as it does not exist in loki-stack v2.9.11 - migrates .loki-stack.promtail.config.lokiAddress to .loki-stack.promtail.config.clients - adds yq command_output helper function - add set value for .loki-stack.promtail.readinessProbe.httpGet.path - migrate .loki-stack.promtail.config.snippets.extraClientConfigs to .loki-stack.promtail.config.clients Signed-off-by: Niladri Halder --- .../src/bin/upgrade-job/common/error.rs | 76 +++++- k8s/upgrade/src/bin/upgrade-job/helm/chart.rs | 245 ++++++++++++++---- .../src/bin/upgrade-job/helm/values.rs | 141 ++++++++-- .../src/bin/upgrade-job/helm/yaml/yq.rs | 162 +++++++----- 4 files changed, 495 insertions(+), 129 deletions(-) diff --git a/k8s/upgrade/src/bin/upgrade-job/common/error.rs b/k8s/upgrade/src/bin/upgrade-job/common/error.rs index 5ed8d9286..f2947022b 100644 --- a/k8s/upgrade/src/bin/upgrade-job/common/error.rs +++ b/k8s/upgrade/src/bin/upgrade-job/common/error.rs @@ -4,6 +4,7 @@ use crate::{ UMBRELLA_CHART_UPGRADE_DOCS_URL, }, events::event_recorder::EventNote, + helm::chart::PromtailConfigClient, }; use snafu::Snafu; use std::path::PathBuf; @@ -426,6 +427,49 @@ pub(crate) enum Error { note: EventNote, }, + /// Error in serializing a helm::chart::PromtailConfigClient to a JSON string. + #[snafu(display( + "Failed to serialize .loki-stack.promtail.config.client {:?}: {}", + object, + source + ))] + SerializePromtailConfigClientToJson { + source: serde_json::Error, + object: PromtailConfigClient, + }, + + /// Error in deserializing a promtail helm chart's deprecated extraClientConfig to a + /// serde_json::Value. + #[snafu(display( + "Failed to deserialize .loki-stack.promtail.config.snippets.extraClientConfig to a serde_json::Value {}: {}", + config, + source + ))] + DeserializePromtailExtraConfig { + source: serde_yaml::Error, + config: String, + }, + + /// Error in serializing a promtail helm chart's deprecated extraClientConfig, in a + /// serde_json::Value, to JSON. + #[snafu(display("Failed to serialize to JSON {:?}: {}", config, source))] + SerializePromtailExtraConfigToJson { + source: serde_json::Error, + config: serde_json::Value, + }, + + /// Error in serializing the deprecated config.snippets.extraClientConfig from the promtail + /// helm chart v3.11.0. + #[snafu(display( + "Failed to serialize object to a serde_json::Value {}: {}", + object, + source + ))] + SerializePromtailExtraClientConfigToJson { + source: serde_json::Error, + object: String, + }, + /// Error for when there are too many io-engine Pods in one single node; #[snafu(display("Too many io-engine Pods in Node '{}'", node_name))] TooManyIoEnginePods { node_name: String }, @@ -601,14 +645,40 @@ pub(crate) enum Error { std_err: String, }, - /// Error for when the yq command to update a yaml object returns an error. + /// Error for when the yq command to delete an object path returns an error. + #[snafu(display( + "`yq` delete-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", + command, + args, + std_err, + ))] + YqDeleteObjectCommand { + command: String, + args: Vec, + std_err: String, + }, + + /// Error for when the yq command to append to an array returns an error. + #[snafu(display( + "`yq` append-to-array-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", + command, + args, + std_err, + ))] + YqAppendToArrayCommand { + command: String, + args: Vec, + std_err: String, + }, + + /// Error for when the yq command to append to an object returns an error. #[snafu(display( - "`yq` set-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", + "`yq` append-to-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", command, args, std_err, ))] - YqSetObjCommand { + YqAppendToObjectCommand { command: String, args: Vec, std_err: String, diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs index edad86521..4c4506a06 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs @@ -1,6 +1,6 @@ use crate::common::error::{ReadingFile, U8VectorToString, YamlParseFromFile, YamlParseFromSlice}; use semver::Version; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use snafu::ResultExt; use std::{fs::read, path::Path, str}; @@ -173,35 +173,57 @@ impl CoreValues { self.csi.node_nvme_io_timeout() } + /// This is a getter for the grafana/loki container's image tag from the loki helm chart. + pub(crate) fn loki_stack_loki_image_tag(&self) -> &str { + self.loki_stack.loki_image_tag() + } + /// This is a getter for the promtail scrapeConfigs. - pub(crate) fn loki_promtail_scrape_configs(&self) -> &str { + pub(crate) fn loki_stack_promtail_scrape_configs(&self) -> &str { self.loki_stack.promtail_scrape_configs() } + + pub(crate) fn loki_stack_promtail_loki_address(&self) -> &str { + self.loki_stack.deprecated_promtail_loki_address() + } + + pub(crate) fn loki_stack_promtail_config_file(&self) -> &str { + self.loki_stack.promtail_config_file() + } + + pub(crate) fn loki_stack_promtail_readiness_probe_path(&self) -> &str { + self.loki_stack.promtail_readiness_probe_path() + } + + /// This returns the config.snippets.extraClientConfigs from the promtail helm chart v3.11.0. + pub(crate) fn promtail_extra_client_configs(&self) -> &str { + self.loki_stack.deprecated_promtail_extra_client_configs() + } } /// This is used to deserialize the yaml object agents. #[derive(Deserialize)] -pub(crate) struct Agents { +struct Agents { ha: Ha, } impl Agents { /// This is a getter for state of the 'ha' feature (enabled/disabled). - pub(crate) fn ha_is_enabled(&self) -> bool { + fn ha_is_enabled(&self) -> bool { self.ha.enabled() } } /// This is used to deserialize the yaml object 'agents.ha'. #[derive(Deserialize)] -pub(crate) struct Ha { +struct Ha { enabled: bool, } impl Ha { /// This returns the value of 'ha.enabled' from the values set. Defaults to 'true' is absent /// from the yaml. - pub(crate) fn enabled(&self) -> bool { + fn enabled(&self) -> bool { self.enabled } } @@ -210,7 +232,7 @@ impl Ha { /// container images. #[derive(Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] -pub(crate) struct Image { +struct Image { /// The container image tag. tag: String, /// This contains image tags set based on which PRODUCT repository the microservice originates @@ -221,22 +243,22 @@ pub(crate) struct Image { impl Image { /// This is a getter for the container image tag used across the helm chart release. - pub(crate) fn tag(&self) -> &str { + fn tag(&self) -> &str { self.tag.as_str() } /// This is a getter for the control-plane repoTag set on a helm chart. - pub(crate) fn control_plane_repotag(&self) -> &str { + fn control_plane_repotag(&self) -> &str { self.repo_tags.control_plane() } /// This is a getter for the data-plane repoTag set on a helm chart. - pub(crate) fn data_plane_repotag(&self) -> &str { + fn data_plane_repotag(&self) -> &str { self.repo_tags.data_plane() } /// This is a getter for the extensions repoTag set on a helm chart. - pub(crate) fn extensions_repotag(&self) -> &str { + fn extensions_repotag(&self) -> &str { self.repo_tags.extensions() } } @@ -245,7 +267,7 @@ impl Image { /// component. #[derive(Deserialize, Default)] #[serde(rename_all(deserialize = "camelCase"))] -pub(crate) struct RepoTags { +struct RepoTags { /// This member of repoTags is used to set image tags for components from the control-plane /// repo. control_plane: String, @@ -257,17 +279,17 @@ pub(crate) struct RepoTags { impl RepoTags { /// This is a getter for the control-plane image tag set on a helm chart. - pub(crate) fn control_plane(&self) -> &str { + fn control_plane(&self) -> &str { self.control_plane.as_str() } /// This is a getter for the data-plane image tag set on a helm chart. - pub(crate) fn data_plane(&self) -> &str { + fn data_plane(&self) -> &str { self.data_plane.as_str() } /// This is a getter for the extensions image tag set on a helm chart. - pub(crate) fn extensions(&self) -> &str { + fn extensions(&self) -> &str { self.extensions.as_str() } } @@ -276,14 +298,14 @@ impl RepoTags { /// io-engine DaemonSet. #[derive(Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] -pub(crate) struct IoEngine { +struct IoEngine { /// Tracing Loglevel details for the io-engine DaemonSet Pods. log_level: String, } impl IoEngine { /// This is a getter for the io-engine DaemonSet Pod's tracing logLevel. - pub(crate) fn log_level(&self) -> &str { + fn log_level(&self) -> &str { self.log_level.as_str() } } @@ -291,7 +313,7 @@ impl IoEngine { /// This is used to deserialize the yaml object 'eventing', v2.3.0 has it disabled by default, /// the default thereafter has it enabled. #[derive(Deserialize, Default)] -pub(crate) struct Eventing { +struct Eventing { // This value is defaulted to 'false' when 'Eventing' is absent in the yaml. // This works fine because we don't use the serde deserialized values during // the values.yaml merge. The merge is done with 'yq'. These are assumed values, @@ -305,14 +327,14 @@ pub(crate) struct Eventing { impl Eventing { /// This is a predicate for the installation setting for eventing. - pub(crate) fn enabled(&self) -> bool { + fn enabled(&self) -> bool { self.enabled } } /// This is used to deserialize the yaml object 'csi'. #[derive(Deserialize)] -pub(crate) struct Csi { +struct Csi { /// This contains the image tags for the kubernetes-csi sidecar containers. image: CsiImage, /// This contains configuration for the CSI node. @@ -321,32 +343,32 @@ pub(crate) struct Csi { impl Csi { /// This is a getter for the sig-storage/csi-provisioner image tag. - pub(crate) fn provisioner_image_tag(&self) -> &str { + fn provisioner_image_tag(&self) -> &str { self.image.provisioner_tag() } /// This is a getter for the sig-storage/csi-attacher image tag. - pub(crate) fn attacher_image_tag(&self) -> &str { + fn attacher_image_tag(&self) -> &str { self.image.attacher_tag() } /// This is a getter for the sig-storage/csi-snapshotter image tag. - pub(crate) fn snapshotter_image_tag(&self) -> &str { + fn snapshotter_image_tag(&self) -> &str { self.image.snapshotter_tag() } /// This is a getter for the sig-storage/snapshot-controller image tag. - pub(crate) fn snapshot_controller_image_tag(&self) -> &str { + fn snapshot_controller_image_tag(&self) -> &str { self.image.snapshot_controller_tag() } /// This is a getter for the sig-storage/csi-node-driver-registrar image tag. - pub(crate) fn node_driver_registrar_image_tag(&self) -> &str { + fn node_driver_registrar_image_tag(&self) -> &str { self.image.node_driver_registrar_tag() } /// This is a getter for the CSI node NVMe io_timeout. - pub(crate) fn node_nvme_io_timeout(&self) -> &str { + fn node_nvme_io_timeout(&self) -> &str { self.node.nvme_io_timeout() } } @@ -354,7 +376,7 @@ impl Csi { /// This contains the image tags for the CSI sidecar containers. #[derive(Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] -pub(crate) struct CsiImage { +struct CsiImage { /// This is the image tag for the csi-provisioner container. provisioner_tag: String, /// This is the image tag for the csi-attacher container. @@ -371,105 +393,240 @@ pub(crate) struct CsiImage { impl CsiImage { /// This is a getter for provisionerTag. - pub(crate) fn provisioner_tag(&self) -> &str { + fn provisioner_tag(&self) -> &str { self.provisioner_tag.as_str() } /// This is a getter for attacherTag. - pub(crate) fn attacher_tag(&self) -> &str { + fn attacher_tag(&self) -> &str { self.attacher_tag.as_str() } /// This is a getter for snapshotterTag. - pub(crate) fn snapshotter_tag(&self) -> &str { + fn snapshotter_tag(&self) -> &str { self.snapshotter_tag.as_str() } /// This is a getter for snapshotControllerTag. - pub(crate) fn snapshot_controller_tag(&self) -> &str { + fn snapshot_controller_tag(&self) -> &str { self.snapshot_controller_tag.as_str() } /// This is a getter for registrarTag. - pub(crate) fn node_driver_registrar_tag(&self) -> &str { + fn node_driver_registrar_tag(&self) -> &str { self.registrar_tag.as_str() } } /// This is used to deserialize the yaml object 'csi.node'. #[derive(Deserialize)] -pub(crate) struct CsiNode { +struct CsiNode { nvme: CsiNodeNvme, } impl CsiNode { /// This is a getter for the NVMe IO timeout. - pub(crate) fn nvme_io_timeout(&self) -> &str { + fn nvme_io_timeout(&self) -> &str { self.nvme.io_timeout() } } /// This is used to deserialize the yaml object 'csi.node.nvme'. #[derive(Deserialize)] -pub(crate) struct CsiNodeNvme { +struct CsiNodeNvme { io_timeout: String, } impl CsiNodeNvme { /// This is a getter for the IO timeout configuration. - pub(crate) fn io_timeout(&self) -> &str { + fn io_timeout(&self) -> &str { self.io_timeout.as_str() } } /// This is used to deserialize the yaml object 'loki-stack'. #[derive(Deserialize)] -pub(crate) struct LokiStack { +struct LokiStack { + loki: Loki, promtail: Promtail, } impl LokiStack { - pub(crate) fn promtail_scrape_configs(&self) -> &str { + /// This is a getter for the promtail scrapeConfigs value. + fn promtail_scrape_configs(&self) -> &str { self.promtail.scrape_configs() } + + /// This returns the config.snippets.extraClientConfigs from the promtail helm chart v3.11.0. + fn deprecated_promtail_extra_client_configs(&self) -> &str { + self.promtail.deprecated_extra_client_configs() + } + + /// This is a getter for the grafana/loki container's image tag. + fn loki_image_tag(&self) -> &str { + self.loki.image_tag() + } + + fn deprecated_promtail_loki_address(&self) -> &str { + self.promtail.deprecated_loki_address() + } + + fn promtail_config_file(&self) -> &str { + self.promtail.config_file() + } + + fn promtail_readiness_probe_path(&self) -> &str { + self.promtail.readiness_probe_http_get_path() + } +} + +/// This is used to deserialize the loki dependent helm chart's values set. +#[derive(Deserialize)] +struct Loki { + image: LokiImage, +} + +impl Loki { + /// This is a getter for the container image tag. + fn image_tag(&self) -> &str { + self.image.tag() + } +} + +/// This is used to deserialize the yaml object 'image' in the loki helm chart. +#[derive(Deserialize)] +struct LokiImage { + tag: String, +} + +impl LokiImage { + /// This is a getter for the image's tag. + fn tag(&self) -> &str { + self.tag.as_str() + } } /// This is used to deserialize the yaml object 'promtail'. #[derive(Deserialize)] -pub(crate) struct Promtail { +#[serde(rename_all(deserialize = "camelCase"))] +struct Promtail { config: PromtailConfig, + readiness_probe: PromtailReadinessProbe, } impl Promtail { /// This returns the promtail.config.snippets.scrapeConfigs as an &str. - pub(crate) fn scrape_configs(&self) -> &str { + fn scrape_configs(&self) -> &str { self.config.scrape_configs() } + + /// This returns the config.snippets.extraClientConfigs from the promtail helm chart v3.11.0. + fn deprecated_extra_client_configs(&self) -> &str { + self.config.deprecated_extra_client_configs() + } + + fn deprecated_loki_address(&self) -> &str { + self.config.deprecated_loki_address() + } + + fn config_file(&self) -> &str { + self.config.file() + } + + fn readiness_probe_http_get_path(&self) -> &str { + self.readiness_probe.http_get_path() + } } /// This is used to deserialize the promtail.config yaml object. #[derive(Deserialize)] -pub(crate) struct PromtailConfig { +struct PromtailConfig { + #[serde(default, rename(deserialize = "lokiAddress"))] + deprecated_loki_address: String, + file: String, snippets: PromtailConfigSnippets, } impl PromtailConfig { /// This returns the config.snippets.scrapeConfigs as an &str. - pub(crate) fn scrape_configs(&self) -> &str { + fn scrape_configs(&self) -> &str { self.snippets.scrape_configs() } + + /// This returns the snippets.extraClientConfigs from the promtail helm chart v3.11.0. + fn deprecated_extra_client_configs(&self) -> &str { + self.snippets.deprecated_extra_client_configs() + } + + /// This is a getter for the lokiAddress in the loki helm chart v2.6.4. + fn deprecated_loki_address(&self) -> &str { + self.deprecated_loki_address.as_str() + } + + fn file(&self) -> &str { + self.file.as_str() + } } /// This is used to deserialize the config.snippets yaml object. #[derive(Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] -pub(crate) struct PromtailConfigSnippets { +struct PromtailConfigSnippets { + #[serde(default, rename(deserialize = "extraClientConfigs"))] + deprecated_extra_client_configs: String, scrape_configs: String, } impl PromtailConfigSnippets { /// This returns the snippets.scrapeConfigs as an &str. - pub(crate) fn scrape_configs(&self) -> &str { + fn scrape_configs(&self) -> &str { self.scrape_configs.as_str() } + + /// This returns the snippets.extraClientConfigs from the promtail helm chart v3.11.0. + fn deprecated_extra_client_configs(&self) -> &str { + self.deprecated_extra_client_configs.as_str() + } +} + +#[derive(Deserialize)] +#[serde(rename_all(deserialize = "camelCase"))] +struct PromtailReadinessProbe { + http_get: PromtailReadinessProbeHttpGet, +} + +impl PromtailReadinessProbe { + fn http_get_path(&self) -> &str { + self.http_get.path() + } +} + +#[derive(Deserialize)] +struct PromtailReadinessProbeHttpGet { + path: String, +} + +impl PromtailReadinessProbeHttpGet { + fn path(&self) -> &str { + self.path.as_str() + } +} + +/// This is used to serialize the config.clients yaml object in promtail chart v6.13.1 +/// when migrating from promtail v3.11.0 to v6.13.1. +#[derive(Debug, Serialize)] +pub(crate) struct PromtailConfigClient { + url: String, +} + +impl PromtailConfigClient { + /// Create a new PromtailConfigClient with a url. + pub(crate) fn with_url(url: U) -> Self + where + U: ToString, + { + Self { + url: url.to_string(), + } + } } diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs index 52c47e1bd..5829890d4 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs @@ -3,11 +3,14 @@ use crate::{ constants::{ TWO_DOT_FIVE, TWO_DOT_FOUR, TWO_DOT_ONE, TWO_DOT_O_RC_ONE, TWO_DOT_SIX, TWO_DOT_THREE, }, - error::{Result, SemverParse}, + error::{ + DeserializePromtailExtraConfig, Result, SemverParse, + SerializePromtailConfigClientToJson, SerializePromtailExtraConfigToJson, + }, file::write_to_tempfile, }, helm::{ - chart::CoreValues, + chart::{CoreValues, PromtailConfigClient}, yaml::yq::{YamlKey, YqV4}, }, }; @@ -81,7 +84,7 @@ where if source_values.io_engine_log_level().eq(log_level_to_replace) && target_values.io_engine_log_level().ne(log_level_to_replace) { - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".io_engine.logLevel")?, target_values.io_engine_log_level(), upgrade_values_file.path(), @@ -94,17 +97,17 @@ where // RepoTags fields will also be set to the values found in the target helm values file // (low_priority file). This is so integration tests which use specific repo commits can // upgrade to a custom helm chart. - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".image.repoTags.controlPlane")?, target_values.control_plane_repotag(), upgrade_values_file.path(), )?; - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".image.repoTags.dataPlane")?, target_values.data_plane_repotag(), upgrade_values_file.path(), )?; - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".image.repoTags.extensions")?, target_values.extensions_repotag(), upgrade_values_file.path(), @@ -124,7 +127,7 @@ where .eventing_enabled() .ne(&target_values.eventing_enabled()) { - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".eventing.enabled")?, target_values.eventing_enabled(), upgrade_values_file.path(), @@ -137,13 +140,14 @@ where })?; if source_version.ge(&two_dot_o_rc_zero) && source_version.lt(&two_dot_five) { // promtail + // TODO: check to see if it is the wrong one first. if source_values - .loki_promtail_scrape_configs() - .ne(target_values.loki_promtail_scrape_configs()) + .loki_stack_promtail_scrape_configs() + .ne(target_values.loki_stack_promtail_scrape_configs()) { - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".loki-stack.promtail.config.snippets.scrapeConfigs")?, - target_values.loki_promtail_scrape_configs(), + target_values.loki_stack_promtail_scrape_configs(), upgrade_values_file.path(), )?; } @@ -157,7 +161,7 @@ where .csi_node_nvme_io_timeout() .ne(io_timeout_to_replace) { - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".csi.node.nvme.io_timeout")?, target_values.csi_node_nvme_io_timeout(), upgrade_values_file.path(), @@ -170,14 +174,36 @@ where version_string: TWO_DOT_SIX.to_string(), })?; if source_version.ge(&two_dot_o_rc_zero) && source_version.lt(&two_dot_six) { - yq.set_obj( - YamlKey::try_from(".loki-stack.loki")?, - target_values_filepath.as_ref(), + // Switch out image tag for the latest one. + yq.set_literal_value( + YamlKey::try_from(".loki-stack.loki.image.tag")?, + target_values.loki_stack_loki_image_tag(), + upgrade_values_file.path(), + )?; + // Delete deprecated objects. + yq.delete_object( + YamlKey::try_from(".loki-stack.loki.config.ingester.lifecycler.ring.kvstore")?, upgrade_values_file.path(), )?; - yq.set_obj( - YamlKey::try_from(".loki-stack.promtail")?, - target_values_filepath.as_ref(), + yq.delete_object( + YamlKey::try_from(".loki-stack.promtail.config.snippets.extraClientConfigs")?, + upgrade_values_file.path(), + )?; + yq.delete_object( + YamlKey::try_from(".loki-stack.promtail.initContainer")?, + upgrade_values_file.path(), + )?; + + loki_address_to_clients(target_values, upgrade_values_file.path(), &yq)?; + + yq.set_literal_value( + YamlKey::try_from(".loki-stack.promtail.config.file")?, + target_values.loki_stack_promtail_config_file(), + upgrade_values_file.path(), + )?; + yq.set_literal_value( + YamlKey::try_from(".loki-stack.promtail.readinessProbe.httpGet.path")?, + target_values.loki_stack_promtail_readiness_probe_path(), upgrade_values_file.path(), )?; } @@ -185,34 +211,34 @@ where // Default options. // Image tag is set because the high_priority file is the user's source options file. // The target's image tag needs to be set for PRODUCT upgrade. - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".image.tag")?, target_values.image_tag(), upgrade_values_file.path(), )?; // The CSI sidecar images need to always be the versions set on the chart by default. - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".csi.image.provisionerTag")?, target_values.csi_provisioner_image_tag(), upgrade_values_file.path(), )?; - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".csi.image.attacherTag")?, target_values.csi_attacher_image_tag(), upgrade_values_file.path(), )?; - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".csi.image.snapshotterTag")?, target_values.csi_snapshotter_image_tag(), upgrade_values_file.path(), )?; - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".csi.image.snapshotControllerTag")?, target_values.csi_snapshot_controller_image_tag(), upgrade_values_file.path(), )?; - yq.set_value( + yq.set_literal_value( YamlKey::try_from(".csi.image.registrarTag")?, target_values.csi_node_driver_registrar_image_tag(), upgrade_values_file.path(), @@ -223,3 +249,70 @@ where Ok(upgrade_values_file) } + +/// Converts config.lokiAddress and config.snippets.extraClientConfigs from the promtail helm chart +/// v3.11.0 to config.clients[] which is compatible with promtail helm chart v6.13.1. +fn loki_address_to_clients( + target_values: &CoreValues, + upgrade_values_filepath: &Path, + yq: &YqV4, +) -> Result<()> { + let promtail_config_clients_yaml_key = + YamlKey::try_from(".loki-stack.promtail.config.clients")?; + // Delete existing array, if any. The merge_files() should have added it with the default value + // set. + yq.delete_object( + promtail_config_clients_yaml_key.clone(), + upgrade_values_filepath, + )?; + let loki_address = target_values.loki_stack_promtail_loki_address(); + let promtail_config_client = PromtailConfigClient::with_url(loki_address); + let promtail_config_client = serde_json::to_string(&promtail_config_client).context( + SerializePromtailConfigClientToJson { + object: promtail_config_client, + }, + )?; + yq.append_to_array( + promtail_config_clients_yaml_key, + promtail_config_client, + upgrade_values_filepath, + )?; + // Merge the extraClientConfigs from the promtail v3.11.0 chart to the v6.13.1 chart's + // config.clients block. Ref: https://github.com/grafana/helm-charts/issues/1214 + // Ref: https://github.com/grafana/helm-charts/pull/1425 + if !target_values.promtail_extra_client_configs().is_empty() { + // Converting the YAML to a JSON because the yq command goes like this... + // yq '.config.clients[0] += {"tenant_id": "1", "basic_auth": {"username": "loki", + // "password": "secret"}}' file + let promtail_extra_client_config: serde_json::Value = serde_yaml::from_str( + target_values.promtail_extra_client_configs(), + ) + .context(DeserializePromtailExtraConfig { + config: target_values.promtail_extra_client_configs().to_string(), + })?; + let promtail_extra_client_config = serde_json::to_string(&promtail_extra_client_config) + .context(SerializePromtailExtraConfigToJson { + config: promtail_extra_client_config, + })?; + + yq.append_to_object( + YamlKey::try_from(".loki-stack.promtail.config.clients[0]")?, + promtail_extra_client_config, + upgrade_values_filepath, + )?; + } + + // Cleanup config.snippets.extraClientConfig from the promtail chart. + yq.delete_object( + YamlKey::try_from(".loki-stack.promtail.config.snippets.extraClientConfigs")?, + upgrade_values_filepath, + )?; + + // Cleanup config.lokiAddress from the promtail chart. + yq.delete_object( + YamlKey::try_from(".loki-stack.promtail.config.lokiAddress")?, + upgrade_values_filepath, + )?; + + Ok(()) +} diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs index 9869d826a..16804e192 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs @@ -1,15 +1,23 @@ use crate::{ common::error::{ NotAValidYamlKeyForStringValue, NotYqV4, RegexCompile, Result, U8VectorToString, - YqCommandExec, YqMergeCommand, YqSetCommand, YqSetObjCommand, YqVersionCommand, + YqAppendToArrayCommand, YqAppendToObjectCommand, YqCommandExec, YqDeleteObjectCommand, + YqMergeCommand, YqSetCommand, YqVersionCommand, }, vec_to_strings, }; use regex::Regex; use snafu::{ensure, ResultExt}; -use std::{fmt::Display, ops::Deref, path::Path, process::Command, str}; +use std::{ + fmt::Display, + ops::Deref, + path::Path, + process::{Command, Output}, + str, +}; /// This is a container for the String of an input yaml key. +#[derive(Clone)] pub(crate) struct YamlKey(String); impl TryFrom<&str> for YamlKey { @@ -60,14 +68,7 @@ impl YqV4 { let yq_version_arg = "-V".to_string(); - let yq_version_output = yq_v4 - .command() - .arg(yq_version_arg.clone()) - .output() - .context(YqCommandExec { - command: yq_v4.command_as_str().to_string(), - args: vec![yq_version_arg.clone()], - })?; + let yq_version_output = yq_v4.command_output(vec![yq_version_arg.clone()])?; ensure!( yq_version_output.status.success(), @@ -98,6 +99,86 @@ impl YqV4 { Ok(yq_v4) } + /// Append objects to yaml arrays. + pub(crate) fn append_to_array(&self, key: YamlKey, value: V, filepath: P) -> Result<()> + where + V: Display + Sized, + P: AsRef, + { + let yq_append_to_array_args = vec_to_strings![ + "-i", + format!(r#"{} += [{value}]"#, key.as_str()), + filepath.as_ref().to_string_lossy() + ]; + let yq_append_to_array_output = self.command_output(yq_append_to_array_args.clone())?; + + ensure!( + yq_append_to_array_output.status.success(), + YqAppendToArrayCommand { + command: self.command_as_str().to_string(), + args: yq_append_to_array_args, + std_err: str::from_utf8(yq_append_to_array_output.stderr.as_slice()) + .context(U8VectorToString)? + .to_string() + } + ); + + Ok(()) + } + + /// Append objects to yaml arrays. + pub(crate) fn append_to_object(&self, key: YamlKey, value: V, filepath: P) -> Result<()> + where + V: Display + Sized, + P: AsRef, + { + let yq_append_to_object_args = vec_to_strings![ + "-i", + format!(r#"{} += {value}"#, key.as_str()), + filepath.as_ref().to_string_lossy() + ]; + let yq_append_to_object_output = self.command_output(yq_append_to_object_args.clone())?; + + ensure!( + yq_append_to_object_output.status.success(), + YqAppendToObjectCommand { + command: self.command_as_str().to_string(), + args: yq_append_to_object_args, + std_err: str::from_utf8(yq_append_to_object_output.stderr.as_slice()) + .context(U8VectorToString)? + .to_string() + } + ); + + Ok(()) + } + + /// Use the yq 'delpaths' operator to delete objects from a yaml file. + pub(crate) fn delete_object

(&self, key: YamlKey, filepath: P) -> Result<()> + where + P: AsRef, + { + let yq_delete_object_args = vec_to_strings![ + "-i", + format!(r#"delpaths([["{}"]])"#, key.as_str()), + filepath.as_ref().to_string_lossy() + ]; + let yq_delete_object_output = self.command_output(yq_delete_object_args.clone())?; + + ensure!( + yq_delete_object_output.status.success(), + YqDeleteObjectCommand { + command: self.command_as_str().to_string(), + args: yq_delete_object_args, + std_err: str::from_utf8(yq_delete_object_output.stderr.as_slice()) + .context(U8VectorToString)? + .to_string() + } + ); + + Ok(()) + } + // TODO: // 1. Arrays are treated like unique values on their own, and high_priority is preferred over // low_priority. Arrays are not merged, if the object in the array member is identical to an @@ -153,14 +234,7 @@ impl YqV4 { low_priority.as_ref().to_string_lossy(), high_priority.as_ref().to_string_lossy() ]; - let yq_merge_output = self - .command() - .args(yq_merge_args.clone()) - .output() - .context(YqCommandExec { - command: self.command_as_str().to_string(), - args: yq_merge_args.clone(), - })?; + let yq_merge_output = self.command_output(yq_merge_args.clone())?; ensure!( yq_merge_output.status.success(), @@ -177,7 +251,7 @@ impl YqV4 { } /// This sets in-place yaml values in yaml files. - pub(crate) fn set_value(&self, key: YamlKey, value: V, filepath: P) -> Result<()> + pub(crate) fn set_literal_value(&self, key: YamlKey, value: V, filepath: P) -> Result<()> where V: Display + Sized, P: AsRef, @@ -234,50 +308,22 @@ impl YqV4 { Ok(()) } - /// This sets yaml objects to a file from the same yaml object in another file. - pub(crate) fn set_obj(&self, key: YamlKey, obj_source: P, filepath: Q) -> Result<()> - where - P: AsRef, - Q: AsRef, - { - let yq_set_obj_args = vec_to_strings![ - "-i", - format!( - r#"{} = load("{}"){}"#, - key.as_str(), - obj_source.as_ref().to_string_lossy(), - key.as_str() - ), - filepath.as_ref().to_string_lossy() - ]; - let yq_set_obj_output = self - .command() - .args(yq_set_obj_args.clone()) - .output() - .context(YqCommandExec { - command: self.command_as_str().to_string(), - args: yq_set_obj_args.clone(), - })?; - - ensure!( - yq_set_obj_output.status.success(), - YqSetObjCommand { - command: self.command_as_str().to_string(), - args: yq_set_obj_args, - std_err: str::from_utf8(yq_set_obj_output.stderr.as_slice()) - .context(U8VectorToString)? - .to_string() - } - ); - - Ok(()) - } - /// Returns an std::process::Command using the command_as_str member's value. fn command(&self) -> Command { Command::new(self.command_name.clone()) } + /// This executes a command and returns the output. + fn command_output(&self, args: Vec) -> Result { + self.command() + .args(args.clone()) + .output() + .context(YqCommandExec { + command: self.command_as_str().to_string(), + args, + }) + } + /// The binary name of the `yq` command. fn command_as_str(&self) -> &str { self.command_name.as_str() From a353530a782b520c590fea03291098b7ebce421e Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Thu, 4 Jan 2024 14:20:15 +0000 Subject: [PATCH 4/9] feat(upgrade-job): add yq command to set objects from other YAMLs Signed-off-by: Niladri Halder --- .../src/bin/upgrade-job/common/error.rs | 13 +++ k8s/upgrade/src/bin/upgrade-job/helm/chart.rs | 90 ------------------- .../src/bin/upgrade-job/helm/upgrade.rs | 1 + .../src/bin/upgrade-job/helm/values.rs | 51 +++++++---- .../src/bin/upgrade-job/helm/yaml/yq.rs | 49 +++++++++- 5 files changed, 91 insertions(+), 113 deletions(-) diff --git a/k8s/upgrade/src/bin/upgrade-job/common/error.rs b/k8s/upgrade/src/bin/upgrade-job/common/error.rs index f2947022b..beb5245b3 100644 --- a/k8s/upgrade/src/bin/upgrade-job/common/error.rs +++ b/k8s/upgrade/src/bin/upgrade-job/common/error.rs @@ -645,6 +645,19 @@ pub(crate) enum Error { std_err: String, }, + /// Error for when the yq command to set an object from another YAML file, returns an error. + #[snafu(display( + "`yq` set-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", + command, + args, + std_err, + ))] + YqSetObjectFromFileCommand { + command: String, + args: Vec, + std_err: String, + }, + /// Error for when the yq command to delete an object path returns an error. #[snafu(display( "`yq` delete-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs index 4c4506a06..2b894a6cc 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs @@ -173,11 +173,6 @@ impl CoreValues { self.csi.node_nvme_io_timeout() } - /// This is a getter for the grafana/loki container's image tag from the loki helm chart. - pub(crate) fn loki_stack_loki_image_tag(&self) -> &str { - self.loki_stack.loki_image_tag() - } - /// This is a getter for the promtail scrapeConfigs. pub(crate) fn loki_stack_promtail_scrape_configs(&self) -> &str { self.loki_stack.promtail_scrape_configs() @@ -187,14 +182,6 @@ impl CoreValues { self.loki_stack.deprecated_promtail_loki_address() } - pub(crate) fn loki_stack_promtail_config_file(&self) -> &str { - self.loki_stack.promtail_config_file() - } - - pub(crate) fn loki_stack_promtail_readiness_probe_path(&self) -> &str { - self.loki_stack.promtail_readiness_probe_path() - } - /// This returns the config.snippets.extraClientConfigs from the promtail helm chart v3.11.0. pub(crate) fn promtail_extra_client_configs(&self) -> &str { self.loki_stack.deprecated_promtail_extra_client_configs() @@ -447,7 +434,6 @@ impl CsiNodeNvme { /// This is used to deserialize the yaml object 'loki-stack'. #[derive(Deserialize)] struct LokiStack { - loki: Loki, promtail: Promtail, } @@ -462,48 +448,9 @@ impl LokiStack { self.promtail.deprecated_extra_client_configs() } - /// This is a getter for the grafana/loki container's image tag. - fn loki_image_tag(&self) -> &str { - self.loki.image_tag() - } - fn deprecated_promtail_loki_address(&self) -> &str { self.promtail.deprecated_loki_address() } - - fn promtail_config_file(&self) -> &str { - self.promtail.config_file() - } - - fn promtail_readiness_probe_path(&self) -> &str { - self.promtail.readiness_probe_http_get_path() - } -} - -/// This is used to deserialize the loki dependent helm chart's values set. -#[derive(Deserialize)] -struct Loki { - image: LokiImage, -} - -impl Loki { - /// This is a getter for the container image tag. - fn image_tag(&self) -> &str { - self.image.tag() - } -} - -/// This is used to deserialize the yaml object 'image' in the loki helm chart. -#[derive(Deserialize)] -struct LokiImage { - tag: String, -} - -impl LokiImage { - /// This is a getter for the image's tag. - fn tag(&self) -> &str { - self.tag.as_str() - } } /// This is used to deserialize the yaml object 'promtail'. @@ -511,7 +458,6 @@ impl LokiImage { #[serde(rename_all(deserialize = "camelCase"))] struct Promtail { config: PromtailConfig, - readiness_probe: PromtailReadinessProbe, } impl Promtail { @@ -528,14 +474,6 @@ impl Promtail { fn deprecated_loki_address(&self) -> &str { self.config.deprecated_loki_address() } - - fn config_file(&self) -> &str { - self.config.file() - } - - fn readiness_probe_http_get_path(&self) -> &str { - self.readiness_probe.http_get_path() - } } /// This is used to deserialize the promtail.config yaml object. @@ -543,7 +481,6 @@ impl Promtail { struct PromtailConfig { #[serde(default, rename(deserialize = "lokiAddress"))] deprecated_loki_address: String, - file: String, snippets: PromtailConfigSnippets, } @@ -562,10 +499,6 @@ impl PromtailConfig { fn deprecated_loki_address(&self) -> &str { self.deprecated_loki_address.as_str() } - - fn file(&self) -> &str { - self.file.as_str() - } } /// This is used to deserialize the config.snippets yaml object. @@ -589,29 +522,6 @@ impl PromtailConfigSnippets { } } -#[derive(Deserialize)] -#[serde(rename_all(deserialize = "camelCase"))] -struct PromtailReadinessProbe { - http_get: PromtailReadinessProbeHttpGet, -} - -impl PromtailReadinessProbe { - fn http_get_path(&self) -> &str { - self.http_get.path() - } -} - -#[derive(Deserialize)] -struct PromtailReadinessProbeHttpGet { - path: String, -} - -impl PromtailReadinessProbeHttpGet { - fn path(&self) -> &str { - self.path.as_str() - } -} - /// This is used to serialize the config.clients yaml object in promtail chart v6.13.1 /// when migrating from promtail v3.11.0 to v6.13.1. #[derive(Debug, Serialize)] diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs b/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs index 1264afceb..4aa48026b 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs @@ -129,6 +129,7 @@ impl HelmUpgraderBuilder { } .build(), )?; + let helm_args_set = self.helm_args_set.unwrap_or_default(); let helm_args_set_file = self.helm_args_set_file.unwrap_or_default(); diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs index 5829890d4..da9847cc4 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs @@ -39,7 +39,7 @@ use tempfile::NamedTempFile as TempFile; /// used with the yq-go binary. /// target_values_filepath --> This is simply the path to the values.yaml file for the target /// helm chart, which is available locally. -/// workdir --> This is the path to a directory that yq-go may use to write its output file +/// chart_dir --> This is the path to a directory that yq-go may use to write its output file /// into. The output file will be a merged values.yaml with special values set as per requirement /// (based on source_version and target_version). pub(crate) fn generate_values_yaml_file( @@ -49,7 +49,7 @@ pub(crate) fn generate_values_yaml_file( target_values: &CoreValues, source_values_buf: Vec, target_values_filepath: P, - workdir: Q, + chart_dir: Q, ) -> Result where P: AsRef, @@ -57,7 +57,7 @@ where { // Write the source_values buffer fetched from the installed helm release to a temporary file. let source_values_file: TempFile = - write_to_tempfile(Some(workdir.as_ref()), source_values_buf.as_slice())?; + write_to_tempfile(Some(chart_dir.as_ref()), source_values_buf.as_slice())?; // Resultant values yaml for helm upgrade command. // Merge the source values with the target values. @@ -65,7 +65,7 @@ where let upgrade_values_yaml = yq.merge_files(source_values_file.path(), target_values_filepath.as_ref())?; let upgrade_values_file: TempFile = - write_to_tempfile(Some(workdir), upgrade_values_yaml.as_slice())?; + write_to_tempfile(Some(chart_dir.as_ref()), upgrade_values_yaml.as_slice())?; // Not using semver::VersionReq because expressions like '>=2.1.0' don't include // 2.3.0-rc.0. 2.3.0, 2.4.0, etc. are supported. So, not using VersionReq in the @@ -175,9 +175,12 @@ where })?; if source_version.ge(&two_dot_o_rc_zero) && source_version.lt(&two_dot_six) { // Switch out image tag for the latest one. - yq.set_literal_value( + yq.set_object_from_file( + YamlKey::try_from(".image.tag")?, + chart_dir + .as_ref() + .join("charts/loki-stack/charts/loki/values.yaml"), YamlKey::try_from(".loki-stack.loki.image.tag")?, - target_values.loki_stack_loki_image_tag(), upgrade_values_file.path(), )?; // Delete deprecated objects. @@ -194,16 +197,27 @@ where upgrade_values_file.path(), )?; - loki_address_to_clients(target_values, upgrade_values_file.path(), &yq)?; + loki_address_to_clients(source_values, upgrade_values_file.path(), &yq)?; - yq.set_literal_value( + let promtail_values_file = chart_dir + .as_ref() + .join("charts/loki-stack/charts/promtail/values.yaml"); + yq.set_object_from_file( + YamlKey::try_from(".config.file")?, + promtail_values_file.as_path(), YamlKey::try_from(".loki-stack.promtail.config.file")?, - target_values.loki_stack_promtail_config_file(), upgrade_values_file.path(), )?; - yq.set_literal_value( + yq.set_object_from_file( + YamlKey::try_from(".initContainer")?, + promtail_values_file.as_path(), + YamlKey::try_from(".loki-stack.promtail.initContainer")?, + upgrade_values_file.path(), + )?; + yq.set_object_from_file( + YamlKey::try_from(".readinessProbe.httpGet.path")?, + promtail_values_file.as_path(), YamlKey::try_from(".loki-stack.promtail.readinessProbe.httpGet.path")?, - target_values.loki_stack_promtail_readiness_probe_path(), upgrade_values_file.path(), )?; } @@ -253,7 +267,7 @@ where /// Converts config.lokiAddress and config.snippets.extraClientConfigs from the promtail helm chart /// v3.11.0 to config.clients[] which is compatible with promtail helm chart v6.13.1. fn loki_address_to_clients( - target_values: &CoreValues, + source_values: &CoreValues, upgrade_values_filepath: &Path, yq: &YqV4, ) -> Result<()> { @@ -265,8 +279,9 @@ fn loki_address_to_clients( promtail_config_clients_yaml_key.clone(), upgrade_values_filepath, )?; - let loki_address = target_values.loki_stack_promtail_loki_address(); + let loki_address = source_values.loki_stack_promtail_loki_address(); let promtail_config_client = PromtailConfigClient::with_url(loki_address); + // Serializing to JSON, because the yq command requires the input in JSON. let promtail_config_client = serde_json::to_string(&promtail_config_client).context( SerializePromtailConfigClientToJson { object: promtail_config_client, @@ -280,15 +295,13 @@ fn loki_address_to_clients( // Merge the extraClientConfigs from the promtail v3.11.0 chart to the v6.13.1 chart's // config.clients block. Ref: https://github.com/grafana/helm-charts/issues/1214 // Ref: https://github.com/grafana/helm-charts/pull/1425 - if !target_values.promtail_extra_client_configs().is_empty() { - // Converting the YAML to a JSON because the yq command goes like this... - // yq '.config.clients[0] += {"tenant_id": "1", "basic_auth": {"username": "loki", - // "password": "secret"}}' file + if !source_values.promtail_extra_client_configs().is_empty() { + // Converting the YAML to a JSON because the yq command requires the object input as a JSON. let promtail_extra_client_config: serde_json::Value = serde_yaml::from_str( - target_values.promtail_extra_client_configs(), + source_values.promtail_extra_client_configs(), ) .context(DeserializePromtailExtraConfig { - config: target_values.promtail_extra_client_configs().to_string(), + config: source_values.promtail_extra_client_configs().to_string(), })?; let promtail_extra_client_config = serde_json::to_string(&promtail_extra_client_config) .context(SerializePromtailExtraConfigToJson { diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs index 16804e192..3c0e3a70d 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs @@ -2,7 +2,7 @@ use crate::{ common::error::{ NotAValidYamlKeyForStringValue, NotYqV4, RegexCompile, Result, U8VectorToString, YqAppendToArrayCommand, YqAppendToObjectCommand, YqCommandExec, YqDeleteObjectCommand, - YqMergeCommand, YqSetCommand, YqVersionCommand, + YqMergeCommand, YqSetCommand, YqSetObjectFromFileCommand, YqVersionCommand, }, vec_to_strings, }; @@ -126,7 +126,7 @@ impl YqV4 { Ok(()) } - /// Append objects to yaml arrays. + /// Append fields to yaml objects. pub(crate) fn append_to_object(&self, key: YamlKey, value: V, filepath: P) -> Result<()> where V: Display + Sized, @@ -153,14 +153,14 @@ impl YqV4 { Ok(()) } - /// Use the yq 'delpaths' operator to delete objects from a yaml file. + /// Use the yq 'del' operator to delete objects from a yaml file. pub(crate) fn delete_object

(&self, key: YamlKey, filepath: P) -> Result<()> where P: AsRef, { let yq_delete_object_args = vec_to_strings![ "-i", - format!(r#"delpaths([["{}"]])"#, key.as_str()), + format!(r#"del({})"#, key.as_str()), filepath.as_ref().to_string_lossy() ]; let yq_delete_object_output = self.command_output(yq_delete_object_args.clone())?; @@ -308,6 +308,47 @@ impl YqV4 { Ok(()) } + /// This sets in-place yaml objects into a YAML file, from a field in another YAML file. + /// The 'source' args are the source of the object (where to take it from). + /// The 'target' args are the destination of the object (where to make the actual change). + pub(crate) fn set_object_from_file( + &self, + source_key: YamlKey, + source_filepath: P, + target_key: YamlKey, + target_filepath: Q, + ) -> Result<()> + where + P: AsRef, + Q: AsRef, + { + let yq_object_from_file_args = vec_to_strings![ + "-i", + format!( + r#"{} = load("{}"){}"#, + target_key.as_str(), + source_filepath.as_ref().to_string_lossy(), + source_key.as_str() + ), + target_filepath.as_ref().to_string_lossy() + ]; + + let yq_object_from_file_output = self.command_output(yq_object_from_file_args.clone())?; + + ensure!( + yq_object_from_file_output.status.success(), + YqSetObjectFromFileCommand { + command: self.command_as_str().to_string(), + args: yq_object_from_file_args, + std_err: str::from_utf8(yq_object_from_file_output.stderr.as_slice()) + .context(U8VectorToString)? + .to_string() + } + ); + + Ok(()) + } + /// Returns an std::process::Command using the command_as_str member's value. fn command(&self) -> Command { Command::new(self.command_name.clone()) From e8576b6a7bed28120f8352db3fa675ec30f0a917 Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Thu, 4 Jan 2024 14:21:47 +0000 Subject: [PATCH 5/9] feat(scripts): unpack subchart archives when building upgrade-job container Signed-off-by: Niladri Halder --- scripts/release.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/release.sh b/scripts/release.sh index 2f5ea20e7..898a24b7d 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -242,8 +242,13 @@ for name in $IMAGES; do echo "Updating helm chart dependencies..." # Helm chart directory path -- /scripts --> /chart CHART_DIR="${SCRIPT_DIR}/../chart" + dep_chart_dir="${CHART_DIR}/charts" $NIX_SHELL --run "helm dependency update ${CHART_DIR}" + for dep_chart_tar in "${dep_chart_dir}"/*.tgz; do + tar -xf "${dep_chart_tar}" -C "${dep_chart_dir}" + rm -f "${dep_chart_tar}" + done # Set flag to true _helm_dependencies_updated=true From 6f3e635e2acda6230722d46d5081cfc8506e73c9 Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Fri, 5 Jan 2024 07:39:52 +0000 Subject: [PATCH 6/9] chore(scripts/release): add clean-up for the helm dependency update Signed-off-by: Niladri Halder --- scripts/release.sh | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 898a24b7d..29f877055 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -8,7 +8,7 @@ CI=${CI-} set -euo pipefail - +trap 'cleanup_and_exit "$?"' EXIT # Test if the image already exists in dockerhub dockerhub_tag_exists() { curl --silent -f -lSL https://hub.docker.com/v2/repositories/$1/tags/$2 1>/dev/null 2>&1 @@ -30,6 +30,17 @@ nix_experimental() { echo -n " " fi } +cleanup_and_exit() { + local -r status=${1} + + # Remove helm subcharts, if `helm dependency update` was run. + if [ "$_helm_dependencies_updated" = "true" ]; then + echo "Cleaning up helm chart dependencies..." + rm -rf "$CHART_DIR"/charts + fi + + exit "$status" +} help() { cat < /chart CHART_DIR="${SCRIPT_DIR}/../chart" - dep_chart_dir="${CHART_DIR}/charts" $NIX_SHELL --run "helm dependency update ${CHART_DIR}" - for dep_chart_tar in "${dep_chart_dir}"/*.tgz; do - tar -xf "${dep_chart_tar}" -C "${dep_chart_dir}" - rm -f "${dep_chart_tar}" - done # Set flag to true - _helm_dependencies_updated=true + _helm_dependencies_updated="true" break fi done From 5a9de004e65d6cc63ebf4fe8cc084422e61430ec Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Tue, 9 Jan 2024 14:23:21 +0000 Subject: [PATCH 7/9] refactor(upgrade-job): set values using deserialized set Signed-off-by: Niladri Halder --- .../src/bin/upgrade-job/common/error.rs | 25 +++-- k8s/upgrade/src/bin/upgrade-job/helm/chart.rs | 97 +++++++++++++++++++ .../src/bin/upgrade-job/helm/upgrade.rs | 1 - .../src/bin/upgrade-job/helm/values.rs | 39 ++++---- .../src/bin/upgrade-job/helm/yaml/yq.rs | 43 +------- scripts/release.sh | 9 ++ 6 files changed, 138 insertions(+), 76 deletions(-) diff --git a/k8s/upgrade/src/bin/upgrade-job/common/error.rs b/k8s/upgrade/src/bin/upgrade-job/common/error.rs index beb5245b3..3e4ed81f0 100644 --- a/k8s/upgrade/src/bin/upgrade-job/common/error.rs +++ b/k8s/upgrade/src/bin/upgrade-job/common/error.rs @@ -6,6 +6,7 @@ use crate::{ events::event_recorder::EventNote, helm::chart::PromtailConfigClient, }; +use k8s_openapi::api::core::v1::Container; use snafu::Snafu; use std::path::PathBuf; use url::Url; @@ -438,6 +439,17 @@ pub(crate) enum Error { object: PromtailConfigClient, }, + /// Error in serializing a k8s_openapi::api::core::v1::Container to a JSON string. + #[snafu(display( + "Failed to serialize .loki-stack.promtail.initContainer {:?}: {}", + object, + source + ))] + SerializePromtailInitContainerToJson { + source: serde_json::Error, + object: Container, + }, + /// Error in deserializing a promtail helm chart's deprecated extraClientConfig to a /// serde_json::Value. #[snafu(display( @@ -645,19 +657,6 @@ pub(crate) enum Error { std_err: String, }, - /// Error for when the yq command to set an object from another YAML file, returns an error. - #[snafu(display( - "`yq` set-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", - command, - args, - std_err, - ))] - YqSetObjectFromFileCommand { - command: String, - args: Vec, - std_err: String, - }, - /// Error for when the yq command to delete an object path returns an error. #[snafu(display( "`yq` delete-object-command returned an error,\ncommand: {},\nargs: {:?},\nstd_err: {}", diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs index 2b894a6cc..1bb77d5f3 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs @@ -1,4 +1,5 @@ use crate::common::error::{ReadingFile, U8VectorToString, YamlParseFromFile, YamlParseFromSlice}; +use k8s_openapi::api::core::v1::{Container, Probe}; use semver::Version; use serde::{Deserialize, Serialize}; use snafu::ResultExt; @@ -173,11 +174,22 @@ impl CoreValues { self.csi.node_nvme_io_timeout() } + /// This is a getter for the grafana/loki container image tag. + pub(crate) fn loki_stack_loki_image_tag(&self) -> &str { + self.loki_stack.loki_image_tag() + } + /// This is a getter for the promtail scrapeConfigs. pub(crate) fn loki_stack_promtail_scrape_configs(&self) -> &str { self.loki_stack.promtail_scrape_configs() } + /// This returns the value of 'promtail.config.file'. + pub(crate) fn loki_stack_promtail_config_file(&self) -> &str { + self.loki_stack.promtail_config_file() + } + + /// This returns the value of the deprecated promtail helm chart field 'config.lokiAddress'. pub(crate) fn loki_stack_promtail_loki_address(&self) -> &str { self.loki_stack.deprecated_promtail_loki_address() } @@ -186,6 +198,14 @@ impl CoreValues { pub(crate) fn promtail_extra_client_configs(&self) -> &str { self.loki_stack.deprecated_promtail_extra_client_configs() } + + pub(crate) fn promtail_init_container(&self) -> Vec { + self.loki_stack.promtail_init_container() + } + + pub(crate) fn promtail_readiness_probe_http_get_path(&self) -> String { + self.loki_stack.promtail_readiness_probe_http_get_path() + } } /// This is used to deserialize the yaml object agents. @@ -434,6 +454,7 @@ impl CsiNodeNvme { /// This is used to deserialize the yaml object 'loki-stack'. #[derive(Deserialize)] struct LokiStack { + loki: Loki, promtail: Promtail, } @@ -443,6 +464,10 @@ impl LokiStack { self.promtail.scrape_configs() } + fn promtail_config_file(&self) -> &str { + self.promtail.config_file() + } + /// This returns the config.snippets.extraClientConfigs from the promtail helm chart v3.11.0. fn deprecated_promtail_extra_client_configs(&self) -> &str { self.promtail.deprecated_extra_client_configs() @@ -451,6 +476,42 @@ impl LokiStack { fn deprecated_promtail_loki_address(&self) -> &str { self.promtail.deprecated_loki_address() } + + fn loki_image_tag(&self) -> &str { + self.loki.image_tag() + } + + fn promtail_init_container(&self) -> Vec { + self.promtail.init_container() + } + + fn promtail_readiness_probe_http_get_path(&self) -> String { + self.promtail.readiness_probe_http_get_path() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.loki'. +#[derive(Deserialize)] +struct Loki { + image: LokiImage, +} + +impl Loki { + fn image_tag(&self) -> &str { + self.image.tag() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.loki.image'. +#[derive(Deserialize)] +struct LokiImage { + tag: String, +} + +impl LokiImage { + fn tag(&self) -> &str { + self.tag.as_str() + } } /// This is used to deserialize the yaml object 'promtail'. @@ -458,6 +519,8 @@ impl LokiStack { #[serde(rename_all(deserialize = "camelCase"))] struct Promtail { config: PromtailConfig, + init_container: PromtailInitContainer, + readiness_probe: Probe, } impl Promtail { @@ -466,6 +529,11 @@ impl Promtail { self.config.scrape_configs() } + /// This returns 'promtail.config.file'. + fn config_file(&self) -> &str { + self.config.file() + } + /// This returns the config.snippets.extraClientConfigs from the promtail helm chart v3.11.0. fn deprecated_extra_client_configs(&self) -> &str { self.config.deprecated_extra_client_configs() @@ -474,6 +542,22 @@ impl Promtail { fn deprecated_loki_address(&self) -> &str { self.config.deprecated_loki_address() } + + fn init_container(&self) -> Vec { + match &self.init_container { + PromtailInitContainer::DeprecatedInitContainer {} => Vec::::default(), + PromtailInitContainer::InitContainer(containers) => containers.clone(), + } + } + + fn readiness_probe_http_get_path(&self) -> String { + self.readiness_probe + .http_get + .clone() + .unwrap_or_default() + .path + .unwrap_or_default() + } } /// This is used to deserialize the promtail.config yaml object. @@ -481,6 +565,7 @@ impl Promtail { struct PromtailConfig { #[serde(default, rename(deserialize = "lokiAddress"))] deprecated_loki_address: String, + file: String, snippets: PromtailConfigSnippets, } @@ -490,6 +575,11 @@ impl PromtailConfig { self.snippets.scrape_configs() } + /// This returns the config.file multi-line literal. + fn file(&self) -> &str { + self.file.as_str() + } + /// This returns the snippets.extraClientConfigs from the promtail helm chart v3.11.0. fn deprecated_extra_client_configs(&self) -> &str { self.snippets.deprecated_extra_client_configs() @@ -522,6 +612,13 @@ impl PromtailConfigSnippets { } } +#[derive(Deserialize)] +#[serde(untagged)] +enum PromtailInitContainer { + DeprecatedInitContainer {}, + InitContainer(Vec), +} + /// This is used to serialize the config.clients yaml object in promtail chart v6.13.1 /// when migrating from promtail v3.11.0 to v6.13.1. #[derive(Debug, Serialize)] diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs b/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs index 4aa48026b..1264afceb 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/upgrade.rs @@ -129,7 +129,6 @@ impl HelmUpgraderBuilder { } .build(), )?; - let helm_args_set = self.helm_args_set.unwrap_or_default(); let helm_args_set_file = self.helm_args_set_file.unwrap_or_default(); diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs index da9847cc4..cd9b6906c 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs @@ -6,6 +6,7 @@ use crate::{ error::{ DeserializePromtailExtraConfig, Result, SemverParse, SerializePromtailConfigClientToJson, SerializePromtailExtraConfigToJson, + SerializePromtailInitContainerToJson, }, file::write_to_tempfile, }, @@ -175,12 +176,9 @@ where })?; if source_version.ge(&two_dot_o_rc_zero) && source_version.lt(&two_dot_six) { // Switch out image tag for the latest one. - yq.set_object_from_file( - YamlKey::try_from(".image.tag")?, - chart_dir - .as_ref() - .join("charts/loki-stack/charts/loki/values.yaml"), + yq.set_literal_value( YamlKey::try_from(".loki-stack.loki.image.tag")?, + target_values.loki_stack_loki_image_tag(), upgrade_values_file.path(), )?; // Delete deprecated objects. @@ -199,25 +197,26 @@ where loki_address_to_clients(source_values, upgrade_values_file.path(), &yq)?; - let promtail_values_file = chart_dir - .as_ref() - .join("charts/loki-stack/charts/promtail/values.yaml"); - yq.set_object_from_file( - YamlKey::try_from(".config.file")?, - promtail_values_file.as_path(), + yq.set_literal_value( YamlKey::try_from(".loki-stack.promtail.config.file")?, + target_values.loki_stack_promtail_config_file(), upgrade_values_file.path(), )?; - yq.set_object_from_file( - YamlKey::try_from(".initContainer")?, - promtail_values_file.as_path(), - YamlKey::try_from(".loki-stack.promtail.initContainer")?, - upgrade_values_file.path(), - )?; - yq.set_object_from_file( - YamlKey::try_from(".readinessProbe.httpGet.path")?, - promtail_values_file.as_path(), + for container in target_values.promtail_init_container() { + let init_container = serde_json::to_string(&container).context( + SerializePromtailInitContainerToJson { + object: container.clone(), + }, + )?; + yq.append_to_object( + YamlKey::try_from(".loki-stack.promtail.initContainer")?, + init_container, + upgrade_values_file.path(), + )?; + } + yq.set_literal_value( YamlKey::try_from(".loki-stack.promtail.readinessProbe.httpGet.path")?, + target_values.promtail_readiness_probe_http_get_path(), upgrade_values_file.path(), )?; } diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs index 3c0e3a70d..9b62fc305 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/yaml/yq.rs @@ -2,7 +2,7 @@ use crate::{ common::error::{ NotAValidYamlKeyForStringValue, NotYqV4, RegexCompile, Result, U8VectorToString, YqAppendToArrayCommand, YqAppendToObjectCommand, YqCommandExec, YqDeleteObjectCommand, - YqMergeCommand, YqSetCommand, YqSetObjectFromFileCommand, YqVersionCommand, + YqMergeCommand, YqSetCommand, YqVersionCommand, }, vec_to_strings, }; @@ -308,47 +308,6 @@ impl YqV4 { Ok(()) } - /// This sets in-place yaml objects into a YAML file, from a field in another YAML file. - /// The 'source' args are the source of the object (where to take it from). - /// The 'target' args are the destination of the object (where to make the actual change). - pub(crate) fn set_object_from_file( - &self, - source_key: YamlKey, - source_filepath: P, - target_key: YamlKey, - target_filepath: Q, - ) -> Result<()> - where - P: AsRef, - Q: AsRef, - { - let yq_object_from_file_args = vec_to_strings![ - "-i", - format!( - r#"{} = load("{}"){}"#, - target_key.as_str(), - source_filepath.as_ref().to_string_lossy(), - source_key.as_str() - ), - target_filepath.as_ref().to_string_lossy() - ]; - - let yq_object_from_file_output = self.command_output(yq_object_from_file_args.clone())?; - - ensure!( - yq_object_from_file_output.status.success(), - YqSetObjectFromFileCommand { - command: self.command_as_str().to_string(), - args: yq_object_from_file_args, - std_err: str::from_utf8(yq_object_from_file_output.stderr.as_slice()) - .context(U8VectorToString)? - .to_string() - } - ); - - Ok(()) - } - /// Returns an std::process::Command using the command_as_str member's value. fn command(&self) -> Command { Command::new(self.command_name.clone()) diff --git a/scripts/release.sh b/scripts/release.sh index 29f877055..05a203569 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -253,8 +253,17 @@ for name in $IMAGES; do echo "Updating helm chart dependencies..." # Helm chart directory path -- /scripts --> /chart CHART_DIR="${SCRIPT_DIR}/../chart" + dep_chart_dir="${CHART_DIR}/charts" + # This performs a dependency update and then extracts the tarballs pulled. + # If and when the `--untar` functionality is added to the `helm dependency + # update` command, the for block can be removed in favour of the `--untar` option. + # Ref: https://github.com/helm/helm/issues/8479 $NIX_SHELL --run "helm dependency update ${CHART_DIR}" + for dep_chart_tar in "${dep_chart_dir}"/*.tgz; do + tar -xf "${dep_chart_tar}" -C "${dep_chart_dir}" + rm -f "${dep_chart_tar}" + done # Set flag to true _helm_dependencies_updated="true" From ab9630284d23f2cd53f7dab26caa1bccf1f4e838 Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Tue, 9 Jan 2024 15:47:56 +0000 Subject: [PATCH 8/9] feat(upgrade-job): add set-value for loki-stack subcharts Signed-off-by: Niladri Halder --- k8s/upgrade/src/bin/upgrade-job/helm/chart.rs | 168 ++++++++++++++++++ .../src/bin/upgrade-job/helm/values.rs | 25 +++ 2 files changed, 193 insertions(+) diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs index 1bb77d5f3..303c786d6 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/chart.rs @@ -199,13 +199,42 @@ impl CoreValues { self.loki_stack.deprecated_promtail_extra_client_configs() } + /// This returns the initContainers array from the promtail chart v6.13.1. pub(crate) fn promtail_init_container(&self) -> Vec { self.loki_stack.promtail_init_container() } + /// This returns the readinessProbe HTTP Get path from the promtail chart v6.13.1. pub(crate) fn promtail_readiness_probe_http_get_path(&self) -> String { self.loki_stack.promtail_readiness_probe_http_get_path() } + + /// This returns the image tag from the filebeat helm chart. Filebeat is a part of the + /// loki-stack chart. + pub(crate) fn filebeat_image_tag(&self) -> &str { + self.loki_stack.filebeat_image_tag() + } + + /// This returns the image tag from the logstash helm chart. Logstash is a part of the + /// loki-stack chart. + pub(crate) fn logstash_image_tag(&self) -> &str { + self.loki_stack.logstash_image_tag() + } + + /// This returns the image tag for the curlimages/curl container. + pub(crate) fn grafana_download_dashboards_image_tag(&self) -> &str { + self.loki_stack.grafana_download_dashboards_image_tag() + } + + /// This returns the image tag for the grafana/grafana container. + pub(crate) fn grafana_image_tag(&self) -> &str { + self.loki_stack.grafana_image_tag() + } + + /// This returns the image tag for the kiwigrid/k8s-sidecar container. + pub(crate) fn grafana_sidecar_image_tag(&self) -> &str { + self.loki_stack.grafana_sidecar_image_tag() + } } /// This is used to deserialize the yaml object agents. @@ -454,6 +483,9 @@ impl CsiNodeNvme { /// This is used to deserialize the yaml object 'loki-stack'. #[derive(Deserialize)] struct LokiStack { + filebeat: Filebeat, + grafana: Grafana, + logstash: Logstash, loki: Loki, promtail: Promtail, } @@ -464,6 +496,7 @@ impl LokiStack { self.promtail.scrape_configs() } + /// This is a getter for the value of 'promtail.config.file'. fn promtail_config_file(&self) -> &str { self.promtail.config_file() } @@ -473,21 +506,156 @@ impl LokiStack { self.promtail.deprecated_extra_client_configs() } + /// This is a getter for the deprecated 'lokiAddress' field in the promtail helm chart v3.11.0. fn deprecated_promtail_loki_address(&self) -> &str { self.promtail.deprecated_loki_address() } + /// This is a getter for the loki/loki container's image tag. fn loki_image_tag(&self) -> &str { self.loki.image_tag() } + /// This is a getter for the initContainer array from promtail chart v6.13.1. fn promtail_init_container(&self) -> Vec { self.promtail.init_container() } + /// This is a getter for the readinessProbe HTTP GET path from promtail chart v6.13.1. fn promtail_readiness_probe_http_get_path(&self) -> String { self.promtail.readiness_probe_http_get_path() } + + /// This is a getter for the filebeat image tag from the loki-stack helm chart. + fn filebeat_image_tag(&self) -> &str { + self.filebeat.image_tag() + } + + /// This is a getter for the logstash image tag from the loki-stack helm chart. + fn logstash_image_tag(&self) -> &str { + self.logstash.image_tag() + } + + /// This is a getter for the curlimages/curl container's image tag from the grafana chart. + fn grafana_download_dashboards_image_tag(&self) -> &str { + self.grafana.download_dashboards_image_tag() + } + + /// This is a getter for the grafana/grafana container's image tag from the grafana chart. + fn grafana_image_tag(&self) -> &str { + self.grafana.image_tag() + } + + /// This is a getter for the kiwigrid/k8s-sidecar container's image tag from the grafana chart. + fn grafana_sidecar_image_tag(&self) -> &str { + self.grafana.sidecar_image_tag() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.filebeat'. +#[derive(Deserialize)] +#[serde(rename_all(deserialize = "camelCase"))] +struct Filebeat { + image_tag: String, +} + +impl Filebeat { + /// This is a getter for the Filebeat image tag. + fn image_tag(&self) -> &str { + self.image_tag.as_str() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.grafana'. +#[derive(Deserialize)] +#[serde(rename_all(deserialize = "camelCase"))] +struct Grafana { + download_dashboards_image: GrafanaDownloadDashboardsImage, + image: GrafanaImage, + sidecar: GrafanaSidecar, +} + +impl Grafana { + /// This is getter for the curlimages/curl container image tag. + fn download_dashboards_image_tag(&self) -> &str { + self.download_dashboards_image.tag() + } + + /// This is getter for the grafana/grafana container image tag. + fn image_tag(&self) -> &str { + self.image.tag() + } + + /// This is a getter for the kiwigrid/k8s-sidecar sidecar container image tag. + fn sidecar_image_tag(&self) -> &str { + self.sidecar.image_tag() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.grafana.downloadDashboardsImage'. +#[derive(Deserialize)] +struct GrafanaDownloadDashboardsImage { + tag: String, +} + +impl GrafanaDownloadDashboardsImage { + /// This is a getter for the curlimages/curl container image on the grafana chart. + fn tag(&self) -> &str { + self.tag.as_str() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.grafana.image'. +#[derive(Deserialize)] +struct GrafanaImage { + tag: String, +} + +impl GrafanaImage { + /// This is a getter for the grafana/grafana container image on the grafana chart. + fn tag(&self) -> &str { + self.tag.as_str() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.grafana.sidecar'. +#[derive(Deserialize)] +struct GrafanaSidecar { + image: GrafanaSidecarImage, +} + +impl GrafanaSidecar { + /// This is a getter for the kiwigrid/k8s-sidecar sidecar container image tag. + fn image_tag(&self) -> &str { + self.image.tag() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.grafana.sidecar.image'. +#[derive(Deserialize)] +struct GrafanaSidecarImage { + tag: String, +} + +impl GrafanaSidecarImage { + /// This is a getter for the kiwigrid/k8s-sidecar container image on the grafana chart. + fn tag(&self) -> &str { + self.tag.as_str() + } +} + +/// This is used to deserialize the YAML object 'loki-stack.logstash'. +#[derive(Deserialize)] +#[serde(rename_all(deserialize = "camelCase"))] +struct Logstash { + image_tag: String, +} + +impl Logstash { + /// This is a getter for the Logstash image tag. + fn image_tag(&self) -> &str { + self.image_tag.as_str() + } } /// This is used to deserialize the YAML object 'loki-stack.loki'. diff --git a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs index cd9b6906c..0a98e7437 100644 --- a/k8s/upgrade/src/bin/upgrade-job/helm/values.rs +++ b/k8s/upgrade/src/bin/upgrade-job/helm/values.rs @@ -181,6 +181,31 @@ where target_values.loki_stack_loki_image_tag(), upgrade_values_file.path(), )?; + yq.set_literal_value( + YamlKey::try_from(".loki-stack.filebeat.imageTag")?, + target_values.filebeat_image_tag(), + upgrade_values_file.path(), + )?; + yq.set_literal_value( + YamlKey::try_from(".loki-stack.logstash.imageTag")?, + target_values.logstash_image_tag(), + upgrade_values_file.path(), + )?; + yq.set_literal_value( + YamlKey::try_from(".loki-stack.grafana.downloadDashboardsImage.tag")?, + target_values.grafana_download_dashboards_image_tag(), + upgrade_values_file.path(), + )?; + yq.set_literal_value( + YamlKey::try_from(".loki-stack.grafana.image.tag")?, + target_values.grafana_image_tag(), + upgrade_values_file.path(), + )?; + yq.set_literal_value( + YamlKey::try_from(".loki-stack.grafana.sidecar.image.tag")?, + target_values.grafana_sidecar_image_tag(), + upgrade_values_file.path(), + )?; // Delete deprecated objects. yq.delete_object( YamlKey::try_from(".loki-stack.loki.config.ingester.lifecycler.ring.kvstore")?, From fe0b5cec401381fef6db648ac60a6d85563313a9 Mon Sep 17 00:00:00 2001 From: Niladri Halder Date: Tue, 9 Jan 2024 15:48:58 +0000 Subject: [PATCH 9/9] ci(scripts/helm): add consolidated helm values generator Signed-off-by: Niladri Halder --- nix/pkgs/images/default.nix | 17 ++++- scripts/helm/generate-consolidated-values.sh | 80 ++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) create mode 100755 scripts/helm/generate-consolidated-values.sh diff --git a/nix/pkgs/images/default.nix b/nix/pkgs/images/default.nix index b909096ab..fab5645ef 100644 --- a/nix/pkgs/images/default.nix +++ b/nix/pkgs/images/default.nix @@ -5,7 +5,7 @@ { dockerTools, lib, extensions, busybox, gnupg, kubernetes-helm-wrapped, semver-tool, yq-go, runCommand, img_tag ? "" }: let whitelistSource = extensions.project-builder.whitelistSource; - helm_chart = whitelistSource ../../.. [ "chart" "scripts/helm" ] "mayastor-extensions"; + helm_chart = whitelistSource ../../.. [ "chart" "scripts/helm" "scripts/utils" ] "mayastor-extensions"; image_suffix = { "release" = ""; "debug" = "-debug"; "coverage" = "-coverage"; }; tag = if img_tag != "" then img_tag else extensions.version; build-extensions-image = { pname, buildType, package, extraCommands ? '''', copyToRoot ? [ ], config ? { } }: @@ -36,10 +36,12 @@ let } '' mkdir -p build && cp -drf ${helm_chart}/* build - chmod +w build/scripts/helm - chmod +w build/chart - chmod +w build/chart/*.yaml + chmod -R +w build/scripts/helm + chmod -R +w build/scripts/utils + chmod -R +w build/chart patchShebangs build/scripts/helm/publish-chart-yaml.sh + patchShebangs build/scripts/helm/generate-consolidated-values.sh + patchShebangs build/scripts/utils/log.sh # if tag is not semver just keep whatever is checked-in # todo: handle this properly? @@ -49,6 +51,13 @@ let [[ ! ${tag} =~ ^(v?[0-9]+\.[0-9]+\.[0-9]+-0-main-unstable(-[0-9]+){6}-0)$ ]]; then CHART_FILE=build/chart/Chart.yaml build/scripts/helm/publish-chart-yaml.sh --app-tag ${tag} --override-index "" fi + + # This modifies the build helm chart in-place with missing values of the + # dependent charts, i.e. the values from the dependent helm charts which + # are left out of the main helm chart, are set explicitly to their default + # values and are included into the main helm chart. + build/scripts/helm/generate-consolidated-values.sh -d build/chart + chmod -w build/chart chmod -w build/chart/*.yaml diff --git a/scripts/helm/generate-consolidated-values.sh b/scripts/helm/generate-consolidated-values.sh new file mode 100755 index 000000000..f8c6d6950 --- /dev/null +++ b/scripts/helm/generate-consolidated-values.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -o errexit + +SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]:-"$0"}")")" +DEFAULT_CHART_DIR="$SCRIPT_DIR/../../chart" +CHART_DIR="$DEFAULT_CHART_DIR" + +# Imports +source "$SCRIPT_DIR/../utils/log.sh" + +# Print usage options for this script. +print_help() { + cat < Specify the helm chart directory (default "$DEFAULT_CHART_DIR") + +Examples: + $(basename "${0}") --chart-dir "./chart" +EOF +} + +# Parse arguments. +parse_args() { + while test $# -gt 0; do + arg="$1" + case "$arg" in + -d | --chart-dir) + test $# -lt 2 && log_fatal "missing value for the optional argument '$arg'" + CHART_DIR="${2%/}" + shift + ;; + -d=* | --chart-dir=*) + CHART_DIR="${arg#*=}" + ;; + -h* | --help*) + print_help + exit 0 + ;; + *) + print_help + log_fatal "unexpected argument '$arg'" + ;; + esac + shift + done +} + +# Generate in-place consolidated values YAMLs throughout the +# helm chart hierarchy (root chart and sub-charts). +consolidate() { + local -r chart_dir="$1" + local -r chart_name="${chart_dir##*/}" + + if stat "$chart_dir"/charts &> /dev/null; then + for dir in "$chart_dir"/charts/*; do + consolidate "$dir" + done + fi + + if [[ $(yq ".$chart_name" "$chart_dir"/../../values.yaml) == null ]]; then + yq -i ".$chart_name = {}" "$chart_dir"/../../values.yaml + fi + + yq -i ".$chart_name |= (load(\"$chart_dir/values.yaml\") * .)" "$chart_dir"/../../values.yaml +} + +# Parse CLI args. +parse_args "$@" + +if ! stat "$CHART_DIR"/charts &> /dev/null; then + exit 0 +fi + +for dir in "$CHART_DIR"/charts/*; do + consolidate "$dir" +done