From b0d1f5c24a6f40b652a7ad39e5fa81fc40bc5f29 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Wed, 21 Oct 2026 18:05:46 +0000 Subject: [PATCH] feat: add optional unique mock roots Add the global --unique-mockroot flag and pass mock a stable --uniqueext for each runner. Limit extensions to 24 characters with component labels of up to 12 characters and a six-character random suffix. Use bld, rndr, qry, and shl for build, render, query, and shell actions. Preserve the extension across runner clones and cleanup without changing default root naming. Propagate component labels through builds, rendering, queries, custom source preparation, and advanced mock commands. Add lifecycle and label boundary tests, update user and agent guidance, and refresh generated CLI docs and help snapshots. Quality checks and affected unit packages pass. Help snapshots were refreshed from verified CLI output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 086236b0-c4cd-4289-ace7-c82d78940883 --- docs/user/how-to/build-component.md | 34 ++++ docs/user/reference/cli/azldev.md | 1 + docs/user/reference/cli/azldev_advanced.md | 1 + .../cli/azldev_advanced_download-sources.md | 1 + .../user/reference/cli/azldev_advanced_mcp.md | 1 + .../reference/cli/azldev_advanced_mock.md | 1 + .../cli/azldev_advanced_mock_build-rpms.md | 1 + .../cli/azldev_advanced_mock_shell.md | 1 + .../reference/cli/azldev_advanced_wget.md | 1 + docs/user/reference/cli/azldev_completion.md | 1 + .../reference/cli/azldev_completion_bash.md | 1 + .../reference/cli/azldev_completion_fish.md | 1 + .../cli/azldev_completion_powershell.md | 1 + .../reference/cli/azldev_completion_zsh.md | 1 + docs/user/reference/cli/azldev_component.md | 1 + .../reference/cli/azldev_component_add.md | 1 + .../reference/cli/azldev_component_build.md | 1 + .../reference/cli/azldev_component_changed.md | 1 + .../cli/azldev_component_diff-sources.md | 1 + .../reference/cli/azldev_component_history.md | 1 + .../reference/cli/azldev_component_list.md | 1 + .../cli/azldev_component_metadata.md | 1 + .../cli/azldev_component_prepare-sources.md | 1 + .../reference/cli/azldev_component_query.md | 1 + .../reference/cli/azldev_component_render.md | 1 + .../reference/cli/azldev_component_test.md | 1 + .../reference/cli/azldev_component_update.md | 1 + docs/user/reference/cli/azldev_config.md | 1 + docs/user/reference/cli/azldev_config_dump.md | 1 + .../cli/azldev_config_generate-schema.md | 1 + docs/user/reference/cli/azldev_docs.md | 1 + docs/user/reference/cli/azldev_docs_agent.md | 1 + .../cli/azldev_docs_agent_install.md | 1 + .../reference/cli/azldev_docs_agent_show.md | 1 + .../reference/cli/azldev_docs_markdown.md | 1 + docs/user/reference/cli/azldev_image.md | 1 + docs/user/reference/cli/azldev_image_boot.md | 1 + docs/user/reference/cli/azldev_image_build.md | 1 + .../reference/cli/azldev_image_customize.md | 1 + .../cli/azldev_image_inject-files.md | 1 + docs/user/reference/cli/azldev_image_list.md | 1 + docs/user/reference/cli/azldev_image_test.md | 1 + docs/user/reference/cli/azldev_package.md | 1 + .../user/reference/cli/azldev_package_list.md | 1 + docs/user/reference/cli/azldev_project.md | 1 + .../user/reference/cli/azldev_project_init.md | 1 + docs/user/reference/cli/azldev_project_new.md | 1 + docs/user/reference/cli/azldev_repo.md | 1 + .../user/reference/cli/azldev_repo_compare.md | 1 + docs/user/reference/cli/azldev_repo_query.md | 1 + docs/user/reference/cli/azldev_version.md | 1 + .../app/azldev/agentskill/agentskill_test.go | 12 ++ .../content/build-component.md.tmpl | 8 + .../azldev/agentskill/content/mock.md.tmpl | 6 +- internal/app/azldev/app.go | 4 + internal/app/azldev/app_test.go | 36 ++++ internal/app/azldev/cmds/advanced/mock.go | 10 ++ .../app/azldev/cmds/advanced/mock_test.go | 52 ++++++ internal/app/azldev/cmds/component/build.go | 5 + .../app/azldev/cmds/component/diffsources.go | 2 +- .../azldev/cmds/component/preparesources.go | 2 +- internal/app/azldev/cmds/component/query.go | 2 + .../cmds/component/refreshupstreamcommit.go | 5 +- internal/app/azldev/cmds/component/render.go | 4 +- internal/app/azldev/cmds/component/update.go | 5 +- internal/app/azldev/core/workdir/workdir.go | 2 +- .../app/azldev/core/workdir/workdir_test.go | 26 +++ internal/app/azldev/env.go | 19 ++ internal/app/azldev/env_test.go | 33 ++++ internal/rpm/mock/mock.go | 56 +++--- internal/rpm/mock/uniqueroot.go | 106 ++++++++++++ internal/rpm/mock/uniqueroot_test.go | 163 ++++++++++++++++++ ...pshotsContainer_--bogus-flag_stderr_1.snap | 1 + ...estSnapshotsContainer_--help_stdout_1.snap | 1 + .../TestSnapshotsContainer_help_stdout_1.snap | 1 + .../TestSnapshots_--bogus-flag_stderr_1.snap | 1 + .../TestSnapshots_--help_stdout_1.snap | 1 + ...tSnapshots_--help_with_color_stdout_1.snap | 1 + .../TestSnapshots_help_stdout_1.snap | 1 + 79 files changed, 620 insertions(+), 29 deletions(-) create mode 100644 internal/rpm/mock/uniqueroot.go create mode 100644 internal/rpm/mock/uniqueroot_test.go diff --git a/docs/user/how-to/build-component.md b/docs/user/how-to/build-component.md index 8cb5701ef..1907b2704 100644 --- a/docs/user/how-to/build-component.md +++ b/docs/user/how-to/build-component.md @@ -32,6 +32,7 @@ Built packages are placed in structured subdirectories under the project's outpu | `--srpm-only` | Build only the SRPM, not the binary RPMs | | `--preserve-buildenv on-failure` | Keep the mock chroot on build failure for debugging | | `--continue-on-error` / `-k` | Continue building remaining components if one fails | +| `--unique-mockroot` | Use distinct mock roots instead of the shared default root | | `--local-repo ` | Add a local RPM repository for build dependencies | | `--local-repo-with-publish ` | Use a local repo and publish built RPMs into it | @@ -49,6 +50,39 @@ azldev comp build --local-repo-with-publish ./base/out -p dep-package -p main-pa 2. Enter the chroot with `azldev adv mock shell` to inspect the build environment 3. Check build logs in the project's log directory +## Isolate Mock Roots + +Use the global `--unique-mockroot` flag to avoid sharing mock roots with other +azldev operations: + +```bash +azldev --unique-mockroot comp build -p curl +azldev --unique-mockroot comp render -p curl -p zlib +``` + +Each mock runner passes `--uniqueext` with an extension of at most 24 ASCII +characters: an abbreviated action, a component label of up to 12 characters, +and six random characters, for example `bld-curl-a2b3c4`. +An extension such as `rndr-abcdefghijkl-a2b3c4` uses the full 24-character limit. +Action codes are `bld` for builds (including advanced mock builds), `rndr` for +rendering, `qry` for queries, and `shl` for mock shells with or without packages. +These codes are never shortened further. Other actions use an abbreviated +command name. + +Batch roots label the first component and include the component count when it +fits; otherwise they use the abbreviated first component without the count. +Labels use letters, digits, `_`, and `-`, never spaces. +The extension stays the same for initialization, commands, and cleanup of that +root; separate roots receive separate random suffixes. Without the flag, +mock's existing root naming is unchanged. + +The flag also applies to queries, custom source generation, and +`azldev advanced mock` commands. A unique `mock shell` session does not reuse a +previous session's root. To inspect a preserved unique build root, take its +`--uniqueext` value from the verbose command log and run +`mock -r --uniqueext --shell` directly; a new azldev +invocation generates a new extension. + ## Related Resources - [Components Reference](../reference/config/components.md) — component definition format diff --git a/docs/user/reference/cli/azldev.md b/docs/user/reference/cli/azldev.md index c5de33703..a3ceb9dfc 100644 --- a/docs/user/reference/cli/azldev.md +++ b/docs/user/reference/cli/azldev.md @@ -29,6 +29,7 @@ lives), or use -C to point to one. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced.md b/docs/user/reference/cli/azldev_advanced.md index f76116d52..ef8efb670 100644 --- a/docs/user/reference/cli/azldev_advanced.md +++ b/docs/user/reference/cli/azldev_advanced.md @@ -32,6 +32,7 @@ output but fully supported. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced_download-sources.md b/docs/user/reference/cli/azldev_advanced_download-sources.md index 7758ecd08..aa14ee1db 100644 --- a/docs/user/reference/cli/azldev_advanced_download-sources.md +++ b/docs/user/reference/cli/azldev_advanced_download-sources.md @@ -70,6 +70,7 @@ azldev advanced download-sources [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced_mcp.md b/docs/user/reference/cli/azldev_advanced_mcp.md index 1f62c5e0c..3d4c384ac 100644 --- a/docs/user/reference/cli/azldev_advanced_mcp.md +++ b/docs/user/reference/cli/azldev_advanced_mcp.md @@ -43,6 +43,7 @@ azldev advanced mcp [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced_mock.md b/docs/user/reference/cli/azldev_advanced_mock.md index 984278eea..2a1005d70 100644 --- a/docs/user/reference/cli/azldev_advanced_mock.md +++ b/docs/user/reference/cli/azldev_advanced_mock.md @@ -31,6 +31,7 @@ starting interactive shell sessions in mock chroot environments. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced_mock_build-rpms.md b/docs/user/reference/cli/azldev_advanced_mock_build-rpms.md index 18e1be1a3..533f22ac6 100644 --- a/docs/user/reference/cli/azldev_advanced_mock_build-rpms.md +++ b/docs/user/reference/cli/azldev_advanced_mock_build-rpms.md @@ -50,6 +50,7 @@ azldev advanced mock build-rpms [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced_mock_shell.md b/docs/user/reference/cli/azldev_advanced_mock_shell.md index 54d6ebc76..f63dd2c4a 100644 --- a/docs/user/reference/cli/azldev_advanced_mock_shell.md +++ b/docs/user/reference/cli/azldev_advanced_mock_shell.md @@ -55,6 +55,7 @@ azldev advanced mock shell [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_advanced_wget.md b/docs/user/reference/cli/azldev_advanced_wget.md index aa23ba04a..eaa056ba5 100644 --- a/docs/user/reference/cli/azldev_advanced_wget.md +++ b/docs/user/reference/cli/azldev_advanced_wget.md @@ -44,6 +44,7 @@ azldev advanced wget URI [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_completion.md b/docs/user/reference/cli/azldev_completion.md index 109d7858e..8abc5a6d0 100644 --- a/docs/user/reference/cli/azldev_completion.md +++ b/docs/user/reference/cli/azldev_completion.md @@ -30,6 +30,7 @@ See each sub-command's help for details on how to use the generated script. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_completion_bash.md b/docs/user/reference/cli/azldev_completion_bash.md index f39580feb..b5f95ac1d 100644 --- a/docs/user/reference/cli/azldev_completion_bash.md +++ b/docs/user/reference/cli/azldev_completion_bash.md @@ -53,6 +53,7 @@ azldev completion bash --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_completion_fish.md b/docs/user/reference/cli/azldev_completion_fish.md index 42eaf1dd5..2a137634d 100644 --- a/docs/user/reference/cli/azldev_completion_fish.md +++ b/docs/user/reference/cli/azldev_completion_fish.md @@ -44,6 +44,7 @@ azldev completion fish [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_completion_powershell.md b/docs/user/reference/cli/azldev_completion_powershell.md index 6565209e0..998cab5d1 100644 --- a/docs/user/reference/cli/azldev_completion_powershell.md +++ b/docs/user/reference/cli/azldev_completion_powershell.md @@ -41,6 +41,7 @@ azldev completion powershell [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_completion_zsh.md b/docs/user/reference/cli/azldev_completion_zsh.md index aa7d5d7a4..ad71d5fc1 100644 --- a/docs/user/reference/cli/azldev_completion_zsh.md +++ b/docs/user/reference/cli/azldev_completion_zsh.md @@ -55,6 +55,7 @@ azldev completion zsh [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component.md b/docs/user/reference/cli/azldev_component.md index df0e9622b..0e6f08f2a 100644 --- a/docs/user/reference/cli/azldev_component.md +++ b/docs/user/reference/cli/azldev_component.md @@ -34,6 +34,7 @@ components defined in the project configuration. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_add.md b/docs/user/reference/cli/azldev_component_add.md index 3b0b66fd0..d951425e3 100644 --- a/docs/user/reference/cli/azldev_component_add.md +++ b/docs/user/reference/cli/azldev_component_add.md @@ -47,6 +47,7 @@ azldev component add [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_build.md b/docs/user/reference/cli/azldev_component_build.md index 278ab3f6c..ee33a04d0 100644 --- a/docs/user/reference/cli/azldev_component_build.md +++ b/docs/user/reference/cli/azldev_component_build.md @@ -79,6 +79,7 @@ azldev component build [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_changed.md b/docs/user/reference/cli/azldev_component_changed.md index 38a7be9f4..95547d9ee 100644 --- a/docs/user/reference/cli/azldev_component_changed.md +++ b/docs/user/reference/cli/azldev_component_changed.md @@ -74,6 +74,7 @@ azldev component changed [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_diff-sources.md b/docs/user/reference/cli/azldev_component_diff-sources.md index 4bcb11e90..ff52d20d1 100644 --- a/docs/user/reference/cli/azldev_component_diff-sources.md +++ b/docs/user/reference/cli/azldev_component_diff-sources.md @@ -41,6 +41,7 @@ azldev component diff-sources [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_history.md b/docs/user/reference/cli/azldev_component_history.md index b269a82d5..c5b33a3fd 100644 --- a/docs/user/reference/cli/azldev_component_history.md +++ b/docs/user/reference/cli/azldev_component_history.md @@ -67,6 +67,7 @@ azldev component history [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_list.md b/docs/user/reference/cli/azldev_component_list.md index f0cdcfd7c..4282bdc06 100644 --- a/docs/user/reference/cli/azldev_component_list.md +++ b/docs/user/reference/cli/azldev_component_list.md @@ -56,6 +56,7 @@ azldev component list [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_metadata.md b/docs/user/reference/cli/azldev_component_metadata.md index 35b0dc208..febaed932 100644 --- a/docs/user/reference/cli/azldev_component_metadata.md +++ b/docs/user/reference/cli/azldev_component_metadata.md @@ -82,6 +82,7 @@ azldev component metadata [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_prepare-sources.md b/docs/user/reference/cli/azldev_component_prepare-sources.md index 45f9f8cb7..337dfcfce 100644 --- a/docs/user/reference/cli/azldev_component_prepare-sources.md +++ b/docs/user/reference/cli/azldev_component_prepare-sources.md @@ -62,6 +62,7 @@ azldev component prepare-sources [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_query.md b/docs/user/reference/cli/azldev_component_query.md index f840ca4cd..516e68e9d 100644 --- a/docs/user/reference/cli/azldev_component_query.md +++ b/docs/user/reference/cli/azldev_component_query.md @@ -59,6 +59,7 @@ azldev component query [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_render.md b/docs/user/reference/cli/azldev_component_render.md index 88a9d5157..de7aded11 100644 --- a/docs/user/reference/cli/azldev_component_render.md +++ b/docs/user/reference/cli/azldev_component_render.md @@ -82,6 +82,7 @@ azldev component render [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_test.md b/docs/user/reference/cli/azldev_component_test.md index e316666e3..5ae0631fa 100644 --- a/docs/user/reference/cli/azldev_component_test.md +++ b/docs/user/reference/cli/azldev_component_test.md @@ -108,6 +108,7 @@ azldev component test COMPONENT [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_component_update.md b/docs/user/reference/cli/azldev_component_update.md index 024d3a153..8fedc7450 100644 --- a/docs/user/reference/cli/azldev_component_update.md +++ b/docs/user/reference/cli/azldev_component_update.md @@ -83,6 +83,7 @@ azldev component update [flags] -C, --project string path to Azure Linux project -q, --quiet only enable minimal output --spec-editor editor Select the RPM spec editor (legacy or experimental) (default legacy) + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_config.md b/docs/user/reference/cli/azldev_config.md index 6eed54b63..d785322cf 100644 --- a/docs/user/reference/cli/azldev_config.md +++ b/docs/user/reference/cli/azldev_config.md @@ -31,6 +31,7 @@ JSON schema used for validating TOML config files. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_config_dump.md b/docs/user/reference/cli/azldev_config_dump.md index 7136efc12..9520c1fe7 100644 --- a/docs/user/reference/cli/azldev_config_dump.md +++ b/docs/user/reference/cli/azldev_config_dump.md @@ -51,6 +51,7 @@ azldev config dump [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_config_generate-schema.md b/docs/user/reference/cli/azldev_config_generate-schema.md index 2f4eece5a..839ba3991 100644 --- a/docs/user/reference/cli/azldev_config_generate-schema.md +++ b/docs/user/reference/cli/azldev_config_generate-schema.md @@ -45,6 +45,7 @@ azldev config generate-schema [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_docs.md b/docs/user/reference/cli/azldev_docs.md index 787033cbb..2023bffa1 100644 --- a/docs/user/reference/cli/azldev_docs.md +++ b/docs/user/reference/cli/azldev_docs.md @@ -31,6 +31,7 @@ command tree, suitable for inclusion in the user guide. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_docs_agent.md b/docs/user/reference/cli/azldev_docs_agent.md index fd1c785b0..3e96e200e 100644 --- a/docs/user/reference/cli/azldev_docs_agent.md +++ b/docs/user/reference/cli/azldev_docs_agent.md @@ -33,6 +33,7 @@ reference so that agents always load the guidance that ships with the binary. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_docs_agent_install.md b/docs/user/reference/cli/azldev_docs_agent_install.md index c8c8853a5..fa56e5407 100644 --- a/docs/user/reference/cli/azldev_docs_agent_install.md +++ b/docs/user/reference/cli/azldev_docs_agent_install.md @@ -73,6 +73,7 @@ azldev docs agent install [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_docs_agent_show.md b/docs/user/reference/cli/azldev_docs_agent_show.md index 11d32383c..db7739a3c 100644 --- a/docs/user/reference/cli/azldev_docs_agent_show.md +++ b/docs/user/reference/cli/azldev_docs_agent_show.md @@ -50,6 +50,7 @@ azldev docs agent show [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_docs_markdown.md b/docs/user/reference/cli/azldev_docs_markdown.md index a86b59b4d..6fe0bbafc 100644 --- a/docs/user/reference/cli/azldev_docs_markdown.md +++ b/docs/user/reference/cli/azldev_docs_markdown.md @@ -52,6 +52,7 @@ azldev docs markdown [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image.md b/docs/user/reference/cli/azldev_image.md index ea678cf94..892ad319c 100644 --- a/docs/user/reference/cli/azldev_image.md +++ b/docs/user/reference/cli/azldev_image.md @@ -32,6 +32,7 @@ can be customized using Azure Linux Image Customizer. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image_boot.md b/docs/user/reference/cli/azldev_image_boot.md index 143536805..66898d187 100644 --- a/docs/user/reference/cli/azldev_image_boot.md +++ b/docs/user/reference/cli/azldev_image_boot.md @@ -96,6 +96,7 @@ azldev image boot [IMAGE_NAME] [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image_build.md b/docs/user/reference/cli/azldev_image_build.md index 3c89f6b7a..c563e340d 100644 --- a/docs/user/reference/cli/azldev_image_build.md +++ b/docs/user/reference/cli/azldev_image_build.md @@ -57,6 +57,7 @@ azldev image build [image-name] [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image_customize.md b/docs/user/reference/cli/azldev_image_customize.md index 929c4f10b..593e6b410 100644 --- a/docs/user/reference/cli/azldev_image_customize.md +++ b/docs/user/reference/cli/azldev_image_customize.md @@ -55,6 +55,7 @@ azldev image customize [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image_inject-files.md b/docs/user/reference/cli/azldev_image_inject-files.md index 748bde7f4..d4eeb7b6c 100644 --- a/docs/user/reference/cli/azldev_image_inject-files.md +++ b/docs/user/reference/cli/azldev_image_inject-files.md @@ -46,6 +46,7 @@ azldev image inject-files [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image_list.md b/docs/user/reference/cli/azldev_image_list.md index a1270bbd3..edfb25fef 100644 --- a/docs/user/reference/cli/azldev_image_list.md +++ b/docs/user/reference/cli/azldev_image_list.md @@ -48,6 +48,7 @@ azldev image list [image-name-pattern...] [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_image_test.md b/docs/user/reference/cli/azldev_image_test.md index e2c1ca54e..d0be33dc8 100644 --- a/docs/user/reference/cli/azldev_image_test.md +++ b/docs/user/reference/cli/azldev_image_test.md @@ -81,6 +81,7 @@ azldev image test IMAGE_NAME [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_package.md b/docs/user/reference/cli/azldev_package.md index 678421bcc..b86f27d6c 100644 --- a/docs/user/reference/cli/azldev_package.md +++ b/docs/user/reference/cli/azldev_package.md @@ -32,6 +32,7 @@ publish channel assignments derived from package groups and component overrides. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_package_list.md b/docs/user/reference/cli/azldev_package_list.md index e6165273e..4f7f49d06 100644 --- a/docs/user/reference/cli/azldev_package_list.md +++ b/docs/user/reference/cli/azldev_package_list.md @@ -81,6 +81,7 @@ azldev package list [package-name...] [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_project.md b/docs/user/reference/cli/azldev_project.md index fef53ace7..3481a66be 100644 --- a/docs/user/reference/cli/azldev_project.md +++ b/docs/user/reference/cli/azldev_project.md @@ -31,6 +31,7 @@ as an Azure Linux project with a basic configuration. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_project_init.md b/docs/user/reference/cli/azldev_project_init.md index 13be79456..ecb50394f 100644 --- a/docs/user/reference/cli/azldev_project_init.md +++ b/docs/user/reference/cli/azldev_project_init.md @@ -42,6 +42,7 @@ azldev project init [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_project_new.md b/docs/user/reference/cli/azldev_project_new.md index b494e46ee..6c8677cfa 100644 --- a/docs/user/reference/cli/azldev_project_new.md +++ b/docs/user/reference/cli/azldev_project_new.md @@ -45,6 +45,7 @@ azldev project new PATH [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_repo.md b/docs/user/reference/cli/azldev_repo.md index ccbc3f5ee..018ed58f3 100644 --- a/docs/user/reference/cli/azldev_repo.md +++ b/docs/user/reference/cli/azldev_repo.md @@ -32,6 +32,7 @@ under one or more URL prefixes. --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_repo_compare.md b/docs/user/reference/cli/azldev_repo_compare.md index 7053a3f19..8249d2c64 100644 --- a/docs/user/reference/cli/azldev_repo_compare.md +++ b/docs/user/reference/cli/azldev_repo_compare.md @@ -50,6 +50,7 @@ azldev repo compare [flags] --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_repo_query.md b/docs/user/reference/cli/azldev_repo_query.md index 9086aa1c9..7a12a5bc5 100644 --- a/docs/user/reference/cli/azldev_repo_query.md +++ b/docs/user/reference/cli/azldev_repo_query.md @@ -73,6 +73,7 @@ azldev repo query [flags] -- --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/docs/user/reference/cli/azldev_version.md b/docs/user/reference/cli/azldev_version.md index 5ebddbcdd..21666d793 100644 --- a/docs/user/reference/cli/azldev_version.md +++ b/docs/user/reference/cli/azldev_version.md @@ -36,6 +36,7 @@ azldev version -O json --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --without-lockfile preview: track resolved upstream commits in generated config instead of lock files ``` diff --git a/internal/app/azldev/agentskill/agentskill_test.go b/internal/app/azldev/agentskill/agentskill_test.go index dd6acd67b..896df8e37 100644 --- a/internal/app/azldev/agentskill/agentskill_test.go +++ b/internal/app/azldev/agentskill/agentskill_test.go @@ -144,6 +144,18 @@ func TestBuildComponentSkillDocumentsRPMDevBumpspecPreview(t *testing.T) { assert.NotContains(t, doc, "rpmdev-bumpspec` or RPM macros") } +func TestSkillsDocumentUniqueMockRoots(t *testing.T) { + for _, name := range []string{"azldev-build-component", "azldev-mock"} { + t.Run(name, func(t *testing.T) { + doc, err := agentskill.SkillDocument(name, testParams()) + require.NoError(t, err) + assert.Contains(t, doc, "--unique-mockroot") + assert.Contains(t, doc, "at most 24 characters") + assert.Contains(t, doc, "--uniqueext --shell") + }) + } +} + func TestComponentTomlSkillDocumentsRPMDevBumpspecPreview(t *testing.T) { doc, err := agentskill.SkillDocument("azldev-comp-toml", testParams()) require.NoError(t, err) diff --git a/internal/app/azldev/agentskill/content/build-component.md.tmpl b/internal/app/azldev/agentskill/content/build-component.md.tmpl index 9375d5e22..46edb5bbe 100644 --- a/internal/app/azldev/agentskill/content/build-component.md.tmpl +++ b/internal/app/azldev/agentskill/content/build-component.md.tmpl @@ -21,6 +21,14 @@ Build foundational packages before their dependents. RPMs land in the project's configured output directory (`out` by default). `-q` quiets output but hides build progress — use it only for inner-loop builds you expect to succeed. +Use the global `--unique-mockroot` flag to isolate mock roots from other azldev +operations. Extensions contain abbreviated action/component labels and a random +suffix, use at most 24 characters total (up to 12 for the component label), and +remain stable through each root's cleanup. +To inspect a preserved unique root, use +`mock -r --uniqueext --shell` with the extension from +the verbose command log; a new azldev shell invocation creates a different root. + ## The inner loop investigate → modify → render → build → test → inspect diff --git a/internal/app/azldev/agentskill/content/mock.md.tmpl b/internal/app/azldev/agentskill/content/mock.md.tmpl index e42d63615..f50ba4a95 100644 --- a/internal/app/azldev/agentskill/content/mock.md.tmpl +++ b/internal/app/azldev/agentskill/content/mock.md.tmpl @@ -73,6 +73,7 @@ react to output between commands. | --- | --- | | `-p`, `--add-package ` | Install a package into the chroot before entering. Accepts an RPM file path or a package name. May be repeated. | | `--enable-network` | Allow network access (dependency resolution, downloads). | +| `--unique-mockroot` | Use a distinct root for this session, passing mock an action/package label (up to 12 characters for the package label) and random suffix via `--uniqueext` (at most 24 characters total). | > **`-p` means `--add-package`, not a component name.** Unlike `azldev comp build -p `, > `mock shell` has no component selector. `-p ` installs a package *by name* from the @@ -82,10 +83,13 @@ react to output between commands. - **Don't mix `-p ` with `--add-package /path/to/.rpm` for the same package** — that installs both the repo build and your local build, which conflict. Use one or the other. -- **The mock chroot is persistent** across `azldev adv mock shell` sessions — installed packages and +- **The mock chroot is persistent by default** across `azldev adv mock shell` sessions — installed packages and created files survive. Handy (install once, re-enter later), but stale state can confuse. azldev has no built-in reset; clear it with `mock` directly (`--scrub=chroot`, or `--scrub=all` to also drop the bootstrap), pointing at the project's distro mock config. +- **`--unique-mockroot` starts a separate root each time.** To re-enter a preserved unique root, + use `mock -r --uniqueext --shell` with the extension from the + verbose command log, not another azldev invocation. - **One mock session per terminal.** Running `azldev adv mock shell` while a terminal is already inside a mock shell fails — run `exit` first. diff --git a/internal/app/azldev/app.go b/internal/app/azldev/app.go index f9cfda1ae..3fa90076b 100644 --- a/internal/app/azldev/app.go +++ b/internal/app/azldev/app.go @@ -55,6 +55,7 @@ type App struct { configFiles []string colorMode ColorMode withoutLockfile bool + uniqueMockRoot bool // Root command for the CLI. cmd cobra.Command @@ -141,6 +142,7 @@ lives), or use -C to point to one.`, env.SetConcurrency(app.concurrency) env.SetNetworkRetries(app.networkRetries) env.SetPermissiveConfigParsing(app.permissiveConfigEnabled()) + env.SetUniqueMockRoot(app.uniqueMockRoot, command.Name()) return nil }, @@ -193,6 +195,8 @@ func (app *App) registerGlobalFlags() { false, "do not fail on unknown fields in TOML config files") app.cmd.PersistentFlags().BoolVar(&app.withoutLockfile, "without-lockfile", false, "preview: track resolved upstream commits in generated config instead of lock files") + app.cmd.PersistentFlags().BoolVar(&app.uniqueMockRoot, "unique-mockroot", false, + "use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters)") } // addAdvancedCommandHint embeds a hint about the hidden "advanced" command group diff --git a/internal/app/azldev/app_test.go b/internal/app/azldev/app_test.go index 64ac1397b..ab7421b19 100644 --- a/internal/app/azldev/app_test.go +++ b/internal/app/azldev/app_test.go @@ -9,6 +9,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/testutils" + "github.com/microsoft/azure-linux-dev-tools/internal/rpm/mock" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -67,6 +68,41 @@ func TestApp_Execute(t *testing.T) { assert.True(t, ran) } +func TestApp_UniqueMockRoot(t *testing.T) { + for name, enabled := range map[string]bool{"default": false, "enabled": true} { + t.Run(name, func(t *testing.T) { + app := createTestApp(t) + ran := false + cmd := &cobra.Command{ + Use: "build", + RunE: func(cmd *cobra.Command, _ []string) error { + ran = true + env, err := azldev.GetEnvFromCommand(cmd) + require.NoError(t, err) + + runner := mock.NewRunner(env.WithMockRootComponents("curl"), "/mock.cfg") + if enabled { + assert.Regexp(t, `^bld-curl-[a-z2-7]{6}$`, runner.UniqueExtension()) + } else { + assert.Empty(t, runner.UniqueExtension()) + } + + return nil + }, + } + app.AddTopLevelCommand(cmd) + + args := []string{"build"} + if enabled { + args = append(args, "--unique-mockroot") + } + + assert.Zero(t, app.Execute(args)) + assert.True(t, ran) + }) + } +} + func TestApp_RegisterPostInitCallback(t *testing.T) { app := createTestApp(t) diff --git a/internal/app/azldev/cmds/advanced/mock.go b/internal/app/azldev/cmds/advanced/mock.go index 18e4f8060..660582a60 100644 --- a/internal/app/azldev/cmds/advanced/mock.go +++ b/internal/app/azldev/cmds/advanced/mock.go @@ -6,6 +6,7 @@ package advanced import ( "fmt" "os" + "path/filepath" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/buildenvfactory" @@ -142,6 +143,8 @@ running smoke tests. Packages can be pre-installed into the chroot using // Builds RPMs from sources, using options. func BuildRPMS(env *azldev.Env, options *BuildRPMOptions) (results interface{}, err error) { + env = env.WithMockRootComponents(filepath.Base(options.SRPMPath)) + runner, err := makeMockRunner(env, &options.MockCmdOptions) if err != nil { return results, err @@ -167,6 +170,13 @@ func BuildRPMS(env *azldev.Env, options *BuildRPMOptions) (results interface{}, // Executes an interactive shell within a mock root. Uses the provided [ShellOptions] to configure the shell. func RunShell(env *azldev.Env, options *ShellOptions, extraArgs []string) error { + packages := make([]string, len(options.PackagesToAdd)) + for idx, pkg := range options.PackagesToAdd { + packages[idx] = filepath.Base(pkg) + } + + env = env.WithMockRootComponents(packages...) + runner, err := makeMockRunner(env, &options.MockCmdOptions) if err != nil { return err diff --git a/internal/app/azldev/cmds/advanced/mock_test.go b/internal/app/azldev/cmds/advanced/mock_test.go index 61a295f19..79e295210 100644 --- a/internal/app/azldev/cmds/advanced/mock_test.go +++ b/internal/app/azldev/cmds/advanced/mock_test.go @@ -35,6 +35,58 @@ func TestNewShellCmd(t *testing.T) { assert.Equal(t, "shell", cmd.Use) } +func TestAdvancedUniqueMockRoots(t *testing.T) { + tests := []struct { + name string + action string + configPath string + prefix string + }{ + {name: "shell explicit config", action: "shell", configPath: "/mock.cfg", prefix: "shl-curl-1-0-rpm-"}, + {name: "shell project config", action: "shell", prefix: "shl-curl-1-0-rpm-"}, + {name: "build explicit config", action: "build-rpms", configPath: "/mock.cfg", prefix: "bld-curl-1-0-src-"}, + {name: "build project config", action: "build-rpms", prefix: "bld-curl-1-0-src-"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + env := testutils.NewTestEnv(t) + env.Env.SetUniqueMockRoot(true, test.action) + env.CmdFactory.RegisterCommandInSearchPath("mock") + + var extensions []string + + env.CmdFactory.RunHandler = func(cmd *exec.Cmd) error { + idx := slices.Index(cmd.Args, "--uniqueext") + require.Positive(t, idx) + require.Less(t, idx+1, len(cmd.Args)) + extensions = append(extensions, cmd.Args[idx+1]) + + return nil + } + + common := advanced.MockCmdOptions{MockConfigPath: test.configPath} + if test.action == "shell" { + require.NoError(t, advanced.RunShell(env.Env, &advanced.ShellOptions{ + MockCmdOptions: common, + PackagesToAdd: []string{"/rpms/curl-1.0.rpm"}, + }, []string{"true"})) + require.Len(t, extensions, 2) + assert.Equal(t, extensions[0], extensions[1]) + } else { + _, err := advanced.BuildRPMS(env.Env, &advanced.BuildRPMOptions{ + MockCmdOptions: common, + SRPMPath: "/srpms/curl-1.0.src.rpm", + OutputDirPath: "/rpms", + }) + require.NoError(t, err) + require.Len(t, extensions, 1) + } + + assert.Regexp(t, "^"+test.prefix+`[a-z2-7]{6}$`, extensions[0]) + }) + } +} + func TestBuildRPMS(t *testing.T) { const ( testMockConfigPath = "/mock/mock.cfg" diff --git a/internal/app/azldev/cmds/component/build.go b/internal/app/azldev/cmds/component/build.go index 2c9f99171..f7139e60c 100644 --- a/internal/app/azldev/cmds/component/build.go +++ b/internal/app/azldev/cmds/component/build.go @@ -201,6 +201,8 @@ func BuildComponents( return nil, fmt.Errorf("failed to create work dir factory:\n%w", err) } + env = env.WithMockRootComponents(components.Names()...) + mockProcessor := createBuildMockProcessor(env) defer destroyMockProcessor(env, mockProcessor) @@ -229,6 +231,8 @@ func BuildComponent( workDirFactory *workdir.Factory, options *ComponentBuildOptions, ) (ComponentBuildResults, error) { + env = env.WithMockRootComponents(component.GetName()) + mockProcessor := createBuildMockProcessor(env) defer destroyMockProcessor(env, mockProcessor) @@ -242,6 +246,7 @@ func buildComponent( mockProcessor *sources.MockProcessor, options *ComponentBuildOptions, ) (results ComponentBuildResults, err error) { + env = env.WithMockRootComponents(component.GetName()) // Resolve the effective distro for this component before creating the source manager. distro, err := sourceproviders.ResolveDistro(env, component) if err != nil { diff --git a/internal/app/azldev/cmds/component/diffsources.go b/internal/app/azldev/cmds/component/diffsources.go index 7fd728050..e021e01f5 100644 --- a/internal/app/azldev/cmds/component/diffsources.go +++ b/internal/app/azldev/cmds/component/diffsources.go @@ -96,7 +96,7 @@ func DiffComponentSources(env *azldev.Env, options *DiffSourcesOptions) (interfa return nil, fmt.Errorf("failed to resolve distro for component %q:\n%w", component.GetName(), err) } - sourceManager, err := sourceproviders.NewSourceManager(env, distro) + sourceManager, err := sourceproviders.NewSourceManager(env.WithMockRootComponents(component.GetName()), distro) if err != nil { return nil, fmt.Errorf("failed to create source manager:\n%w", err) } diff --git a/internal/app/azldev/cmds/component/preparesources.go b/internal/app/azldev/cmds/component/preparesources.go index 317b7fb38..4be62ae43 100644 --- a/internal/app/azldev/cmds/component/preparesources.go +++ b/internal/app/azldev/cmds/component/preparesources.go @@ -126,7 +126,7 @@ func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) er } // Create source manager to handle all source fetching, both local and upstream. - sourceManager, err = sourceproviders.NewSourceManager(env, distro) + sourceManager, err = sourceproviders.NewSourceManager(env.WithMockRootComponents(component.GetName()), distro) if err != nil { return fmt.Errorf("failed to create source manager:\n%w", err) } diff --git a/internal/app/azldev/cmds/component/query.go b/internal/app/azldev/cmds/component/query.go index 83014b12c..8c09e6563 100644 --- a/internal/app/azldev/cmds/component/query.go +++ b/internal/app/azldev/cmds/component/query.go @@ -154,6 +154,8 @@ func QueryComponents( comps.Len()) } + env = env.WithMockRootComponents(comps.Names()...) + mockProcessor := createQueryMockProcessor(env) if mockProcessor == nil { return nil, errors.New( diff --git a/internal/app/azldev/cmds/component/refreshupstreamcommit.go b/internal/app/azldev/cmds/component/refreshupstreamcommit.go index 02da3bddf..a5c7dc30b 100644 --- a/internal/app/azldev/cmds/component/refreshupstreamcommit.go +++ b/internal/app/azldev/cmds/component/refreshupstreamcommit.go @@ -15,6 +15,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" "github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders" + "github.com/microsoft/azure-linux-dev-tools/internal/rpm/mock" "github.com/microsoft/azure-linux-dev-tools/internal/upstreamcommit" "github.com/microsoft/azure-linux-dev-tools/internal/utils/parmap" "github.com/spf13/cobra" @@ -619,7 +620,9 @@ func resolveUpstreamCommit( return "", fmt.Errorf("resolving distro for %#q:\n%w", componentName, err) } - sourceManager, err := sourceproviders.NewSourceManager(env, distro) + //nolint:contextcheck // The cloned operational Env carries ctx and component-specific root labels. + sourceManager, err := sourceproviders.NewSourceManager( + env.WithContext(mock.WithRootComponents(ctx, componentName)), distro) if err != nil { return "", fmt.Errorf("creating source manager for %#q:\n%w", componentName, err) } diff --git a/internal/app/azldev/cmds/component/render.go b/internal/app/azldev/cmds/component/render.go index ff99942bd..afe1e95cc 100644 --- a/internal/app/azldev/cmds/component/render.go +++ b/internal/app/azldev/cmds/component/render.go @@ -177,6 +177,8 @@ func RenderComponents(env *azldev.Env, options *RenderOptions) ([]*RenderResult, } // Create mock processor for rpmautospec/spectool. + env = env.WithMockRootComponents(comps.Names()...) + mockProcessor := createMockProcessor(env) if mockProcessor == nil { return nil, errors.New( @@ -516,7 +518,7 @@ func prepareComponentSources( } // Create source manager. - sourceManager, err := sourceproviders.NewSourceManager(env, distro) + sourceManager, err := sourceproviders.NewSourceManager(env.WithMockRootComponents(componentName), distro) if err != nil { return nil, fmt.Errorf("creating source manager for %#q:\n%w", componentName, err) } diff --git a/internal/app/azldev/cmds/component/update.go b/internal/app/azldev/cmds/component/update.go index 9da1b4f75..32a027477 100644 --- a/internal/app/azldev/cmds/component/update.go +++ b/internal/app/azldev/cmds/component/update.go @@ -17,6 +17,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/lockfile" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" "github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders" + "github.com/microsoft/azure-linux-dev-tools/internal/rpm/mock" "github.com/microsoft/azure-linux-dev-tools/internal/utils/parmap" "github.com/spf13/cobra" ) @@ -886,7 +887,9 @@ func resolveOneSourceIdentity( return "", fmt.Errorf("resolving distro for %#q:\n%w", componentName, err) } - sourceManager, err := sourceproviders.NewSourceManager(env, distro) + //nolint:contextcheck // The cloned operational Env carries ctx and component-specific root labels. + sourceManager, err := sourceproviders.NewSourceManager( + env.WithContext(mock.WithRootComponents(ctx, componentName)), distro) if err != nil { return "", fmt.Errorf("creating source manager for %#q:\n%w", componentName, err) } diff --git a/internal/app/azldev/core/workdir/workdir.go b/internal/app/azldev/core/workdir/workdir.go index f611485e5..8af71c2fc 100644 --- a/internal/app/azldev/core/workdir/workdir.go +++ b/internal/app/azldev/core/workdir/workdir.go @@ -92,7 +92,7 @@ func MkComponentBuildEnvironment( // We'll nest the build environment in a subdirectory under the work directory. buildEnvDir := filepath.Join(workDir, "buildenv") - buildEnvFactory, err := buildenvfactory.NewMockRootFactoryForEnv(env) + buildEnvFactory, err := buildenvfactory.NewMockRootFactoryForEnv(env.WithMockRootComponents(component.Name)) if err != nil { return nil, fmt.Errorf("failed to create mock root factory:\n%w", err) } diff --git a/internal/app/azldev/core/workdir/workdir_test.go b/internal/app/azldev/core/workdir/workdir_test.go index fdc6d53b5..f61df3f59 100644 --- a/internal/app/azldev/core/workdir/workdir_test.go +++ b/internal/app/azldev/core/workdir/workdir_test.go @@ -9,6 +9,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/testutils" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/workdir" + "github.com/microsoft/azure-linux-dev-tools/internal/buildenv" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -59,3 +60,28 @@ func Test_Create(t *testing.T) { require.NoError(t, statErr) assert.True(t, stat.IsDir()) } + +func TestMkComponentBuildEnvironmentUniqueRoots(t *testing.T) { + env := testutils.NewTestEnv(t) + env.Env.SetUniqueMockRoot(true, "build") + factory, err := workdir.NewFactory(env.FS(), env.Env.WorkDir(), env.Env.ConstructionTime()) + require.NoError(t, err) + + names := []string{"curl", "zlib", "curl"} + + extensions := make([]string, 0, len(names)) + for _, name := range names { + root, createErr := workdir.MkComponentBuildEnvironment(env.Env, factory, + &projectconfig.ComponentConfig{Name: name}, "build", nil) + require.NoError(t, createErr) + + mockRoot, ok := root.(*buildenv.MockRoot) + require.True(t, ok) + + extension := mockRoot.GetRunner().UniqueExtension() + assert.Regexp(t, `^bld-`+name+`-[a-z2-7]{6}$`, extension) + assert.Equal(t, extension, mockRoot.GetRunner().UniqueExtension()) + assert.NotContains(t, extensions, extension) + extensions = append(extensions, extension) + } +} diff --git a/internal/app/azldev/env.go b/internal/app/azldev/env.go index 9b426033f..0c2729b0f 100644 --- a/internal/app/azldev/env.go +++ b/internal/app/azldev/env.go @@ -23,6 +23,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/global/opctx" "github.com/microsoft/azure-linux-dev-tools/internal/lockfile" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/microsoft/azure-linux-dev-tools/internal/rpm/mock" ) // Parameters used to construct an [Env]. @@ -227,6 +228,24 @@ func (env *Env) SetVerbose(verbose bool) { env.verbose = verbose } +// SetUniqueMockRoot configures unique mock roots for the current CLI action. +func (env *Env) SetUniqueMockRoot(enabled bool, action string) { + env.ctx = mock.WithUniqueRoots(env.ctx, enabled, action) +} + +// WithMockRootComponents returns a shallow copy with component-specific mock root labels. +func (env *Env) WithMockRootComponents(names ...string) *Env { + return env.WithContext(mock.WithRootComponents(env.ctx, names...)) +} + +// WithContext returns a shallow copy using the supplied context for cancellation and values. +func (env *Env) WithContext(ctx context.Context) *Env { + childEnv := *env + childEnv.ctx = ctx + + return &childEnv +} + // NetworkRetries returns the maximum number of attempts for network operations. func (env *Env) NetworkRetries() int { return env.networkRetries diff --git a/internal/app/azldev/env_test.go b/internal/app/azldev/env_test.go index 7aa094960..423ffbe28 100644 --- a/internal/app/azldev/env_test.go +++ b/internal/app/azldev/env_test.go @@ -17,6 +17,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/testutils" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/microsoft/azure-linux-dev-tools/internal/rpm/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,6 +35,38 @@ func setupTestLogger(logBuffer *bytes.Buffer) func() { } } +func TestEnvMockRootContext(t *testing.T) { + env := testutils.NewTestEnv(t).Env + env.SetUniqueMockRoot(true, "render") + + child := env.WithMockRootComponents("curl", "zlib") + + worker, cancel := child.WithCancel() + defer cancel() + + assert.Regexp(t, `^rndr-c_2-[a-z2-7]{6}$`, mock.NewRunner(worker, "/mock.cfg").UniqueExtension()) + assert.Regexp(t, `^rndr-[a-z2-7]{6}$`, mock.NewRunner(env, "/mock.cfg").UniqueExtension()) + + env.SetUniqueMockRoot(false, "render") + assert.Empty(t, mock.NewRunner(env, "/mock.cfg").UniqueExtension()) + assert.NotEmpty(t, mock.NewRunner(child, "/mock.cfg").UniqueExtension()) +} + +func TestEnvWithContext(t *testing.T) { + env := testutils.NewTestEnv(t).Env + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + + child := env.WithContext(ctx) + assert.Equal(t, env.FS(), child.FS()) + assert.Equal(t, env.Config(), child.Config()) + cancel() + + require.ErrorIs(t, child.Err(), context.Canceled) + require.NoError(t, env.Err()) +} + func TestNewEnv(t *testing.T) { const ( testProjectRoot = "/non/existent/dir" diff --git a/internal/rpm/mock/mock.go b/internal/rpm/mock/mock.go index 566aa4973..a38a2f979 100644 --- a/internal/rpm/mock/mock.go +++ b/internal/rpm/mock/mock.go @@ -46,9 +46,10 @@ type Runner struct { // Verbosity verbose bool - mockConfigPath string - bindMounts []bindMountRequest - enableNetwork bool + mockConfigPath string + uniqueExtension string + bindMounts []bindMountRequest + enableNetwork bool // noPreClean requests that the root *not* be cleaned on command execution, even if mock deems // it ought to be. @@ -90,31 +91,33 @@ type bindMountRequest struct { // Constructs a new [Runner] that can be used to invoke mock. func NewRunner(ctx opctx.Ctx, mockConfigPath string) *Runner { return &Runner{ - fs: ctx.FS(), - osEnv: ctx.OSEnv(), - cmdFactory: ctx, - eventListener: ctx, - verbose: ctx.Verbose(), - mockConfigPath: mockConfigPath, + fs: ctx.FS(), + osEnv: ctx.OSEnv(), + cmdFactory: ctx, + eventListener: ctx, + verbose: ctx.Verbose(), + mockConfigPath: mockConfigPath, + uniqueExtension: newUniqueExtension(ctx), } } // Clone creates a deep copy of the provided [Runner] instance. func (r *Runner) Clone() *Runner { return &Runner{ - fs: r.fs, - osEnv: r.osEnv, - cmdFactory: r.cmdFactory, - eventListener: r.eventListener, - verbose: r.verbose, - mockConfigPath: r.mockConfigPath, - bindMounts: deep.MustCopy(r.bindMounts), - enableNetwork: r.enableNetwork, - noPreClean: r.noPreClean, - unprivileged: r.unprivileged, - baseDir: r.baseDir, - rootDir: r.rootDir, - configOpts: deep.MustCopy(r.configOpts), + fs: r.fs, + osEnv: r.osEnv, + cmdFactory: r.cmdFactory, + eventListener: r.eventListener, + verbose: r.verbose, + mockConfigPath: r.mockConfigPath, + uniqueExtension: r.uniqueExtension, + bindMounts: deep.MustCopy(r.bindMounts), + enableNetwork: r.enableNetwork, + noPreClean: r.noPreClean, + unprivileged: r.unprivileged, + baseDir: r.baseDir, + rootDir: r.rootDir, + configOpts: deep.MustCopy(r.configOpts), } } @@ -221,6 +224,11 @@ func (r *Runner) ConfigPath() string { return r.mockConfigPath } +// UniqueExtension returns the mock root extension, or empty when unique roots are disabled. +func (r *Runner) UniqueExtension() string { + return r.uniqueExtension +} + // InitRoot initializes a mock root. func (r *Runner) InitRoot(ctx context.Context) (err error) { slog.Debug("Initializing mock root", "dir", r.rootDir) @@ -665,7 +673,11 @@ func (r *Runner) getBaseArgs() (args []string) { } args = append(args, "-r", r.mockConfigPath) + args = append(args, "--configdir", filepath.Dir(r.mockConfigPath)) + if r.uniqueExtension != "" { + args = append(args, "--uniqueext", r.uniqueExtension) + } if r.baseDir != "" { args = append(args, "--config-opts", "basedir="+r.baseDir) diff --git a/internal/rpm/mock/uniqueroot.go b/internal/rpm/mock/uniqueroot.go new file mode 100644 index 000000000..5b8ce59dd --- /dev/null +++ b/internal/rpm/mock/uniqueroot.go @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package mock + +import ( + "context" + "crypto/rand" + "strconv" + "strings" +) + +type uniqueRootKey struct{} + +const ( + maxUniqueExtensionLength = 24 + uniqueRandomLength = 6 + maxRootActionLength = 4 + maxRootActionWithoutComponents = 8 + maxRootComponentLength = maxUniqueExtensionLength - maxRootActionLength - uniqueRandomLength - 2 +) + +type uniqueRootOptions struct { + enabled bool + action string + components []string +} + +// WithUniqueRoots configures newly created runners to use distinct mock roots. +func WithUniqueRoots(ctx context.Context, enabled bool, action string) context.Context { + return context.WithValue(ctx, uniqueRootKey{}, uniqueRootOptions{ + enabled: enabled, + action: action, + }) +} + +// WithRootComponents specializes the component label for newly created runners, +// without changing whether unique roots are enabled. +func WithRootComponents(ctx context.Context, components ...string) context.Context { + options, _ := ctx.Value(uniqueRootKey{}).(uniqueRootOptions) + + options.components = append([]string(nil), components...) + + return context.WithValue(ctx, uniqueRootKey{}, options) +} + +func newUniqueExtension(ctx context.Context) string { + options, _ := ctx.Value(uniqueRootKey{}).(uniqueRootOptions) + if !options.enabled { + return "" + } + + action := rootAction(options.action) + + label := action[:min(len(action), maxRootActionWithoutComponents)] + if len(options.components) > 0 { + action = action[:min(len(action), maxRootActionLength)] + component := rootLabel(options.components[0]) + + componentLabel := component[:min(len(component), maxRootComponentLength)] + if len(options.components) > 1 { + count := strconv.Itoa(len(options.components)) + + batchLabel := component[:min(len(component), 1)] + "_" + count + if len(batchLabel) <= maxRootComponentLength { + componentLabel = batchLabel + } + } + + label = action + "-" + componentLabel + } + + // Reserve six random base32 characters and a separator within the 24-byte limit. + return label + "-" + strings.ToLower(rand.Text()[:uniqueRandomLength]) +} + +func rootAction(action string) string { + action = rootLabel(action) + switch action { + case "build", "build-rpms": + return "bld" + case "render": + return "rndr" + case "query": + return "qry" + case "shell": + return "shl" + case "": + return "mock" + default: + return action + } +} + +func rootLabel(value string) string { + value = strings.ToLower(value) + value = strings.Map(func(char rune) rune { + if char >= 'a' && char <= 'z' || char >= '0' && char <= '9' || char == '_' || char == '-' { + return char + } + + return '-' + }, value) + + return strings.Trim(value, "-_") +} diff --git a/internal/rpm/mock/uniqueroot_test.go b/internal/rpm/mock/uniqueroot_test.go new file mode 100644 index 000000000..b57a1dc95 --- /dev/null +++ b/internal/rpm/mock/uniqueroot_test.go @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package mock_test + +import ( + "os/exec" + "slices" + "strings" + "testing" + + "github.com/microsoft/azure-linux-dev-tools/internal/rpm/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUniqueExtensionLabels(t *testing.T) { + tests := []struct { + name string + action string + components []string + prefix string + }{ + {name: "shell without packages", action: "shell", prefix: "shl-"}, + {name: "shell with packages", action: "shell", components: []string{"curl"}, prefix: "shl-curl-"}, + {name: "build without components", action: "build", prefix: "bld-"}, + {name: "build with one component", action: "build", components: []string{"curl"}, prefix: "bld-curl-"}, + {name: "advanced build", action: "build-rpms", components: []string{"curl"}, prefix: "bld-curl-"}, + {name: "render without components", action: "render", prefix: "rndr-"}, + {name: "render with one component", action: "render", components: []string{"curl"}, prefix: "rndr-curl-"}, + {name: "query without components", action: "query", prefix: "qry-"}, + {name: "query with one component", action: "query", components: []string{"curl"}, prefix: "qry-curl-"}, + { + name: "exact middle field and total limits", action: "render", + components: []string{"abcdefghijkl"}, prefix: "rndr-abcdefghijkl-", + }, + { + name: "middle field over limit", action: "render", + components: []string{"abcdefghijklm"}, prefix: "rndr-abcdefghijkl-", + }, + { + name: "middle field capped with shorter action", action: "build", + components: []string{"abcdefghijklm"}, prefix: "bld-abcdefghijkl-", + }, + { + name: "multiple components", action: "render", + components: []string{"curl", "zlib"}, prefix: "rndr-c_2-", + }, + { + name: "large batch", action: "render", + components: append([]string{"curl"}, make([]string, 999)...), prefix: "rndr-c_1000-", + }, + { + name: "large shell package list", action: "shell", + components: append([]string{"curl"}, make([]string, 999)...), prefix: "shl-c_1000-", + }, + { + name: "two digit batch count", action: "build", + components: append([]string{"curl"}, make([]string, 9)...), prefix: "bld-c_10-", + }, + { + name: "three digit batch count", action: "build", + components: append([]string{"curl"}, make([]string, 99)...), prefix: "bld-c_100-", + }, + { + name: "long component label", action: "query", + components: []string{strings.Repeat("rust-component", 10)}, prefix: "qry-rust-compone-", + }, + {name: "long action without components", action: "build-source-rpms", prefix: "build-so-"}, + {name: "allowed character boundaries", action: "AZ_az-09", prefix: "az_az-09-"}, + { + name: "unsafe characters", action: "BUILD", components: []string{"a/b \t\u00e9"}, + prefix: "bld-a-b-", + }, + {name: "empty action", prefix: "mock-"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := newTestCtxWithMockPrereqsPresent() + ctx.Ctx = mock.WithUniqueRoots(ctx.Ctx, true, test.action) + ctx.Ctx = mock.WithRootComponents(ctx.Ctx, test.components...) + + runner := mock.NewRunner(ctx, testMockConfigPath) + extension := runner.UniqueExtension() + + assert.LessOrEqual(t, len(extension), 24) + assert.Regexp(t, `^[a-z0-9_-]+$`, extension) + assert.True(t, strings.HasPrefix(extension, test.prefix), extension) + assert.Regexp(t, `^[a-z2-7]{6}$`, strings.TrimPrefix(extension, test.prefix)) + }) + } +} + +func TestUniqueExtensionLifecycle(t *testing.T) { + ctx := newTestCtxWithMockPrereqsPresent() + ctx.Ctx = mock.WithUniqueRoots(ctx.Ctx, true, "build") + ctx.Ctx = mock.WithRootComponents(ctx.Ctx, "curl") + + var extensions []string + + capture := func(cmd *exec.Cmd) { + t.Helper() + + index := slices.Index(cmd.Args, "--uniqueext") + require.Positive(t, index) + require.Less(t, index+1, len(cmd.Args)) + extensions = append(extensions, cmd.Args[index+1]) + } + ctx.CmdFactory.RunHandler = func(cmd *exec.Cmd) error { + capture(cmd) + + return nil + } + ctx.CmdFactory.RunAndGetOutputHandler = func(cmd *exec.Cmd) (string, error) { + capture(cmd) + + return testMockRootPath, nil + } + + runner := mock.NewRunner(ctx, testMockConfigPath) + require.NoError(t, runner.InitRoot(ctx)) + require.NoError(t, runner.InstallPackages(ctx, []string{"rpm-build"})) + require.NoError(t, runner.BuildSRPM(ctx, testSpecPath, testSourceDirPath, testOutputDirPath, mock.SRPMBuildOptions{})) + require.NoError(t, runner.BuildRPM(ctx, testSRPMPath, testOutputDirPath, mock.RPMBuildOptions{})) + _, err := runner.GetRootPath(ctx) + require.NoError(t, err) + + cmd, err := runner.Clone().CmdInChroot(ctx, []string{"true"}, false) + require.NoError(t, err) + require.NoError(t, cmd.Run(ctx)) + require.NoError(t, runner.Clone().ScrubRoot(ctx)) + + require.Len(t, extensions, 7) + + for _, extension := range extensions { + assert.Equal(t, runner.UniqueExtension(), extension) + } + + assert.NotEqual(t, runner.UniqueExtension(), mock.NewRunner(ctx, testMockConfigPath).UniqueExtension()) +} + +func TestUniqueExtensionDisabled(t *testing.T) { + for name, explicitlyDisabled := range map[string]bool{"default": false, "explicit": true} { + t.Run(name, func(t *testing.T) { + ctx := newTestCtxWithMockPrereqsPresent() + if explicitlyDisabled { + ctx.Ctx = mock.WithUniqueRoots(ctx.Ctx, false, "build") + } + + ctx.Ctx = mock.WithRootComponents(ctx.Ctx, "curl") + ctx.CmdFactory.RunHandler = func(cmd *exec.Cmd) error { + assert.NotContains(t, cmd.Args, "--uniqueext") + + return nil + } + + runner := mock.NewRunner(ctx, testMockConfigPath) + assert.Empty(t, runner.UniqueExtension()) + require.NoError(t, runner.InitRoot(ctx)) + }) + } +} diff --git a/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap b/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap index 6832b09a4..79a84ba98 100755 --- a/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap +++ b/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap @@ -29,6 +29,7 @@ Flags: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files diff --git a/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap b/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap index e15aef19c..5f985e03e 100755 --- a/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap @@ -36,6 +36,7 @@ Flags: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files diff --git a/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap b/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap index e15aef19c..5f985e03e 100755 --- a/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap @@ -36,6 +36,7 @@ Flags: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files diff --git a/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap b/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap index 6832b09a4..79a84ba98 100755 --- a/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap +++ b/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap @@ -29,6 +29,7 @@ Flags: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files diff --git a/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap b/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap index e15aef19c..5f985e03e 100755 --- a/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap @@ -36,6 +36,7 @@ Flags: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files diff --git a/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap b/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap index 4a47c31e2..3501c447d 100755 --- a/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap @@ -36,6 +36,7 @@ Meta commands: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files diff --git a/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap b/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap index e15aef19c..5f985e03e 100755 --- a/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap @@ -36,6 +36,7 @@ Flags: --permissive-config do not fail on unknown fields in TOML config files -C, --project string path to Azure Linux project -q, --quiet only enable minimal output + --unique-mockroot use unique mock roots labeled with the action, components, and a random suffix (at most 24 characters) -v, --verbose enable verbose output --version version for azldev --without-lockfile preview: track resolved upstream commits in generated config instead of lock files