diff --git a/Release.toml b/Release.toml index 9cfa1e04aa0..94dfdb489b2 100644 --- a/Release.toml +++ b/Release.toml @@ -297,4 +297,6 @@ version = "1.20.0" "migrate_v1.20.0_update-ecs-config-path.lz4", "migrate_v1.20.0_update-ecs-config-template-path.lz4", "migrate_v1.20.0_add-ntp-default-options-v0-1-0.lz4", + "migrate_v1.20.0_static-pods-add-prefix-v0-1-0.lz4", + "migrate_v1.20.0_static-pods-services-cfg-v0-1-0.lz4", ] diff --git a/packages/kubernetes-1.23/Cargo.toml b/packages/kubernetes-1.23/Cargo.toml index 2d4dc3e7fc0..38050ec02ef 100644 --- a/packages/kubernetes-1.23/Cargo.toml +++ b/packages/kubernetes-1.23/Cargo.toml @@ -45,6 +45,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider = { path = "../ecr-credential-provider" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.23/kubernetes-1.23.spec b/packages/kubernetes-1.23/kubernetes-1.23.spec index e75039b6201..5b7b1ddcad8 100644 --- a/packages/kubernetes-1.23/kubernetes-1.23.spec +++ b/packages/kubernetes-1.23/kubernetes-1.23.spec @@ -69,6 +69,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.23 %{summary}. diff --git a/packages/kubernetes-1.24/Cargo.toml b/packages/kubernetes-1.24/Cargo.toml index 6700fea5dc7..a92c9f1d424 100644 --- a/packages/kubernetes-1.24/Cargo.toml +++ b/packages/kubernetes-1.24/Cargo.toml @@ -24,6 +24,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider = { path = "../ecr-credential-provider" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.24/kubernetes-1.24.spec b/packages/kubernetes-1.24/kubernetes-1.24.spec index 7ae8db082eb..f4e1a0c3f6a 100644 --- a/packages/kubernetes-1.24/kubernetes-1.24.spec +++ b/packages/kubernetes-1.24/kubernetes-1.24.spec @@ -70,6 +70,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.24 %{summary}. diff --git a/packages/kubernetes-1.25/Cargo.toml b/packages/kubernetes-1.25/Cargo.toml index 3311b6647c9..81d08768ef9 100644 --- a/packages/kubernetes-1.25/Cargo.toml +++ b/packages/kubernetes-1.25/Cargo.toml @@ -24,6 +24,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider = { path = "../ecr-credential-provider" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.25/kubernetes-1.25.spec b/packages/kubernetes-1.25/kubernetes-1.25.spec index de7319c4ee7..a8c0bcd96a2 100644 --- a/packages/kubernetes-1.25/kubernetes-1.25.spec +++ b/packages/kubernetes-1.25/kubernetes-1.25.spec @@ -70,6 +70,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.25 %{summary}. diff --git a/packages/kubernetes-1.26/Cargo.toml b/packages/kubernetes-1.26/Cargo.toml index 5060df71392..2c5810b0aa9 100644 --- a/packages/kubernetes-1.26/Cargo.toml +++ b/packages/kubernetes-1.26/Cargo.toml @@ -24,6 +24,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider = { path = "../ecr-credential-provider" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.26/kubernetes-1.26.spec b/packages/kubernetes-1.26/kubernetes-1.26.spec index 84536887c64..16167cba983 100644 --- a/packages/kubernetes-1.26/kubernetes-1.26.spec +++ b/packages/kubernetes-1.26/kubernetes-1.26.spec @@ -70,6 +70,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.26 %{summary}. diff --git a/packages/kubernetes-1.27/Cargo.toml b/packages/kubernetes-1.27/Cargo.toml index e49007e0fe3..6708659e1bc 100644 --- a/packages/kubernetes-1.27/Cargo.toml +++ b/packages/kubernetes-1.27/Cargo.toml @@ -25,6 +25,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider-1_27 = { path = "../ecr-credential-provider-1.27" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.27/kubernetes-1.27.spec b/packages/kubernetes-1.27/kubernetes-1.27.spec index f7061661b2f..16f38c6fb94 100644 --- a/packages/kubernetes-1.27/kubernetes-1.27.spec +++ b/packages/kubernetes-1.27/kubernetes-1.27.spec @@ -70,6 +70,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider-1.27 Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.27 %{summary}. diff --git a/packages/kubernetes-1.28/Cargo.toml b/packages/kubernetes-1.28/Cargo.toml index 8cb8816f28f..cdddb1a2ddc 100644 --- a/packages/kubernetes-1.28/Cargo.toml +++ b/packages/kubernetes-1.28/Cargo.toml @@ -25,6 +25,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider-1_27 = { path = "../ecr-credential-provider-1.27" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.28/kubernetes-1.28.spec b/packages/kubernetes-1.28/kubernetes-1.28.spec index c8a49a30059..4c4595b7cb6 100644 --- a/packages/kubernetes-1.28/kubernetes-1.28.spec +++ b/packages/kubernetes-1.28/kubernetes-1.28.spec @@ -70,6 +70,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider-1.27 Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.28 %{summary}. diff --git a/packages/kubernetes-1.29/Cargo.toml b/packages/kubernetes-1.29/Cargo.toml index a27508ce49d..33c27286d2a 100644 --- a/packages/kubernetes-1.29/Cargo.toml +++ b/packages/kubernetes-1.29/Cargo.toml @@ -25,6 +25,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider-1_29 = { path = "../ecr-credential-provider-1.29" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.29/kubernetes-1.29.spec b/packages/kubernetes-1.29/kubernetes-1.29.spec index 9149941888d..d3b377549c5 100644 --- a/packages/kubernetes-1.29/kubernetes-1.29.spec +++ b/packages/kubernetes-1.29/kubernetes-1.29.spec @@ -70,6 +70,7 @@ Requires: %{_cross_os}containerd Requires: %{_cross_os}findutils Requires: %{_cross_os}ecr-credential-provider-1.29 Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.29 %{summary}. diff --git a/packages/kubernetes-1.30/Cargo.toml b/packages/kubernetes-1.30/Cargo.toml index 8fd9e9ffcd4..f8c8c63e9c0 100644 --- a/packages/kubernetes-1.30/Cargo.toml +++ b/packages/kubernetes-1.30/Cargo.toml @@ -26,6 +26,7 @@ glibc = { path = "../glibc" } [dependencies] aws-signing-helper = { path = "../aws-signing-helper" } ecr-credential-provider-1_29 = { path = "../ecr-credential-provider-1.29" } +static-pods = { path = "../static-pods" } # `conntrack-tools`, `containerd` and `findutils` are only needed at runtime, # and are pulled in by `release`. # conntrack-tools = { path = "../conntrack-tools" } diff --git a/packages/kubernetes-1.30/kubernetes-1.30.spec b/packages/kubernetes-1.30/kubernetes-1.30.spec index c2bb78ec2be..e8049e35be5 100644 --- a/packages/kubernetes-1.30/kubernetes-1.30.spec +++ b/packages/kubernetes-1.30/kubernetes-1.30.spec @@ -72,6 +72,7 @@ Requires: %{_cross_os}findutils # TODO: update to ecr-credential-provider-1.30 Requires: %{_cross_os}ecr-credential-provider-1.29 Requires: %{_cross_os}aws-signing-helper +Requires: %{_cross_os}static-pods %description -n %{_cross_os}kubelet-1.30 %{summary}. diff --git a/packages/os/os.spec b/packages/os/os.spec index 4f083daf5ec..b6c33c52f97 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -108,10 +108,6 @@ Requires: %{_cross_os}updog Requires: %{_cross_os}pluto %endif -%if %{with k8s_runtime} -Requires: %{_cross_os}static-pods -%endif - %if %{with aws_platform} Requires: %{_cross_os}shibaken Requires: %{_cross_os}cfsignal @@ -244,13 +240,6 @@ Summary: Dynamic setting generator for kubernetes %{summary}. %endif -%if %{with k8s_runtime} -%package -n %{_cross_os}static-pods -Summary: Manages user-defined K8S static pods -%description -n %{_cross_os}static-pods -%{summary}. -%endif - %if %{with aws_platform} %package -n %{_cross_os}shibaken Summary: Run tasks reliant on IMDS @@ -384,7 +373,6 @@ echo "** Output from non-static builds:" -p bloodhound \ -p xfscli \ %{?with_aws_platform: -p shibaken} \ - %{?with_k8s_runtime: -p static-pods} \ %{?with_nvidia_flavor: -p driverdog} \ %{nil} @@ -418,7 +406,6 @@ for p in \ ghostdog bootstrap-containers \ shimpei bloodhound bottlerocket-checks \ %{?with_aws_platform: shibaken} \ - %{?with_k8s_runtime: static-pods} \ %{?with_nvidia_flavor: driverdog} \ ; do install -p -m 0755 ${HOME}/.cache/%{__cargo_target}/release/${p} %{buildroot}%{_cross_bindir} @@ -699,11 +686,6 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %{_cross_datadir}/eks/eni-max-pods %endif -%if %{with k8s_runtime} -%files -n %{_cross_os}static-pods -%{_cross_bindir}/static-pods -%endif - %files -n %{_cross_os}shimpei %{_cross_bindir}/shimpei %{_cross_templatedir}/oci-default-hooks-json diff --git a/packages/static-pods/Cargo.toml b/packages/static-pods/Cargo.toml new file mode 100644 index 00000000000..69290ff19af --- /dev/null +++ b/packages/static-pods/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "static-pods" +version = "0.1.0" +edition = "2021" +publish = false +build = "../build.rs" + +[package.metadata.build-package] +source-groups = [ + "api/static-pods" +] + +[lib] +path = "../packages.rs" + +[build-dependencies] +glibc = { path = "../glibc" } + +[dependencies] diff --git a/packages/static-pods/static-pods-toml b/packages/static-pods/static-pods-toml new file mode 100644 index 00000000000..1c6d7bebe49 --- /dev/null +++ b/packages/static-pods/static-pods-toml @@ -0,0 +1,15 @@ +[required-extensions] +kubernetes = "v1" +std = { version = "v1", helpers = ["if_not_null"] } ++++ +{{#if_not_null settings.kubernetes.static-pods}} +{{#each settings.kubernetes.static-pods}} +["{{@key}}"] +{{#if_not_null this.enabled}} +enabled = {{this.enabled}} +{{/if_not_null}} +{{#if_not_null this.manifest}} +manifest = "{{{this.manifest}}}" +{{/if_not_null}} +{{/each}} +{{/if_not_null}} diff --git a/packages/static-pods/static-pods.spec b/packages/static-pods/static-pods.spec new file mode 100644 index 00000000000..363723148b8 --- /dev/null +++ b/packages/static-pods/static-pods.spec @@ -0,0 +1,38 @@ +%global _cross_first_party 1 +%undefine _debugsource_packages + +Name: %{_cross_os}static-pods +Version: 0.0 +Release: 0%{?dist} +Summary: Manages user-defined K*S static pods +License: Apache-2.0 OR MIT +URL: https://github.com/bottlerocket-os/bottlerocket + +Source0: static-pods-toml + +BuildRequires: %{_cross_os}glibc-devel + +%description +%{summary}. + +%prep +%setup -T -c +%cargo_prep + +%build +mkdir bin + +echo "** Compile static-pods agent" +%cargo_build --manifest-path %{_builddir}/sources/Cargo.toml \ + -p static-pods + +%install +install -d %{buildroot}%{_cross_bindir} +install -p -m 0755 ${HOME}/.cache/%{__cargo_target}/release/static-pods %{buildroot}%{_cross_bindir} + +install -d %{buildroot}%{_cross_templatedir} +install -p -m 0644 %{S:0} %{buildroot}%{_cross_templatedir} + +%files +%{_cross_bindir}/static-pods +%{_cross_templatedir}/static-pods-toml diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 77f26dd4df8..8a372f9e505 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -4095,16 +4095,28 @@ name = "static-pods" version = "0.1.0" dependencies = [ "base64", - "bottlerocket-variant", - "constants", "generate-readme", "log", - "models", - "schnauzer", + "modeled-types", + "serde", "simplelog", "snafu 0.8.2", "tempfile", - "tokio", + "toml", +] + +[[package]] +name = "static-pods-add-prefix-v0-1-0" +version = "0.1.0" +dependencies = [ + "migration-helpers", +] + +[[package]] +name = "static-pods-services-cfg-v0-1-0" +version = "0.1.0" +dependencies = [ + "migration-helpers", ] [[package]] diff --git a/sources/Cargo.toml b/sources/Cargo.toml index c0a8e8f95e2..f6b69248da2 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -80,6 +80,8 @@ members = [ "api/migration/migrations/v1.20.0/update-ecs-config-path", "api/migration/migrations/v1.20.0/update-ecs-config-template-path", "api/migration/migrations/v1.20.0/add-ntp-default-options-v0-1-0", + "api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0", + "api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0", "bloodhound", diff --git a/sources/api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0/Cargo.toml b/sources/api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0/Cargo.toml new file mode 100644 index 00000000000..08354cf089c --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "static-pods-add-prefix-v0-1-0" +version = "0.1.0" +authors = ["Jarrett Tierney "] +license = "Apache-2.0 OR MIT" +edition = "2021" +publish = false +exclude = ["README.md"] + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0/src/main.rs b/sources/api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0/src/main.rs new file mode 100644 index 00000000000..c0a95ae7118 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/static-pods-add-prefix-v0-1-0/src/main.rs @@ -0,0 +1,16 @@ +use migration_helpers::common_migrations::AddPrefixesMigration; +use migration_helpers::{migrate, Result}; +use std::process; + +fn run() -> Result<()> { + migrate(AddPrefixesMigration(vec![ + "configuration-files.static-pods-toml", + ])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0/Cargo.toml b/sources/api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0/Cargo.toml new file mode 100644 index 00000000000..f1e72224cb1 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "static-pods-services-cfg-v0-1-0" +version = "0.1.0" +authors = ["Jarrett Tierney "] +license = "Apache-2.0 OR MIT" +edition = "2021" +publish = false +exclude = ["README.md"] + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0/src/main.rs b/sources/api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0/src/main.rs new file mode 100644 index 00000000000..6c170aa6fa7 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/static-pods-services-cfg-v0-1-0/src/main.rs @@ -0,0 +1,18 @@ +use migration_helpers::common_migrations::{ListReplacement, ReplaceListsMigration}; +use migration_helpers::{migrate, Result}; +use std::process; + +fn run() -> Result<()> { + migrate(ReplaceListsMigration(vec![ListReplacement { + setting: "services.static-pods.configuration-files", + old_vals: &[], + new_vals: &["static-pods-toml"], + }])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/static-pods/Cargo.toml b/sources/api/static-pods/Cargo.toml index 342840ce4c3..6354c7f9f59 100644 --- a/sources/api/static-pods/Cargo.toml +++ b/sources/api/static-pods/Cargo.toml @@ -10,16 +10,14 @@ build = "build.rs" exclude = ["README.md"] [dependencies] -constants = { path = "../../constants", version = "0.1" } base64 = "0.21" log = "0.4" -models = { path = "../../models", version = "0.1" } -schnauzer = { path = "../schnauzer", version = "0.1" } +modeled-types = { path = "../../models/modeled-types", version = "0.1" } +serde = { version = "1", features = ["default"]} simplelog = "0.12" snafu = "0.8" -tokio = { version = "~1.32", default-features = false, features = ["macros", "rt-multi-thread", "time"] } # LTS +toml = "0.8" tempfile = "3" [build-dependencies] -bottlerocket-variant = { version = "0.1", path = "../../bottlerocket-variant" } generate-readme = { version = "0.1", path = "../../generate-readme" } diff --git a/sources/api/static-pods/build.rs b/sources/api/static-pods/build.rs index a32db23ad0b..5b3a661c3f5 100644 --- a/sources/api/static-pods/build.rs +++ b/sources/api/static-pods/build.rs @@ -1,7 +1,3 @@ -use bottlerocket_variant::Variant; - fn main() { - let variant = Variant::from_env().unwrap(); - variant.emit_cfgs(); - generate_readme::from_file("src/static_pods.rs").unwrap(); + generate_readme::from_main().unwrap(); } diff --git a/sources/api/static-pods/src/main.rs b/sources/api/static-pods/src/main.rs index f98ea26a14c..c4af33d49bf 100644 --- a/sources/api/static-pods/src/main.rs +++ b/sources/api/static-pods/src/main.rs @@ -1,14 +1,334 @@ -#[cfg(variant_runtime = "k8s")] -mod static_pods; -#[cfg(variant_runtime = "k8s")] +/*! +# Background + +static-pods ensures static pods are running as defined in settings. + +It queries for all existing static pod settings, then configures the system as follows: +* If the pod is enabled, it creates the manifest file in the pod manifest path that kubelet is + configured to read from and populates the file with the base64-decoded manifest setting value. +* If the pod is enabled and the manifest file already exists, it overwrites the existing manifest + file with the base64-decoded manifest setting value. +* If the pod is disabled, it ensures the manifest file is removed from the pod manifest path. +*/ #[macro_use] extern crate log; -#[cfg(variant_runtime = "k8s")] -#[tokio::main] -async fn main() { - static_pods::main().await +use base64::Engine; +use modeled_types::{Identifier, ValidBase64}; +use serde::{Deserialize, Serialize}; +use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger}; +use snafu::{ensure, OptionExt, ResultExt}; +use std::collections::HashMap; +use std::env; +use std::fs; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process; +use std::str::FromStr; +use tempfile::{NamedTempFile, TempDir}; + +const DEFAULT_CONFIG_PATH: &str = "/etc/kubernetes/static-pods-manifest.toml"; +const STATIC_POD_DIR: &str = "/etc/kubernetes/static-pods"; +const ETC_KUBE_DIR: &str = "/etc/kubernetes"; + +#[derive(Serialize, Deserialize, Debug)] +struct StaticPod { + #[serde(default, skip_serializing_if = "Option::is_none")] + enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + manifest: Option, +} + +type Result = std::result::Result; + +/// Query the API for the currently defined static pods +fn get_static_pods

(config_path: P) -> Result>> +where + P: AsRef, +{ + debug!("Loading static pod information from configuration file"); + let config_path = config_path.as_ref(); + let config_str = fs::read_to_string(config_path).context(error::ReadConfigSnafu { + path: config_path.to_path_buf(), + })?; + toml::from_str(config_str.as_str()).context(error::DeserializationSnafu { + path: config_path.to_path_buf(), + }) +} + +/// Write out the manifest file to the pod manifest path with a given filename +fn write_manifest_file(name: S1, manifest: S2) -> Result<()> +where + S1: AsRef, + S2: AsRef<[u8]>, +{ + let name = name.as_ref(); + let manifest = manifest.as_ref(); + + let target_dir = Path::new(STATIC_POD_DIR); + fs::create_dir_all(target_dir).context(error::MkdirSnafu { dir: &target_dir })?; + + // Create a temporary directory adjacent to the static pods directory. This directory will be + // automatically cleaned-up as soon as it goes out of scope. + let tmp_dir = TempDir::new_in(ETC_KUBE_DIR).context(error::CreateTempdirSnafu)?; + + // Create the pod manifest file as a temporary file in an adjacent temp directory first and + // finish writing to it before swapping any files out in the target static pods directory. + let mut temp_manifest_file = + NamedTempFile::new_in(tmp_dir.path()).context(error::CreateTempfileSnafu)?; + temp_manifest_file + .write(manifest) + .context(error::ManifestWriteSnafu { name })?; + + let target_path = target_dir.join(name); + debug!( + "Writing static pod manifest file to '{}'", + target_path.display() + ); + // Create the file if it does not exist. If it does exist, atomically replace it. + temp_manifest_file + .persist(&target_path) + .context(error::PersistPodManifestSnafu { path: target_path })?; + + Ok(()) +} + +/// Deletes the named manifest file if it exists +fn delete_manifest_file(name: S1) -> Result<()> +where + S1: AsRef, +{ + let name = name.as_ref(); + let path = Path::new(STATIC_POD_DIR).join(name); + if path.exists() { + fs::remove_file(path).context(error::ManifestDeleteSnafu { name })?; + } + + Ok(()) } -#[cfg(not(variant_runtime = "k8s"))] -fn main() {} +fn handle_static_pod(name: S, pod_info: &StaticPod) -> Result<()> +where + S: AsRef, +{ + // Get basic settings, as retrieved from API. + let name = name.as_ref(); + let enabled = pod_info.enabled.context(error::MissingFieldSnafu { + name, + field: "enabled", + })?; + + if enabled { + let manifest = pod_info + .manifest + .as_ref() + .context(error::MissingFieldSnafu { + name, + field: "manifest", + })?; + + let manifest = base64::engine::general_purpose::STANDARD + .decode(manifest.as_bytes()) + .context(error::Base64DecodeSnafu { name })?; + + info!("Writing static pod '{}' to '{}'", name, STATIC_POD_DIR); + + // Write the manifest file for this static pod + write_manifest_file(name, manifest)?; + } else { + info!("Removing static pod '{}' from '{}'", name, STATIC_POD_DIR); + + // Delete the manifest file so the static pod no longer runs (disabled) + delete_manifest_file(name)?; + } + + Ok(()) +} + +fn run() -> Result<()> { + let args = parse_args(env::args())?; + + // SimpleLogger will send errors to stderr and anything less to stdout. + SimpleLogger::init(args.log_level, LogConfig::default()).context(error::LoggerSnafu)?; + + info!("static-pods started"); + + let mut failed = 0u32; + if let Some(static_pods) = get_static_pods(args.config_path)? { + for (name, pod) in static_pods.iter() { + // Continue to handle other static pods if we fail one + if let Err(e) = handle_static_pod(name, pod) { + failed += 1; + error!("Failed to handle static pod '{}': {}", &name, e); + } + } + + ensure!( + failed == 0, + error::ManageStaticPodsFailedSnafu { + failed, + tried: static_pods.len() + } + ); + } + + Ok(()) +} + +/// Store the args we receive on the command line +struct Args { + log_level: LevelFilter, + config_path: PathBuf, +} + +/// Print a usage message in the event a bad arg is passed +fn usage() { + let program_name = env::args().next().unwrap_or_else(|| "program".to_string()); + eprintln!( + r"Usage: {} + [ --config-path PATH ] + [ --log-level trace|debug|info|warn|error ] + + Socket path defaults to {}", + program_name, DEFAULT_CONFIG_PATH, + ); +} + +/// Parse the args to the program and return an Args struct +fn parse_args(args: env::Args) -> Result { + let mut log_level = None; + let mut config_path = None; + + let mut iter = args.skip(1); + while let Some(arg) = iter.next() { + match arg.as_ref() { + "--log-level" => { + let log_level_str = iter.next().ok_or_else(|| error::Error::Usage { + message: "Did not give argument to --log-level".into(), + })?; + log_level = Some(LevelFilter::from_str(&log_level_str).map_err(|_| { + error::Error::Usage { + message: format!("Invalid log level '{}'", log_level_str), + } + })?); + } + + "--config-path" => { + config_path = Some( + iter.next() + .ok_or_else(|| error::Error::Usage { + message: "Did not give argument to --config-path".into(), + })? + .into(), + ) + } + + _ => { + return Err(error::Error::Usage { + message: "unexpected argument".into(), + }) + } + } + } + + Ok(Args { + log_level: log_level.unwrap_or(LevelFilter::Info), + config_path: config_path.unwrap_or_else(|| DEFAULT_CONFIG_PATH.into()), + }) +} + +// Returning a Result from main makes it print a Debug representation of the error, but with Snafu +// we have nice Display representations of the error, so we wrap "main" (run) and print any error. +// https://github.com/shepmaster/snafu/issues/110 +fn main() { + if let Err(e) = run() { + match e { + error::Error::Usage { .. } => { + eprintln!("{}", e); + usage(); + process::exit(2); + } + _ => { + eprintln!("{}", e); + process::exit(1); + } + } + } +} + +mod error { + use snafu::Snafu; + use std::path::PathBuf; + + #[derive(Debug, Snafu)] + #[snafu(visibility(pub(super)))] + pub(super) enum Error { + #[snafu(display("{}", message))] + Usage { message: String }, + + #[snafu(display("Failed to read static-pods configuration file at '{}': {}", path.display(), source))] + ReadConfig { + path: PathBuf, + source: std::io::Error, + }, + + #[snafu(display("Failed to deserialize static pod configuration at '{}': {}", path.display(), source))] + Deserialization { + path: PathBuf, + source: toml::de::Error, + }, + + #[snafu(display("Static pod '{}' missing field '{}'", name, field))] + MissingField { name: String, field: String }, + + #[snafu(display("Failed to manage {} of {} static pods", failed, tried))] + ManageStaticPodsFailed { failed: u32, tried: usize }, + + #[snafu(display("Logger setup error: {}", source))] + Logger { source: log::SetLoggerError }, + + #[snafu(display( + "Unable to decode base64 in static pod '{}' manifest: {}", + name, + source + ))] + Base64Decode { + name: String, + source: base64::DecodeError, + }, + + #[snafu(display("Failed to create directory '{}': '{}'", dir.display(), source))] + Mkdir { + dir: PathBuf, + source: std::io::Error, + }, + + #[snafu(display("Failed to write manifest for static pod '{}': {}", name, source))] + ManifestWrite { + name: String, + source: std::io::Error, + }, + + #[snafu(display( + "Failed to delete manifest file for static pod '{}': {}'", + name, + source + ))] + ManifestDelete { + name: String, + source: std::io::Error, + }, + + #[snafu(display("Failed to create temporary directory for pod manifests: {}", source))] + CreateTempdir { source: std::io::Error }, + + #[snafu(display("Failed to create tempfile for writing pod manifest: {}", source))] + CreateTempfile { source: std::io::Error }, + + #[snafu(display("Failed to create pod manifest file '{}': {}", path.display(), source))] + PersistPodManifest { + path: PathBuf, + source: tempfile::PersistError, + }, + } +} diff --git a/sources/api/static-pods/src/static_pods.rs b/sources/api/static-pods/src/static_pods.rs deleted file mode 100644 index e93804fc455..00000000000 --- a/sources/api/static-pods/src/static_pods.rs +++ /dev/null @@ -1,320 +0,0 @@ -/*! -# Background - -static-pods ensures static pods are running as defined in settings. - -It queries for all existing static pod settings, then configures the system as follows: -* If the pod is enabled, it creates the manifest file in the pod manifest path that kubelet is - configured to read from and populates the file with the base64-decoded manifest setting value. -* If the pod is enabled and the manifest file already exists, it overwrites the existing manifest - file with the base64-decoded manifest setting value. -* If the pod is disabled, it ensures the manifest file is removed from the pod manifest path. -*/ - -use base64::Engine; -use model::modeled_types::Identifier; -use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger}; -use snafu::{ensure, OptionExt, ResultExt}; -use std::collections::HashMap; -use std::env; -use std::fs; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::process; -use std::str::FromStr; -use tempfile::{NamedTempFile, TempDir}; - -const STATIC_POD_DIR: &str = "/etc/kubernetes/static-pods"; -const ETC_KUBE_DIR: &str = "/etc/kubernetes"; - -type Result = std::result::Result; - -/// Query the API for the currently defined static pods -async fn get_static_pods

(socket_path: P) -> Result>> -where - P: AsRef, -{ - debug!("Requesting settings values"); - let settings = schnauzer::v1::get_settings(socket_path) - .await - .context(error::RetrieveSettingsSnafu)? - .settings - .context(error::MissingSettingsSnafu)?; - - Ok(settings - .kubernetes - .context(error::MissingSettingsSnafu)? - .static_pods) -} - -/// Write out the manifest file to the pod manifest path with a given filename -fn write_manifest_file(name: S1, manifest: S2) -> Result<()> -where - S1: AsRef, - S2: AsRef<[u8]>, -{ - let name = name.as_ref(); - let manifest = manifest.as_ref(); - - let target_dir = Path::new(STATIC_POD_DIR); - fs::create_dir_all(target_dir).context(error::MkdirSnafu { dir: &target_dir })?; - - // Create a temporary directory adjacent to the static pods directory. This directory will be - // automatically cleaned-up as soon as it goes out of scope. - let tmp_dir = TempDir::new_in(ETC_KUBE_DIR).context(error::CreateTempdirSnafu)?; - - // Create the pod manifest file as a temporary file in an adjacent temp directory first and - // finish writing to it before swapping any files out in the target static pods directory. - let mut temp_manifest_file = - NamedTempFile::new_in(tmp_dir.path()).context(error::CreateTempfileSnafu)?; - temp_manifest_file - .write(manifest) - .context(error::ManifestWriteSnafu { name })?; - - let target_path = target_dir.join(name); - debug!( - "Writing static pod manifest file to '{}'", - target_path.display() - ); - // Create the file if it does not exist. If it does exist, atomically replace it. - temp_manifest_file - .persist(&target_path) - .context(error::PersistPodManifestSnafu { path: target_path })?; - - Ok(()) -} - -/// Deletes the named manifest file if it exists -fn delete_manifest_file(name: S1) -> Result<()> -where - S1: AsRef, -{ - let name = name.as_ref(); - let path = Path::new(STATIC_POD_DIR).join(name); - if path.exists() { - fs::remove_file(path).context(error::ManifestDeleteSnafu { name })?; - } - - Ok(()) -} - -fn handle_static_pod(name: S, pod_info: &model::StaticPod) -> Result<()> -where - S: AsRef, -{ - // Get basic settings, as retrieved from API. - let name = name.as_ref(); - let enabled = pod_info.enabled.context(error::MissingFieldSnafu { - name, - field: "enabled", - })?; - - if enabled { - let manifest = pod_info - .manifest - .as_ref() - .context(error::MissingFieldSnafu { - name, - field: "manifest", - })?; - - let manifest = base64::engine::general_purpose::STANDARD - .decode(manifest.as_bytes()) - .context(error::Base64DecodeSnafu { name })?; - - info!("Writing static pod '{}' to '{}'", name, STATIC_POD_DIR); - - // Write the manifest file for this static pod - write_manifest_file(name, manifest)?; - } else { - info!("Removing static pod '{}' from '{}'", name, STATIC_POD_DIR); - - // Delete the manifest file so the static pod no longer runs (disabled) - delete_manifest_file(name)?; - } - - Ok(()) -} - -async fn run() -> Result<()> { - let args = parse_args(env::args())?; - - // SimpleLogger will send errors to stderr and anything less to stdout. - SimpleLogger::init(args.log_level, LogConfig::default()).context(error::LoggerSnafu)?; - - info!("static-pods started"); - - let mut failed = 0u32; - if let Some(static_pods) = get_static_pods(args.socket_path).await? { - for (name, pod) in static_pods.iter() { - // Continue to handle other static pods if we fail one - if let Err(e) = handle_static_pod(name, pod) { - failed += 1; - error!("Failed to handle static pod '{}': {}", &name, e); - } - } - - ensure!( - failed == 0, - error::ManageStaticPodsFailedSnafu { - failed, - tried: static_pods.len() - } - ); - } - - Ok(()) -} - -/// Store the args we receive on the command line -struct Args { - log_level: LevelFilter, - socket_path: PathBuf, -} - -/// Print a usage message in the event a bad arg is passed -fn usage() { - let program_name = env::args().next().unwrap_or_else(|| "program".to_string()); - eprintln!( - r"Usage: {} - [ --socket-path PATH ] - [ --log-level trace|debug|info|warn|error ] - - Socket path defaults to {}", - program_name, - constants::API_SOCKET, - ); -} - -/// Parse the args to the program and return an Args struct -fn parse_args(args: env::Args) -> Result { - let mut log_level = None; - let mut socket_path = None; - - let mut iter = args.skip(1); - while let Some(arg) = iter.next() { - match arg.as_ref() { - "--log-level" => { - let log_level_str = iter.next().ok_or_else(|| error::Error::Usage { - message: "Did not give argument to --log-level".into(), - })?; - log_level = Some(LevelFilter::from_str(&log_level_str).map_err(|_| { - error::Error::Usage { - message: format!("Invalid log level '{}'", log_level_str), - } - })?); - } - - "--socket-path" => { - socket_path = Some( - iter.next() - .ok_or_else(|| error::Error::Usage { - message: "Did not give argument to --socket-path".into(), - })? - .into(), - ) - } - - _ => { - return Err(error::Error::Usage { - message: "unexpected argument".into(), - }) - } - } - } - - Ok(Args { - log_level: log_level.unwrap_or(LevelFilter::Info), - socket_path: socket_path.unwrap_or_else(|| constants::API_SOCKET.into()), - }) -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -pub(crate) async fn main() { - if let Err(e) = run().await { - match e { - error::Error::Usage { .. } => { - eprintln!("{}", e); - usage(); - process::exit(2); - } - _ => { - eprintln!("{}", e); - process::exit(1); - } - } - } -} - -mod error { - use snafu::Snafu; - use std::path::PathBuf; - - #[derive(Debug, Snafu)] - #[snafu(visibility(pub(super)))] - pub(super) enum Error { - #[snafu(display("{}", message))] - Usage { message: String }, - - #[snafu(display("Failed to retrieve settings: {}", source))] - RetrieveSettings { source: schnauzer::v1::Error }, - - #[snafu(display("settings.kubernetes.static_pods missing in API response"))] - MissingSettings {}, - - #[snafu(display("Static pod '{}' missing field '{}'", name, field))] - MissingField { name: String, field: String }, - - #[snafu(display("Failed to manage {} of {} static pods", failed, tried))] - ManageStaticPodsFailed { failed: u32, tried: usize }, - - #[snafu(display("Logger setup error: {}", source))] - Logger { source: log::SetLoggerError }, - - #[snafu(display( - "Unable to decode base64 in static pod '{}' manifest: {}", - name, - source - ))] - Base64Decode { - name: String, - source: base64::DecodeError, - }, - - #[snafu(display("Failed to create directory '{}': '{}'", dir.display(), source))] - Mkdir { - dir: PathBuf, - source: std::io::Error, - }, - - #[snafu(display("Failed to write manifest for static pod '{}': {}", name, source))] - ManifestWrite { - name: String, - source: std::io::Error, - }, - - #[snafu(display( - "Failed to delete manifest file for static pod '{}': {}'", - name, - source - ))] - ManifestDelete { - name: String, - source: std::io::Error, - }, - - #[snafu(display("Failed to create temporary directory for pod manifests: {}", source))] - CreateTempdir { source: std::io::Error }, - - #[snafu(display("Failed to create tempfile for writing pod manifest: {}", source))] - CreateTempfile { source: std::io::Error }, - - #[snafu(display("Failed to create pod manifest file '{}': {}", path.display(), source))] - PersistPodManifest { - path: PathBuf, - source: tempfile::PersistError, - }, - } -} diff --git a/sources/models/shared-defaults/kubernetes-services.toml b/sources/models/shared-defaults/kubernetes-services.toml index a31b389d7a6..fd206df1bb0 100644 --- a/sources/models/shared-defaults/kubernetes-services.toml +++ b/sources/models/shared-defaults/kubernetes-services.toml @@ -57,8 +57,12 @@ path = "/etc/kubernetes/kubelet/credential-provider-config.yaml" template-path = "/usr/share/templates/credential-provider-config-yaml" mode = "0600" +[configuration-files.static-pods-toml] +path = "/etc/kubernetes/static-pods-manifest.toml" +template-path = "/usr/share/templates/static-pods-toml" + [services.static-pods] -configuration-files = [] +configuration-files = ["static-pods-toml"] restart-commands = ["/usr/bin/static-pods"] [metadata.settings.kubernetes.static-pods] diff --git a/variants/Cargo.lock b/variants/Cargo.lock index 6ab5f2857e1..2331fdcec78 100644 --- a/variants/Cargo.lock +++ b/variants/Cargo.lock @@ -626,6 +626,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider", "glibc", + "static-pods", ] [[package]] @@ -635,6 +636,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider", "glibc", + "static-pods", ] [[package]] @@ -644,6 +646,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider", "glibc", + "static-pods", ] [[package]] @@ -653,6 +656,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider", "glibc", + "static-pods", ] [[package]] @@ -662,6 +666,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider-1_27", "glibc", + "static-pods", ] [[package]] @@ -671,6 +676,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider-1_27", "glibc", + "static-pods", ] [[package]] @@ -680,6 +686,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider-1_29", "glibc", + "static-pods", ] [[package]] @@ -689,6 +696,7 @@ dependencies = [ "aws-signing-helper", "ecr-credential-provider-1_29", "glibc", + "static-pods", ] [[package]] @@ -1173,6 +1181,13 @@ version = "0.1.0" name = "shim" version = "0.1.0" +[[package]] +name = "static-pods" +version = "0.1.0" +dependencies = [ + "glibc", +] + [[package]] name = "strace" version = "0.1.0"