Better support users/groups existing before install (#238)

* Better support users/groups existing before install

* Skip instead of complete if found

* Prod CI

* Add debuging messages

* Mark completed instead of skipped
This commit is contained in:
Ana Hobden 2023-02-09 10:34:34 -08:00 committed by GitHub
parent 28db9f2953
commit ce28eedf2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 220 additions and 176 deletions

View file

@ -105,7 +105,7 @@ impl Action for CreateDirectory {
let gid = if let Some(group) = group { let gid = if let Some(group) = group {
Some( Some(
Group::from_name(group.as_str()) Group::from_name(group.as_str())
.map_err(|e| ActionError::GroupId(group.clone(), e))? .map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
.ok_or(ActionError::NoGroup(group.clone()))? .ok_or(ActionError::NoGroup(group.clone()))?
.gid, .gid,
) )
@ -115,7 +115,7 @@ impl Action for CreateDirectory {
let uid = if let Some(user) = user { let uid = if let Some(user) = user {
Some( Some(
User::from_name(user.as_str()) User::from_name(user.as_str())
.map_err(|e| ActionError::UserId(user.clone(), e))? .map_err(|e| ActionError::GettingUserId(user.clone(), e))?
.ok_or(ActionError::NoUser(user.clone()))? .ok_or(ActionError::NoUser(user.clone()))?
.uid, .uid,
) )

View file

@ -118,7 +118,7 @@ impl Action for CreateFile {
let gid = if let Some(group) = group { let gid = if let Some(group) = group {
Some( Some(
Group::from_name(group.as_str()) Group::from_name(group.as_str())
.map_err(|e| ActionError::GroupId(group.clone(), e))? .map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
.ok_or(ActionError::NoGroup(group.clone()))? .ok_or(ActionError::NoGroup(group.clone()))?
.gid, .gid,
) )
@ -128,7 +128,7 @@ impl Action for CreateFile {
let uid = if let Some(user) = user { let uid = if let Some(user) = user {
Some( Some(
User::from_name(user.as_str()) User::from_name(user.as_str())
.map_err(|e| ActionError::UserId(user.clone(), e))? .map_err(|e| ActionError::GettingUserId(user.clone(), e))?
.ok_or(ActionError::NoUser(user.clone()))? .ok_or(ActionError::NoUser(user.clone()))?
.uid, .uid,
) )

View file

@ -1,3 +1,4 @@
use nix::unistd::Group;
use tokio::process::Command; use tokio::process::Command;
use tracing::{span, Span}; use tracing::{span, Span};
@ -12,13 +13,32 @@ Create an operating system level user group
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroup { pub struct CreateGroup {
name: String, name: String,
gid: usize, gid: u32,
} }
impl CreateGroup { impl CreateGroup {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn plan(name: String, gid: usize) -> StatefulAction<Self> { pub fn plan(name: String, gid: u32) -> Result<StatefulAction<Self>, ActionError> {
Self { name, gid }.into() let this = Self {
name: name.clone(),
gid,
};
// Ensure group does not exists
if let Some(group) = Group::from_name(name.as_str())
.map_err(|e| ActionError::GettingGroupId(name.clone(), e))?
{
if group.gid.as_raw() != gid {
return Err(ActionError::GroupGidMismatch(
name.clone(),
group.gid.as_raw(),
gid,
));
}
tracing::debug!("Creating group `{}` already complete", this.name);
return Ok(StatefulAction::completed(this));
}
Ok(StatefulAction::uncompleted(this))
} }
} }
@ -59,36 +79,22 @@ impl Action for CreateGroup {
patch: _, patch: _,
} }
| OperatingSystem::Darwin => { | OperatingSystem::Darwin => {
if Command::new("/usr/bin/dscl") execute_command(
.process_group(0) Command::new("/usr/sbin/dseditgroup")
.args([".", "-read", &format!("/Groups/{name}")]) .process_group(0)
.stdin(std::process::Stdio::null()) .args([
.stdout(std::process::Stdio::null()) "-o",
.stderr(std::process::Stdio::piped()) "create",
.status() "-r",
.await "Nix build group for nix-daemon",
.map_err(ActionError::Command)? "-i",
.success() &format!("{gid}"),
{ name.as_str(),
() ])
} else { .stdin(std::process::Stdio::null()),
execute_command( )
Command::new("/usr/sbin/dseditgroup") .await
.process_group(0) .map_err(|e| ActionError::Command(e))?;
.args([
"-o",
"create",
"-r",
"Nix build group for nix-daemon",
"-i",
&format!("{gid}"),
name.as_str(),
])
.stdin(std::process::Stdio::null()),
)
.await
.map_err(|e| ActionError::Command(e))?;
}
}, },
_ => { _ => {
execute_command( execute_command(

View file

@ -158,7 +158,7 @@ impl Action for CreateOrInsertIntoFile {
let gid = if let Some(group) = group { let gid = if let Some(group) = group {
Some( Some(
Group::from_name(group.as_str()) Group::from_name(group.as_str())
.map_err(|e| ActionError::GroupId(group.clone(), e))? .map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
.ok_or(ActionError::NoGroup(group.clone()))? .ok_or(ActionError::NoGroup(group.clone()))?
.gid, .gid,
) )
@ -168,7 +168,7 @@ impl Action for CreateOrInsertIntoFile {
let uid = if let Some(user) = user { let uid = if let Some(user) = user {
Some( Some(
User::from_name(user.as_str()) User::from_name(user.as_str())
.map_err(|e| ActionError::UserId(user.clone(), e))? .map_err(|e| ActionError::GettingUserId(user.clone(), e))?
.ok_or(ActionError::NoUser(user.clone()))? .ok_or(ActionError::NoUser(user.clone()))?
.uid, .uid,
) )

View file

@ -1,3 +1,4 @@
use nix::unistd::User;
use tokio::process::Command; use tokio::process::Command;
use tracing::{span, Span}; use tracing::{span, Span};
@ -12,21 +13,50 @@ Create an operating system level user in the given group
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUser { pub struct CreateUser {
name: String, name: String,
uid: usize, uid: u32,
groupname: String, groupname: String,
gid: usize, gid: u32,
} }
impl CreateUser { impl CreateUser {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn plan(name: String, uid: usize, groupname: String, gid: usize) -> StatefulAction<Self> { pub fn plan(
Self { name: String,
name, uid: u32,
groupname: String,
gid: u32,
) -> Result<StatefulAction<Self>, ActionError> {
let this = Self {
name: name.clone(),
uid, uid,
groupname, groupname,
gid, gid,
};
// Ensure user does not exists
if let Some(user) = User::from_name(name.as_str())
.map_err(|e| ActionError::GettingUserId(name.clone(), e))?
{
if user.uid.as_raw() != uid {
return Err(ActionError::UserUidMismatch(
name.clone(),
user.uid.as_raw(),
uid,
));
}
if user.gid.as_raw() != gid {
return Err(ActionError::UserGidMismatch(
name.clone(),
user.gid.as_raw(),
gid,
));
}
tracing::debug!("Creating user `{}` already complete", this.name);
return Ok(StatefulAction::completed(this));
} }
.into()
Ok(StatefulAction::uncompleted(this))
} }
} }
@ -77,122 +107,105 @@ impl Action for CreateUser {
patch: _, patch: _,
} }
| OperatingSystem::Darwin => { | OperatingSystem::Darwin => {
// TODO(@hoverbear): Make this actually work... execute_command(
// Right now, our test machines do not have a secure token and cannot delete users. Command::new("/usr/bin/dscl")
.process_group(0)
if Command::new("/usr/bin/dscl") .args([".", "-create", &format!("/Users/{name}")])
.process_group(0) .stdin(std::process::Stdio::null()),
.args([".", "-read", &format!("/Users/{name}")]) )
.stdin(std::process::Stdio::null()) .await
.stdout(std::process::Stdio::null()) .map_err(|e| ActionError::Command(e))?;
.stderr(std::process::Stdio::piped()) execute_command(
.status() Command::new("/usr/bin/dscl")
.await .process_group(0)
.map_err(ActionError::Command)? .args([
.success() ".",
{ "-create",
() &format!("/Users/{name}"),
} else { "UniqueID",
execute_command( &format!("{uid}"),
Command::new("/usr/bin/dscl") ])
.process_group(0) .stdin(std::process::Stdio::null()),
.args([".", "-create", &format!("/Users/{name}")]) )
.stdin(std::process::Stdio::null()), .await
) .map_err(|e| ActionError::Command(e))?;
.await execute_command(
.map_err(|e| ActionError::Command(e))?; Command::new("/usr/bin/dscl")
execute_command( .process_group(0)
Command::new("/usr/bin/dscl") .args([
.process_group(0) ".",
.args([ "-create",
".", &format!("/Users/{name}"),
"-create", "PrimaryGroupID",
&format!("/Users/{name}"), &format!("{gid}"),
"UniqueID", ])
&format!("{uid}"), .stdin(std::process::Stdio::null()),
]) )
.stdin(std::process::Stdio::null()), .await
) .map_err(|e| ActionError::Command(e))?;
.await execute_command(
.map_err(|e| ActionError::Command(e))?; Command::new("/usr/bin/dscl")
execute_command( .process_group(0)
Command::new("/usr/bin/dscl") .args([
.process_group(0) ".",
.args([ "-create",
".", &format!("/Users/{name}"),
"-create", "NFSHomeDirectory",
&format!("/Users/{name}"), "/var/empty",
"PrimaryGroupID", ])
&format!("{gid}"), .stdin(std::process::Stdio::null()),
]) )
.stdin(std::process::Stdio::null()), .await
) .map_err(|e| ActionError::Command(e))?;
.await execute_command(
.map_err(|e| ActionError::Command(e))?; Command::new("/usr/bin/dscl")
execute_command( .process_group(0)
Command::new("/usr/bin/dscl") .args([
.process_group(0) ".",
.args([ "-create",
".", &format!("/Users/{name}"),
"-create", "UserShell",
&format!("/Users/{name}"), "/sbin/nologin",
"NFSHomeDirectory", ])
"/var/empty", .stdin(std::process::Stdio::null()),
]) )
.stdin(std::process::Stdio::null()), .await
) .map_err(|e| ActionError::Command(e))?;
.await execute_command(
.map_err(|e| ActionError::Command(e))?; Command::new("/usr/bin/dscl")
execute_command( .process_group(0)
Command::new("/usr/bin/dscl") .args([
.process_group(0) ".",
.args([ "-append",
".", &format!("/Groups/{groupname}"),
"-create", "GroupMembership",
&format!("/Users/{name}"), ])
"UserShell", .arg(&name)
"/sbin/nologin", .stdin(std::process::Stdio::null()),
]) )
.stdin(std::process::Stdio::null()), .await
) .map_err(|e| ActionError::Command(e))?;
.await execute_command(
.map_err(|e| ActionError::Command(e))?; Command::new("/usr/bin/dscl")
execute_command( .process_group(0)
Command::new("/usr/bin/dscl") .args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"])
.process_group(0) .stdin(std::process::Stdio::null()),
.args([ )
".", .await
"-append", .map_err(|e| ActionError::Command(e))?;
&format!("/Groups/{groupname}"), execute_command(
"GroupMembership", Command::new("/usr/sbin/dseditgroup")
]) .process_group(0)
.arg(&name) .args(["-o", "edit"])
.stdin(std::process::Stdio::null()), .arg("-a")
) .arg(&name)
.await .arg("-t")
.map_err(|e| ActionError::Command(e))?; .arg(&name)
execute_command( .arg(groupname)
Command::new("/usr/bin/dscl") .stdin(std::process::Stdio::null()),
.process_group(0) )
.args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"]) .await
.stdin(std::process::Stdio::null()), .map_err(|e| ActionError::Command(e))?;
)
.await
.map_err(|e| ActionError::Command(e))?;
execute_command(
Command::new("/usr/sbin/dseditgroup")
.process_group(0)
.args(["-o", "edit"])
.arg("-a")
.arg(&name)
.arg("-t")
.arg(&name)
.arg(groupname)
.stdin(std::process::Stdio::null()),
)
.await
.map_err(|e| ActionError::Command(e))?;
}
}, },
_ => { _ => {
execute_command( execute_command(

View file

@ -10,11 +10,11 @@ use tracing::{span, Instrument, Span};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroups { pub struct CreateUsersAndGroups {
nix_build_user_count: usize, nix_build_user_count: u32,
nix_build_group_name: String, nix_build_group_name: String,
nix_build_group_id: usize, nix_build_group_id: u32,
nix_build_user_prefix: String, nix_build_user_prefix: String,
nix_build_user_id_base: usize, nix_build_user_id_base: u32,
create_group: StatefulAction<CreateGroup>, create_group: StatefulAction<CreateGroup>,
create_users: Vec<StatefulAction<CreateUser>>, create_users: Vec<StatefulAction<CreateUser>>,
} }
@ -22,12 +22,10 @@ pub struct CreateUsersAndGroups {
impl CreateUsersAndGroups { impl CreateUsersAndGroups {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(settings: CommonSettings) -> Result<StatefulAction<Self>, ActionError> { pub async fn plan(settings: CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
// TODO(@hoverbear): CHeck if it exist, error if so
let create_group = CreateGroup::plan( let create_group = CreateGroup::plan(
settings.nix_build_group_name.clone(), settings.nix_build_group_name.clone(),
settings.nix_build_group_id, settings.nix_build_group_id,
); )?;
// TODO(@hoverbear): CHeck if they exist, error if so
let create_users = (0..settings.nix_build_user_count) let create_users = (0..settings.nix_build_user_count)
.map(|count| { .map(|count| {
CreateUser::plan( CreateUser::plan(
@ -37,7 +35,7 @@ impl CreateUsersAndGroups {
settings.nix_build_group_id, settings.nix_build_group_id,
) )
}) })
.collect(); .collect::<Result<_, _>>()?;
Ok(Self { Ok(Self {
nix_build_user_count: settings.nix_build_user_count, nix_build_user_count: settings.nix_build_user_count,
nix_build_group_name: settings.nix_build_group_name, nix_build_group_name: settings.nix_build_group_name,

View file

@ -303,11 +303,17 @@ pub enum ActionError {
#[error("Truncating `{0}`")] #[error("Truncating `{0}`")]
Truncate(std::path::PathBuf, #[source] std::io::Error), Truncate(std::path::PathBuf, #[source] std::io::Error),
#[error("Getting uid for user `{0}`")] #[error("Getting uid for user `{0}`")]
UserId(String, #[source] nix::errno::Errno), GettingUserId(String, #[source] nix::errno::Errno),
#[error("User `{0}` existed but had a different uid ({1}) than planned ({2})")]
UserUidMismatch(String, u32, u32),
#[error("User `{0}` existed but had a different gid ({1}) than planned ({2})")]
UserGidMismatch(String, u32, u32),
#[error("Getting user `{0}`")] #[error("Getting user `{0}`")]
NoUser(String), NoUser(String),
#[error("Getting gid for group `{0}`")] #[error("Getting gid for group `{0}`")]
GroupId(String, #[source] nix::errno::Errno), GettingGroupId(String, #[source] nix::errno::Errno),
#[error("Group `{0}` existed but had a different gid ({1}) than planned ({2})")]
GroupGidMismatch(String, u32, u32),
#[error("Getting group `{0}`")] #[error("Getting group `{0}`")]
NoGroup(String), NoGroup(String),
#[error("Chowning path `{0}`")] #[error("Chowning path `{0}`")]

View file

@ -217,6 +217,27 @@ where
}, },
} }
} }
pub fn completed(action: A) -> Self {
Self {
state: ActionState::Completed,
action,
}
}
pub fn skipped(action: A) -> Self {
Self {
state: ActionState::Skipped,
action,
}
}
pub fn uncompleted(action: A) -> Self {
Self {
state: ActionState::Uncompleted,
action,
}
}
} }
/** The state of an [`Action`](crate::action::Action) /** The state of an [`Action`](crate::action::Action)

View file

@ -95,7 +95,7 @@ pub struct CommonSettings {
global = true global = true
) )
)] )]
pub(crate) nix_build_user_count: usize, pub(crate) nix_build_user_count: u32,
/// The Nix build group name /// The Nix build group name
#[cfg_attr( #[cfg_attr(
@ -119,7 +119,7 @@ pub struct CommonSettings {
global = true global = true
) )
)] )]
pub(crate) nix_build_group_id: usize, pub(crate) nix_build_group_id: u32,
/// The Nix build user prefix (user numbers will be postfixed) /// The Nix build user prefix (user numbers will be postfixed)
#[cfg_attr( #[cfg_attr(
@ -147,7 +147,7 @@ pub struct CommonSettings {
all(target_os = "linux", feature = "cli"), all(target_os = "linux", feature = "cli"),
clap(default_value_t = 30_000) clap(default_value_t = 30_000)
)] )]
pub(crate) nix_build_user_id_base: usize, pub(crate) nix_build_user_id_base: u32,
/// The Nix package URL /// The Nix package URL
#[cfg_attr( #[cfg_attr(
@ -361,7 +361,7 @@ async fn linux_detect_init() -> (InitSystem, bool) {
// Builder Pattern // Builder Pattern
impl CommonSettings { impl CommonSettings {
/// Number of build users to create /// Number of build users to create
pub fn nix_build_user_count(&mut self, count: usize) -> &mut Self { pub fn nix_build_user_count(&mut self, count: u32) -> &mut Self {
self.nix_build_user_count = count; self.nix_build_user_count = count;
self self
} }
@ -385,7 +385,7 @@ impl CommonSettings {
} }
/// The Nix build group GID /// The Nix build group GID
pub fn nix_build_group_id(&mut self, count: usize) -> &mut Self { pub fn nix_build_group_id(&mut self, count: u32) -> &mut Self {
self.nix_build_group_id = count; self.nix_build_group_id = count;
self self
} }
@ -397,7 +397,7 @@ impl CommonSettings {
} }
/// The Nix build user base UID (ascending) /// The Nix build user base UID (ascending)
pub fn nix_build_user_id_base(&mut self, count: usize) -> &mut Self { pub fn nix_build_user_id_base(&mut self, count: u32) -> &mut Self {
self.nix_build_user_id_base = count; self.nix_build_user_id_base = count;
self self
} }