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

prairiedog: migrate to config file #3713

Merged
merged 1 commit into from
Feb 28, 2024
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 @@ -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
jmt-lab marked this conversation as resolved.
Show resolved Hide resolved
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)?;
jmt-lab marked this conversation as resolved.
Show resolved Hide resolved

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)
jmt-lab marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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