Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
This is a wrapper around `compose tree` currently.
  • Loading branch information
cgwalters committed May 21, 2018
0 parents commit 35bd3c6
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

/target
**/*.rs.bk
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "coreos-assembler"
version = "0.1.0"
authors = ["Colin Walters <walters@verbum.org>"]

[dependencies]
tempfile = "3.0.2"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.7"
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM registry.fedoraproject.org/fedora:28
RUN yum -y install rpm-ostree make cargo git && yum clean all
ADD build.sh /root
RUN mkdir /root/src
COPY Cargo.toml /root/src/
COPY src /root/src/src
RUN ./root/build.sh && rm -f /root/build.sh
10 changes: 10 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/bash
set -xeuo pipefail
mkdir -p /usr/app/
cd /usr/app/
git clone https://github.com/ostreedev/ostree-releng-scripts
cd /root/src
ls -al
cargo build --release
mv target/release/coreos-assembler /usr/bin
rm target -rf
122 changes: 122 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;
extern crate tempfile;

use std::{env, fs, io, process, mem};
use std::ops::Deref;
use std::borrow::Cow;
use std::path::Path;

mod treefile;
use treefile::TreeComposeConfig;

// For convenience we allow the list to have multiple packages
// per item (intended for YAML).
fn whitespace_split_packages(pkgs: &Vec<String>) -> Vec<String> {
let mut ret = Vec::with_capacity(pkgs.len());
for pkg in pkgs {
for pkg_item in pkg.split_whitespace() {
ret.push(pkg_item.into());
}
}
return ret;
}

fn manifest_data_to_tmpdir(path: &Path, manifest: &TreeComposeConfig) -> io::Result<tempfile::TempDir> {
let tmpdir = tempfile::tempdir_in("/tmp")?;
let postprocess_script : &str = manifest.postprocess_script.as_ref().map_or("", String::as_str);
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if !path.is_file() {
continue;
}
// Hardcoded list of external files
let bn = entry.file_name();
let bn = bn.to_str().unwrap();
if bn.ends_with(".repo") || bn.ends_with(".json")
|| bn == "passwd" || bn == "group" || bn == postprocess_script {
fs::copy(path, tmpdir.path().join(bn))?;
}
}
return Ok(tmpdir);
}

fn is_yaml(name: &str) -> bool {
name.ends_with(".yaml")
}

fn run() -> Result<(), String> {
let mut manifest_index : Option<usize> = None;
let env_args : Vec<String> = env::args().skip(1).collect();
// Replace our argv with rpm-ostree compose tree [original argv]
let base_args = &["compose", "tree"];
let mut args : Vec<Cow<str>> = base_args.iter().map(|v: &&str| *v)
.chain(env_args.iter().map(|v| v.as_str()))
.map(From::from)
.collect();
for (i,arg) in args.iter().enumerate() {
if is_yaml(&arg) || arg.ends_with(".json") {
manifest_index = Some(i);
}
}
if manifest_index.is_none() {
return Err("no manifest in arguments, expected *.yaml or *.json".into());
}
let manifest_index = manifest_index.unwrap();
let manifest_path = (args[manifest_index]).to_string();
let manifest_path = Path::new(&manifest_path);
let manifest_f = fs::File::open(manifest_path).map_err(|err| err.to_string())?;

// In the YAML case, we generate JSON from it in a temporary directory,
// copying in the other files that are referenced by the manifest.
let mut tmpd : Option<tempfile::TempDir> = None;
if is_yaml(manifest_path.to_str().unwrap()) {
let mut manifest : TreeComposeConfig =
serde_yaml::from_reader(manifest_f).map_err(|err| err.to_string())?;
if manifest.include.is_some() {
return Err("include: is currently not supported in YAML syntax".into());
}
let new_pkgs = whitespace_split_packages(&manifest.packages);
manifest.packages = new_pkgs;
println!("Parsed manifest:");
println!(" {:?}", manifest);

tmpd = Some(manifest_data_to_tmpdir(manifest_path.parent().unwrap(), &manifest).map_err(|err| err.to_string())?);
let tmpd_v = tmpd.as_ref().unwrap();
let tmpd_path = tmpd_v.path();
println!("Converting to JSON, tmpdir={:?}", tmpd_path);
let bfn = manifest_path.file_name().unwrap();
let bn = bfn.to_str().unwrap().replace(".yaml", ".json");
let manifest_json_path = tmpd_path.join(bn);
let out_json = fs::File::create(&manifest_json_path).map_err(|err| err.to_string())?;
serde_json::to_writer_pretty(out_json, &manifest).map_err(|err| err.to_string())?;

// Replace the YAML argument with JSON
let manifest_path_str = manifest_json_path.to_str().unwrap();
args[manifest_index] = Cow::Owned(manifest_path_str.to_string());
}
// libc::execve() is unsafe sadly, and also we want to clean up the tmpdir.
// But we basically pass through all arguments other than the manifest
// unchanged.
println!("Executing: rpm-ostree {:?}", args);
let status = process::Command::new("rpm-ostree").args(args.iter().map(|v| v.deref()))
.stdin(process::Stdio::null()).status().map_err(|err| err.to_string())?;
mem::forget(tmpd);
if status.success() {
Ok(())
} else {
Err(format!("rpm-ostree compose tree failed: {}", status))
}
}

fn main() {
::process::exit(
match run() {
Ok(_) => 0,
Err(e) => { println!("{}", e); 1 }
})
}
116 changes: 116 additions & 0 deletions src/treefile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#[derive(Serialize, Deserialize, Debug)]
pub enum BootLocation {
#[serde(rename = "both")]
Both,
#[serde(rename = "legacy")]
Legacy,
#[serde(rename = "new")]
New,
}

impl Default for BootLocation {
fn default() -> Self {
BootLocation::Both
}
}

#[derive(Serialize, Deserialize, Debug)]
pub enum CheckPasswdType {
#[serde(rename = "none")]
None,
#[serde(rename = "previous")]
Previous,
#[serde(rename = "file")]
File,
#[serde(rename = "data")]
Data,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct CheckPasswd {
#[serde(rename = "type")]
variant: CheckPasswdType,
filename: Option<String>,
// Skip this for now, a separate file is easier
// and anyways we want to switch to sysusers
// entries: Option<Map<>String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct TreeComposeConfig {
// Compose controls
#[serde(rename = "ref")]
pub treeref: String,
repos: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub selinux: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gpg_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<String>,

// Core content
pub packages: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bootstrap_packages: Option<Vec<String>>,

// Content installation opts
#[serde(skip_serializing_if = "Option::is_none")]
pub documentation: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub install_langs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="initramfs-args")]
pub initramfs_args: Option<Vec<String>>,

// Tree layout options
#[serde(default)]
pub boot_location: BootLocation,
#[serde(default)]
pub tmp_is_dir: bool,

// systemd
#[serde(skip_serializing_if = "Option::is_none")]
pub units: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_target: Option<String>,

// versioning
#[serde(skip_serializing_if = "Option::is_none")]
pub releasever: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub automatic_version_prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="mutate-os-relase")]
pub mutate_os_release: Option<String>,

// passwd-related bits
#[serde(skip_serializing_if = "Option::is_none")]
pub etc_group_members: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="preserve-passwd")]
pub preserve_passwd: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="check-passwd")]
pub check_passwd: Option<CheckPasswd>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="check-groups")]
pub check_groups: Option<CheckPasswd>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_removed_users: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_removed_groups: Option<Vec<String>>,

// Content manimulation
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="postprocess-script")]
pub postprocess_script: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="add-files")]
pub add_files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remove_files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename="remove-from-packages")]
pub remove_from_packages: Option<Vec<Vec<String>>>,
}

0 comments on commit 35bd3c6

Please sign in to comment.