Skip to content

Commit

Permalink
host-containers: get containers from config file
Browse files Browse the repository at this point in the history
This removes the dependency on calling the Settings API. It instead
depends on a config file (defaults to
/etc/host-containers/host-containers.toml)

Signed-off-by: Sam Berning <[email protected]>
  • Loading branch information
sam-berning committed Feb 14, 2024
1 parent 589d466 commit 86607d1
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 78 deletions.
9 changes: 4 additions & 5 deletions sources/Cargo.lock

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

9 changes: 4 additions & 5 deletions sources/api/host-containers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ 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" }
serde_json = "1"
modeled-types = { path = "../../models/modeled-types", version = "0.1" }
model-derive = { path = "../../models/model-derive", 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"

[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
16 changes: 16 additions & 0 deletions sources/api/host-containers/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use model_derive::model;
use modeled_types::{Identifier, Url, ValidBase64};
use std::collections::HashMap;

#[model]
struct HostContainersConfig {
host_containers: HashMap<Identifier, HostContainer>,
}

#[model]
struct HostContainer {
source: Url,
enabled: bool,
superpowered: bool,
user_data: ValidBase64,
}
107 changes: 40 additions & 67 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,23 @@ 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>>
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,
}
);

// Build a Settings struct from the response string
let settings: model::Settings =
serde_json::from_str(&response_body).context(error::ResponseJsonSnafu { method, uri })?;
debug!("Reading containers from the config file!");
let config_path = config_path.as_ref();
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 +263,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 +289,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 +303,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 +317,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 +443,7 @@ fn is_container_affected(settings: &[&str], container_name: &str) -> bool {
false
}

async fn run() -> Result<()> {
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 +455,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,9 +481,8 @@ 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);
}
Expand Down

0 comments on commit 86607d1

Please sign in to comment.