Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cgwalters committed Oct 20, 2023
1 parent 02f9aef commit be85997
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 58 deletions.
84 changes: 26 additions & 58 deletions lib/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::collections::{BTreeMap, HashMap};
use std::io::Read;

use crate::k8sapitypes::ConfigMap;
use crate::k8sapitypes::{self, ConfigMap};
use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
use cap_std::fs::Dir;
use cap_std_ext::cap_std;
use fn_error_context::context;
use ostree_ext::container as ostree_container;
use ostree_ext::oci_spec;
use ostree_ext::prelude::{Cast, FileExt, InputStreamExtManual, ToVariant};
use ostree_ext::{gio, glib, ostree};
use ostree_ext::{ostree::Deployment, sysroot::SysrootLock};
Expand All @@ -22,7 +23,7 @@ const CONFIGMAP_PREFIX_ANNOTATION_KEY: &str = "bootc.prefix";
const DEFAULT_MOUNT_PREFIX: &str = "etc";

/// The key used to store the configmap metadata
const CONFIGMAP_METADATA_KEY: &str = "bootc.configmap.metadata";
const CONFIGMAP_MANIFEST_KEY: &str = "bootc.configmap.metadata";
/// The key used to store the etag from the HTTP request
const CONFIGMAP_ETAG_KEY: &str = "bootc.configmap.etag";

Expand Down Expand Up @@ -163,8 +164,8 @@ fn load_config(sysroot: &SysrootLock, name: &str) -> Result<ConfigMap> {
let commitmeta = commitv.child_value(0);
let commitmeta = &glib::VariantDict::new(Some(&commitmeta));
let cfgdata = commitmeta
.lookup_value(CONFIGMAP_METADATA_KEY, Some(glib::VariantTy::STRING))
.ok_or_else(|| anyhow!("Missing metadata key {CONFIGMAP_METADATA_KEY}"))?;
.lookup_value(CONFIGMAP_MANIFEST_KEY, Some(glib::VariantTy::STRING))
.ok_or_else(|| anyhow!("Missing metadata key {CONFIGMAP_MANIFEST_KEY}"))?;
let cfgdata = cfgdata.str().unwrap();
let mut cfg: ConfigMap = serde_json::from_str(cfgdata)?;
let prefix = Utf8Path::new(get_prefix(&cfg).trim_start_matches('/'));
Expand Down Expand Up @@ -220,17 +221,12 @@ async fn remove(sysroot: &SysrootLock, name: &str) -> Result<()> {
Ok(())
}

#[derive(Debug)]
struct HttpCachableReply<T> {
content: T,
etag: Option<String>,
}

#[context("Writing configmap")]
fn write_configmap(
sysroot: &SysrootLock,
sepolicy: Option<&ostree::SePolicy>,
spec: &ConfigSpec,
manifest: &oci_spec::image::ImageManifest,
map: &ConfigMap,
etag: Option<&str>,
cancellable: Option<&gio::Cancellable>,
Expand All @@ -244,56 +240,28 @@ fn write_configmap(
let tree = &ostree::MutableTree::new();
let dirmeta =
create_and_commit_dirmeta(&repo, "/etc/some-unshipped-config-file".into(), sepolicy)?;
// Create an iterator over the string data
let string_data = map.data.iter().flatten().map(|(k, v)| (k, v.as_bytes()));
// Create an iterator over the binary data
let binary_data = map
.binary_data
.iter()
.flatten()
.map(|(k, v)| (k, v.0.as_slice()));
let prefix = get_prefix(map);
tracing::trace!("prefix={prefix}");
// For each string and binary value, write a file
let mut has_content = false;
for (k, v) in string_data.chain(binary_data) {
let path = Utf8Path::new(prefix).join(k);
tracing::trace!("Writing {path}");
write_file(repo, tree, &path, &dirmeta, v, DEFAULT_MODE, sepolicy)?;
has_content = true;
{
let serialized = serde_json::to_string(map).context("Serializing")?;
write_file(
repo,
tree,
"config.json".into(),
&dirmeta,
serialized.as_bytes(),
DEFAULT_MODE,
sepolicy,
)?;
}
if !has_content {
anyhow::bail!("ConfigMap has no data");
}
// Empty out the values, since we wrote them into the ostree commit on the filesystem
let binary_data = map.binary_data.as_ref().map(|v| {
v.keys()
.map(|k| (k.clone(), k8s_openapi::ByteString(Vec::new())))
.collect::<BTreeMap<_, _>>()
});
let data = map.data.as_ref().map(|v| {
v.keys()
.map(|k| (k.clone(), "".to_string()))
.collect::<BTreeMap<_, _>>()
});
let rest = ConfigMap {
binary_data,
data,
immutable: map.immutable.clone(),
metadata: map.metadata.clone(),
};
let serialized_map_metadata =
serde_json::to_string(&rest).context("Serializing configmap metadata")?;
let mut metadata = HashMap::new();
metadata.insert(CONFIGMAP_METADATA_KEY, serialized_map_metadata.to_variant());
if let Some(etag) = etag {
metadata.insert(CONFIGMAP_ETAG_KEY, etag.to_variant());
}
let timestamp = map
.metadata
.creation_timestamp
.as_ref()
.map(|t| t.0.timestamp() as u64)
let serialized_manifest = serde_json::to_string(manifest).context("Serializing manifest")?;
metadata.insert(CONFIGMAP_MANIFEST_KEY, serialized_manifest.to_variant());
let timestamp = manifest
.annotations()
.and_then(|m| m.get(oci_spec::image::ANNOTATION_CREATED))
.map(|v| chrono::DateTime::parse_from_rfc3339(v))
.transpose()
.context("Parsing created annotation")?
.map(|t| t.timestamp() as u64)
.unwrap_or_default();
tracing::trace!("Writing commit with ts {timestamp}");

Expand Down
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
pub mod cli;
pub(crate) mod deploy;
mod lsm;
mod ostree_generation;
mod reexec;
mod status;
mod utils;
Expand Down
95 changes: 95 additions & 0 deletions lib/src/ostree_generation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use anyhow::{anyhow, Result};
use camino::{Utf8Component, Utf8Path};
use fn_error_context::context;
use ostree_ext::{gio, glib, ostree};

/// The default access mode for directories: rwxr-xr-x
const DEFAULT_DIRECTORY_MODE: u32 = 0o755;

/// Generate directory metadata variant for root/root 0755 directory with an optional SELinux label.
#[context("Creating dirmeta")]
pub(crate) fn create_dirmeta(
path: &Utf8Path,
sepolicy: Option<&ostree::SePolicy>,
) -> Result<glib::Variant> {
let finfo = gio::FileInfo::new();
finfo.set_attribute_uint32("unix::uid", 0);
finfo.set_attribute_uint32("unix::gid", 0);
finfo.set_attribute_uint32("unix::mode", libc::S_IFDIR | DEFAULT_DIRECTORY_MODE);
let xattrs = sepolicy
.map(|policy| crate::lsm::new_xattrs_with_selinux(policy, path, 0o644))
.transpose()?;
Ok(ostree::create_directory_metadata(&finfo, xattrs.as_ref()))
}

/// Wraps [`create_dirmeta`] and commits it, returning the digest.
#[context("Committing dirmeta")]
pub(crate) fn create_and_commit_dirmeta(
repo: &ostree::Repo,
path: &Utf8Path,
sepolicy: Option<&ostree::SePolicy>,
) -> Result<String> {
let v = create_dirmeta(path, sepolicy)?;
let r = repo.write_metadata(
ostree::ObjectType::DirMeta,
None,
&v,
gio::Cancellable::NONE,
)?;
Ok(r.to_hex())
}

// Drop any leading / or . from the path,
fn relative_path_components(p: &Utf8Path) -> impl Iterator<Item = Utf8Component> {
p.components()
.filter(|p| matches!(p, Utf8Component::Normal(_)))
}

#[context("Creating parents")]
fn ensure_parent_dirs(
mt: &ostree::MutableTree,
path: &Utf8Path,
metadata_checksum: &str,
) -> Result<ostree::MutableTree> {
let parts = relative_path_components(path)
.map(|s| s.as_str())
.collect::<Vec<_>>();
mt.ensure_parent_dirs(&parts, metadata_checksum)
.map_err(Into::into)
}

#[context("Writing file to ostree repo")]
pub fn write_file(
repo: &ostree::Repo,
root: &ostree::MutableTree,
path: &Utf8Path,
parent_dirmeta: &str,
contents: &[u8],
mode: u32,
sepolicy: Option<&ostree::SePolicy>,
) -> Result<()> {
let name = path
.file_name()
.ok_or_else(|| anyhow!("Expecting a filename in {path}"))?;
let parent = if path.parent().is_some() {
Some(ensure_parent_dirs(root, &path, parent_dirmeta)?)
} else {
None
};
let parent = parent.as_ref().unwrap_or(root);
let xattrs = sepolicy
.map(|policy| crate::lsm::new_xattrs_with_selinux(policy, path, 0o644))
.transpose()?;
let xattrs = xattrs.as_ref();
let checksum = repo.write_regfile_inline(
None,
0,
0,
libc::S_IFREG | mode,
xattrs,
contents,
gio::Cancellable::NONE,
)?;
parent.replace_file(name, checksum.as_str())?;
Ok(())
}

0 comments on commit be85997

Please sign in to comment.