Skip to content
Open
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
9 changes: 2 additions & 7 deletions packages/os/pluto.service
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Description=Generate additional settings for Kubernetes
# settings generators, and before settings-applier commits all the settings, renders
# config files, and restarts services.
After=network-online.target apiserver.service sundog.service
# Use Before here rather than After=pluto.service in settings-applier.service
# since this unit is only present on k8s variants.
Before=settings-applier.service
Requires=sundog.service
# We don't want to restart the unit if the network goes offline or apiserver restarts
Expand All @@ -15,16 +17,9 @@ RefuseManualStop=true

[Service]
Type=oneshot
# pluto needs access to any Kubernetes settings supplied through user-data, along with
# network-related settings such as proxy servers. Commit any settings that might have
# been generated during the sundog phase.
ExecStartPre=/usr/bin/settings-committer
ExecStart=/usr/bin/pluto
RemainAfterExit=true
StandardError=journal+console

[Install]
# settings-applier requires sundog to succeed as a signal that all settings generators ran
# successfully. Since pluto is an honorary settings generator, settings-applier also needs
# it to succeed before it can start.
RequiredBy=settings-applier.service
1 change: 0 additions & 1 deletion packages/os/settings-applier.service
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ RefuseManualStop=true

[Service]
Type=oneshot
ExecStartPre=/usr/bin/settings-committer
ExecStart=/usr/bin/thar-be-settings --all
RemainAfterExit=true
StandardError=journal+console
Expand Down
2 changes: 0 additions & 2 deletions packages/os/sundog.service
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ RefuseManualStop=true

[Service]
Type=oneshot
# Commit first so we can use settings from user data.
ExecStartPre=/usr/bin/settings-committer
ExecStart=/usr/bin/sundog
RemainAfterExit=true
StandardError=journal+console
Expand Down
5 changes: 5 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 @@ -96,6 +96,7 @@ models = { version = "0.1", path = "models" }
parse-datetime = { version = "0.1", path = "parse-datetime" }
pciclient = { version = "0.1", path = "pciclient" }
retry-read = { version = "0.1", path = "retry-read" }
settings-committer = { version = "0.1", path = "api/settings-committer" }
signpost = { version = "0.1", path = "updater/signpost" }
storewolf = { version = "0.1", path = "api/storewolf" }
simple-settings-plugin = { version = "0.1", path = "api/simple-settings-plugin" }
Expand Down
3 changes: 3 additions & 0 deletions sources/api/pluto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ bottlerocket-modeled-types.workspace = true
bottlerocket-settings-models.workspace = true
constants.workspace = true
imdsclient.workspace = true
log.workspace = true
rustls.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
settings-committer.workspace = true
simplelog.workspace = true
snafu.workspace = true
tempfile.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
Expand Down
20 changes: 20 additions & 0 deletions sources/api/pluto/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ Pluto returns a special exit code of 2 to inform `sundog` that a setting should
example, if `max-pods` cannot be generated, we want `sundog` to skip it without failing since a
reasonable default is available.
*/
#[macro_use]
extern crate log;

mod api;
mod aws;
mod ec2;
mod eks;

use api::{settings_view_get, settings_view_set, SettingsViewDelta};
use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger};
use aws_smithy_experimental::hyper_1_0::CryptoMode;
use base64::Engine;
use bottlerocket_modeled_types::{KubernetesClusterDnsIp, KubernetesHostnameOverrideSource};
Expand Down Expand Up @@ -72,6 +75,7 @@ const PROVIDER: CryptoMode = CryptoMode::AwsLcFips;

mod error {
use crate::{api, ec2, eks};
use settings_committer::SettingsCommitterError;
use snafu::Snafu;
use std::net::AddrParseError;

Expand Down Expand Up @@ -162,6 +166,12 @@ mod error {

#[snafu(display("Unable to create tempdir: {}", source))]
Tempdir { source: std::io::Error },

#[snafu(display("Unable to commit pending transaction: {}", source))]
Commit { source: SettingsCommitterError },

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

Expand Down Expand Up @@ -488,6 +498,16 @@ fn set_aws_config(aws_k8s_info: &SettingsViewDelta, filepath: &Path) -> Result<(
}

async fn run() -> Result<()> {
// SimpleLogger will send errors to stderr and anything less to stdout.
SimpleLogger::init(LevelFilter::Info, LogConfig::default()).context(error::LoggerSnafu)?;
info!("Pluto started");

// pluto needs access to any Kubernetes settings supplied through user-data, along with
// network-related settings such as proxy servers. Commit any settings that might have
// been generated during the sundog phase.
settings_committer::commit(constants::API_SOCKET, constants::LAUNCH_TRANSACTION).await.context(error::CommitSnafu)?;

info!("Retrieving settings values");
let mut client = ImdsClient::new();
let current_settings = api::get_aws_k8s_info().await.context(error::AwsInfoSnafu)?;
let mut aws_k8s_info = SettingsViewDelta::from_api_response(current_settings);
Expand Down
118 changes: 118 additions & 0 deletions sources/api/settings-committer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#[macro_use]
extern crate log;

use snafu::ResultExt;
use std::{collections::HashMap};

const API_PENDING_URI_BASE: &str = "/v2/tx";
const API_COMMIT_URI_BASE: &str = "/tx/commit";

pub mod error {
use http::StatusCode;
use snafu::Snafu;

/// Potential errors during user data management.
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum SettingsCommitterError {
#[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,
},
}
}
pub use error::SettingsCommitterError;
pub type Result<T> = std::result::Result<T, error::SettingsCommitterError>;

/// Checks pending settings and logs them. We don't want to prevent a
/// commit if there's a blip in retrieval or parsing of the pending
/// settings. We know the system won't be functional without a commit,
/// but we can live without logging what was committed.
async fn check_pending_settings<S: AsRef<str>>(socket_path: S, transaction: &str) {
let uri = format!("{API_PENDING_URI_BASE}?tx={transaction}");

debug!("GET-ing {uri} to determine if there are pending settings");
let get_result = apiclient::raw_request(socket_path.as_ref(), &uri, "GET", None).await;
let response_body = match get_result {
Ok((code, response_body)) => {
if !code.is_success() {
warn!("Got {code} when sending GET to {uri}: {response_body}");
return;
}
response_body
}
Err(err) => {
warn!("Failed to GET pending settings from {uri}: {err}");
return;
}
};

let pending_result: serde_json::Result<HashMap<String, serde_json::Value>> =
serde_json::from_str(&response_body);
match pending_result {
Ok(pending) => {
debug!("Pending settings for tx {}: {:?}", transaction, &pending);
}
Err(err) => {
warn!("Failed to parse response from {uri}: {err}");
}
}
}

/// Commits pending settings to live.
async fn commit_pending_settings<S: AsRef<str>>(socket_path: S, transaction: &str) -> Result<()> {
let uri = format!("{API_COMMIT_URI_BASE}?tx={transaction}");
debug!("POST-ing to {uri} to move pending settings to live");

if let Err(e) = apiclient::raw_request(socket_path.as_ref(), &uri, "POST", None).await {
match e {
// Some types of response errors are OK for this use.
apiclient::Error::ResponseStatus { code, body, .. } => {
if code.as_u16() == 422 {
info!("settings-committer found no settings changes to commit");
return Ok(());
} else {
return error::APIResponseSnafu {
method: "POST",
uri,
code,
response_body: body,
}
.fail();
}
}
// Any other type of error means we couldn't even make the request.
_ => {
return Err(e).context(error::APIRequestSnafu {
method: "POST",
uri,
});
}
}
}
Ok(())
}

pub async fn commit(socket_path: &str, transaction: &str) -> Result<()> {
if log_enabled!(log::Level::Debug) {
// We log the pending settings at Debug, so only fetch them if they won't be filtered.
info!("Checking pending settings.");
check_pending_settings(socket_path, transaction).await;
}

info!("Committing settings.");
commit_pending_settings(socket_path, transaction).await?;

Ok(())
}
Loading
Loading