Skip to content

Commit

Permalink
Merge pull request #777 from bottlerocket-os/os-api
Browse files Browse the repository at this point in the history
Add /os API
  • Loading branch information
tjkirch authored Feb 26, 2020
2 parents 230cb2c + e5e6571 commit eb8ad56
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 29 deletions.
13 changes: 13 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/api/apiserver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ build = "build.rs"

[dependencies]
actix-web = { version = "1.0.5", default-features = false, features = ["uds"] }
bottlerocket-release = { path = "../../bottlerocket-release" }
libc = "0.2"
log = "0.4"
models = { path = "../../models" }
Expand Down
5 changes: 5 additions & 0 deletions sources/api/apiserver/src/server/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ pub enum Error {
#[snafu(display("Tried to commit with no pending changes"))]
CommitWithNoPending,

#[snafu(display("Unable to get OS release data: {}", source))]
ReleaseData {
source: bottlerocket_release::Error,
},

// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=

// Controller errors
Expand Down
33 changes: 24 additions & 9 deletions sources/api/apiserver/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@ mod controller;
mod error;
pub use error::Error;

use crate::datastore::{Committed, FilesystemDataStore, Key, Value};
use actix_web::{error::ResponseError, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
use bottlerocket_release::BottlerocketRelease;
use error::Result;
use log::info;
use model::{ConfigurationFiles, Services, Settings};
use nix::unistd::{chown, Gid};
use snafu::{ensure, OptionExt, ResultExt};
use std::collections::{HashMap, HashSet};
use std::env;
use std::path::Path;
use std::process::Command;
use std::sync;

use crate::datastore::{Committed, FilesystemDataStore, Key, Value};
use error::Result;
use model::{ConfigurationFiles, Services, Settings};

use nix::unistd::{chown, Gid};
use std::fs::set_permissions;
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;
use std::sync;

// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=

Expand Down Expand Up @@ -83,6 +82,10 @@ where
web::post().to(commit_transaction_and_apply),
),
)
.service(
web::scope("/os")
.route("", web::get().to(get_os_info))
)
.service(
web::scope("/metadata")
.route("/affected-services", web::get().to(get_affected_services))
Expand Down Expand Up @@ -240,6 +243,13 @@ fn commit_transaction_and_apply(
Ok(ChangedKeysResponse(changes))
}

// The "os" APIs don't deal with the data store at all, they just read a release field, so we
// handle them here.
fn get_os_info() -> Result<BottlerocketReleaseResponse> {
let br = BottlerocketRelease::new().context(error::ReleaseData)?;
Ok(BottlerocketReleaseResponse(br))
}

/// Get the affected services for a list of data keys
fn get_affected_services(
query: web::Query<HashMap<String, String>>,
Expand Down Expand Up @@ -369,6 +379,7 @@ impl ResponseError for error::Error {
SystemdNotifyStatus {} => HttpResponse::InternalServerError(),
SetPermissions { .. } => HttpResponse::InternalServerError(),
SetGroup { .. } => HttpResponse::InternalServerError(),
ReleaseData { .. } => HttpResponse::InternalServerError(),
}
.finish()
}
Expand Down Expand Up @@ -403,6 +414,10 @@ macro_rules! impl_responder_for {
struct SettingsResponse(Settings);
impl_responder_for!(SettingsResponse, self, self.0);

/// This lets us respond from our handler methods with a BottlerocketRelease (or Result<BottlerocketRelease>)
struct BottlerocketReleaseResponse(BottlerocketRelease);
impl_responder_for!(BottlerocketReleaseResponse, self, self.0);

/// This lets us respond from our handler methods with a HashMap (or Result<HashMap>) for metadata
struct MetadataResponse(HashMap<String, Value>);
impl_responder_for!(MetadataResponse, self, self.0);
Expand Down
18 changes: 18 additions & 0 deletions sources/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,24 @@ paths:
500:
description: "Server error"

/os:
get:
summary: "Get OS information such as version, variant, and architecture"
operationId: "get_os_info"
responses:
200:
description: "Successful request"
content:
application/json:
# The response is a hashmap of string to string. Example:
# { "arch": "x86_64" }
schema:
type: object
additionalProperties:
type: string
500:
description: "Server error"

/metadata/affected-services:
get:
summary: "Get affected services"
Expand Down
2 changes: 2 additions & 0 deletions sources/api/schnauzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ build = "build.rs"
[dependencies]
apiclient = { path = "../apiclient" }
base64 = "0.11"
bottlerocket-release = { path = "../../bottlerocket-release" }
erased-serde = "0.3"
handlebars = "3.0"
http = "0.1"
log = "0.4"
Expand Down
24 changes: 15 additions & 9 deletions sources/api/schnauzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate log;

mod helpers;

use bottlerocket_release::BottlerocketRelease;
use handlebars::Handlebars;
use serde::de::DeserializeOwned;
use snafu::ResultExt;
Expand Down Expand Up @@ -94,23 +95,28 @@ where

/// Requests all settings from the API and wraps them in a "settings" key, so they can be used as
/// the data source for a handlebars templating call.
pub fn get_settings<P>(socket_path: P) -> Result<HashMap<String, model::Settings>>
pub fn get_settings<P>(socket_path: P) -> Result<HashMap<String, Box<dyn erased_serde::Serialize>>>
where
P: AsRef<Path>,
{
debug!("Querying API for settings data");
let settings: model::Settings =
get_json(socket_path, "/settings", None as Option<(String, String)>)?;
get_json(&socket_path, "/settings", None as Option<(String, String)>)?;

trace!("Settings values: {:?}", settings);

// The following helps satisfy the Handlebars templating library.
// The variables in the templates are prefixed with "settings"
// {{ settings.foo.bar }} so we need to wrap the Settings struct in a map
// with the key "settings".
let mut wrapped_template_keys: HashMap<String, model::Settings> = HashMap::new();
wrapped_template_keys.insert("settings".to_string(), settings);
trace!("Final template keys map: {:?}", &wrapped_template_keys);
debug!("Querying API for OS data");
let br: BottlerocketRelease =
get_json(&socket_path, "/os", None as Option<(String, String)>)?;

trace!("OS values: {:?}", br);

// The following helps satisfy the Handlebars templating library. The variables in the
// templates include a prefix like "settings" or "os", for example {{ settings.foo.bar }}
// so we need to wrap the structs struct in a map with the relevant prefixes.
let mut wrapped_template_keys: HashMap<String, Box<dyn erased_serde::Serialize>> = HashMap::new();
wrapped_template_keys.insert("settings".to_string(), Box::new(settings));
wrapped_template_keys.insert("os".to_string(), Box::new(br));

Ok(wrapped_template_keys)
}
Expand Down
2 changes: 1 addition & 1 deletion sources/api/storewolf/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ type Result<T> = std::result::Result<T, StorewolfError>;
/// Given a base path, create a brand new datastore with the appropriate
/// symlink structure for the desired datastore version.
///
/// If `version` is given, uses it, otherwise pulls version from /etc/os-release.
/// If `version` is given, uses it, otherwise pulls version from bottlerocket-release.
///
/// An example setup for theoretical version 1.5:
/// /path/to/datastore/current
Expand Down
1 change: 1 addition & 0 deletions sources/api/thar-be-settings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ build = "build.rs"

[dependencies]
apiclient = { path = "../apiclient" }
erased-serde = "0.3"
handlebars = "3.0"
http = "0.1"
itertools = "0.8"
Expand Down
2 changes: 1 addition & 1 deletion sources/api/thar-be-settings/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn get_config_file_names(services: &model::Services) -> HashSet<String> {
pub fn render_config_files(
registry: &handlebars::Handlebars<'_>,
config_files: model::ConfigurationFiles,
settings: HashMap<String, model::Settings>,
settings: HashMap<String, Box<dyn erased_serde::Serialize>>,
strict: bool,
) -> Result<Vec<RenderedConfigFile>> {
// Go write all the configuration files from template
Expand Down
4 changes: 3 additions & 1 deletion sources/bottlerocket-release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ Current version: 0.1.0

## Background

This library lets you get a BottlerocketRelease struct that represents the data in the /etc/os-release file, or another file you point to.
This library lets you get a BottlerocketRelease struct that represents the data in the standard os-release file, or another file you point to.
The VERSION_ID is returned as a semver::Version for convenience.

The information is pulled at runtime because build_id changes frequently and would cause unnecessary rebuilds.

## Colophon

This text was generated from `README.tpl` using [cargo-readme](https://crates.io/crates/cargo-readme), and includes the rustdoc from `src/lib.rs`.
19 changes: 16 additions & 3 deletions sources/bottlerocket-release/build.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Automatically generate README.md from rustdoc.

use std::env;
use std::fs::File;
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

fn main() {
fn generate_readme() {
// Check for environment variable "SKIP_README". If it is set,
// skip README generation
if env::var_os("SKIP_README").is_some() {
Expand All @@ -30,3 +30,16 @@ fn main() {
let mut readme = File::create("README.md").unwrap();
readme.write_all(content.as_bytes()).unwrap();
}

fn generate_constants() {
let out_dir = env::var("OUT_DIR").unwrap();
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let contents = format!("const ARCH: &str = \"{}\";", arch);
let path = Path::new(&out_dir).join("constants.rs");
fs::write(path, contents).unwrap();
}

fn main() {
generate_readme();
generate_constants();
}
21 changes: 16 additions & 5 deletions sources/bottlerocket-release/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
/*!
# Background
This library lets you get a BottlerocketRelease struct that represents the data in the /etc/os-release file, or another file you point to.
This library lets you get a BottlerocketRelease struct that represents the data in the standard os-release file, or another file you point to.
The VERSION_ID is returned as a semver::Version for convenience.
The information is pulled at runtime because build_id changes frequently and would cause unnecessary rebuilds.
*/

const DEFAULT_RELEASE_FILE: &str = "/etc/os-release";
const DEFAULT_RELEASE_FILE: &str = "/usr/lib/os-release";

include!(concat!(env!("OUT_DIR"), "/constants.rs"));

use log::debug;
use semver::Version;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use snafu::ResultExt;
use std::fs;
use std::path::Path;

/// BottlerocketRelease represents the data found in the release file.
#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BottlerocketRelease {
// Fields from os-release
pub pretty_name: String,
pub variant_id: String,
pub version_id: Version,
pub build_id: String,

// Other system information
pub arch: String,
}

mod error {
Expand Down Expand Up @@ -55,7 +63,7 @@ impl BottlerocketRelease {
let release_data = fs::read_to_string(path).context(error::ReadReleaseFile { path })?;

// Split and process each line
let pairs: Vec<(String, String)> = release_data
let mut pairs: Vec<(String, String)> = release_data
.lines()
.filter_map(|line| {
// Allow for comments
Expand Down Expand Up @@ -84,6 +92,9 @@ impl BottlerocketRelease {
})
.collect();

// Add information from other sources
pairs.push(("arch".to_string(), ARCH.to_string()));

envy::from_iter(pairs).context(error::LoadReleaseData { path })
}
}

0 comments on commit eb8ad56

Please sign in to comment.