Skip to content

feat: config file support for deterministic re-generation#56

Merged
omattsson merged 7 commits into
mainfrom
feature/config-file-support
Apr 11, 2026
Merged

feat: config file support for deterministic re-generation#56
omattsson merged 7 commits into
mainfrom
feature/config-file-support

Conversation

@omattsson

Copy link
Copy Markdown
Owner

Summary

Closes #52 — adds config file support for deterministic re-generation.

Changes

  1. New module cli/bootstrap_iac/config.py:

    • find_config(workspace) — auto-detects .bootstrap-iac.yaml / .bootstrap-iac.yml in workspace root
    • load_config(path) — loads YAML config, normalises values (e.g. azureAzure, github-actionsGitHub Actions)
    • save_config(answers, path) — writes interview answers back to YAML
  2. CLI integration (cli.py):

    • --config PATH flag to specify a config file explicitly
    • --save-config flag to write answers after generation
    • Auto-detection of config files in workspace root
    • Config values serve as defaults; CLI flags override them
    • Config file not written on --dry-run
  3. Added pyyaml>=6.0 dependency (pyproject.toml)

  4. 21 new tests (cli/tests/test_config.py):

    • find_config: auto-detection, priority (.yaml over .yml), missing file
    • load_config: basic load, normalisation (cloud/orchestration/cicd), unknown keys, empty file, non-mapping rejection, all fields
    • save_config: roundtrip, skips empty values, save-then-load roundtrip
    • CLI integration: config auto-loading, flag override, explicit --config path, missing config error, --save-config, dry-run skips save
  5. Updated cli/README.md: new Config File section, added --config and --save-config to options table

Config File Format

company: Acme Corp
cloud: azure
module_prefix: tf-module
orchestration: terragrunt
ci_cd: github-actions
org: acme
target: both

Precedence Order

CLI flags > config file > discovery defaults > interactive prompts

Test Results

132 tests passing (111 existing + 21 new)

- New config module (config.py): find, load, save .bootstrap-iac.yaml
- Auto-detects config in workspace root; --config for explicit path
- Config values serve as defaults; CLI flags override them
- --save-config writes answers to .bootstrap-iac.yaml after generation
- Added pyyaml dependency
- 21 new tests (unit + CLI integration) covering load, save, roundtrip,
  normalisation, flag override, auto-detection, dry-run skip
- Updated cli/README.md with Config File section and new flags
Copilot AI review requested due to automatic review settings April 7, 2026 18:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds YAML config-file support to the bootstrap-iac CLI so teams can commit interview answers and deterministically re-generate outputs across runs.

Changes:

  • Introduces cli/bootstrap_iac/config.py to find/load/save .bootstrap-iac.{yaml,yml} config files with value normalisation.
  • Extends the CLI with --config (explicit path) and --save-config (persist answers) and merges config defaults into the existing override flow.
  • Adds a new test suite for config functionality and CLI integration, and documents the config file format in cli/README.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
cli/bootstrap_iac/config.py New config module for discovering, parsing, normalising, and writing config files.
cli/bootstrap_iac/cli.py Wires config defaults + save-config into the CLI execution path.
cli/tests/test_config.py Adds unit + integration tests around config and CLI precedence behavior.
cli/pyproject.toml Adds PyYAML dependency needed for config support.
cli/README.md Documents config file usage and new CLI flags.

Comment thread cli/tests/test_config.py
Comment on lines +262 to +273

result = cli_runner.invoke(main, [
"--workspace", str(ws),
"--output-dir", str(tmp_path / "out"),
"--company", "FlagCorp",
"--target", "copilot",
"--non-interactive",
])
assert result.exit_code == 0
# FlagCorp should be used, not ConfigCorp


Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_cli_flag_overrides_config doesn’t assert that the CLI flag values actually override the config defaults (the test ends after exit_code and a comment). This will pass even if precedence is broken; add an assertion that verifies the generated output (or derived context) reflects --company FlagCorp/--target copilot instead of the config values.

Suggested change
result = cli_runner.invoke(main, [
"--workspace", str(ws),
"--output-dir", str(tmp_path / "out"),
"--company", "FlagCorp",
"--target", "copilot",
"--non-interactive",
])
assert result.exit_code == 0
# FlagCorp should be used, not ConfigCorp
out_dir = tmp_path / "out"
result = cli_runner.invoke(main, [
"--workspace", str(ws),
"--output-dir", str(out_dir),
"--company", "FlagCorp",
"--target", "copilot",
"--non-interactive",
])
assert result.exit_code == 0
# --target copilot should win over config target: both.
assert (out_dir / ".github").exists()
assert not (out_dir / "CLAUDE.md").exists()
# --company FlagCorp should be rendered into generated output instead of ConfigCorp.
generated_text = "\n".join(
path.read_text()
for path in out_dir.rglob("*")
if path.is_file()
)
assert "FlagCorp" in generated_text
assert "ConfigCorp" not in generated_text

Copilot uses AI. Check for mistakes.
Comment thread cli/tests/test_config.py Outdated
"CI_CD_PLATFORM": "GitHub Actions",
"AUTH_PATTERN": "OIDC",
"STATE_BACKEND": "azurerm",
"NAMING_PATTERN": "{prefix}-{resource}-{suffix}",

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The saved NAMING_PATTERN example uses {resource} here, but the rest of the CLI/interview defaults use {resource_abbreviation}. Using a different placeholder in new tests/examples is likely to confuse users and can propagate an incorrect convention; consider aligning this value with the canonical placeholder name.

Suggested change
"NAMING_PATTERN": "{prefix}-{resource}-{suffix}",
"NAMING_PATTERN": "{prefix}-{resource_abbreviation}-{suffix}",

Copilot uses AI. Check for mistakes.
Comment thread cli/README.md Outdated
ci_cd: github-actions
auth: workload-identity
state_backend: azurerm
naming: "${var.prefix}-${var.resource_type}-${local.suffix}"

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README config example uses ${var.resource_type} in the naming field, but the CLI’s default naming pattern placeholder is {resource_abbreviation} (see interview defaults). This example should be updated to match the documented/implemented placeholder, otherwise users may commit a config that doesn’t match the generated instructions.

Suggested change
naming: "${var.prefix}-${var.resource_type}-${local.suffix}"
naming: "${var.prefix}-{resource_abbreviation}-${local.suffix}"

Copilot uses AI. Check for mistakes.
Comment thread cli/README.md
Comment on lines +150 to +154
company: Acme Corp
cloud: azure
module_prefix: tf-module
orchestration: terragrunt
orchestration_dir: infrastructure-config

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README’s config example shows cloud: azure, orchestration: terragrunt, ci_cd: github-actions, but --save-config currently writes the title-cased interview values (e.g. Azure, Terragrunt, GitHub Actions). Either document that config values are case/format-insensitive or update the example to match the actual saved output to avoid confusion.

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/config.py Outdated
Comment on lines +79 to +80
str_val = str(value)

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_config coerces all YAML values via str(value). If a user sets a key to null, this becomes the literal string 'None' and will be treated as a real override (breaking enum handling like CLOUD_PROVIDER/TARGET). Consider treating None (and possibly empty/whitespace-only strings) as “unset” and either skipping non-scalar values or raising a validation error.

Suggested change
str_val = str(value)
if value is None:
continue # treat YAML null as unset
if isinstance(value, str):
str_val = value.strip()
if not str_val:
continue # treat empty/whitespace-only strings as unset
elif isinstance(value, (bool, int, float)):
str_val = str(value)
else:
raise ValueError(
f"Config key '{file_key}' must be a YAML scalar, got {type(value).__name__}"
)

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/config.py Outdated
Comment on lines +81 to +86
# Normalise known enum values
if upper_key == "CLOUD_PROVIDER":
str_val = _CLOUD_MAP.get(str_val.lower(), str_val)
elif upper_key == "ORCHESTRATION_TOOL":
str_val = _ORCH_MAP.get(str_val.lower(), str_val)
elif upper_key == "CI_CD_PLATFORM":

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only cloud/orchestration/ci_cd get normalised, but target is passed through as-is. Since generate_files() only recognises copilot|claude|both (lowercase), a config value like Both would result in generating nothing. Consider normalising/validating TARGET (e.g., .lower() + allowed-values check) the same way as the other enums.

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/config.py Outdated
config[file_key] = value

with open(path, "w", encoding="utf-8") as fh:
yaml.dump(config, fh, default_flow_style=False, sort_keys=True, allow_unicode=True)

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save_config uses yaml.dump(...). Since this file is intended to be human-edited config, yaml.safe_dump(...) is a safer default (avoids emitting Python-specific tags if value types ever change) and better matches the use of safe_load on read.

Suggested change
yaml.dump(config, fh, default_flow_style=False, sort_keys=True, allow_unicode=True)
yaml.safe_dump(config, fh, default_flow_style=False, sort_keys=True, allow_unicode=True)

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/cli.py Outdated
Comment on lines +264 to +266
if cfg_file:
click.echo(f" Loading config: {cfg_file}")
config_defaults = load_config(cfg_file)

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_config(cfg_file) can raise yaml.YAMLError/ValueError (e.g., invalid YAML or non-mapping root), which will currently bubble up as an unhandled exception/traceback. Consider catching these errors here and exiting with a user-friendly message and a non-zero status code (similar to the “config not found” handling).

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/cli.py Outdated
Comment on lines +359 to +363
if save_config and not dry_run:
config_out = ws_path / ".bootstrap-iac.yaml"
write_config(answers, config_out)
click.echo(f" Saved config: {config_out}")

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --save-config help text says the config is written “after generation”, but this block runs before build_context()/generate_files(). If generation fails, the config may still be written, and the behavior doesn’t match the flag description; consider moving the write to after successful generation (and only when not dry-run).

Copilot uses AI. Check for mistakes.
…ngthen tests

- config.py: skip null/empty values, reject non-scalar types, normalise
  TARGET to lowercase, use yaml.safe_dump, rename save_config→write_config
- cli.py: catch yaml.YAMLError/ValueError with user-friendly error, move
  --save-config after generation, update import to write_config
- test_config.py: strengthen test_cli_flag_overrides_config with real
  assertions (output dir checks, company name in generated files), fix
  NAMING_PATTERN to use {resource_abbreviation}, rename save_config refs
- README.md: update config example to match --save-config output
  (title-cased values, {resource_abbreviation} naming pattern)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread cli/bootstrap_iac/config.py Outdated
Comment on lines +94 to +104
# Normalise known enum values
if upper_key == "CLOUD_PROVIDER":
str_val = _CLOUD_MAP.get(str_val.lower(), str_val)
elif upper_key == "ORCHESTRATION_TOOL":
str_val = _ORCH_MAP.get(str_val.lower(), str_val)
elif upper_key == "CI_CD_PLATFORM":
str_val = _CICD_MAP.get(str_val.lower(), str_val)
elif upper_key == "TARGET":
str_val = str_val.lower()

overrides[upper_key] = str_val

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_config() currently normalizes known enum-like values (cloud/orchestration/ci_cd/target) but accepts unknown values without validation. Because run_interview() returns override values without checking them, an invalid target (or other enum) from config can lead to silently generating zero files (e.g., generate_files() filters by target). Consider validating these keys against the supported choice sets and raising ValueError for unknown values so CI/config-driven runs fail fast with a clear message.

Copilot uses AI. Check for mistakes.
Comment thread cli/tests/test_config.py
Comment on lines +247 to +256
result = cli_runner.invoke(main, [
"--workspace", str(ws),
"--output-dir", str(tmp_path / "out"),
"--non-interactive",
])
assert result.exit_code == 0
assert "Loading config" in result.output
assert "ConfigCorp" in result.output or (tmp_path / "out" / ".github").exists()


Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_cli_loads_config_file() doesn’t actually assert that config values were applied: "ConfigCorp" in result.output is unlikely to be true (generation doesn’t print file contents), and the fallback or (out/.github).exists() will pass even if defaults weren’t read correctly. It would be more robust to read a generated output file (e.g., .github/copilot-instructions.md) and assert it contains ConfigCorp / expected normalized values from the config.

Suggested change
result = cli_runner.invoke(main, [
"--workspace", str(ws),
"--output-dir", str(tmp_path / "out"),
"--non-interactive",
])
assert result.exit_code == 0
assert "Loading config" in result.output
assert "ConfigCorp" in result.output or (tmp_path / "out" / ".github").exists()
out_dir = tmp_path / "out"
result = cli_runner.invoke(main, [
"--workspace", str(ws),
"--output-dir", str(out_dir),
"--non-interactive",
])
assert result.exit_code == 0
assert "Loading config" in result.output
copilot_instructions = out_dir / ".github" / "copilot-instructions.md"
assert copilot_instructions.is_file()
rendered = copilot_instructions.read_text()
assert "ConfigCorp" in rendered
assert "Azure" in rendered

Copilot uses AI. Check for mistakes.
- load_config() now rejects unsupported values for cloud, orchestration,
  ci_cd, and target with clear error messages listing valid options
- Accepts both slug keys (github-actions) and display values (GitHub Actions)
  so hand-written and --save-config-generated configs both work
- test_cli_loads_config_file asserts ConfigCorp and Azure appear in the
  rendered copilot-instructions.md output
- Added 4 tests for invalid enum value rejection

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Comment thread cli/bootstrap_iac/cli.py Outdated
Comment on lines +385 to +388
# --save-config: write answers to .bootstrap-iac.yaml #
# ------------------------------------------------------------------ #
if save_config and not dry_run:
config_out = ws_path / ".bootstrap-iac.yaml"

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--save-config always writes to <workspace>/.bootstrap-iac.yaml with mode w, which will overwrite any existing config (including a committed .bootstrap-iac.yml) without warning. Consider writing back to the detected/explicit --config path when present, and/or refusing to overwrite unless --overwrite (or a dedicated --overwrite-config) is set.

Suggested change
# --save-config: write answers to .bootstrap-iac.yaml #
# ------------------------------------------------------------------ #
if save_config and not dry_run:
config_out = ws_path / ".bootstrap-iac.yaml"
# --save-config: write answers to the detected config path, or default #
# ------------------------------------------------------------------ #
if save_config and not dry_run:
existing_config = find_config(ws_path)
config_out = existing_config or (ws_path / ".bootstrap-iac.yaml")
if config_out.exists() and not overwrite:
raise click.ClickException(
f"Refusing to overwrite existing config: {config_out}. "
"Re-run with --overwrite to replace it."
)

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/cli.py
if cfg_file:
click.echo(f" Loading config: {cfg_file}")
try:
config_defaults = load_config(cfg_file)

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the config file exists but can’t be read (permissions, broken symlink, etc.), load_config() will raise OSError and the CLI will crash with a traceback because only yaml.YAMLError/ValueError are caught here. Catch OSError as well and surface a friendly Click error message + exit code 1.

Suggested change
config_defaults = load_config(cfg_file)
config_defaults = load_config(cfg_file)
except OSError as exc:
click.secho(f" ✗ Unable to read config file: {exc}", fg="red")
sys.exit(1)

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +50
# Values that need normalisation from CLI lowercase to interview title-case
_CLOUD_MAP: dict[str, str] = {"azure": "Azure", "aws": "AWS", "gcp": "GCP"}
_ORCH_MAP: dict[str, str] = {
"terragrunt": "Terragrunt",
"terramate": "Terramate",
"pulumi": "Pulumi",
"none": "None",
}
_CICD_MAP: dict[str, str] = {
"github-actions": "GitHub Actions",
"azure-devops": "Azure DevOps",
"gitlab-ci": "GitLab CI",
"atlantis": "Atlantis",
}

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module re-defines the cloud/orchestration/CI-CD normalization maps that are also defined in bootstrap_iac.cli for flag normalization. Having two sources of truth risks drift (e.g., adding a new CI/CD choice in one place). Consider centralizing these mappings (e.g., export them from this module and have cli.py reuse them).

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/config.py Outdated
"target": "TARGET",
}

# Reverse mapping for save_config

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment says “Reverse mapping for save_config”, but the public helper is named write_config. Consider updating the comment to match the current function name to avoid confusion.

Suggested change
# Reverse mapping for save_config
# Reverse mapping for write_config

Copilot uses AI. Check for mistakes.
Comment thread cli/tests/test_config.py
Comment on lines +247 to +255
# ---------------------------------------------------------------------------
# CLI integration (config file loading via CliRunner)
# ---------------------------------------------------------------------------

from click.testing import CliRunner
from bootstrap_iac.cli import main

cli_runner = CliRunner()

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports for CliRunner and main are placed mid-file after other test definitions. Other test modules in this repo keep imports at the top; moving these to the top improves readability and avoids potential circular-import surprises during collection.

Copilot uses AI. Check for mistakes.
…SError

- config.py: fix stale 'save_config' comment → 'write_config'
- cli.py: derive Click choices from config.py maps (single source of truth),
  catch OSError when reading config, --save-config writes back to detected
  config path and refuses overwrite without --overwrite flag
- test_config.py: move mid-file imports to top, add tests for overwrite
  guard, --overwrite flag, and .yml write-back behaviour

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread cli/bootstrap_iac/cli.py
Comment on lines 28 to +36
from bootstrap_iac import __version__
from bootstrap_iac.config import (
_CICD_MAP,
_CLOUD_MAP,
_ORCH_MAP,
find_config,
load_config,
write_config,
)

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cli.py imports _CLOUD_MAP/_ORCH_MAP/_CICD_MAP from bootstrap_iac.config, but these are underscored (private) constants, and cli.py still defines its own _cloud_map/_orch_map/_cicd_map later for normalization. This duplication increases the chance of the CLI choices and normalization drifting apart. Consider making the maps public in config.py (no leading underscore) and reusing a single shared source for both click Choice values and CLI normalization (or moving the shared constants to a dedicated module).

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/cli.py Outdated
Comment on lines +392 to +399
# --save-config: write answers to config file #
# ------------------------------------------------------------------ #
if save_config and not dry_run:
config_out = cfg_file if cfg_file else (ws_path / ".bootstrap-iac.yaml")
if config_out.exists() and not overwrite:
click.secho(
f" ✗ Refusing to overwrite existing config: {config_out}. "
"Re-run with --overwrite to replace it.",

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--save-config relies on the global --overwrite flag to permit overwriting an existing config file, but the --overwrite help text (and README option table) only mentions generated files. This makes the flag semantics ambiguous for users. Consider either documenting that --overwrite also applies to the config file when --save-config is set, or introducing a dedicated flag (e.g., --overwrite-config) to keep behaviors explicit.

Suggested change
# --save-config: write answers to config file #
# ------------------------------------------------------------------ #
if save_config and not dry_run:
config_out = cfg_file if cfg_file else (ws_path / ".bootstrap-iac.yaml")
if config_out.exists() and not overwrite:
click.secho(
f" ✗ Refusing to overwrite existing config: {config_out}. "
"Re-run with --overwrite to replace it.",
# --save-config: write answers to config file. #
# The global --overwrite flag also applies to the saved config file. #
# ------------------------------------------------------------------ #
if save_config and not dry_run:
config_out = cfg_file if cfg_file else (ws_path / ".bootstrap-iac.yaml")
if config_out.exists() and not overwrite:
click.secho(
f" ✗ Refusing to overwrite existing config: {config_out}. "
"Re-run with --overwrite to replace it; this global flag "
"applies to both generated files and the saved config.",

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/config.py Outdated
config[file_key] = value

with open(path, "w", encoding="utf-8") as fh:
yaml.safe_dump(config, fh, default_flow_style=False, sort_keys=True, allow_unicode=True)

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write_config() sorts _REVERSE_KEY_MAP.items() and then calls yaml.safe_dump(..., sort_keys=True), which is redundant (and can make the intended ordering unclear). Consider choosing one ordering mechanism: either rely on sort_keys=True without pre-sorting, or set sort_keys=False and keep the explicit sort in Python.

Suggested change
yaml.safe_dump(config, fh, default_flow_style=False, sort_keys=True, allow_unicode=True)
yaml.safe_dump(config, fh, default_flow_style=False, sort_keys=False, allow_unicode=True)

Copilot uses AI. Check for mistakes.
…rite scope

- config.py: rename _CLOUD_MAP/_ORCH_MAP/_CICD_MAP to public CLOUD_MAP/
  ORCH_MAP/CICD_MAP; remove redundant sort_keys=True from write_config
- cli.py: remove duplicate local normalization maps, reuse config.py maps
  for both Click choices and flag normalization; update --overwrite help
  to clarify it applies to both generated files and saved config
- README.md: update --overwrite description to match

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

cli/bootstrap_iac/cli.py:40

  • get_templates_dir is imported but never used in this module (searching the file shows no references beyond the import). Consider removing the unused import to avoid confusion and keep the CLI entrypoint minimal.
)
from bootstrap_iac.discovery import scan_workspace
from bootstrap_iac.generator import generate_files, get_templates_dir
from bootstrap_iac.interview import build_context, run_interview
from bootstrap_iac.validator import validate_directory, validate_file

"auth": "AUTH_PATTERN",
"state_backend": "STATE_BACKEND",
"naming": "NAMING_PATTERN",
"tag_strategy": "TAG_STRATEGY",

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config key mapping does not include STANDARD_VARIABLES, but run_interview() collects this answer and build_context() consumes it for template rendering. Without supporting it here (load + write), --save-config can’t round-trip all interview answers, so re-generation won’t be deterministic for users who customized standard variables. Consider adding a standard_variables key to _KEY_MAP/_REVERSE_KEY_MAP, updating load_config() to accept it, and extending tests + README example accordingly.

Suggested change
"tag_strategy": "TAG_STRATEGY",
"tag_strategy": "TAG_STRATEGY",
"standard_variables": "STANDARD_VARIABLES",

Copilot uses AI. Check for mistakes.
- config.py: add standard_variables → STANDARD_VARIABLES to _KEY_MAP
- README.md: add standard_variables to config file example
- test_config.py: add STANDARD_VARIABLES to all_fields and roundtrip tests

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread cli/bootstrap_iac/cli.py Outdated
"--save-config",
is_flag=True,
default=False,
help="Write interview answers to .bootstrap-iac.yaml in the workspace after generation.",

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--save-config help text says it writes to .bootstrap-iac.yaml in the workspace, but the implementation writes to cfg_file when --config PATH is provided (which may be outside the workspace). Either change the behavior to always write to the workspace (and only use detected .yml/.yaml there), or update the flag help/README to document that --save-config writes back to the loaded --config path when provided.

Suggested change
help="Write interview answers to .bootstrap-iac.yaml in the workspace after generation.",
help=(
"Write interview answers after generation. Saves back to --config PATH "
"when provided; otherwise writes .bootstrap-iac.yaml in the workspace."
),

Copilot uses AI. Check for mistakes.
Comment thread cli/bootstrap_iac/cli.py
Comment on lines +381 to +392
if save_config and not dry_run:
config_out = cfg_file if cfg_file else (ws_path / ".bootstrap-iac.yaml")
if config_out.exists() and not overwrite:
click.secho(
f" \u2717 Refusing to overwrite existing config: {config_out}. "
"Re-run with --overwrite to replace it; this flag "
"applies to both generated files and the saved config.",
fg="red",
)
else:
write_config(answers, config_out)
click.echo(f" Saved config: {config_out}")

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--save-config uses config_out = cfg_file if cfg_file else (ws_path / ".bootstrap-iac.yaml"). When --config points to an arbitrary path (e.g., a shared config outside the workspace), this will overwrite that file, which contradicts the current --save-config help text and could be surprising. Consider writing only to the workspace root by default (or gating write-back to cfg_file behind an explicit flag), and documenting the chosen precedence.

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +149
def write_config(answers: dict[str, str], path: Path) -> None:
"""Write interview answers to a YAML config file."""

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions a save_config(answers, path) helper, but the implementation exports write_config(...) and tests import write_config. To avoid confusion for API consumers and future maintainers, align the function name with the documented API (rename to save_config, or update the PR description/docs to consistently use write_config).

Copilot uses AI. Check for mistakes.
- cli.py: --save-config help now documents it writes back to --config
  PATH when provided, otherwise .bootstrap-iac.yaml in the workspace
- README.md: update options table to match

Note: PR description references save_config() but the code consistently
uses write_config() — the rename happened during earlier review rounds.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread cli/bootstrap_iac/cli.py
Comment on lines +264 to +272
if config_path:
cfg_file = Path(config_path).resolve()
if not cfg_file.is_file():
click.secho(f" ✗ Config file not found: {cfg_file}", fg="red")
sys.exit(1)
else:
cfg_file = find_config(ws_path)

if cfg_file:

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--config PATH currently requires the file to already exist (exits 1 if missing). That prevents a common workflow implied by --save-config (generate answers and write a new config to the provided PATH). Consider only requiring existence when you actually intend to load defaults (e.g., when --config is provided without --save-config, or when the file exists); otherwise treat a missing file as “no defaults” and allow write_config to create it after generation.

Suggested change
if config_path:
cfg_file = Path(config_path).resolve()
if not cfg_file.is_file():
click.secho(f" ✗ Config file not found: {cfg_file}", fg="red")
sys.exit(1)
else:
cfg_file = find_config(ws_path)
if cfg_file:
should_load_config = False
if config_path:
cfg_file = Path(config_path).resolve()
if cfg_file.is_file():
should_load_config = True
elif not save_config:
click.secho(f" ✗ Config file not found: {cfg_file}", fg="red")
sys.exit(1)
else:
cfg_file = find_config(ws_path)
should_load_config = cfg_file is not None
if should_load_config and cfg_file:

Copilot uses AI. Check for mistakes.
@omattsson omattsson merged commit f6c4041 into main Apr 11, 2026
10 checks passed
@omattsson omattsson deleted the feature/config-file-support branch April 11, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Config file support for deterministic re-generation

2 participants