diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f5aee6..0c3e93f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Support the annotation `restarter.stackable.tech/ignore` on ConfigMaps and Secrets and the + annotations `restarter.stackable.tech/ignore-configmap.x` and + `restarter.stackable.tech/ignore-secret.x` on StatefulSets to exclude ConfigMaps and Secrets from + the restarter controller ([#410]). + +[#410]: https://github.com/stackabletech/commons-operator/pull/410 + ## [26.3.0] - 2026-03-16 ## [26.3.0-rc1] - 2026-03-16 diff --git a/Cargo.lock b/Cargo.lock index 038e8ea..37b5cbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2428,9 +2428,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", diff --git a/Cargo.nix b/Cargo.nix index c6cadaa..c1f0ce5 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4801,7 +4801,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "k8s_version"; authors = [ @@ -8178,9 +8178,9 @@ rec { }; "rustls-webpki" = rec { crateName = "rustls-webpki"; - version = "0.103.9"; + version = "0.103.10"; edition = "2021"; - sha256 = "0lwg1nnyv7pp2lfwwjhy81bxm233am99jnsp3iymdhd6k8827pyp"; + sha256 = "1vyipcdbazvhl6kyi1m8n0bg98sk25iv12bby2xcly653awb4cyz"; libName = "webpki"; dependencies = [ { @@ -9293,7 +9293,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "stackable_certs"; authors = [ @@ -9391,7 +9391,7 @@ rec { "stackable-commons-operator" = rec { crateName = "stackable-commons-operator"; version = "0.0.0-dev"; - edition = "2021"; + edition = "2024"; crateBin = [ { name = "stackable-commons-operator"; @@ -9479,7 +9479,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "stackable_operator"; authors = [ @@ -9651,7 +9651,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9686,7 +9686,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "stackable_shared"; authors = [ @@ -9767,7 +9767,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "stackable_telemetry"; authors = [ @@ -9877,7 +9877,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "stackable_versioned"; authors = [ @@ -9921,7 +9921,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -9989,7 +9989,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; rev = "8425ce312cfadcc49c157bada79cac04c3ad5229"; - sha256 = "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c"; + sha256 = "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3"; }; libName = "stackable_webhook"; authors = [ diff --git a/Cargo.toml b/Cargo.toml index 76451a1..f205682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ resolver = "2" version = "0.0.0-dev" authors = ["Stackable GmbH "] license = "OSL-3.0" -edition = "2021" +edition = "2024" repository = "https://github.com/stackabletech/commons-operator" [workspace.dependencies] diff --git a/crate-hashes.json b/crate-hashes.json index 6839c8b..2bebff2 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -4,14 +4,14 @@ "git+https://github.com/kube-rs/kube-rs?rev=fe69cc486ff8e62a7da61d64ec3ebbd9e64c43b5#kube-derive@3.0.1": "1irm4g79crlxjm3iqrgvx0f6wxdcj394ky84q89pk9i36y2mlw3n", "git+https://github.com/kube-rs/kube-rs?rev=fe69cc486ff8e62a7da61d64ec3ebbd9e64c43b5#kube-runtime@3.0.1": "1irm4g79crlxjm3iqrgvx0f6wxdcj394ky84q89pk9i36y2mlw3n", "git+https://github.com/kube-rs/kube-rs?rev=fe69cc486ff8e62a7da61d64ec3ebbd9e64c43b5#kube@3.0.1": "1irm4g79crlxjm3iqrgvx0f6wxdcj394ky84q89pk9i36y2mlw3n", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#k8s-version@0.1.3": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-certs@0.4.0": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-operator-derive@0.3.1": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-operator@0.107.1": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-shared@0.1.0": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-telemetry@0.6.2": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-versioned-macros@0.8.3": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-versioned@0.8.3": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-webhook@0.9.0": "1yg7hbpgclp1zvfnhi4qkrwbgsa19v86plh77vqvwxzdxxxvxr4c", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#k8s-version@0.1.3": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-certs@0.4.0": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-operator-derive@0.3.1": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-operator@0.107.1": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-shared@0.1.0": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-telemetry@0.6.2": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-versioned-macros@0.8.3": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-versioned@0.8.3": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.107.1#stackable-webhook@0.9.0": "08ahxagis53c7bxnj53xgzv5l619av1lwfc67cswrsp2wcakzns3", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/docs/modules/commons-operator/pages/restarter.adoc b/docs/modules/commons-operator/pages/restarter.adoc index e2e2c8b..9f951bc 100644 --- a/docs/modules/commons-operator/pages/restarter.adoc +++ b/docs/modules/commons-operator/pages/restarter.adoc @@ -30,3 +30,61 @@ Label:: `restarter.stackable.tech/enabled` The operator can restart StatefulSets when any referenced configuration object (ConfigMap or Secret) changes. To enable this, set the `restarter.stackable.tech/enabled` label on the StatefulSet to `true`. + +Annotation:: `restarter.stackable.tech/ignore-configmap.*` +Annotation:: `restarter.stackable.tech/ignore-secret.*` + +These annotations can be added if the restarter is enabled on a StatefulSet, but some ConfigMaps or Secrets should be excluded from triggering a restart. `*` can be replaced with any value and is only used to make the annotation key unique. + +[source,yaml] +---- +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: statefulset-with-enabled-restarter + labels: + restarter.stackable.tech/enabled: "true" + annotations: + restarter.stackable.tech/ignore-configmap.0: hot-reloaded-configmap + restarter.stackable.tech/ignore-secret.0: hot-reloaded-secret +spec: + template: + spec: + volumes: + - name: configuration + configMap: + name: hot-reloaded-configmap + - name: credentials + secret: + secretName: hot-reloaded-secret +... +---- + +== ConfigMap/Secret + +Label:: `restarter.stackable.tech/ignore` + +If a ConfigMap or Secret is only used for initializing a StatefulSet or contains data which can be hot-reloaded, add the label `restarter.stackable.tech/ignore: "true"` to avoid unnecessary restarts of the StatefulSet pods: + +[source,yaml] +---- +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: hot-reloaded-configmap + labels: + restarter.stackable.tech/ignore: "true" +... +--- +apiVersion: v1 +kind: Secret +metadata: + name: hot-reloaded-secret + labels: + restarter.stackable.tech/ignore: "true" +... +---- + +Unlike the StatefulSet annotations `restarter.stackable.tech/ignore-configmap.\*` and `restarter.stackable.tech/ignore-secret.*`, this label affects every StatefulSet that references the labeled ConfigMaps or Secrets. diff --git a/rust/operator-binary/src/restart_controller/pod.rs b/rust/operator-binary/src/restart_controller/pod.rs index 0fffaa7..7a7e2e1 100644 --- a/rust/operator-binary/src/restart_controller/pod.rs +++ b/rust/operator-binary/src/restart_controller/pod.rs @@ -220,23 +220,20 @@ async fn report_result( const EVICT_ERROR_MESSAGE: &str = "Cannot evict pod as it would violate the pod's disruption budget."; - // TODO: We need Rust 1.88 and 2024 edition for if-let-chains - if let kube::Error::Api(s) = evict_pod_error { - if let Status { + if let kube::Error::Api(s) = evict_pod_error + && let Status { code: TOO_MANY_REQUESTS_HTTP_CODE, message: error_message, .. } = s.deref() - { - if error_message == EVICT_ERROR_MESSAGE { - tracing::info!( - k8s.object.ref = %pod_ref, - error = %evict_pod_error, - "Tried to evict Pod, but wasn't allowed to do so, as it would violate the Pod's disruption budget. Retrying later" - ); - return; - } - } + && error_message == EVICT_ERROR_MESSAGE + { + tracing::info!( + k8s.object.ref = %pod_ref, + error = %evict_pod_error, + "Tried to evict Pod, but wasn't allowed to do so, as it would violate the Pod's disruption budget. Retrying later" + ); + return; } } diff --git a/rust/operator-binary/src/restart_controller/statefulset.rs b/rust/operator-binary/src/restart_controller/statefulset.rs index 2cbdd92..5d9d5a7 100644 --- a/rust/operator-binary/src/restart_controller/statefulset.rs +++ b/rust/operator-binary/src/restart_controller/statefulset.rs @@ -1,4 +1,9 @@ -use std::{collections::BTreeMap, future::Future, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, BTreeSet}, + future::Future, + sync::Arc, + time::Duration, +}; use futures::{Stream, StreamExt, TryStream, stream}; use serde_json::json; @@ -128,13 +133,20 @@ pub async fn start( trigger_all( { let cm_reader = cm_store.as_reader(); - reflector(cm_store, metadata_watcher(cms, watcher::Config::default())) - .inspect(move |_| { - if let Some(tx) = cm_store_tx.take() { - tx.init(cm_reader.clone()); - } - }) - .touched_objects() + reflector( + cm_store, + metadata_watcher( + cms, + watcher::Config::default() + .labels("restarter.stackable.tech/ignore != true"), + ), + ) + .inspect(move |_| { + if let Some(tx) = cm_store_tx.take() { + tx.init(cm_reader.clone()); + } + }) + .touched_objects() }, sts_store.as_reader(), ), @@ -143,7 +155,11 @@ pub async fn start( let secret_reader = secret_store.as_reader(); reflector( secret_store, - metadata_watcher(secrets, watcher::Config::default()), + metadata_watcher( + secrets, + watcher::Config::default() + .labels("restarter.stackable.tech/ignore != true"), + ), ) .inspect(move |_| { if let Some(tx) = secret_store_tx.take() { @@ -235,11 +251,13 @@ pub async fn get_updated_restarter_annotations( let ns = sts.metadata.namespace.as_deref().expect( "A StatefulSet observed by a reflector (so send by Kubernetes) always has a namespace set", ); + let mut annotations = BTreeMap::::new(); let pod_specs = sts .spec .iter() .flat_map(|sts_spec| sts_spec.template.spec.as_ref()); + let cm_refs = pod_specs .clone() .flat_map(|pod_spec| { @@ -269,19 +287,37 @@ pub async fn get_updated_restarter_annotations( }) .map(|cm_ref| cm_ref.within(ns)); let cms = ctx.cms.get().await.context(ConfigMapsUninitializedSnafu)?; - annotations.extend(cm_refs.flat_map(|cm_ref| cms.get(&cm_ref)).flat_map(|cm| { - Some(( - format!( - "configmap.restarter.stackable.tech/{}", - cm.metadata.name.as_ref()? - ), - format!( - "{}/{}", - cm.metadata.uid.as_ref()?, - cm.metadata.resource_version.as_ref()? - ), - )) - })); + let ignored_cms = sts + .metadata + .annotations + .iter() + .flatten() + .filter(|annotation| { + annotation + .0 + .starts_with("restarter.stackable.tech/ignore-configmap.") + }) + .map(|x| x.1) + .collect::>(); + annotations.extend( + cm_refs + .map(|cm_ref| (cm_ref.name.clone(), cms.get(&cm_ref))) + .map(|(cm_name, cm)| { + ( + format!("configmap.restarter.stackable.tech/{cm_name}",), + if let Some(cm) = cm + && let Some(uid) = &cm.metadata.uid + && let Some(resource_version) = &cm.metadata.resource_version + && !ignored_cms.contains(&cm_name) + { + format!("{uid}/{resource_version}",) + } else { + "changes-ignored".to_owned() + }, + ) + }), + ); + let secret_refs = pod_specs .flat_map(|pod_spec| { find_pod_refs( @@ -305,23 +341,37 @@ pub async fn get_updated_restarter_annotations( }) .map(|secret_ref| secret_ref.within(ns)); let secrets = ctx.secrets.get().await.context(SecretsUninitializedSnafu)?; + let ignored_secrets = sts + .metadata + .annotations + .iter() + .flatten() + .filter(|annotation| { + annotation + .0 + .starts_with("restarter.stackable.tech/ignore-secret.") + }) + .map(|x| x.1) + .collect::>(); annotations.extend( secret_refs - .flat_map(|secret_ref| secrets.get(&secret_ref)) - .flat_map(|cm| { - Some(( - format!( - "secret.restarter.stackable.tech/{}", - cm.metadata.name.as_ref()? - ), - format!( - "{}/{}", - cm.metadata.uid.as_ref()?, - cm.metadata.resource_version.as_ref()? - ), - )) + .map(|secret_ref| (secret_ref.name.clone(), secrets.get(&secret_ref))) + .map(|(secret_name, secret)| { + ( + format!("secret.restarter.stackable.tech/{secret_name}",), + if let Some(secret) = secret + && let Some(uid) = &secret.metadata.uid + && let Some(resource_version) = &secret.metadata.resource_version + && !ignored_secrets.contains(&secret_name) + { + format!("{uid}/{resource_version}",) + } else { + "changes-ignored".to_owned() + }, + ) }), ); + Ok(annotations) } diff --git a/tests/templates/kuttl/restarter/10-assert.yaml b/tests/templates/kuttl/restarter/10-assert.yaml index 8926a3d..dbf238f 100644 --- a/tests/templates/kuttl/restarter/10-assert.yaml +++ b/tests/templates/kuttl/restarter/10-assert.yaml @@ -1,12 +1,12 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -timeout: 30 +timeout: 120 --- apiVersion: apps/v1 kind: StatefulSet metadata: - name: sleep + name: test status: readyReplicas: 1 replicas: 1 diff --git a/tests/templates/kuttl/restarter/10-create-test-resources.yaml b/tests/templates/kuttl/restarter/10-create-test-resources.yaml new file mode 100644 index 0000000..0bc86fc --- /dev/null +++ b/tests/templates/kuttl/restarter/10-create-test-resources.yaml @@ -0,0 +1,127 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-not-ignored +data: + revision: "1" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-not-ignored +stringData: + revision: "1" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-self-ignored + annotations: + restarter.stackable.tech/ignore: "true" +data: + revision: "1" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-self-ignored + annotations: + restarter.stackable.tech/ignore: "true" +stringData: + revision: "1" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-ignored-in-statefulset +data: + revision: "1" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-ignored-in-statefulset +stringData: + revision: "1" +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test + labels: + restarter.stackable.tech/enabled: "true" + restarter.stackable.tech/ignore-configmap.0: configmap-ignored-in-statefulset + restarter.stackable.tech/ignore-secret.0: secret-ignored-in-statefulset +spec: + selector: + matchLabels: + app: test + serviceName: test + replicas: 1 + template: + metadata: + labels: + app: test + spec: + serviceAccount: integration-tests-sa + volumes: + - name: configmap-not-ignored + configMap: + name: configmap-not-ignored + - name: secret-not-ignored + secret: + secretName: secret-not-ignored + - name: configmap-self-ignored + configMap: + name: configmap-self-ignored + - name: secret-self-ignored + secret: + secretName: secret-self-ignored + - name: configmap-ignored-in-statefulset + configMap: + name: configmap-ignored-in-statefulset + - name: secret-ignored-in-statefulset + secret: + secretName: secret-ignored-in-statefulset + containers: + - name: test + image: alpine + command: + - sleep + args: + - infinity + volumeMounts: + - mountPath: /config/configmap-not-ignored-subpath/revision + name: configmap-not-ignored + # Use a subPath, so that changes are only visible after a restart. + subPath: revision + - mountPath: /config/secret-not-ignored-subpath/revision + name: secret-not-ignored + # Use a subPath, so that changes are only visible after a restart. + subPath: revision + - mountPath: /config/configmap-self-ignored + name: configmap-self-ignored + - mountPath: /config/secret-self-ignored + name: secret-self-ignored + - mountPath: /config/configmap-self-ignored-subpath/revision + name: configmap-self-ignored + # Use a subPath, so that changes are only visible after a restart. + subPath: revision + - mountPath: /config/secret-self-ignored-subpath/revision + name: secret-self-ignored + # Use a subPath, so that changes are only visible after a restart. + subPath: revision + - mountPath: /config/configmap-ignored-in-statefulset + name: configmap-ignored-in-statefulset + - mountPath: /config/secret-ignored-in-statefulset + name: secret-ignored-in-statefulset + - mountPath: /config/configmap-ignored-in-statefulset-subpath/revision + name: configmap-ignored-in-statefulset + # Use a subPath, so that changes are only visible after a restart. + subPath: revision + - mountPath: /config/secret-ignored-in-statefulset-subpath/revision + name: secret-ignored-in-statefulset + # Use a subPath, so that changes are only visible after a restart. + subPath: revision + terminationGracePeriodSeconds: 5 diff --git a/tests/templates/kuttl/restarter/10-sleep.yaml b/tests/templates/kuttl/restarter/10-sleep.yaml deleted file mode 100644 index 4d592f6..0000000 --- a/tests/templates/kuttl/restarter/10-sleep.yaml +++ /dev/null @@ -1,44 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: sleep -data: - property: value ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: sleep - labels: - restarter.stackable.tech/enabled: "true" -spec: - selector: - matchLabels: - app: sleep - serviceName: "sleep" - replicas: 1 - template: - metadata: - labels: - app: sleep - spec: - serviceAccount: integration-tests-sa - volumes: - - name: config - configMap: - name: sleep - items: - - key: property - path: property - containers: - - name: sleep - image: alpine - command: - - sleep - args: - - infinity - volumeMounts: - - name: config - mountPath: /config - terminationGracePeriodSeconds: 5 diff --git a/tests/templates/kuttl/restarter/20-assert.yaml b/tests/templates/kuttl/restarter/20-assert.yaml index 5235897..42f197f 100644 --- a/tests/templates/kuttl/restarter/20-assert.yaml +++ b/tests/templates/kuttl/restarter/20-assert.yaml @@ -3,4 +3,20 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 180 commands: - - script: test "restarted" = $(kubectl exec sleep-0 -c sleep -n $NAMESPACE -- cat /config/property) + - script: | + . ../../../../templates/kuttl/restarter/test-script.sh + + # Resources mounted via subPath are not hot-reloaded by Kubernetes. + # It is expected, that the restart controller ignored the annotated resources and that only + # the resources not mounted via subPath were updated. + + assert_revision 1 configmap-not-ignored-subpath + assert_revision 1 secret-not-ignored-subpath + assert_revision 1 configmap-self-ignored-subpath + assert_revision 1 secret-self-ignored-subpath + assert_revision 2 configmap-self-ignored + assert_revision 2 secret-self-ignored + assert_revision 1 configmap-ignored-in-statefulset-subpath + assert_revision 1 secret-ignored-in-statefulset-subpath + assert_revision 2 configmap-ignored-in-statefulset + assert_revision 2 secret-ignored-in-statefulset diff --git a/tests/templates/kuttl/restarter/20-hot-reload.yaml b/tests/templates/kuttl/restarter/20-hot-reload.yaml new file mode 100644 index 0000000..3ef493e --- /dev/null +++ b/tests/templates/kuttl/restarter/20-hot-reload.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-self-ignored +data: + revision: "2" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-self-ignored +stringData: + revision: "2" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-ignored-in-statefulset +data: + revision: "2" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-ignored-in-statefulset +stringData: + revision: "2" diff --git a/tests/templates/kuttl/restarter/20-update-cm.yaml b/tests/templates/kuttl/restarter/20-update-cm.yaml deleted file mode 100644 index 0ac0446..0000000 --- a/tests/templates/kuttl/restarter/20-update-cm.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: sleep -data: - property: restarted diff --git a/tests/templates/kuttl/restarter/21-assert.yaml b/tests/templates/kuttl/restarter/21-assert.yaml new file mode 100644 index 0000000..974de6f --- /dev/null +++ b/tests/templates/kuttl/restarter/21-assert.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 180 +commands: + - script: | + . ../../../../templates/kuttl/restarter/test-script.sh + + # After a restart, all resources should have been updated. + + assert_revision 2 configmap-not-ignored-subpath + assert_revision 2 secret-not-ignored-subpath + assert_revision 2 configmap-self-ignored-subpath + assert_revision 2 secret-self-ignored-subpath + assert_revision 2 configmap-self-ignored + assert_revision 2 secret-self-ignored + assert_revision 2 configmap-ignored-in-statefulset-subpath + assert_revision 2 secret-ignored-in-statefulset-subpath + assert_revision 2 configmap-ignored-in-statefulset + assert_revision 2 secret-ignored-in-statefulset diff --git a/tests/templates/kuttl/restarter/21-trigger-restart.yaml b/tests/templates/kuttl/restarter/21-trigger-restart.yaml new file mode 100644 index 0000000..08650bf --- /dev/null +++ b/tests/templates/kuttl/restarter/21-trigger-restart.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-not-ignored +data: + revision: "2" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-not-ignored +stringData: + revision: "2" diff --git a/tests/templates/kuttl/restarter/test-script.sh b/tests/templates/kuttl/restarter/test-script.sh new file mode 100644 index 0000000..b2e69dc --- /dev/null +++ b/tests/templates/kuttl/restarter/test-script.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +get_revision () { + resource="$1" + + kubectl exec test-0 \ + --namespace "$NAMESPACE" \ + --container test -- \ + cat "/config/$resource/revision" +} + +assert_revision () { + expected_value="$1" + resource="$2" + + actual_value="$(get_revision "$resource")" + if test "$expected_value" = "$actual_value" + then + echo "[PASS] $resource contains expected value" + else + echo "[FAIL] $resource does not contain expected value: " \ + "expected: $expected_value != actual: $actual_value" + exit 1 + fi +}