Always update connections.env and fix double-toggled connection line

Drop the --legacy flag: set_local_db and toggle_index now run
unconditionally instead of only when -l was passed, since that was the
flag's only effect.

Fix set_dot_env writing the connection/RCC_CONNECTION line twice with
opposing polarity (once from the #docker pass, once from the #cidb
pass), which flipped it to the wrong value. Collapsed the three
separate open/read/write passes into one, toggling both sections and
the connection line in a single scan so the connection line is only
ever touched once.
This commit is contained in:
2026-09-17 15:12:32 +02:00
parent 36752606a6
commit 3873987c19
4 changed files with 112 additions and 140 deletions
+3 -7
View File
@@ -35,14 +35,13 @@ cargo install --path .
## Usage ## Usage
``` ```
dev_tools <project> <up|down> [--legacy] dev_tools <project> <up|down>
``` ```
| Argument | Description | | Argument | Description |
|---|---| |---|---|
| `project` | Project key (matches `<PROJECT>_DIR` in the config, case-insensitive) | | `project` | Project key (matches `<PROJECT>_DIR` in the config, case-insensitive) |
| `up` / `down` | Start or stop the environment | | `up` / `down` | Start or stop the environment |
| `-l`, `--legacy` | Enable legacy mode: also toggles `conf/global/connections.env` and runs `git update-index` |
### Examples ### Examples
@@ -52,16 +51,13 @@ dev_tools foo up
# Stop it # Stop it
dev_tools foo down dev_tools foo down
# Start with legacy env handling
dev_tools foo up --legacy
``` ```
## What it does ## What it does
1. Starts or stops the Docker Compose service defined by `DOCKER_SERVICE` / `DOCKER_DIR`. 1. Starts or stops the Docker Compose service defined by `DOCKER_SERVICE` / `DOCKER_DIR`.
2. Toggles database connection entries in `<PROJECT_DIR>/conf/local/.env` between `staging` and CI values. 2. Modifies `conf/global/connections.env` and marks it with `git update-index --assume-unchanged` to avoid accidental commits.
3. With `--legacy`: additionally modifies `conf/global/connections.env` and marks it with `git update-index --assume-unchanged` to avoid accidental commits. 3. Toggles database connection entries in `<PROJECT_DIR>/conf/local/.env`: `up` switches the connection to `ci`, `down` switches it to `staging`.
## Tests ## Tests
-8
View File
@@ -13,13 +13,6 @@ pub struct Arguments {
#[arg(help = "Possible values are 'up' and 'down'")] #[arg(help = "Possible values are 'up' and 'down'")]
pub action: String, pub action: String,
#[arg(
short = 'l',
long = "legacy",
help = "Legacy mode for old env reading."
)]
pub legacy: bool,
} }
impl Arguments { impl Arguments {
@@ -40,7 +33,6 @@ mod tests {
Arguments { Arguments {
project: "test".to_string(), project: "test".to_string(),
action: action.to_string(), action: action.to_string(),
legacy: false,
} }
} }
+107 -118
View File
@@ -69,11 +69,81 @@ pub fn set_local_db(start: bool, config: &DevToolsConf) -> Result<FileChange> {
}) })
} }
const DOCKER_SECTION: &str = "#docker";
const DOCKER_SECTION_LINES: usize = 4;
const CIDB_SECTION: &str = "#cidb";
const CIDB_SECTION_LINES: usize = 2;
/// Toggles the `#docker` and `#cidb` sections and the `connection` line of
/// `conf/local/.env` in a single read/write pass. `#docker` follows `start`
/// directly; `#cidb` and the connection line follow its inverse (`up` means
/// the local docker db is reachable, so `#cidb`'s CI credentials switch off
/// and the connection moves to `ci`; `down` is the reverse, ending on
/// `staging`). Every line outside both sections is checked against
/// `connection_key` exactly once, so it can't be toggled twice.
pub fn set_dot_env(start: bool, config: &DevToolsConf) -> Result<FileChange> { pub fn set_dot_env(start: bool, config: &DevToolsConf) -> Result<FileChange> {
let mut docker_change = toggle_after_line("#docker", 4, start, config)?; let local_connection_env = format!(
let cidb_change = toggle_after_line("#cidb", 2, !start, config)?; "{}{}",
docker_change.diffs.extend(cidb_change.diffs); config.project_dir_as_string(),
Ok(docker_change) LOCAL_CONNECTION_PATH
);
let file: File = File::open(&local_connection_env)
.with_context(|| format!("could not read file `{}`", local_connection_env))?;
let reader: BufReader<File> = BufReader::new(file);
let mut modified_lines: Vec<String> = Vec::new();
let mut diffs: Vec<(String, String)> = Vec::new();
let mut section: Option<(bool, usize)> = None;
for line in reader.lines() {
let line_content = line?;
let trimmed = line_content.trim_start();
if let Some((_, remaining)) = section {
if remaining == 0 {
section = None;
}
}
if trimmed.starts_with(DOCKER_SECTION) {
section = Some((start, DOCKER_SECTION_LINES));
modified_lines.push(line_content);
continue;
}
if trimmed.starts_with(CIDB_SECTION) {
section = Some((!start, CIDB_SECTION_LINES));
modified_lines.push(line_content);
continue;
}
let modified: Option<String> = if let Some((activate, remaining)) = &mut section {
let new_line = toggle(&line_content, *activate);
*remaining -= 1;
(new_line != line_content).then_some(new_line)
} else {
connection_key(&line_content).and_then(|key| {
let value = if start { "ci" } else { "staging" };
let new_line = format!("{}='{}'", key, value);
(new_line != line_content).then_some(new_line)
})
};
match modified {
Some(new_line) => {
diffs.push((line_content, new_line.clone()));
modified_lines.push(new_line);
}
None => modified_lines.push(line_content),
}
}
fs::write(&local_connection_env, modified_lines.join("\n"))
.context("Cannot write local connection env")?;
Ok(FileChange {
path: local_connection_env,
diffs,
})
} }
const CI_DB_MASTER_IP: &str = "ci.db.master.ip"; const CI_DB_MASTER_IP: &str = "ci.db.master.ip";
@@ -112,68 +182,6 @@ pub(crate) fn should_commented_out(line: &str) -> bool {
} }
} }
fn toggle_after_line(
target_line_prefix: &str,
num_lines: usize,
activate: bool,
config: &DevToolsConf,
) -> Result<FileChange> {
let local_connection_env = format!(
"{}{}",
config.project_dir_as_string(),
LOCAL_CONNECTION_PATH
);
let file: File = File::open(&local_connection_env)
.with_context(|| format!("could not read file `{}`", local_connection_env))?;
let reader: BufReader<File> = BufReader::new(file);
let mut modified_lines: Vec<String> = Vec::new();
let mut diffs: Vec<(String, String)> = Vec::new();
let mut relevant = false;
let mut counter: usize = 0;
for line in reader.lines() {
let line_content = line?;
if counter == num_lines {
relevant = false;
}
if line_content.trim_start().starts_with(target_line_prefix) {
relevant = true;
modified_lines.push(line_content);
continue;
}
let modified: Option<String> = if relevant {
counter += 1;
let new_line = toggle(&line_content, activate);
(new_line != line_content).then_some(new_line)
} else if let Some(key) = connection_key(&line_content) {
let value = if activate { "staging" } else { "ci" };
let new_line = format!("{}='{}'", key, value);
(new_line != line_content).then_some(new_line)
} else {
None
};
match modified {
Some(new_line) => {
diffs.push((line_content, new_line.clone()));
modified_lines.push(new_line);
}
None => modified_lines.push(line_content),
}
}
fs::write(&local_connection_env, modified_lines.join("\n"))
.context("Cannot write local connection env")?;
Ok(FileChange {
path: local_connection_env,
diffs,
})
}
/// Returns the `connection` key as it appears on the given line (either the /// Returns the `connection` key as it appears on the given line (either the
/// legacy `connection` or the new `RCC_CONNECTION`), if the line sets it. /// legacy `connection` or the new `RCC_CONNECTION`), if the line sets it.
fn connection_key(line: &str) -> Option<&'static str> { fn connection_key(line: &str) -> Option<&'static str> {
@@ -303,58 +311,6 @@ mod tests {
); );
} }
#[test]
fn toggle_after_line_records_diffs_for_toggled_section() {
let dir = TempProjectDir::new("toggle_section");
fs::write(
dir.join(LOCAL_CONNECTION_PATH),
"#docker\nDOCKER_HOST=tcp://localhost:2375\n#DOCKER_TLS_VERIFY=1\n#DOCKER_CERT_PATH=/certs\nDOCKER_API_VERSION=1.41\nother=1",
)
.unwrap();
let change = toggle_after_line("#docker", 4, true, &config_for(&dir)).unwrap();
assert_eq!(
change.diffs,
vec![
(
"#DOCKER_TLS_VERIFY=1".to_string(),
"DOCKER_TLS_VERIFY=1".to_string()
),
(
"#DOCKER_CERT_PATH=/certs".to_string(),
"DOCKER_CERT_PATH=/certs".to_string()
),
]
);
}
#[test]
fn toggle_after_line_records_diffs_for_both_connection_keys() {
let dir = TempProjectDir::new("connection_keys");
fs::write(
dir.join(LOCAL_CONNECTION_PATH),
"#docker\nline1\nline2\nline3\nline4\nconnection='staging'\nRCC_CONNECTION='staging'",
)
.unwrap();
let change = toggle_after_line("#cidb", 2, false, &config_for(&dir)).unwrap();
assert_eq!(
change.diffs,
vec![
(
"connection='staging'".to_string(),
"connection='ci'".to_string()
),
(
"RCC_CONNECTION='staging'".to_string(),
"RCC_CONNECTION='ci'".to_string()
),
]
);
}
#[test] #[test]
fn set_dot_env_merges_diffs_from_both_sections() { fn set_dot_env_merges_diffs_from_both_sections() {
let dir = TempProjectDir::new("merge_sections"); let dir = TempProjectDir::new("merge_sections");
@@ -386,6 +342,39 @@ mod tests {
} }
} }
#[test]
fn set_dot_env_merges_diffs_from_both_sections_on_stop() {
let dir = TempProjectDir::new("merge_sections_stop");
fs::write(
dir.join(LOCAL_CONNECTION_PATH),
"#docker\nDOCKER_HOST=tcp://localhost:2375\n#DOCKER_TLS_VERIFY=1\n#DOCKER_CERT_PATH=/certs\nDOCKER_API_VERSION=1.41\n#cidb\n#ci.db.master.ip=127.0.0.1\n#ci.db.master.port=5432\nconnection='ci'\nRCC_CONNECTION='ci'",
)
.unwrap();
let change = set_dot_env(false, &config_for(&dir)).unwrap();
assert_eq!(change.diffs.len(), 6);
for expected in [
(
"DOCKER_HOST=tcp://localhost:2375",
"#DOCKER_HOST=tcp://localhost:2375",
),
("DOCKER_API_VERSION=1.41", "#DOCKER_API_VERSION=1.41"),
("#ci.db.master.ip=127.0.0.1", "ci.db.master.ip=127.0.0.1"),
("#ci.db.master.port=5432", "ci.db.master.port=5432"),
("connection='ci'", "connection='staging'"),
("RCC_CONNECTION='ci'", "RCC_CONNECTION='staging'"),
] {
assert!(
change
.diffs
.contains(&(expected.0.to_string(), expected.1.to_string())),
"missing diff {:?}",
expected
);
}
}
#[test] #[test]
fn file_change_display_no_diffs() { fn file_change_display_no_diffs() {
let change = FileChange { let change = FileChange {
+2 -7
View File
@@ -22,14 +22,9 @@ fn main() -> Result<()> {
let start: bool = args.action == "up"; let start: bool = args.action == "up";
start_docker_compose(&config.container_service, &config.container_dir, start)?; start_docker_compose(&config.container_service, &config.container_dir, start)?;
if args.legacy {
println!("[!] Legacy mode active.");
print!("{}", set_local_db(start, &config)?);
toggle_index(start, &config)?;
} else {
println!("[!] Legacy mode deactivated.");
}
print!("{}", set_local_db(start, &config)?);
toggle_index(start, &config)?;
print!("{}", set_dot_env(start, &config)?); print!("{}", set_dot_env(start, &config)?);
println!( println!(