Skip to content

Commit

Permalink
Merge pull request #133 from DeterminateSystems/colemickens/uds2
Browse files Browse the repository at this point in the history
uds2: move merge_nix_configs to shared
  • Loading branch information
colemickens authored Aug 2, 2024
2 parents a1129cb + 5f22d11 commit 526eae5
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 142 deletions.
147 changes: 8 additions & 139 deletions src/cli/cmd/login/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl LoginSubcommand {
// $XDG_CONFIG_HOME/nix/nix.conf; basically ~/.config/nix/nix.conf
let nix_config_path = xdg.place_config_file("nix/nix.conf")?;
// $XDG_CONFIG_HOME/fh/auth; basically ~/.config/fh/auth
let token_path = auth_token_path()?;
let token_path = user_auth_token_path()?;

let dnixd_state_dir = Path::new(&DETERMINATE_STATE_DIR);
let netrc_file_path: PathBuf = dnixd_state_dir.join(DETERMINATE_NIXD_NETRC_NAME);
Expand Down Expand Up @@ -270,7 +270,7 @@ impl LoginSubcommand {

// TODO(cole-h): make this atomic -- copy the nix_config_path to some temporary file, then operate
// on that, then move it back if all is good
async fn upsert_user_nix_config(
pub async fn upsert_user_nix_config(
nix_config_path: &Path,
netrc_file_string: &str,
netrc_contents: &str,
Expand Down Expand Up @@ -369,8 +369,11 @@ async fn upsert_user_nix_config(
.await
{
Ok(mut file) => {
let nix_config_contents =
merge_nix_configs(nix_config, nix_config_contents, merged_nix_config);
let nix_config_contents = crate::shared::merge_nix_configs(
nix_config,
nix_config_contents,
merged_nix_config,
);
let write_status = file.write_all(nix_config_contents.as_bytes()).await;
Some(write_status.is_ok())
}
Expand Down Expand Up @@ -399,144 +402,10 @@ async fn upsert_user_nix_config(
Ok(())
}

pub(crate) fn auth_token_path() -> Result<PathBuf, FhError> {
pub(crate) fn user_auth_token_path() -> Result<PathBuf, FhError> {
let xdg = xdg::BaseDirectories::new()?;
// $XDG_CONFIG_HOME/flakehub/auth; basically ~/.config/flakehub/auth
let token_path = xdg.place_config_file("flakehub/auth")?;

Ok(token_path)
}

// NOTE(cole-h): Adapted from
// https:/DeterminateSystems/nix-installer/blob/0b0172547c4666f6b1eacb6561a59d6b612505a3/src/action/base/create_or_merge_nix_config.rs#L284
const NIX_CONF_COMMENT_CHAR: char = '#';
fn merge_nix_configs(
mut existing_nix_config: nix_config_parser::NixConfig,
mut existing_nix_config_contents: String,
mut merged_nix_config: nix_config_parser::NixConfig,
) -> String {
let mut new_config = String::new();

// We append a newline to ensure that, in the case there are comments at the end of the
// file and _NO_ trailing newline, we still preserve the entire block of comments.
existing_nix_config_contents.push('\n');

let (associated_lines, _, _) = existing_nix_config_contents.split('\n').fold(
(Vec::new(), Vec::new(), false),
|(mut all_assoc, mut current_assoc, mut associating): (
Vec<Vec<String>>,
Vec<String>,
bool,
),
line| {
let line = line.trim();

if line.starts_with(NIX_CONF_COMMENT_CHAR) {
associating = true;
} else if line.is_empty() || !line.starts_with(NIX_CONF_COMMENT_CHAR) {
associating = false;
}

current_assoc.push(line.to_string());

if !associating {
all_assoc.push(current_assoc);
current_assoc = Vec::new();
}

(all_assoc, current_assoc, associating)
},
);

for line_group in associated_lines {
if line_group.is_empty() || line_group.iter().all(|line| line.is_empty()) {
continue;
}

// This expect should never reasonably panic, because we would need a line group
// consisting solely of a comment and nothing else, but unconditionally appending a
// newline to the config string before grouping above prevents this from occurring.
let line_idx = line_group
.iter()
.position(|line| !line.starts_with(NIX_CONF_COMMENT_CHAR))
.expect("There should always be one line without a comment character");

let setting_line = &line_group[line_idx];
let comments = line_group[..line_idx].join("\n");

// If we're here, but the line without a comment char is empty, we have
// standalone comments to preserve, but no settings with inline comments.
if setting_line.is_empty() {
for line in &line_group {
new_config.push_str(line);
new_config.push('\n');
}

continue;
}

// Preserve inline comments for settings we've merged
let to_remove = if let Some((name, value)) = existing_nix_config
.settings()
.iter()
.find(|(name, _value)| setting_line.starts_with(*name))
{
new_config.push_str(&comments);
new_config.push('\n');
new_config.push_str(name);
new_config.push_str(" = ");

if let Some(merged_value) = merged_nix_config.settings_mut().shift_remove(name) {
new_config.push_str(&merged_value);
new_config.push(' ');
} else {
new_config.push_str(value);
}

if let Some(inline_comment_idx) = setting_line.find(NIX_CONF_COMMENT_CHAR) {
let inline_comment = &setting_line[inline_comment_idx..];
new_config.push_str(inline_comment);
new_config.push('\n');
}

Some(name.clone())
} else {
new_config.push_str(&comments);
new_config.push('\n');
new_config.push_str(setting_line);
new_config.push('\n');

None
};

if let Some(to_remove) = to_remove {
existing_nix_config.settings_mut().shift_remove(&to_remove);
}
}

// Add the leftover existing nix config
for (name, value) in existing_nix_config.settings() {
if merged_nix_config.settings().get(name).is_some() {
continue;
}

new_config.push_str(name);
new_config.push_str(" = ");
new_config.push_str(value);
new_config.push('\n');
}

new_config.push('\n');

for (name, value) in merged_nix_config.settings() {
new_config.push_str(name);
new_config.push_str(" = ");
new_config.push_str(value);
new_config.push('\n');
}

new_config
.strip_prefix('\n')
.unwrap_or(&new_config)
.to_owned()
}
4 changes: 2 additions & 2 deletions src/cli/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,13 @@ async fn make_base_client(_authenticated: bool) -> Result<Client, FhError> {

#[cfg(not(test))]
async fn make_base_client(authenticated: bool) -> Result<Client, FhError> {
use self::login::auth_token_path;
use self::login::user_auth_token_path;

let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));

if authenticated {
if let Ok(token) = tokio::fs::read_to_string(auth_token_path()?).await {
if let Ok(token) = tokio::fs::read_to_string(user_auth_token_path()?).await {
if !token.is_empty() {
headers.insert(
AUTHORIZATION,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/cmd/status/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl CommandExecute for StatusSubcommand {
pub(crate) async fn get_status_from_auth_file(
api_addr: url::Url,
) -> color_eyre::Result<TokenStatus> {
let auth_token_path = crate::cli::cmd::login::auth_token_path()?;
let auth_token_path = crate::cli::cmd::login::user_auth_token_path()?;
let token = tokio::fs::read_to_string(&auth_token_path)
.await
.wrap_err_with(|| format!("Could not open {}", auth_token_path.display()))?;
Expand Down
135 changes: 135 additions & 0 deletions src/shared/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub async fn update_netrc_file(
.await
.wrap_err("failed to update netrc file contents")
}

pub fn netrc_contents(
frontend_addr: &url::Url,
backend_addr: &url::Url,
Expand All @@ -47,3 +48,137 @@ pub fn netrc_contents(
);
Ok(contents)
}

// NOTE(cole-h): Adapted from
// https:/DeterminateSystems/nix-installer/blob/0b0172547c4666f6b1eacb6561a59d6b612505a3/src/action/base/create_or_merge_nix_config.rs#L284
const NIX_CONF_COMMENT_CHAR: char = '#';
pub fn merge_nix_configs(
mut existing_nix_config: nix_config_parser::NixConfig,
mut existing_nix_config_contents: String,
mut merged_nix_config: nix_config_parser::NixConfig,
) -> String {
let mut new_config = String::new();

// We append a newline to ensure that, in the case there are comments at the end of the
// file and _NO_ trailing newline, we still preserve the entire block of comments.
existing_nix_config_contents.push('\n');

let (associated_lines, _, _) = existing_nix_config_contents.split('\n').fold(
(Vec::new(), Vec::new(), false),
|(mut all_assoc, mut current_assoc, mut associating): (
Vec<Vec<String>>,
Vec<String>,
bool,
),
line| {
let line = line.trim();

if line.starts_with(NIX_CONF_COMMENT_CHAR) {
associating = true;
} else if line.is_empty() || !line.starts_with(NIX_CONF_COMMENT_CHAR) {
associating = false;
}

current_assoc.push(line.to_string());

if !associating {
all_assoc.push(current_assoc);
current_assoc = Vec::new();
}

(all_assoc, current_assoc, associating)
},
);

for line_group in associated_lines {
if line_group.is_empty() || line_group.iter().all(|line| line.is_empty()) {
continue;
}

// This expect should never reasonably panic, because we would need a line group
// consisting solely of a comment and nothing else, but unconditionally appending a
// newline to the config string before grouping above prevents this from occurring.
let line_idx = line_group
.iter()
.position(|line| !line.starts_with(NIX_CONF_COMMENT_CHAR))
.expect("There should always be one line without a comment character");

let setting_line = &line_group[line_idx];
let comments = line_group[..line_idx].join("\n");

// If we're here, but the line without a comment char is empty, we have
// standalone comments to preserve, but no settings with inline comments.
if setting_line.is_empty() {
for line in &line_group {
new_config.push_str(line);
new_config.push('\n');
}

continue;
}

// Preserve inline comments for settings we've merged
let to_remove = if let Some((name, value)) = existing_nix_config
.settings()
.iter()
.find(|(name, _value)| setting_line.starts_with(*name))
{
new_config.push_str(&comments);
new_config.push('\n');
new_config.push_str(name);
new_config.push_str(" = ");

if let Some(merged_value) = merged_nix_config.settings_mut().shift_remove(name) {
new_config.push_str(&merged_value);
new_config.push(' ');
} else {
new_config.push_str(value);
}

if let Some(inline_comment_idx) = setting_line.find(NIX_CONF_COMMENT_CHAR) {
let inline_comment = &setting_line[inline_comment_idx..];
new_config.push_str(inline_comment);
new_config.push('\n');
}

Some(name.clone())
} else {
new_config.push_str(&comments);
new_config.push('\n');
new_config.push_str(setting_line);
new_config.push('\n');

None
};

if let Some(to_remove) = to_remove {
existing_nix_config.settings_mut().shift_remove(&to_remove);
}
}

// Add the leftover existing nix config
for (name, value) in existing_nix_config.settings() {
if merged_nix_config.settings().get(name).is_some() {
continue;
}

new_config.push_str(name);
new_config.push_str(" = ");
new_config.push_str(value);
new_config.push('\n');
}

new_config.push('\n');

for (name, value) in merged_nix_config.settings() {
new_config.push_str(name);
new_config.push_str(" = ");
new_config.push_str(value);
new_config.push('\n');
}

new_config
.strip_prefix('\n')
.unwrap_or(&new_config)
.to_owned()
}

0 comments on commit 526eae5

Please sign in to comment.