Skip to content

Commit

Permalink
Merge pull request #3713 from jmt-lab/jmt/prairiedog/config-file
Browse files Browse the repository at this point in the history
Jmt/prairiedog/config file
  • Loading branch information
jmt-lab authored Feb 28, 2024
2 parents 85996fc + efa092b commit f6c1b7a
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 67 deletions.
2 changes: 2 additions & 0 deletions Release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,6 @@ version = "1.19.3"
"migrate_v1.19.2_add-ecs-enable-container-metadata.lz4",
]
"(1.19.2, 1.19.3)" = [
"migrate_v1.19.3_prairiedog-config-file-v0-1-0.lz4",
"migrate_v1.19.3_prairiedog-services-cfg-v0-1-0.lz4",
]
4 changes: 3 additions & 1 deletion packages/os/os.spec
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Source12: 00-resolved.conf
Source13: cis-checks-k8s-metadata-json
%endif
Source14: certdog-toml
Source15: prairiedog-toml

# 1xx sources: systemd units
Source100: apiserver.service
Expand Down Expand Up @@ -473,7 +474,7 @@ 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} %{buildroot}%{_cross_templatedir}
install -p -m 0644 %{S:5} %{S:6} %{S:7} %{S:8} %{S:14} %{S:15} %{buildroot}%{_cross_templatedir}

install -d %{buildroot}%{_cross_unitdir}
install -p -m 0644 \
Expand Down Expand Up @@ -682,6 +683,7 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir}
%files -n %{_cross_os}prairiedog
%{_cross_bindir}/prairiedog
%{_cross_unitdir}/reboot-if-required.service
%{_cross_templatedir}/prairiedog-toml

%files -n %{_cross_os}certdog
%{_cross_bindir}/certdog
Expand Down
20 changes: 20 additions & 0 deletions packages/os/prairiedog-toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[required-extensions]
boot = "v1"
+++
{{#if settings.boot}}
{{#if settings.boot.reboot-to-reconcile}}
reboot-to-reconcile = {{settings.boot.reboot-to-reconcile}}
{{/if}}
{{#if settings.boot.kernel}}
[kernel]
{{#each settings.boot.kernel}}
"{{@key}}" = [ {{#each this}}"{{{this}}}",{{/each}} ]
{{/each}}
{{/if}}
{{#if settings.boot.init}}
[init]
{{#each settings.boot.init}}
"{{@key}}" = [ {{#each this}}"{{{this}}}",{{/each}} ]
{{/each}}
{{/if}}
{{/if}}
19 changes: 17 additions & 2 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 @@ -64,6 +64,8 @@ members = [
"api/migration/migrations/v1.19.2/certdog-config-file-v0-1-0",
"api/migration/migrations/v1.19.2/certdog-service-cfg-v0-1-0",
"api/migration/migrations/v1.19.2/add-ecs-enable-container-metadata",
"api/migration/migrations/v1.19.3/prairiedog-config-file-v0-1-0",
"api/migration/migrations/v1.19.3/prairiedog-services-cfg-v0-1-0",

"bloodhound",

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "prairiedog-config-file-v0-1-0"
version = "0.1.0"
authors = ["Jarrett Tierney <[email protected]>"]
license = "Apache-2.0 OR MIT"
edition = "2021"
publish = false
exclude = ["README.md"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" }
Original file line number Diff line number Diff line change
@@ -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.prairiedog-toml",
]))
}

fn main() {
if let Err(e) = run() {
eprintln!("{}", e);
process::exit(1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "prairiedog-services-cfg-v0-1-0"
version = "0.1.0"
authors = ["Jarrett Tierney <[email protected]>"]
license = "Apache-2.0 OR MIT"
edition = "2021"
publish = false
exclude = ["README.md"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" }
Original file line number Diff line number Diff line change
@@ -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.bootconfig.configuration-files",
old_vals: &[],
new_vals: &["prairiedog-toml"],
}]))
}

fn main() {
if let Err(e) = run() {
eprintln!("{}", e);
process::exit(1);
}
}
7 changes: 4 additions & 3 deletions sources/api/prairiedog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ bytes = "1"
constants = { path = "../../constants", version = "0.1" }
log = "0.4"
nix = "0.26"
models = { path = "../../models", version = "0.1" }
modeled-types = { path = "../../models/modeled-types", version = "0.1"}
schnauzer = { path = "../schnauzer", version = "0.1" }
signpost = { path = "../../updater/signpost", version = "0.1" }
simplelog = "0.12"
snafu = "0.7"
serde_json = "1"
tokio = { version = "~1.32", default-features = false, features = ["macros", "rt-multi-thread"] } # LTS
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
toml = "0.8"

[dev-dependencies]
maplit = "1"
Expand Down
78 changes: 43 additions & 35 deletions sources/api/prairiedog/src/bootconfig.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::error;
use crate::error::Result;
use crate::initrd::generate_initrd;
use model::modeled_types::{BootConfigKey, BootConfigValue};
use model::BootSettings;
use modeled_types::{BootConfigKey, BootConfigValue};
use serde::{Deserialize, Serialize};
use snafu::{ensure, ResultExt};
use std::collections::HashMap;
use std::convert::TryInto;
use std::path::Path;
use tokio::io;
use std::{fs, io};

// Boot config related consts
const BOOTCONFIG_INITRD_PATH: &str = "/var/lib/bottlerocket/bootconfig.data";
Expand All @@ -22,6 +22,27 @@ const DEFAULT_BOOT_SETTINGS: BootSettings = BootSettings {
init_parameters: None,
};

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct BootSettings {
#[serde(default, skip_serializing_if = "Option::is_none")]
reboot_to_reconcile: Option<bool>,
#[serde(
alias = "kernel",
rename(serialize = "kernel"),
default,
skip_serializing_if = "Option::is_none"
)]
kernel_parameters: Option<HashMap<BootConfigKey, Vec<BootConfigValue>>>,
#[serde(
alias = "init",
rename(serialize = "init"),
default,
skip_serializing_if = "Option::is_none"
)]
init_parameters: Option<HashMap<BootConfigKey, Vec<BootConfigValue>>>,
}

fn append_boot_config_value_list(values: &[BootConfigValue], output: &mut String) {
for (i, v) in values.iter().enumerate() {
if i > 0 {
Expand Down Expand Up @@ -66,11 +87,11 @@ fn serialize_boot_settings_to_boot_config(boot_settings: &BootSettings) -> Resul
}

/// Queries Bottlerocket boot settings and generates initrd image file with boot config as the only data
pub(crate) async fn generate_boot_config<P>(socket_path: P) -> Result<()>
pub(crate) fn generate_boot_config<P>(config_path: P) -> Result<()>
where
P: AsRef<Path>,
{
let bootconfig_bytes = match get_boot_config_settings(socket_path).await? {
let bootconfig_bytes = match get_boot_config_settings(config_path)? {
Some(boot_settings) => {
info!("Generating initrd boot config from boot settings");
trace!("Boot settings: {:?}", boot_settings);
Expand All @@ -86,36 +107,25 @@ where
};
let initrd = generate_initrd(&bootconfig_bytes)?;
trace!("Writing initrd image file: {:?}", initrd);
tokio::fs::write(BOOTCONFIG_INITRD_PATH, &initrd)
.await
.context(error::WriteInitrdSnafu)?;
fs::write(BOOTCONFIG_INITRD_PATH, &initrd).context(error::WriteInitrdSnafu)?;
Ok(())
}

/// Retrieves boot config related Bottlerocket settings. If they don't exist in the settings model,
/// we return `None` instead.
async fn get_boot_config_settings<P>(socket_path: P) -> Result<Option<BootSettings>>
fn get_boot_config_settings<P>(config_path: P) -> Result<Option<BootSettings>>
where
P: AsRef<Path>,
{
let uri = "/settings";
let settings: serde_json::Value =
schnauzer::v1::get_json(socket_path, uri, Some(("prefix", "boot")))
.await
.context(error::RetrieveSettingsSnafu)?;

match settings.get("boot") {
None => Ok(None),
Some(boot_settings_val) => Ok(Some(
serde_json::from_value(boot_settings_val.to_owned())
.context(error::BootSettingsFromJsonValueSnafu)?,
)),
}
let config_str = fs::read_to_string(config_path.as_ref()).context(error::ReadFileSnafu {
path: config_path.as_ref().to_path_buf(),
})?;
toml::from_str(config_str.as_str()).context(error::InputTomlSnafu)
}

/// Reads `/proc/bootconfig`. Not having any boot config is ignored.
async fn read_proc_bootconfig() -> Result<Option<String>> {
match tokio::fs::read_to_string(PROC_BOOTCONFIG).await {
fn read_proc_bootconfig() -> Result<Option<String>> {
match fs::read_to_string(PROC_BOOTCONFIG) {
Ok(s) => Ok(Some(s)),
Err(e) => {
// If there's no `/proc/bootconfig`, then the user hasn't provisioned any kernel boot configuration.
Expand All @@ -131,8 +141,8 @@ async fn read_proc_bootconfig() -> Result<Option<String>> {
}

/// Reads `/proc/bootconfig` and populates the Bottlerocket boot settings based on the existing boot config data
pub(crate) async fn generate_boot_settings() -> Result<()> {
if let Some(proc_bootconfig) = read_proc_bootconfig().await? {
pub(crate) fn generate_boot_settings() -> Result<()> {
if let Some(proc_bootconfig) = read_proc_bootconfig()? {
debug!(
"Generating kernel boot config settings from `{}`",
PROC_BOOTCONFIG
Expand Down Expand Up @@ -265,7 +275,7 @@ fn parse_boot_config_to_boot_settings(bootconfig: &str) -> Result<BootSettings>
})
}

/// Given a boot config string, deserialize it to `model::BootSettings` and then serialize it back
/// Given a boot config string, deserialize it to `BootSettings` and then serialize it back
/// out as a JSON string for sundog consumption
fn boot_config_to_boot_settings_json(bootconfig_str: &str) -> Result<String> {
// We'll only send the setting if the existing boot config file fits our settings model
Expand All @@ -275,18 +285,16 @@ fn boot_config_to_boot_settings_json(bootconfig_str: &str) -> Result<String> {
}

/// Decides whether the host should be rebooted to have its boot settings take effect
pub(crate) async fn is_reboot_required<P>(socket_path: P) -> Result<bool>
pub(crate) fn is_reboot_required<P>(config_path: P) -> Result<bool>
where
P: AsRef<Path>,
{
let old_boot_settings = match read_proc_bootconfig().await? {
let old_boot_settings = match read_proc_bootconfig()? {
Some(proc_bootconfig) => parse_boot_config_to_boot_settings(&proc_bootconfig)?,
None => DEFAULT_BOOT_SETTINGS,
};

let new_boot_settings = get_boot_config_settings(socket_path)
.await?
.unwrap_or(DEFAULT_BOOT_SETTINGS);
let new_boot_settings = get_boot_config_settings(config_path)?.unwrap_or(DEFAULT_BOOT_SETTINGS);

let reboot_required = if new_boot_settings.reboot_to_reconcile.unwrap_or(false) {
boot_settings_change_requires_reboot(&old_boot_settings, &new_boot_settings)
Expand All @@ -297,7 +305,7 @@ where
Ok(reboot_required)
}

/// Check whether `model::BootSettings` changed in a way to warrant a reboot
/// Check whether `BootSettings` changed in a way to warrant a reboot
fn boot_settings_change_requires_reboot(
old_boot_settings: &BootSettings,
new_boot_settings: &BootSettings,
Expand Down Expand Up @@ -328,13 +336,13 @@ fn boot_settings_change_requires_reboot(

#[cfg(test)]
mod boot_settings_tests {
use super::BootSettings;
use crate::bootconfig::{
boot_config_to_boot_settings_json, boot_settings_change_requires_reboot,
serialize_boot_settings_to_boot_config, DEFAULT_BOOTCONFIG_STR,
};
use maplit::hashmap;
use model::modeled_types::{BootConfigKey, BootConfigValue};
use model::BootSettings;
use modeled_types::{BootConfigKey, BootConfigValue};
use serde_json::json;
use serde_json::value::Value;
use std::collections::HashMap;
Expand Down
17 changes: 5 additions & 12 deletions sources/api/prairiedog/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ pub(super) enum Error {
path: PathBuf,
},

#[snafu(display("Failed to retrieve settings: {}", source))]
RetrieveSettings { source: schnauzer::v1::Error },

#[snafu(display("Failed to convert usize to u32: {}", source))]
UsizeToU32 { source: std::num::TryFromIntError },

Expand All @@ -68,29 +65,25 @@ pub(super) enum Error {
#[snafu(display("Failed to write initrd image file: {}", source))]
WriteInitrd { source: std::io::Error },

#[snafu(display("Error deserializing `BootSettings` from TOML: {}", source))]
InputToml { source: toml::de::Error },

#[snafu(display("Error serializing `BootSettings` to JSON: {}", source))]
OutputJson { source: serde_json::error::Error },

#[snafu(display("Failed to deserialize `BootSettings` from JSON value: {}", source))]
BootSettingsFromJsonValue { source: serde_json::error::Error },

#[snafu(display(
"Invalid boot config file, expected key-value, or key entries for each line"
))]
InvalidBootConfig,

#[snafu(display("Failed to parse boot config key: {}", source))]
ParseBootConfigKey {
source: model::modeled_types::error::Error,
},
ParseBootConfigKey { source: modeled_types::error::Error },

#[snafu(display("Invalid boot config value '{}'. Boot config values may only contain ASCII printable characters except for delimiters such as ';', '\n', ',', '#', and '}}'", input))]
InvalidBootConfigValue { input: String },

#[snafu(display("Failed to parse boot config value: {}", source))]
ParseBootConfigValue {
source: model::modeled_types::error::Error,
},
ParseBootConfigValue { source: modeled_types::error::Error },

#[snafu(display("Unsupported boot config key '{}'. `BootSettings` currently only supports boot configuration for 'kernel' and 'init'", key))]
UnsupportedBootConfigKey { key: String },
Expand Down
Loading

0 comments on commit f6c1b7a

Please sign in to comment.