Merge pull request #192 from numtide/rvdp/etc_ownership
Implement the user and group options for etc files
This commit is contained in:
commit
a7229c8a11
5 changed files with 80 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -411,6 +411,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"log",
|
||||
"nix",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ im = { version = "15.1.0", features = ["serde"] }
|
|||
itertools = "0.14.0"
|
||||
log = "0.4.17"
|
||||
nix = { version = "0.29.0", features = ["hostname", "user"] }
|
||||
regex = "1.11.1"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
thiserror = "2.0.0"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
source = "/run/systemd/system/";
|
||||
};
|
||||
|
||||
test_perms = {
|
||||
with_ownership = {
|
||||
text = ''
|
||||
This is just a test!
|
||||
'';
|
||||
|
|
@ -63,6 +63,14 @@
|
|||
uid = 5;
|
||||
gid = 6;
|
||||
};
|
||||
with_ownership2 = {
|
||||
text = ''
|
||||
This is just a test!
|
||||
'';
|
||||
mode = "0755";
|
||||
user = "nobody";
|
||||
group = "users";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ mod etc_tree;
|
|||
|
||||
use im::HashMap;
|
||||
use itertools::Itertools;
|
||||
use regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{DirBuilder, Permissions};
|
||||
use std::os::unix::fs as unixfs;
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
use std::path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
use std::{fs, io};
|
||||
|
||||
use self::etc_tree::FileStatus;
|
||||
|
|
@ -22,6 +24,12 @@ pub use etc_tree::FileTree;
|
|||
|
||||
type EtcActivationResult = ActivationResult<FileTree>;
|
||||
|
||||
static UID_GID_REGEX: OnceLock<regex::Regex> = OnceLock::new();
|
||||
|
||||
fn get_uid_gid_regex() -> &'static regex::Regex {
|
||||
UID_GID_REGEX.get_or_init(|| regex::Regex::new(r"^\+[0-9]+$").expect("could not compile regex"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EtcFile {
|
||||
|
|
@ -352,9 +360,7 @@ fn create_etc_entry(
|
|||
match copy_file(
|
||||
&entry.source.store_path.join(&entry.target),
|
||||
&target_path,
|
||||
&entry.mode,
|
||||
entry.uid,
|
||||
entry.gid,
|
||||
entry,
|
||||
old_state,
|
||||
) {
|
||||
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
|
||||
|
|
@ -414,8 +420,54 @@ fn create_dir_recursively(dir: &Path, state: FileTree) -> EtcActivationResult {
|
|||
new_state
|
||||
}
|
||||
|
||||
fn copy_file(source: &Path, target: &Path, mode: &str,
|
||||
uid: u32, gid: u32, old_state: &FileTree) -> anyhow::Result<()> {
|
||||
fn find_uid(entry: &EtcFile) -> anyhow::Result<u32> {
|
||||
if !get_uid_gid_regex().is_match(&entry.user) {
|
||||
nix::unistd::User::from_name(&entry.user)
|
||||
.map(|maybe_user| {
|
||||
maybe_user.map_or_else(
|
||||
|| {
|
||||
log::warn!(
|
||||
"Specified user {} not found, defaulting to root",
|
||||
&entry.user
|
||||
);
|
||||
0
|
||||
},
|
||||
|user| user.uid.as_raw(),
|
||||
)
|
||||
})
|
||||
.map_err(|err| anyhow::anyhow!(err).context("Failed to determine user"))
|
||||
} else {
|
||||
Ok(entry.uid)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_gid(entry: &EtcFile) -> anyhow::Result<u32> {
|
||||
if !get_uid_gid_regex().is_match(&entry.group) {
|
||||
nix::unistd::Group::from_name(&entry.group)
|
||||
.map(|maybe_group| {
|
||||
maybe_group.map_or_else(
|
||||
|| {
|
||||
log::warn!(
|
||||
"Specified group {} not found, defaulting to root",
|
||||
&entry.group
|
||||
);
|
||||
0
|
||||
},
|
||||
|group| group.gid.as_raw(),
|
||||
)
|
||||
})
|
||||
.map_err(|err| anyhow::anyhow!(err).context("Failed to determine group"))
|
||||
} else {
|
||||
Ok(entry.gid)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_file(
|
||||
source: &Path,
|
||||
target: &Path,
|
||||
entry: &EtcFile,
|
||||
old_state: &FileTree,
|
||||
) -> anyhow::Result<()> {
|
||||
let exists = target.try_exists()?;
|
||||
if !exists || old_state.is_managed(target) {
|
||||
log::debug!(
|
||||
|
|
@ -424,9 +476,9 @@ fn copy_file(source: &Path, target: &Path, mode: &str,
|
|||
target.display()
|
||||
);
|
||||
fs::copy(source, target)?;
|
||||
let mode_int = u32::from_str_radix(mode, 8)?;
|
||||
let mode_int = u32::from_str_radix(&entry.mode, 8)?;
|
||||
fs::set_permissions(target, Permissions::from_mode(mode_int))?;
|
||||
unixfs::chown(target, Some(uid), Some(gid))?;
|
||||
unixfs::chown(target, Some(find_uid(entry)?), Some(find_gid(entry)?))?;
|
||||
Ok(())
|
||||
} else {
|
||||
anyhow::bail!("File {} already exists, ignoring.", target.display());
|
||||
|
|
|
|||
|
|
@ -175,10 +175,17 @@ forEachUbuntuImage "example" {
|
|||
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
|
||||
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
|
||||
|
||||
uid = vm.succeed("stat -c %u /etc/test_perms").strip()
|
||||
gid = vm.succeed("stat -c %g /etc/test_perms").strip()
|
||||
uid = vm.succeed("stat -c %u /etc/with_ownership").strip()
|
||||
gid = vm.succeed("stat -c %g /etc/with_ownership").strip()
|
||||
assert uid == "5", f"uid was {uid}, expected 5"
|
||||
assert gid == "6", f"uid was {gid}, expected 6"
|
||||
assert gid == "6", f"gid was {gid}, expected 6"
|
||||
|
||||
print(vm.succeed("cat /etc/passwd"))
|
||||
|
||||
user = vm.succeed("stat -c %U /etc/with_ownership2").strip()
|
||||
group = vm.succeed("stat -c %G /etc/with_ownership2").strip()
|
||||
assert user == "nobody", f"user was {user}, expected nobody"
|
||||
assert group == "users", f"group was {group}, expected users"
|
||||
|
||||
vm.succeed("test -d /var/tmp/system-manager")
|
||||
vm.succeed("test -d /var/tmp/sample")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue