Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

host-containers: migrate to use config file #3777

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,6 @@ version = "1.19.3"
"migrate_v1.19.3_prairiedog-services-cfg-v0-1-0.lz4",
"migrate_v1.19.3_thar-be-updates-config-file-v0-1-0.lz4",
"migrate_v1.19.3_thar-be-updates-affected-services-v0-1-0.lz4",
"migrate_v1.19.3_host-containers-config-file-v0-1-0.lz4",
"migrate_v1.19.3_host-containers-config-list-v0-1-0.lz4",
]
20 changes: 20 additions & 0 deletions packages/os/host-containers-toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[required-extensions]
host-containers = "v1"
+++
{{#if settings.host-containers}}
{{#each settings.host-containers}}
[host-containers."{{{@key}}}"]
{{#if this.source}}
source = "{{{this.source}}}"
{{/if}}
{{#if this.enabled}}
enabled = {{{this.enabled}}}
{{/if}}
{{#if this.superpowered}}
superpowered = {{{this.superpowered}}}
{{/if}}
{{#if this.user-data}}
user-data = "{{{this.user-data}}}"
{{/if}}
{{/each}}
{{/if}}
5 changes: 4 additions & 1 deletion packages/os/os.spec
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Source13: cis-checks-k8s-metadata-json
Source14: certdog-toml
Source15: prairiedog-toml
Source16: thar-be-updates-toml
Source19: host-containers-toml

# 1xx sources: systemd units
Source100: apiserver.service
Expand Down Expand Up @@ -475,7 +476,8 @@ install -d %{buildroot}%{_cross_datadir}/updog
install -p -m 0644 %{_cross_repo_root_json} %{buildroot}%{_cross_datadir}/updog

install -d %{buildroot}%{_cross_templatedir}
install -p -m 0644 %{S:5} %{S:6} %{S:7} %{S:8} %{S:14} %{S:15} %{S:16} %{buildroot}%{_cross_templatedir}
install -p -m 0644 %{S:5} %{S:6} %{S:7} %{S:8} %{S:14} %{S:15} %{S:16} %{S:19} \
%{buildroot}%{_cross_templatedir}

install -d %{buildroot}%{_cross_unitdir}
install -p -m 0644 \
Expand Down Expand Up @@ -594,6 +596,7 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir}
%{_cross_tmpfilesdir}/host-containers.conf
%dir %{_cross_templatedir}
%{_cross_templatedir}/host-ctr-toml
%{_cross_templatedir}/host-containers-toml
webern marked this conversation as resolved.
Show resolved Hide resolved

%files -n %{_cross_os}storewolf
%{_cross_bindir}/storewolf
Expand Down
23 changes: 18 additions & 5 deletions sources/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions sources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ members = [
"api/migration/migrations/v1.19.3/prairiedog-services-cfg-v0-1-0",
"api/migration/migrations/v1.19.3/thar-be-updates-config-file-v0-1-0",
"api/migration/migrations/v1.19.3/thar-be-updates-affected-services-v0-1-0",
"api/migration/migrations/v1.19.3/host-containers-config-file-v0-1-0",
"api/migration/migrations/v1.19.3/host-containers-config-list-v0-1-0",

"bloodhound",

Expand Down
11 changes: 6 additions & 5 deletions sources/api/host-containers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ build = "build.rs"
exclude = ["README.md"]

[dependencies]
apiclient = { path = "../apiclient", version = "0.1" }
base64 = "0.13"
constants = { path = "../../constants", version = "0.1" }
http = "0.2"
log = "0.4"
models = { path = "../../models", version = "0.1" }
webern marked this conversation as resolved.
Show resolved Hide resolved
serde_json = "1"
modeled-types = { path = "../../models/modeled-types", version = "0.1" }
serde = { version = "1", features = ["derive"] }
simplelog = "0.12"
snafu = "0.7"
tokio = { version = "~1.32", default-features = false, features = ["macros", "rt-multi-thread"] } # LTS
toml = "0.8"

[dev-dependencies]
tempfile = "3"

[build-dependencies]
generate-readme = { version = "0.1", path = "../../generate-readme" }
2 changes: 1 addition & 1 deletion sources/api/host-containers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Current version: 0.1.0

host-containers ensures that host containers are running as defined in system settings.

It queries the API for their settings, then configures the system by:
It reads the currently configured containers from its config file, then configures the system by:
* creating a user-data file in the host container's persistent storage area, if a base64-encoded
user-data setting is set for the host container. (The decoded contents are available to the
container at /.bottlerocket/host-containers/NAME/user-data)
Expand Down
18 changes: 18 additions & 0 deletions sources/api/host-containers/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use modeled_types::{Identifier, Url, ValidBase64};
use serde::Deserialize;
use std::collections::HashMap;

#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct HostContainersConfig {
pub(crate) host_containers: Option<HashMap<Identifier, HostContainer>>,
}

#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct HostContainer {
pub(crate) source: Option<Url>,
pub(crate) enabled: Option<bool>,
pub(crate) superpowered: Option<bool>,
pub(crate) user_data: Option<ValidBase64>,
}
143 changes: 77 additions & 66 deletions sources/api/host-containers/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

host-containers ensures that host containers are running as defined in system settings.

It queries the API for their settings, then configures the system by:
It reads the currently configured containers from its config file, then configures the system by:
* creating a user-data file in the host container's persistent storage area, if a base64-encoded
user-data setting is set for the host container. (The decoded contents are available to the
container at /.bottlerocket/host-containers/NAME/user-data)
Expand All @@ -26,13 +26,15 @@ use std::path::{Path, PathBuf};
use std::process::{self, Command};
use std::str::FromStr;

use model::modeled_types::Identifier;
use modeled_types::Identifier;

const ENV_FILE_DIR: &str = "/etc/host-containers";
const CONFIG_FILE: &str = "/etc/host-containers/host-containers.toml";
const PERSISTENT_STORAGE_BASE_DIR: &str = "/local/host-containers";

mod config;

mod error {
use http::StatusCode;
use snafu::Snafu;
use std::fmt;
use std::io;
Expand All @@ -42,32 +44,16 @@ mod error {
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub(super) enum Error {
#[snafu(display("Error sending {} to {}: {}", method, uri, source))]
APIRequest {
method: String,
uri: String,
#[snafu(source(from(apiclient::Error, Box::new)))]
source: Box<apiclient::Error>,
},

#[snafu(display("Error {} when sending {} to {}: {}", code, method, uri, response_body))]
APIResponse {
method: String,
uri: String,
code: StatusCode,
response_body: String,
#[snafu(display("Error reading config from {}: {}", config_file, source))]
ReadConfig {
config_file: String,
source: io::Error,
},

#[snafu(display(
"Error deserializing response as JSON from {} to {}: {}",
method,
uri,
source
))]
ResponseJson {
method: &'static str,
uri: String,
source: serde_json::Error,
#[snafu(display("Error parsing config toml from {}: {}", config_file, source))]
ConfigToml {
config_file: String,
source: toml::de::Error,
},

#[snafu(display("Host containers '{}' missing field '{}'", name, field))]
Expand Down Expand Up @@ -134,34 +120,26 @@ mod error {

type Result<T> = std::result::Result<T, error::Error>;

/// Query the API for the currently defined host containers
async fn get_host_containers<P>(socket_path: P) -> Result<HashMap<Identifier, model::HostContainer>>
/// Read the currently defined host containers from the config file
fn get_host_containers<P>(config_path: P) -> Result<HashMap<Identifier, config::HostContainer>>
webern marked this conversation as resolved.
Show resolved Hide resolved
where
P: AsRef<Path>,
{
debug!("Querying the API for settings");

let method = "GET";
let uri = constants::API_SETTINGS_URI;
let (code, response_body) = apiclient::raw_request(&socket_path, uri, method, None)
.await
.context(error::APIRequestSnafu { method, uri })?;
ensure!(
code.is_success(),
error::APIResponseSnafu {
method,
uri,
code,
response_body,
}
let config_path = config_path.as_ref();
debug!(
"Reading containers from the config file: {}",
config_path.display()
);

// Build a Settings struct from the response string
let settings: model::Settings =
serde_json::from_str(&response_body).context(error::ResponseJsonSnafu { method, uri })?;
let config = std::fs::read_to_string(config_path).context(error::ReadConfigSnafu {
config_file: format!("{:?}", config_path),
})?;
let config: config::HostContainersConfig =
toml::from_str(&config).context(error::ConfigTomlSnafu {
config_file: format!("{:?}", config_path),
})?;

// If host containers aren't defined, return an empty map
Ok(settings.host_containers.unwrap_or_default())
Ok(config.host_containers.unwrap_or_default())
}

/// SystemdUnit stores the systemd unit being manipulated
Expand Down Expand Up @@ -288,20 +266,19 @@ where
/// Store the args we receive on the command line
struct Args {
log_level: LevelFilter,
socket_path: PathBuf,
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: {}
[ --socket-path PATH ]
[ --config-path PATH ]
[ --log-level trace|debug|info|warn|error ]

Socket path defaults to {}",
program_name,
constants::API_SOCKET,
Config path defaults to {}",
program_name, CONFIG_FILE,
);
process::exit(2);
}
Expand All @@ -315,7 +292,7 @@ fn usage_msg<S: AsRef<str>>(msg: S) -> ! {
/// Parse the args to the program and return an Args struct
fn parse_args(args: env::Args) -> Args {
let mut log_level = None;
let mut socket_path = None;
let mut config_path = None;

let mut iter = args.skip(1);
while let Some(arg) = iter.next() {
Expand All @@ -329,10 +306,10 @@ fn parse_args(args: env::Args) -> Args {
}));
}

"--socket-path" => {
socket_path = Some(
"--config-path" => {
config_path = Some(
iter.next()
.unwrap_or_else(|| usage_msg("Did not give argument to --socket-path"))
.unwrap_or_else(|| usage_msg("Did not give argument to --config-path"))
.into(),
)
}
Expand All @@ -343,15 +320,15 @@ fn parse_args(args: env::Args) -> Args {

Args {
log_level: log_level.unwrap_or(LevelFilter::Info),
socket_path: socket_path.unwrap_or_else(|| constants::API_SOCKET.into()),
config_path: config_path.unwrap_or_else(|| CONFIG_FILE.into()),
}
}

fn handle_host_container<S>(name: S, image_details: &model::HostContainer) -> Result<()>
fn handle_host_container<S>(name: S, image_details: &config::HostContainer) -> Result<()>
where
S: AsRef<str>,
{
// Get basic settings, as retrieved from API.
// Get basic settings, as retrieved from the config file.
let name = name.as_ref();
let source = image_details
.source
Expand Down Expand Up @@ -469,7 +446,7 @@ fn is_container_affected(settings: &[&str], container_name: &str) -> bool {
false
}

async fn run() -> Result<()> {
webern marked this conversation as resolved.
Show resolved Hide resolved
fn run() -> Result<()> {
let args = parse_args(env::args());
// this env var is passed by thar-be-settings
let changed_settings_env = env::var("CHANGED_SETTINGS").unwrap_or_else(|_| "".to_string());
Expand All @@ -481,7 +458,7 @@ async fn run() -> Result<()> {
info!("host-containers started");

let mut failed = 0usize;
let host_containers = get_host_containers(args.socket_path).await?;
let host_containers = get_host_containers(args.config_path)?;
for (name, image_details) in host_containers.iter() {
// handle all host containers during startup
// handle the host container that has settings changed during restart
Expand All @@ -507,10 +484,44 @@ async fn run() -> Result<()> {
// 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
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
fn main() {
if let Err(e) = run() {
eprintln!("{}", e);
process::exit(1);
}
}

#[cfg(test)]
mod test {
use super::*;
use modeled_types::{Identifier, Url, ValidBase64};

#[test]
fn test_get_host_containers() {
let config_toml = r#"[host-containers."foo"]
source = "https://example.com"
enabled = true
superpowered = true
user-data = "Zm9vCg=="
"#;

let temp_dir = tempfile::TempDir::new().unwrap();
let temp_config = Path::join(temp_dir.path(), "host-containers.toml");
let _ = std::fs::write(&temp_config, config_toml).unwrap();

let host_containers = get_host_containers(&temp_config).unwrap();

let mut expected_host_containers = HashMap::new();
expected_host_containers.insert(
Identifier::try_from("foo").unwrap(),
config::HostContainer {
source: Some(Url::try_from("https://example.com").unwrap()),
enabled: Some(true),
superpowered: Some(true),
user_data: Some(ValidBase64::try_from("Zm9vCg==").unwrap()),
},
);

assert_eq!(host_containers, expected_host_containers)
}
}
Loading