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,19 +79,6 @@ impl Action for CreateGroup {
patch: _, patch: _,
} }
| OperatingSystem::Darwin => { | OperatingSystem::Darwin => {
if Command::new("/usr/bin/dscl")
.process_group(0)
.args([".", "-read", &format!("/Groups/{name}")])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.status()
.await
.map_err(ActionError::Command)?
.success()
{
()
} else {
execute_command( execute_command(
Command::new("/usr/sbin/dseditgroup") Command::new("/usr/sbin/dseditgroup")
.process_group(0) .process_group(0)
@ -88,7 +95,6 @@ impl Action for CreateGroup {
) )
.await .await
.map_err(|e| ActionError::Command(e))?; .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,
));
} }
.into()
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));
}
Ok(StatefulAction::uncompleted(this))
} }
} }
@ -77,22 +107,6 @@ impl Action for CreateUser {
patch: _, patch: _,
} }
| OperatingSystem::Darwin => { | OperatingSystem::Darwin => {
// TODO(@hoverbear): Make this actually work...
// Right now, our test machines do not have a secure token and cannot delete users.
if Command::new("/usr/bin/dscl")
.process_group(0)
.args([".", "-read", &format!("/Users/{name}")])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.status()
.await
.map_err(ActionError::Command)?
.success()
{
()
} else {
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -192,7 +206,6 @@ impl Action for CreateUser {
) )
.await .await
.map_err(|e| ActionError::Command(e))?; .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
} }