all:
  children:
    zuul_unreachable:
      hosts: {}
  hosts:
    orchestrator:
      ansible_connection: ssh
      ansible_host: 81.163.193.140
      ansible_port: 22
      ansible_python_interpreter: auto
      ansible_user: zuul-testbed02
      ceph_version: reef
      nodepool:
        az: null
        cloud: null
        external_id: 81.163.193.140
        host_id: null
        interface_ip: 81.163.193.140
        label: testbed-orchestrator
        node_properties: {}
        private_ipv4: null
        private_ipv6: null
        provider: static-testbed
        public_ipv4: 81.163.193.140
        public_ipv6: null
        region: null
        slot: 0
      nutshell: true
      openstack_version: '2024.2'
      terraform_blueprint: testbed-default
      terraform_environment: ci-ubuntu-24.04
  vars:
    ceph_version: reef
    nutshell: true
    openstack_version: '2024.2'
    terraform_blueprint: testbed-default
    terraform_environment: ci-ubuntu-24.04
    zuul:
      _inheritance_path:
      - '<Job base explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/zuul-config/zuul.d/jobs.yaml@main#2>'
      - '<Job base-extra-logs explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/zuul-config/zuul.d/jobs.yaml@main#24>'
      - '<Job abstract-testbed-deploy explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/testbed/.zuul.yaml@main#48>'
      - '<Job abstract-testbed-deploy-in-a-nutshell explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/testbed/.zuul.yaml@main#82>'
      - '<Job testbed-deploy-in-a-nutshell-ubuntu-24.04 explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/testbed/.zuul.yaml@main#152>'
      - '<Job testbed-deploy-current-in-a-nutshell-ubuntu-24.04 explicit: None implied:
        {MatchAny:{ImpliedBranchMatcher:main}} source: osism/testbed/.zuul.yaml@main#197>'
      - '<Job testbed-deploy-current-in-a-nutshell-ubuntu-24.04 explicit: None implied:
        None source: osism/testbed/.zuul.yaml@main#324>'
      ansible_version: '9'
      attempts: 1
      branch: main
      build: 780629414a3d4ed7af05cbd911bccaa7
      build_refs:
      - branch: main
        change: '2896'
        change_message: "Stop cloning osism/testbed during deployment\n\n## Problem\n\nThe
          testbed deploy flow has a subtle self-defeating step: `setup-testbed.py\n--prepare`
          (triggered by `make bootstrap`) clones a fresh copy of `osism/testbed`\nfrom
          GitHub into `<checkout>/.src/github.com/osism/testbed`. The\n`manager-part-1.yml`
          synchronize task then pushes *that GitHub clone* \u2014\nnot the developer's
          working checkout \u2014 to `/opt/configuration` on the manager.\n\nThe practical
          effect: local edits to the testbed checkout never reach the\nmanager. To
          test a local change you had to push it to GitHub first.\n\nThe Makefile
          worked around this by overriding `basepath` and `repo_path`\nin the bootstrap
          deployment invocation, keeping local runs functional. But\nthis masked rather
          than fixed the problem: the override used `$(PWD)`, which\nGNU make resolves
          to the *caller's* directory, not the testbed checkout, so\n`make -C /path/to/testbed
          deploy` from a different CWD would break. And it\nmeant the playbook defaults
          were wrong for anyone not using the Makefile.\n\nZuul was unaffected because
          it never runs `--prepare`; its `basepath`\nresolved correctly via the old
          default (Zuul's checkout path matched the\nhardcoded convention). So the
          problem was invisible in CI.\n\n## What changed\n\n**1. `basepath` derives
          from `playbook_dir | dirname`.**\n\nEvery playbook used to compute `basepath`
          as\n`{{ ansible_user_dir }}/src/{{ repositories['testbed']['path'] }}` \u2014
          a\nhardcoded path that only worked if the checkout lived at\n`~/src/github.com/osism/testbed`.
          It now uses\n`{{ playbook_dir | dirname }}`, Ansible's built-in variable
          for the\ndirectory of the executing playbook. `dirname` strips one level
          to give\nthe checkout root.\n\nThis is correct for the intended invocation
          paths: Zuul's checkout, a\nlocal developer's path, or a worktree at an arbitrary
          location.\n`playbook_dir` resolves to where the playbook actually is. No
          convention,\nsymlink, or override needed. The sanity check (Change 4) catches\nmisconfigured
          invocations.\n\nThe Makefile's `-e basepath=...` and `-e repo_path=...`
          overrides are\nremoved; `playbook_dir | dirname` is now the single source
          of truth.\n(`deploy.yml`'s `repo_path` default is also fixed to derive from
          `basepath`\nrather than `ansible_user_dir`, for the same reason.)\n\n**2.
          The `testbed:` entry is removed from `playbooks/vars/repositories.yml`.**\n\n`setup-testbed.py
          --prepare` clones every entry in `repositories.yml` into\n`<checkout>/.src/\u2026`.
          After Change 1, nothing reads `repositories['testbed']`\nfor its path, so
          the clone serves no purpose. Removing the entry stops\n`--prepare` from
          pulling a fresh `osism/testbed` from GitHub on every run.\n\nThe remaining
          entries (`ansible-collection-commons`,\n`ansible-collection-services`, `terraform-base`)
          are unaffected; they are\nstill needed for orchestrator-side `ansible-galaxy
          collection install`.\n\n**3. The synchronize task is replaced with an ephemeral
          worktree sync.**\n\nThe old task pushed the contents of\n`{{ repo_path }}/osism/testbed/`
          \u2014 the stale GitHub clone \u2014 to\n`/opt/configuration` on the manager.
          The replacement:\n\n1. Creates a temporary directory on the orchestrator.\n2.
          Adds an ephemeral detached git worktree at HEAD into that directory\n   (`git
          worktree add --detach`).\n3. Rsyncs the worktree's contents to `/opt/configuration`
          (`delete: true`,\n   `--exclude=/.git`).\n4. Removes the worktree in an
          `always:` block, even on sync failure.\n\nA git worktree at HEAD materializes
          only tracked files in the state recorded\nat that commit \u2014 no working-tree
          modifications, no untracked files, no\n`.git/` directory, no build artifacts.
          The source is always clean, so\n`delete: true` is safe and leaves `/opt/configuration`
          in an exact, known\nstate.\n\nDevelopment iteration goes through `git commit`
          (or `--amend`). The\nmanager's deployed state corresponds to a specific
          SHA, which is useful for\ncorrelating manager behaviour with a particular
          tree version in logs and bug\nreports.\n\n**4. A fast-fail sanity check
          is added to all seven playbooks.**\n\nA shared task list (`playbooks/tasks/_basepath_check.yml`)
          checks that\n`basepath` points to a directory containing `ansible/manager-part-1.yml`.\nEach
          playbook imports it as a `pre_task`. A misconfigured `basepath` now\nfails
          immediately with a clear message instead of silently mis-deploying.\n\n##
          Scope\n\nThis change covers only the `osism/testbed` self-clone. The other
          repos in\n`repositories.yml` (`ansible-collection-*`, `terraform-base`)
          still follow\nthe existing path: `setup-testbed.py --prepare` clones them,
          and\n`deploy.yml` installs them via `ansible-galaxy`. They are out of scope
          here.\n\n## Before / after\n\n| | Before | After |\n|---|---|---|\n| `basepath`
          source | Hardcoded `~/src/github.com/osism/testbed` (Makefile override for
          local) | `playbook_dir \\| dirname` (always the invoking checkout) |\n|
          Testbed content on manager | GitHub `main` at clone time | Committed state
          of invoking checkout at deploy time |\n| `setup-testbed.py --prepare` clones
          testbed | Yes | No |\n| `.src/github.com/osism/testbed` created | Yes |
          No |\n| `.git/` in `/opt/configuration` | No | No |\n| Local edits visible
          without push | No | Yes (after `git commit`) |\n| Zuul behaviour | Unchanged
          | Unchanged |\n\n## Test plan\n\nEnd-to-end verified on regiocloud (DEPLOY_MODE=manager,
          VERSION_MANAGER=latest):\n\n- [x] Sanity check fires for all 7 playbooks
          when invoked with `basepath=/tmp`\n- [x] `setup-testbed.py --prepare` no
          longer creates `.src/github.com/osism/testbed`\n- [x] `Run manager part
          1 + 2` completed without error\n- [x] A test commit visible in `/opt/configuration/environments/manager/configuration.yml`
          on manager (confirms worktree sync pushed committed content)\n- [x] No `.git/`
          directory on manager (`test ! -d /opt/configuration/.git && echo OK`)\n-
          [x] No `clone.*osism/testbed` references in deploy log\n\n\U0001F916 Generated
          with [Claude Code](https://claude.com/claude-code)"
        change_url: https://github.com/osism/testbed/pull/2896
        commit_id: 488706e407938c9b8d95aba3e49ab300be621af3
        patchset: 488706e407938c9b8d95aba3e49ab300be621af3
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/testbed
          name: osism/testbed
          short_name: testbed
        src_dir: src/github.com/osism/testbed
        topic: null
      buildset: 9b39c9ebcc5f473893dfd7fade11470d
      buildset_refs:
      - branch: main
        change: '2896'
        change_message: "Stop cloning osism/testbed during deployment\n\n## Problem\n\nThe
          testbed deploy flow has a subtle self-defeating step: `setup-testbed.py\n--prepare`
          (triggered by `make bootstrap`) clones a fresh copy of `osism/testbed`\nfrom
          GitHub into `<checkout>/.src/github.com/osism/testbed`. The\n`manager-part-1.yml`
          synchronize task then pushes *that GitHub clone* \u2014\nnot the developer's
          working checkout \u2014 to `/opt/configuration` on the manager.\n\nThe practical
          effect: local edits to the testbed checkout never reach the\nmanager. To
          test a local change you had to push it to GitHub first.\n\nThe Makefile
          worked around this by overriding `basepath` and `repo_path`\nin the bootstrap
          deployment invocation, keeping local runs functional. But\nthis masked rather
          than fixed the problem: the override used `$(PWD)`, which\nGNU make resolves
          to the *caller's* directory, not the testbed checkout, so\n`make -C /path/to/testbed
          deploy` from a different CWD would break. And it\nmeant the playbook defaults
          were wrong for anyone not using the Makefile.\n\nZuul was unaffected because
          it never runs `--prepare`; its `basepath`\nresolved correctly via the old
          default (Zuul's checkout path matched the\nhardcoded convention). So the
          problem was invisible in CI.\n\n## What changed\n\n**1. `basepath` derives
          from `playbook_dir | dirname`.**\n\nEvery playbook used to compute `basepath`
          as\n`{{ ansible_user_dir }}/src/{{ repositories['testbed']['path'] }}` \u2014
          a\nhardcoded path that only worked if the checkout lived at\n`~/src/github.com/osism/testbed`.
          It now uses\n`{{ playbook_dir | dirname }}`, Ansible's built-in variable
          for the\ndirectory of the executing playbook. `dirname` strips one level
          to give\nthe checkout root.\n\nThis is correct for the intended invocation
          paths: Zuul's checkout, a\nlocal developer's path, or a worktree at an arbitrary
          location.\n`playbook_dir` resolves to where the playbook actually is. No
          convention,\nsymlink, or override needed. The sanity check (Change 4) catches\nmisconfigured
          invocations.\n\nThe Makefile's `-e basepath=...` and `-e repo_path=...`
          overrides are\nremoved; `playbook_dir | dirname` is now the single source
          of truth.\n(`deploy.yml`'s `repo_path` default is also fixed to derive from
          `basepath`\nrather than `ansible_user_dir`, for the same reason.)\n\n**2.
          The `testbed:` entry is removed from `playbooks/vars/repositories.yml`.**\n\n`setup-testbed.py
          --prepare` clones every entry in `repositories.yml` into\n`<checkout>/.src/\u2026`.
          After Change 1, nothing reads `repositories['testbed']`\nfor its path, so
          the clone serves no purpose. Removing the entry stops\n`--prepare` from
          pulling a fresh `osism/testbed` from GitHub on every run.\n\nThe remaining
          entries (`ansible-collection-commons`,\n`ansible-collection-services`, `terraform-base`)
          are unaffected; they are\nstill needed for orchestrator-side `ansible-galaxy
          collection install`.\n\n**3. The synchronize task is replaced with an ephemeral
          worktree sync.**\n\nThe old task pushed the contents of\n`{{ repo_path }}/osism/testbed/`
          \u2014 the stale GitHub clone \u2014 to\n`/opt/configuration` on the manager.
          The replacement:\n\n1. Creates a temporary directory on the orchestrator.\n2.
          Adds an ephemeral detached git worktree at HEAD into that directory\n   (`git
          worktree add --detach`).\n3. Rsyncs the worktree's contents to `/opt/configuration`
          (`delete: true`,\n   `--exclude=/.git`).\n4. Removes the worktree in an
          `always:` block, even on sync failure.\n\nA git worktree at HEAD materializes
          only tracked files in the state recorded\nat that commit \u2014 no working-tree
          modifications, no untracked files, no\n`.git/` directory, no build artifacts.
          The source is always clean, so\n`delete: true` is safe and leaves `/opt/configuration`
          in an exact, known\nstate.\n\nDevelopment iteration goes through `git commit`
          (or `--amend`). The\nmanager's deployed state corresponds to a specific
          SHA, which is useful for\ncorrelating manager behaviour with a particular
          tree version in logs and bug\nreports.\n\n**4. A fast-fail sanity check
          is added to all seven playbooks.**\n\nA shared task list (`playbooks/tasks/_basepath_check.yml`)
          checks that\n`basepath` points to a directory containing `ansible/manager-part-1.yml`.\nEach
          playbook imports it as a `pre_task`. A misconfigured `basepath` now\nfails
          immediately with a clear message instead of silently mis-deploying.\n\n##
          Scope\n\nThis change covers only the `osism/testbed` self-clone. The other
          repos in\n`repositories.yml` (`ansible-collection-*`, `terraform-base`)
          still follow\nthe existing path: `setup-testbed.py --prepare` clones them,
          and\n`deploy.yml` installs them via `ansible-galaxy`. They are out of scope
          here.\n\n## Before / after\n\n| | Before | After |\n|---|---|---|\n| `basepath`
          source | Hardcoded `~/src/github.com/osism/testbed` (Makefile override for
          local) | `playbook_dir \\| dirname` (always the invoking checkout) |\n|
          Testbed content on manager | GitHub `main` at clone time | Committed state
          of invoking checkout at deploy time |\n| `setup-testbed.py --prepare` clones
          testbed | Yes | No |\n| `.src/github.com/osism/testbed` created | Yes |
          No |\n| `.git/` in `/opt/configuration` | No | No |\n| Local edits visible
          without push | No | Yes (after `git commit`) |\n| Zuul behaviour | Unchanged
          | Unchanged |\n\n## Test plan\n\nEnd-to-end verified on regiocloud (DEPLOY_MODE=manager,
          VERSION_MANAGER=latest):\n\n- [x] Sanity check fires for all 7 playbooks
          when invoked with `basepath=/tmp`\n- [x] `setup-testbed.py --prepare` no
          longer creates `.src/github.com/osism/testbed`\n- [x] `Run manager part
          1 + 2` completed without error\n- [x] A test commit visible in `/opt/configuration/environments/manager/configuration.yml`
          on manager (confirms worktree sync pushed committed content)\n- [x] No `.git/`
          directory on manager (`test ! -d /opt/configuration/.git && echo OK`)\n-
          [x] No `clone.*osism/testbed` references in deploy log\n\n\U0001F916 Generated
          with [Claude Code](https://claude.com/claude-code)"
        change_url: https://github.com/osism/testbed/pull/2896
        commit_id: 488706e407938c9b8d95aba3e49ab300be621af3
        patchset: 488706e407938c9b8d95aba3e49ab300be621af3
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/testbed
          name: osism/testbed
          short_name: testbed
        src_dir: src/github.com/osism/testbed
        topic: null
      change: '2896'
      change_message: "Stop cloning osism/testbed during deployment\n\n## Problem\n\nThe
        testbed deploy flow has a subtle self-defeating step: `setup-testbed.py\n--prepare`
        (triggered by `make bootstrap`) clones a fresh copy of `osism/testbed`\nfrom
        GitHub into `<checkout>/.src/github.com/osism/testbed`. The\n`manager-part-1.yml`
        synchronize task then pushes *that GitHub clone* \u2014\nnot the developer's
        working checkout \u2014 to `/opt/configuration` on the manager.\n\nThe practical
        effect: local edits to the testbed checkout never reach the\nmanager. To test
        a local change you had to push it to GitHub first.\n\nThe Makefile worked
        around this by overriding `basepath` and `repo_path`\nin the bootstrap deployment
        invocation, keeping local runs functional. But\nthis masked rather than fixed
        the problem: the override used `$(PWD)`, which\nGNU make resolves to the *caller's*
        directory, not the testbed checkout, so\n`make -C /path/to/testbed deploy`
        from a different CWD would break. And it\nmeant the playbook defaults were
        wrong for anyone not using the Makefile.\n\nZuul was unaffected because it
        never runs `--prepare`; its `basepath`\nresolved correctly via the old default
        (Zuul's checkout path matched the\nhardcoded convention). So the problem was
        invisible in CI.\n\n## What changed\n\n**1. `basepath` derives from `playbook_dir
        | dirname`.**\n\nEvery playbook used to compute `basepath` as\n`{{ ansible_user_dir
        }}/src/{{ repositories['testbed']['path'] }}` \u2014 a\nhardcoded path that
        only worked if the checkout lived at\n`~/src/github.com/osism/testbed`. It
        now uses\n`{{ playbook_dir | dirname }}`, Ansible's built-in variable for
        the\ndirectory of the executing playbook. `dirname` strips one level to give\nthe
        checkout root.\n\nThis is correct for the intended invocation paths: Zuul's
        checkout, a\nlocal developer's path, or a worktree at an arbitrary location.\n`playbook_dir`
        resolves to where the playbook actually is. No convention,\nsymlink, or override
        needed. The sanity check (Change 4) catches\nmisconfigured invocations.\n\nThe
        Makefile's `-e basepath=...` and `-e repo_path=...` overrides are\nremoved;
        `playbook_dir | dirname` is now the single source of truth.\n(`deploy.yml`'s
        `repo_path` default is also fixed to derive from `basepath`\nrather than `ansible_user_dir`,
        for the same reason.)\n\n**2. The `testbed:` entry is removed from `playbooks/vars/repositories.yml`.**\n\n`setup-testbed.py
        --prepare` clones every entry in `repositories.yml` into\n`<checkout>/.src/\u2026`.
        After Change 1, nothing reads `repositories['testbed']`\nfor its path, so
        the clone serves no purpose. Removing the entry stops\n`--prepare` from pulling
        a fresh `osism/testbed` from GitHub on every run.\n\nThe remaining entries
        (`ansible-collection-commons`,\n`ansible-collection-services`, `terraform-base`)
        are unaffected; they are\nstill needed for orchestrator-side `ansible-galaxy
        collection install`.\n\n**3. The synchronize task is replaced with an ephemeral
        worktree sync.**\n\nThe old task pushed the contents of\n`{{ repo_path }}/osism/testbed/`
        \u2014 the stale GitHub clone \u2014 to\n`/opt/configuration` on the manager.
        The replacement:\n\n1. Creates a temporary directory on the orchestrator.\n2.
        Adds an ephemeral detached git worktree at HEAD into that directory\n   (`git
        worktree add --detach`).\n3. Rsyncs the worktree's contents to `/opt/configuration`
        (`delete: true`,\n   `--exclude=/.git`).\n4. Removes the worktree in an `always:`
        block, even on sync failure.\n\nA git worktree at HEAD materializes only tracked
        files in the state recorded\nat that commit \u2014 no working-tree modifications,
        no untracked files, no\n`.git/` directory, no build artifacts. The source
        is always clean, so\n`delete: true` is safe and leaves `/opt/configuration`
        in an exact, known\nstate.\n\nDevelopment iteration goes through `git commit`
        (or `--amend`). The\nmanager's deployed state corresponds to a specific SHA,
        which is useful for\ncorrelating manager behaviour with a particular tree
        version in logs and bug\nreports.\n\n**4. A fast-fail sanity check is added
        to all seven playbooks.**\n\nA shared task list (`playbooks/tasks/_basepath_check.yml`)
        checks that\n`basepath` points to a directory containing `ansible/manager-part-1.yml`.\nEach
        playbook imports it as a `pre_task`. A misconfigured `basepath` now\nfails
        immediately with a clear message instead of silently mis-deploying.\n\n##
        Scope\n\nThis change covers only the `osism/testbed` self-clone. The other
        repos in\n`repositories.yml` (`ansible-collection-*`, `terraform-base`) still
        follow\nthe existing path: `setup-testbed.py --prepare` clones them, and\n`deploy.yml`
        installs them via `ansible-galaxy`. They are out of scope here.\n\n## Before
        / after\n\n| | Before | After |\n|---|---|---|\n| `basepath` source | Hardcoded
        `~/src/github.com/osism/testbed` (Makefile override for local) | `playbook_dir
        \\| dirname` (always the invoking checkout) |\n| Testbed content on manager
        | GitHub `main` at clone time | Committed state of invoking checkout at deploy
        time |\n| `setup-testbed.py --prepare` clones testbed | Yes | No |\n| `.src/github.com/osism/testbed`
        created | Yes | No |\n| `.git/` in `/opt/configuration` | No | No |\n| Local
        edits visible without push | No | Yes (after `git commit`) |\n| Zuul behaviour
        | Unchanged | Unchanged |\n\n## Test plan\n\nEnd-to-end verified on regiocloud
        (DEPLOY_MODE=manager, VERSION_MANAGER=latest):\n\n- [x] Sanity check fires
        for all 7 playbooks when invoked with `basepath=/tmp`\n- [x] `setup-testbed.py
        --prepare` no longer creates `.src/github.com/osism/testbed`\n- [x] `Run manager
        part 1 + 2` completed without error\n- [x] A test commit visible in `/opt/configuration/environments/manager/configuration.yml`
        on manager (confirms worktree sync pushed committed content)\n- [x] No `.git/`
        directory on manager (`test ! -d /opt/configuration/.git && echo OK`)\n- [x]
        No `clone.*osism/testbed` references in deploy log\n\n\U0001F916 Generated
        with [Claude Code](https://claude.com/claude-code)"
      change_url: https://github.com/osism/testbed/pull/2896
      child_jobs: []
      commit_id: 488706e407938c9b8d95aba3e49ab300be621af3
      event_id: a465b940-5a8b-11f1-9aac-dc168893ad6e
      executor:
        hostname: 521e9411259a
        inventory_file: /var/lib/zuul/builds/780629414a3d4ed7af05cbd911bccaa7/ansible/inventory.yaml
        log_root: /var/lib/zuul/builds/780629414a3d4ed7af05cbd911bccaa7/work/logs
        result_data_file: /var/lib/zuul/builds/780629414a3d4ed7af05cbd911bccaa7/work/results.json
        src_root: /var/lib/zuul/builds/780629414a3d4ed7af05cbd911bccaa7/work/src
        work_root: /var/lib/zuul/builds/780629414a3d4ed7af05cbd911bccaa7/work
      include_vars: []
      items:
      - branch: main
        change: '2896'
        change_message: "Stop cloning osism/testbed during deployment\n\n## Problem\n\nThe
          testbed deploy flow has a subtle self-defeating step: `setup-testbed.py\n--prepare`
          (triggered by `make bootstrap`) clones a fresh copy of `osism/testbed`\nfrom
          GitHub into `<checkout>/.src/github.com/osism/testbed`. The\n`manager-part-1.yml`
          synchronize task then pushes *that GitHub clone* \u2014\nnot the developer's
          working checkout \u2014 to `/opt/configuration` on the manager.\n\nThe practical
          effect: local edits to the testbed checkout never reach the\nmanager. To
          test a local change you had to push it to GitHub first.\n\nThe Makefile
          worked around this by overriding `basepath` and `repo_path`\nin the bootstrap
          deployment invocation, keeping local runs functional. But\nthis masked rather
          than fixed the problem: the override used `$(PWD)`, which\nGNU make resolves
          to the *caller's* directory, not the testbed checkout, so\n`make -C /path/to/testbed
          deploy` from a different CWD would break. And it\nmeant the playbook defaults
          were wrong for anyone not using the Makefile.\n\nZuul was unaffected because
          it never runs `--prepare`; its `basepath`\nresolved correctly via the old
          default (Zuul's checkout path matched the\nhardcoded convention). So the
          problem was invisible in CI.\n\n## What changed\n\n**1. `basepath` derives
          from `playbook_dir | dirname`.**\n\nEvery playbook used to compute `basepath`
          as\n`{{ ansible_user_dir }}/src/{{ repositories['testbed']['path'] }}` \u2014
          a\nhardcoded path that only worked if the checkout lived at\n`~/src/github.com/osism/testbed`.
          It now uses\n`{{ playbook_dir | dirname }}`, Ansible's built-in variable
          for the\ndirectory of the executing playbook. `dirname` strips one level
          to give\nthe checkout root.\n\nThis is correct for the intended invocation
          paths: Zuul's checkout, a\nlocal developer's path, or a worktree at an arbitrary
          location.\n`playbook_dir` resolves to where the playbook actually is. No
          convention,\nsymlink, or override needed. The sanity check (Change 4) catches\nmisconfigured
          invocations.\n\nThe Makefile's `-e basepath=...` and `-e repo_path=...`
          overrides are\nremoved; `playbook_dir | dirname` is now the single source
          of truth.\n(`deploy.yml`'s `repo_path` default is also fixed to derive from
          `basepath`\nrather than `ansible_user_dir`, for the same reason.)\n\n**2.
          The `testbed:` entry is removed from `playbooks/vars/repositories.yml`.**\n\n`setup-testbed.py
          --prepare` clones every entry in `repositories.yml` into\n`<checkout>/.src/\u2026`.
          After Change 1, nothing reads `repositories['testbed']`\nfor its path, so
          the clone serves no purpose. Removing the entry stops\n`--prepare` from
          pulling a fresh `osism/testbed` from GitHub on every run.\n\nThe remaining
          entries (`ansible-collection-commons`,\n`ansible-collection-services`, `terraform-base`)
          are unaffected; they are\nstill needed for orchestrator-side `ansible-galaxy
          collection install`.\n\n**3. The synchronize task is replaced with an ephemeral
          worktree sync.**\n\nThe old task pushed the contents of\n`{{ repo_path }}/osism/testbed/`
          \u2014 the stale GitHub clone \u2014 to\n`/opt/configuration` on the manager.
          The replacement:\n\n1. Creates a temporary directory on the orchestrator.\n2.
          Adds an ephemeral detached git worktree at HEAD into that directory\n   (`git
          worktree add --detach`).\n3. Rsyncs the worktree's contents to `/opt/configuration`
          (`delete: true`,\n   `--exclude=/.git`).\n4. Removes the worktree in an
          `always:` block, even on sync failure.\n\nA git worktree at HEAD materializes
          only tracked files in the state recorded\nat that commit \u2014 no working-tree
          modifications, no untracked files, no\n`.git/` directory, no build artifacts.
          The source is always clean, so\n`delete: true` is safe and leaves `/opt/configuration`
          in an exact, known\nstate.\n\nDevelopment iteration goes through `git commit`
          (or `--amend`). The\nmanager's deployed state corresponds to a specific
          SHA, which is useful for\ncorrelating manager behaviour with a particular
          tree version in logs and bug\nreports.\n\n**4. A fast-fail sanity check
          is added to all seven playbooks.**\n\nA shared task list (`playbooks/tasks/_basepath_check.yml`)
          checks that\n`basepath` points to a directory containing `ansible/manager-part-1.yml`.\nEach
          playbook imports it as a `pre_task`. A misconfigured `basepath` now\nfails
          immediately with a clear message instead of silently mis-deploying.\n\n##
          Scope\n\nThis change covers only the `osism/testbed` self-clone. The other
          repos in\n`repositories.yml` (`ansible-collection-*`, `terraform-base`)
          still follow\nthe existing path: `setup-testbed.py --prepare` clones them,
          and\n`deploy.yml` installs them via `ansible-galaxy`. They are out of scope
          here.\n\n## Before / after\n\n| | Before | After |\n|---|---|---|\n| `basepath`
          source | Hardcoded `~/src/github.com/osism/testbed` (Makefile override for
          local) | `playbook_dir \\| dirname` (always the invoking checkout) |\n|
          Testbed content on manager | GitHub `main` at clone time | Committed state
          of invoking checkout at deploy time |\n| `setup-testbed.py --prepare` clones
          testbed | Yes | No |\n| `.src/github.com/osism/testbed` created | Yes |
          No |\n| `.git/` in `/opt/configuration` | No | No |\n| Local edits visible
          without push | No | Yes (after `git commit`) |\n| Zuul behaviour | Unchanged
          | Unchanged |\n\n## Test plan\n\nEnd-to-end verified on regiocloud (DEPLOY_MODE=manager,
          VERSION_MANAGER=latest):\n\n- [x] Sanity check fires for all 7 playbooks
          when invoked with `basepath=/tmp`\n- [x] `setup-testbed.py --prepare` no
          longer creates `.src/github.com/osism/testbed`\n- [x] `Run manager part
          1 + 2` completed without error\n- [x] A test commit visible in `/opt/configuration/environments/manager/configuration.yml`
          on manager (confirms worktree sync pushed committed content)\n- [x] No `.git/`
          directory on manager (`test ! -d /opt/configuration/.git && echo OK`)\n-
          [x] No `clone.*osism/testbed` references in deploy log\n\n\U0001F916 Generated
          with [Claude Code](https://claude.com/claude-code)"
        change_url: https://github.com/osism/testbed/pull/2896
        commit_id: 488706e407938c9b8d95aba3e49ab300be621af3
        patchset: 488706e407938c9b8d95aba3e49ab300be621af3
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/testbed
          name: osism/testbed
          short_name: testbed
          src_dir: src/github.com/osism/testbed
        topic: null
      job: testbed-deploy-current-in-a-nutshell-ubuntu-24.04
      jobtags: []
      max_attempts: 3
      message: U3RvcCBjbG9uaW5nIG9zaXNtL3Rlc3RiZWQgZHVyaW5nIGRlcGxveW1lbnQKCiMjIFByb2JsZW0KClRoZSB0ZXN0YmVkIGRlcGxveSBmbG93IGhhcyBhIHN1YnRsZSBzZWxmLWRlZmVhdGluZyBzdGVwOiBgc2V0dXAtdGVzdGJlZC5weQotLXByZXBhcmVgICh0cmlnZ2VyZWQgYnkgYG1ha2UgYm9vdHN0cmFwYCkgY2xvbmVzIGEgZnJlc2ggY29weSBvZiBgb3Npc20vdGVzdGJlZGAKZnJvbSBHaXRIdWIgaW50byBgPGNoZWNrb3V0Pi8uc3JjL2dpdGh1Yi5jb20vb3Npc20vdGVzdGJlZGAuIFRoZQpgbWFuYWdlci1wYXJ0LTEueW1sYCBzeW5jaHJvbml6ZSB0YXNrIHRoZW4gcHVzaGVzICp0aGF0IEdpdEh1YiBjbG9uZSog4oCUCm5vdCB0aGUgZGV2ZWxvcGVyJ3Mgd29ya2luZyBjaGVja291dCDigJQgdG8gYC9vcHQvY29uZmlndXJhdGlvbmAgb24gdGhlIG1hbmFnZXIuCgpUaGUgcHJhY3RpY2FsIGVmZmVjdDogbG9jYWwgZWRpdHMgdG8gdGhlIHRlc3RiZWQgY2hlY2tvdXQgbmV2ZXIgcmVhY2ggdGhlCm1hbmFnZXIuIFRvIHRlc3QgYSBsb2NhbCBjaGFuZ2UgeW91IGhhZCB0byBwdXNoIGl0IHRvIEdpdEh1YiBmaXJzdC4KClRoZSBNYWtlZmlsZSB3b3JrZWQgYXJvdW5kIHRoaXMgYnkgb3ZlcnJpZGluZyBgYmFzZXBhdGhgIGFuZCBgcmVwb19wYXRoYAppbiB0aGUgYm9vdHN0cmFwIGRlcGxveW1lbnQgaW52b2NhdGlvbiwga2VlcGluZyBsb2NhbCBydW5zIGZ1bmN0aW9uYWwuIEJ1dAp0aGlzIG1hc2tlZCByYXRoZXIgdGhhbiBmaXhlZCB0aGUgcHJvYmxlbTogdGhlIG92ZXJyaWRlIHVzZWQgYCQoUFdEKWAsIHdoaWNoCkdOVSBtYWtlIHJlc29sdmVzIHRvIHRoZSAqY2FsbGVyJ3MqIGRpcmVjdG9yeSwgbm90IHRoZSB0ZXN0YmVkIGNoZWNrb3V0LCBzbwpgbWFrZSAtQyAvcGF0aC90by90ZXN0YmVkIGRlcGxveWAgZnJvbSBhIGRpZmZlcmVudCBDV0Qgd291bGQgYnJlYWsuIEFuZCBpdAptZWFudCB0aGUgcGxheWJvb2sgZGVmYXVsdHMgd2VyZSB3cm9uZyBmb3IgYW55b25lIG5vdCB1c2luZyB0aGUgTWFrZWZpbGUuCgpadXVsIHdhcyB1bmFmZmVjdGVkIGJlY2F1c2UgaXQgbmV2ZXIgcnVucyBgLS1wcmVwYXJlYDsgaXRzIGBiYXNlcGF0aGAKcmVzb2x2ZWQgY29ycmVjdGx5IHZpYSB0aGUgb2xkIGRlZmF1bHQgKFp1dWwncyBjaGVja291dCBwYXRoIG1hdGNoZWQgdGhlCmhhcmRjb2RlZCBjb252ZW50aW9uKS4gU28gdGhlIHByb2JsZW0gd2FzIGludmlzaWJsZSBpbiBDSS4KCiMjIFdoYXQgY2hhbmdlZAoKKioxLiBgYmFzZXBhdGhgIGRlcml2ZXMgZnJvbSBgcGxheWJvb2tfZGlyIHwgZGlybmFtZWAuKioKCkV2ZXJ5IHBsYXlib29rIHVzZWQgdG8gY29tcHV0ZSBgYmFzZXBhdGhgIGFzCmB7eyBhbnNpYmxlX3VzZXJfZGlyIH19L3NyYy97eyByZXBvc2l0b3JpZXNbJ3Rlc3RiZWQnXVsncGF0aCddIH19YCDigJQgYQpoYXJkY29kZWQgcGF0aCB0aGF0IG9ubHkgd29ya2VkIGlmIHRoZSBjaGVja291dCBsaXZlZCBhdApgfi9zcmMvZ2l0aHViLmNvbS9vc2lzbS90ZXN0YmVkYC4gSXQgbm93IHVzZXMKYHt7IHBsYXlib29rX2RpciB8IGRpcm5hbWUgfX1gLCBBbnNpYmxlJ3MgYnVpbHQtaW4gdmFyaWFibGUgZm9yIHRoZQpkaXJlY3Rvcnkgb2YgdGhlIGV4ZWN1dGluZyBwbGF5Ym9vay4gYGRpcm5hbWVgIHN0cmlwcyBvbmUgbGV2ZWwgdG8gZ2l2ZQp0aGUgY2hlY2tvdXQgcm9vdC4KClRoaXMgaXMgY29ycmVjdCBmb3IgdGhlIGludGVuZGVkIGludm9jYXRpb24gcGF0aHM6IFp1dWwncyBjaGVja291dCwgYQpsb2NhbCBkZXZlbG9wZXIncyBwYXRoLCBvciBhIHdvcmt0cmVlIGF0IGFuIGFyYml0cmFyeSBsb2NhdGlvbi4KYHBsYXlib29rX2RpcmAgcmVzb2x2ZXMgdG8gd2hlcmUgdGhlIHBsYXlib29rIGFjdHVhbGx5IGlzLiBObyBjb252ZW50aW9uLApzeW1saW5rLCBvciBvdmVycmlkZSBuZWVkZWQuIFRoZSBzYW5pdHkgY2hlY2sgKENoYW5nZSA0KSBjYXRjaGVzCm1pc2NvbmZpZ3VyZWQgaW52b2NhdGlvbnMuCgpUaGUgTWFrZWZpbGUncyBgLWUgYmFzZXBhdGg9Li4uYCBhbmQgYC1lIHJlcG9fcGF0aD0uLi5gIG92ZXJyaWRlcyBhcmUKcmVtb3ZlZDsgYHBsYXlib29rX2RpciB8IGRpcm5hbWVgIGlzIG5vdyB0aGUgc2luZ2xlIHNvdXJjZSBvZiB0cnV0aC4KKGBkZXBsb3kueW1sYCdzIGByZXBvX3BhdGhgIGRlZmF1bHQgaXMgYWxzbyBmaXhlZCB0byBkZXJpdmUgZnJvbSBgYmFzZXBhdGhgCnJhdGhlciB0aGFuIGBhbnNpYmxlX3VzZXJfZGlyYCwgZm9yIHRoZSBzYW1lIHJlYXNvbi4pCgoqKjIuIFRoZSBgdGVzdGJlZDpgIGVudHJ5IGlzIHJlbW92ZWQgZnJvbSBgcGxheWJvb2tzL3ZhcnMvcmVwb3NpdG9yaWVzLnltbGAuKioKCmBzZXR1cC10ZXN0YmVkLnB5IC0tcHJlcGFyZWAgY2xvbmVzIGV2ZXJ5IGVudHJ5IGluIGByZXBvc2l0b3JpZXMueW1sYCBpbnRvCmA8Y2hlY2tvdXQ+Ly5zcmMv4oCmYC4gQWZ0ZXIgQ2hhbmdlIDEsIG5vdGhpbmcgcmVhZHMgYHJlcG9zaXRvcmllc1sndGVzdGJlZCddYApmb3IgaXRzIHBhdGgsIHNvIHRoZSBjbG9uZSBzZXJ2ZXMgbm8gcHVycG9zZS4gUmVtb3ZpbmcgdGhlIGVudHJ5IHN0b3BzCmAtLXByZXBhcmVgIGZyb20gcHVsbGluZyBhIGZyZXNoIGBvc2lzbS90ZXN0YmVkYCBmcm9tIEdpdEh1YiBvbiBldmVyeSBydW4uCgpUaGUgcmVtYWluaW5nIGVudHJpZXMgKGBhbnNpYmxlLWNvbGxlY3Rpb24tY29tbW9uc2AsCmBhbnNpYmxlLWNvbGxlY3Rpb24tc2VydmljZXNgLCBgdGVycmFmb3JtLWJhc2VgKSBhcmUgdW5hZmZlY3RlZDsgdGhleSBhcmUKc3RpbGwgbmVlZGVkIGZvciBvcmNoZXN0cmF0b3Itc2lkZSBgYW5zaWJsZS1nYWxheHkgY29sbGVjdGlvbiBpbnN0YWxsYC4KCioqMy4gVGhlIHN5bmNocm9uaXplIHRhc2sgaXMgcmVwbGFjZWQgd2l0aCBhbiBlcGhlbWVyYWwgd29ya3RyZWUgc3luYy4qKgoKVGhlIG9sZCB0YXNrIHB1c2hlZCB0aGUgY29udGVudHMgb2YKYHt7IHJlcG9fcGF0aCB9fS9vc2lzbS90ZXN0YmVkL2Ag4oCUIHRoZSBzdGFsZSBHaXRIdWIgY2xvbmUg4oCUIHRvCmAvb3B0L2NvbmZpZ3VyYXRpb25gIG9uIHRoZSBtYW5hZ2VyLiBUaGUgcmVwbGFjZW1lbnQ6CgoxLiBDcmVhdGVzIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeSBvbiB0aGUgb3JjaGVzdHJhdG9yLgoyLiBBZGRzIGFuIGVwaGVtZXJhbCBkZXRhY2hlZCBnaXQgd29ya3RyZWUgYXQgSEVBRCBpbnRvIHRoYXQgZGlyZWN0b3J5CiAgIChgZ2l0IHdvcmt0cmVlIGFkZCAtLWRldGFjaGApLgozLiBSc3luY3MgdGhlIHdvcmt0cmVlJ3MgY29udGVudHMgdG8gYC9vcHQvY29uZmlndXJhdGlvbmAgKGBkZWxldGU6IHRydWVgLAogICBgLS1leGNsdWRlPS8uZ2l0YCkuCjQuIFJlbW92ZXMgdGhlIHdvcmt0cmVlIGluIGFuIGBhbHdheXM6YCBibG9jaywgZXZlbiBvbiBzeW5jIGZhaWx1cmUuCgpBIGdpdCB3b3JrdHJlZSBhdCBIRUFEIG1hdGVyaWFsaXplcyBvbmx5IHRyYWNrZWQgZmlsZXMgaW4gdGhlIHN0YXRlIHJlY29yZGVkCmF0IHRoYXQgY29tbWl0IOKAlCBubyB3b3JraW5nLXRyZWUgbW9kaWZpY2F0aW9ucywgbm8gdW50cmFja2VkIGZpbGVzLCBubwpgLmdpdC9gIGRpcmVjdG9yeSwgbm8gYnVpbGQgYXJ0aWZhY3RzLiBUaGUgc291cmNlIGlzIGFsd2F5cyBjbGVhbiwgc28KYGRlbGV0ZTogdHJ1ZWAgaXMgc2FmZSBhbmQgbGVhdmVzIGAvb3B0L2NvbmZpZ3VyYXRpb25gIGluIGFuIGV4YWN0LCBrbm93bgpzdGF0ZS4KCkRldmVsb3BtZW50IGl0ZXJhdGlvbiBnb2VzIHRocm91Z2ggYGdpdCBjb21taXRgIChvciBgLS1hbWVuZGApLiBUaGUKbWFuYWdlcidzIGRlcGxveWVkIHN0YXRlIGNvcnJlc3BvbmRzIHRvIGEgc3BlY2lmaWMgU0hBLCB3aGljaCBpcyB1c2VmdWwgZm9yCmNvcnJlbGF0aW5nIG1hbmFnZXIgYmVoYXZpb3VyIHdpdGggYSBwYXJ0aWN1bGFyIHRyZWUgdmVyc2lvbiBpbiBsb2dzIGFuZCBidWcKcmVwb3J0cy4KCioqNC4gQSBmYXN0LWZhaWwgc2FuaXR5IGNoZWNrIGlzIGFkZGVkIHRvIGFsbCBzZXZlbiBwbGF5Ym9va3MuKioKCkEgc2hhcmVkIHRhc2sgbGlzdCAoYHBsYXlib29rcy90YXNrcy9fYmFzZXBhdGhfY2hlY2sueW1sYCkgY2hlY2tzIHRoYXQKYGJhc2VwYXRoYCBwb2ludHMgdG8gYSBkaXJlY3RvcnkgY29udGFpbmluZyBgYW5zaWJsZS9tYW5hZ2VyLXBhcnQtMS55bWxgLgpFYWNoIHBsYXlib29rIGltcG9ydHMgaXQgYXMgYSBgcHJlX3Rhc2tgLiBBIG1pc2NvbmZpZ3VyZWQgYGJhc2VwYXRoYCBub3cKZmFpbHMgaW1tZWRpYXRlbHkgd2l0aCBhIGNsZWFyIG1lc3NhZ2UgaW5zdGVhZCBvZiBzaWxlbnRseSBtaXMtZGVwbG95aW5nLgoKIyMgU2NvcGUKClRoaXMgY2hhbmdlIGNvdmVycyBvbmx5IHRoZSBgb3Npc20vdGVzdGJlZGAgc2VsZi1jbG9uZS4gVGhlIG90aGVyIHJlcG9zIGluCmByZXBvc2l0b3JpZXMueW1sYCAoYGFuc2libGUtY29sbGVjdGlvbi0qYCwgYHRlcnJhZm9ybS1iYXNlYCkgc3RpbGwgZm9sbG93CnRoZSBleGlzdGluZyBwYXRoOiBgc2V0dXAtdGVzdGJlZC5weSAtLXByZXBhcmVgIGNsb25lcyB0aGVtLCBhbmQKYGRlcGxveS55bWxgIGluc3RhbGxzIHRoZW0gdmlhIGBhbnNpYmxlLWdhbGF4eWAuIFRoZXkgYXJlIG91dCBvZiBzY29wZSBoZXJlLgoKIyMgQmVmb3JlIC8gYWZ0ZXIKCnwgfCBCZWZvcmUgfCBBZnRlciB8CnwtLS18LS0tfC0tLXwKfCBgYmFzZXBhdGhgIHNvdXJjZSB8IEhhcmRjb2RlZCBgfi9zcmMvZ2l0aHViLmNvbS9vc2lzbS90ZXN0YmVkYCAoTWFrZWZpbGUgb3ZlcnJpZGUgZm9yIGxvY2FsKSB8IGBwbGF5Ym9va19kaXIgXHwgZGlybmFtZWAgKGFsd2F5cyB0aGUgaW52b2tpbmcgY2hlY2tvdXQpIHwKfCBUZXN0YmVkIGNvbnRlbnQgb24gbWFuYWdlciB8IEdpdEh1YiBgbWFpbmAgYXQgY2xvbmUgdGltZSB8IENvbW1pdHRlZCBzdGF0ZSBvZiBpbnZva2luZyBjaGVja291dCBhdCBkZXBsb3kgdGltZSB8CnwgYHNldHVwLXRlc3RiZWQucHkgLS1wcmVwYXJlYCBjbG9uZXMgdGVzdGJlZCB8IFllcyB8IE5vIHwKfCBgLnNyYy9naXRodWIuY29tL29zaXNtL3Rlc3RiZWRgIGNyZWF0ZWQgfCBZZXMgfCBObyB8CnwgYC5naXQvYCBpbiBgL29wdC9jb25maWd1cmF0aW9uYCB8IE5vIHwgTm8gfAp8IExvY2FsIGVkaXRzIHZpc2libGUgd2l0aG91dCBwdXNoIHwgTm8gfCBZZXMgKGFmdGVyIGBnaXQgY29tbWl0YCkgfAp8IFp1dWwgYmVoYXZpb3VyIHwgVW5jaGFuZ2VkIHwgVW5jaGFuZ2VkIHwKCiMjIFRlc3QgcGxhbgoKRW5kLXRvLWVuZCB2ZXJpZmllZCBvbiByZWdpb2Nsb3VkIChERVBMT1lfTU9ERT1tYW5hZ2VyLCBWRVJTSU9OX01BTkFHRVI9bGF0ZXN0KToKCi0gW3hdIFNhbml0eSBjaGVjayBmaXJlcyBmb3IgYWxsIDcgcGxheWJvb2tzIHdoZW4gaW52b2tlZCB3aXRoIGBiYXNlcGF0aD0vdG1wYAotIFt4XSBgc2V0dXAtdGVzdGJlZC5weSAtLXByZXBhcmVgIG5vIGxvbmdlciBjcmVhdGVzIGAuc3JjL2dpdGh1Yi5jb20vb3Npc20vdGVzdGJlZGAKLSBbeF0gYFJ1biBtYW5hZ2VyIHBhcnQgMSArIDJgIGNvbXBsZXRlZCB3aXRob3V0IGVycm9yCi0gW3hdIEEgdGVzdCBjb21taXQgdmlzaWJsZSBpbiBgL29wdC9jb25maWd1cmF0aW9uL2Vudmlyb25tZW50cy9tYW5hZ2VyL2NvbmZpZ3VyYXRpb24ueW1sYCBvbiBtYW5hZ2VyIChjb25maXJtcyB3b3JrdHJlZSBzeW5jIHB1c2hlZCBjb21taXR0ZWQgY29udGVudCkKLSBbeF0gTm8gYC5naXQvYCBkaXJlY3Rvcnkgb24gbWFuYWdlciAoYHRlc3QgISAtZCAvb3B0L2NvbmZpZ3VyYXRpb24vLmdpdCAmJiBlY2hvIE9LYCkKLSBbeF0gTm8gYGNsb25lLipvc2lzbS90ZXN0YmVkYCByZWZlcmVuY2VzIGluIGRlcGxveSBsb2cKCvCfpJYgR2VuZXJhdGVkIHdpdGggW0NsYXVkZSBDb2RlXShodHRwczovL2NsYXVkZS5jb20vY2xhdWRlLWNvZGUp
      patchset: 488706e407938c9b8d95aba3e49ab300be621af3
      pipeline: label
      playbook_context:
        playbook_projects:
          trusted/project_0/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: 019f0261a526db7d8989ff1abefc2299a8c0ffbc
          trusted/project_1/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 038dd8b856c62af4433c8477c5cb3d0480847059
          trusted/project_2/github.com/osism/zuul-jobs:
            canonical_name: github.com/osism/zuul-jobs
            checkout: main
            commit: 0c74c57cedff2b36bb37ca68a5338d1c49991496
          untrusted/project_0/github.com/osism/testbed:
            canonical_name: github.com/osism/testbed
            checkout: main
            commit: 1bd285bbcb57ac2c6848a718a073e7b1a036e7ed
          untrusted/project_1/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: 019f0261a526db7d8989ff1abefc2299a8c0ffbc
          untrusted/project_2/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 038dd8b856c62af4433c8477c5cb3d0480847059
          untrusted/project_3/github.com/osism/zuul-jobs:
            canonical_name: github.com/osism/zuul-jobs
            checkout: main
            commit: 0c74c57cedff2b36bb37ca68a5338d1c49991496
        playbooks:
        - path: untrusted/project_0/github.com/osism/testbed/playbooks/deploy.yml
          roles:
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/playbook_0/role_1/zuul-config
            link_target: untrusted/project_1/github.com/osism/zuul-config
            role_path: ansible/playbook_0/role_1/zuul-config/roles
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/playbook_0/role_2/openinfra-zuul-jobs
            link_target: untrusted/project_2/github.com/osism/openinfra-zuul-jobs
            role_path: ansible/playbook_0/role_2/openinfra-zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/playbook_0/role_3/zuul-jobs
            link_target: untrusted/project_3/github.com/osism/zuul-jobs
            role_path: ansible/playbook_0/role_3/zuul-jobs/roles
      post_review: true
      post_timeout: null
      pre_timeout: null
      project:
        canonical_hostname: github.com
        canonical_name: github.com/osism/testbed
        name: osism/testbed
        short_name: testbed
        src_dir: src/github.com/osism/testbed
      projects:
        github.com/osism/ansible-collection-commons:
          canonical_hostname: github.com
          canonical_name: github.com/osism/ansible-collection-commons
          checkout: main
          checkout_description: zuul branch
          commit: 2187f84bfbeaa021bea4d2c119df223badc87f75
          name: osism/ansible-collection-commons
          required: true
          short_name: ansible-collection-commons
          src_dir: src/github.com/osism/ansible-collection-commons
        github.com/osism/ansible-collection-services:
          canonical_hostname: github.com
          canonical_name: github.com/osism/ansible-collection-services
          checkout: main
          checkout_description: zuul branch
          commit: 9d77d7a65870987132fc55022a3e555bbd9f99fe
          name: osism/ansible-collection-services
          required: true
          short_name: ansible-collection-services
          src_dir: src/github.com/osism/ansible-collection-services
        github.com/osism/terraform-base:
          canonical_hostname: github.com
          canonical_name: github.com/osism/terraform-base
          checkout: main
          checkout_description: zuul branch
          commit: e7dc4a432af720c7433727a4035f99b639e73888
          name: osism/terraform-base
          required: true
          short_name: terraform-base
          src_dir: src/github.com/osism/terraform-base
        github.com/osism/testbed:
          canonical_hostname: github.com
          canonical_name: github.com/osism/testbed
          checkout: main
          checkout_description: zuul branch
          commit: 1bd285bbcb57ac2c6848a718a073e7b1a036e7ed
          name: osism/testbed
          required: true
          short_name: testbed
          src_dir: src/github.com/osism/testbed
      ref: refs/pull/2896/head
      resources: {}
      tenant: osism
      timeout: 16200
      topic: null
      voting: true
