Skip to content

Commit

Permalink
Add host-container user-data setting
Browse files Browse the repository at this point in the history
You can set settings.host-containers.NAME.user-data to a valid base64-encoded
string to have the (decoded) data placed in a file accessible to the host
container at /.bottlerocket/host-containers/NAME/user-data.
  • Loading branch information
tjkirch committed Dec 16, 2020
1 parent 989bcbf commit 1e21634
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 7 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ These settings can be changed at any time.
Beyond just changing the settings above to affect the `admin` and `control` containers, you can add and remove host containers entirely.
As long as you define the three fields above -- `source` with a URI, and `enabled` and `superpowered` with true/false -- you can add host containers with an API call or user data.

You can optionally define a `user-data` field with arbitrary base64-encoded data, which will be made available in the container at `/.bottlerocket/host-containers/$HOST_CONTAINER_NAME/user-data`.
(It was inspired by instance user data, but is entirely separate; it can be any data your host container feels like interpreting.)

Here's an example of adding a custom host container with API calls:
```
apiclient -u /settings -X PATCH -d '{"host-containers": {"custom": {"source": "MY-CONTAINER-URI", "enabled": true, "superpowered": false}}}'
Expand Down
2 changes: 1 addition & 1 deletion Release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ version = "1.0.4"
"(1.0.1, 1.0.2)" = ["migrate_v1.0.2_add-enable-spot-instance-draining.lz4"]
"(1.0.2, 1.0.3)" = ["migrate_v1.0.3_add-sysctl.lz4"]
"(1.0.3, 1.0.4)" = []
"(1.0.4, 1.0.5)" = ["migrate_v1.0.5_add-lockdown.lz4", "migrate_v1.0.5_sysctl-subcommand.lz4"]
"(1.0.4, 1.0.5)" = ["migrate_v1.0.5_add-lockdown.lz4", "migrate_v1.0.5_sysctl-subcommand.lz4", "migrate_v1.0.5_add-user-data.lz4"]
8 changes: 8 additions & 0 deletions sources/Cargo.lock

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

1 change: 1 addition & 0 deletions sources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ members = [
"api/migration/migrations/v1.0.3/add-sysctl",
"api/migration/migrations/v1.0.5/add-lockdown",
"api/migration/migrations/v1.0.5/sysctl-subcommand",
"api/migration/migrations/v1.0.5/add-user-data",

"bottlerocket-release",

Expand Down
1 change: 1 addition & 0 deletions sources/api/host-containers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ exclude = ["README.md"]

[dependencies]
apiclient = { path = "../apiclient" }
base64 = "0.13"
http = "0.2"
log = "0.4"
models = { path = "../../models" }
Expand Down
11 changes: 8 additions & 3 deletions sources/api/host-containers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ Current version: 0.1.0

## Background

host-containers is a tool that queries the API for the currently enabled host containers and
ensures the relevant systemd service is enabled/started or disabled/stopped for each one depending
on its 'enabled' flag.
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:
* 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)
* creating an environment file used by a host-container-specific instance of a systemd service
* ensuring the host container's systemd service is enabled/started or disabled/stopped

## Colophon

Expand Down
44 changes: 41 additions & 3 deletions sources/api/host-containers/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/*!
# Background
host-containers is a tool that queries the API for the currently enabled host containers and
ensures the relevant systemd service is enabled/started or disabled/stopped for each one depending
on its 'enabled' flag.
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:
* 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)
* creating an environment file used by a host-container-specific instance of a systemd service
* ensuring the host container's systemd service is enabled/started or disabled/stopped
*/

#![deny(rust_2018_idioms)]
Expand All @@ -28,6 +33,7 @@ use model::modeled_types::Identifier;
const DEFAULT_API_SOCKET: &str = "/run/api.sock";
const API_SETTINGS_URI: &str = "/settings";
const ENV_FILE_DIR: &str = "/etc/host-containers";
const PERSISTENT_STORAGE_BASE_DIR: &str = "/local/host-containers";

const SYSTEMCTL_BIN: &str = "/bin/systemctl";
const HOST_CTR_BIN: &str = "/bin/host-ctr";
Expand Down Expand Up @@ -100,6 +106,24 @@ mod error {

#[snafu(display("Logger setup error: {}", source))]
Logger { source: simplelog::TermLogError },

#[snafu(display("Unable to base64 decode user-data '{}': '{}'", base64_string, source))]
Base64Decode {
base64_string: 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 user-data for host container '{}': {}", name, source))]
UserDataWrite {
name: String,
source: std::io::Error,
},
}
}

Expand Down Expand Up @@ -310,6 +334,7 @@ fn handle_host_container<S>(name: S, image_details: &model::ContainerImage) -> R
where
S: AsRef<str>,
{
// Get basic settings, as retrieved from API.
let name = name.as_ref();
let source = image_details.source.as_ref().context(error::MissingField {
name,
Expand All @@ -329,6 +354,19 @@ where
name, enabled
);

// If user data was specified, unencode it and write it out before we start the container.
if let Some(user_data) = &image_details.user_data {
let decoded_bytes = base64::decode(user_data.as_bytes()).context(error::Base64Decode {
base64_string: user_data.as_ref(),
})?;

let dir = Path::new(PERSISTENT_STORAGE_BASE_DIR).join(name);
fs::create_dir_all(&dir).context(error::Mkdir { dir: &dir })?;

let path = dir.join("user-data");
fs::write(path, decoded_bytes).context(error::UserDataWrite { name })?;
}

// Write the environment file needed for the systemd service to have details about this
// specific host container
write_env_file(name, source, enabled, superpowered)?;
Expand Down
12 changes: 12 additions & 0 deletions sources/api/migration/migrations/v1.0.5/add-user-data/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "add-user-data"
version = "0.1.0"
authors = ["Tom Kirchner <[email protected]>"]
license = "Apache-2.0 OR MIT"
edition = "2018"
publish = false
# Don't rebuild crate just because of changes to README.
exclude = ["README.md"]

[dependencies]
migration-helpers = { path = "../../../migration-helpers" }
45 changes: 45 additions & 0 deletions sources/api/migration/migrations/v1.0.5/add-user-data/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#![deny(rust_2018_idioms)]

use migration_helpers::{migrate, Migration, MigrationData, Result};
use std::process;

/// This migration removes host-container user data settings when downgrading to versions that
/// don't understand them.
pub struct AddUserDataMigration;

impl Migration for AddUserDataMigration {
/// There's no user data by default, it's just left empty on upgrade.
fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
println!("AddUserDataMigration has no work to do on upgrade.");
Ok(input)
}

/// Older versions don't know about the user-data settings; we remove them so that old versions
/// don't see them and fail deserialization.
fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
for setting in input.data.clone().keys() {
// We don't currently have structured data available to migrations, and we don't want
// to re-parse keys. We know no other keys could match these basic patterns.
if setting.starts_with("settings.host-containers.") && setting.ends_with(".user-data") {
if let Some(data) = input.data.remove(setting) {
println!("Removed {}, which was set to '{}'", setting, data);
}
}
}
Ok(input)
}
}

fn run() -> Result<()> {
migrate(AddUserDataMigration)
}

// 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() {
eprintln!("{}", e);
process::exit(1);
}
}
1 change: 1 addition & 0 deletions sources/models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ struct ContainerImage {
source: Url,
enabled: bool,
superpowered: bool,
user_data: ValidBase64,
}

// NTP settings
Expand Down

0 comments on commit 1e21634

Please sign in to comment.