all:
  children:
    zuul_unreachable:
      hosts: {}
  hosts:
    debian-bookworm:
      ansible_connection: ssh
      ansible_host: 2a13:1a81:8000:3124:f816:3eff:fea1:e864
      ansible_port: 22
      ansible_python_interpreter: auto
      ansible_user: zuul
      nodepool:
        az: nova
        cloud: regiocloud-a-ci
        external_id: 507532a6-d253-4bfd-87b8-f9f12eb88942
        host_id: 82101243836454d7401d5893feae9e2c5df7cc291646d8a99b33b53b
        interface_ip: 2a13:1a81:8000:3124:f816:3eff:fea1:e864
        label: debian-bookworm
        node_properties: {}
        private_ipv4: 10.128.7.218
        private_ipv6: null
        provider: regiocloud-a
        public_ipv4: 81.163.193.42
        public_ipv6: 2a13:1a81:8000:3124:f816:3eff:fea1:e864
        region: null
        slot: null
  vars:
    zuul:
      _inheritance_path:
      - '<Job base explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/zuul-config/zuul.d/jobs.yaml@main#2>'
      - '<Job python-black explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/zuul-jobs/.zuul.yaml@main#54>'
      - '<Job python-black explicit: None implied: None source: osism/release/.zuul.yaml@main#14>'
      ansible_version: '9'
      attempts: 1
      branch: main
      build: 0ff9054e3ed84ab19cf9652ce0ffc356
      build_refs:
      - branch: main
        change: '2561'
        change_message: "Add drift-detection tooling (check-drift) to the release
          repo\n\n## What\n\nAdds `check-drift.py`, a plugin-based drift detector
          for OSISM's image and\nversion wiring, and wires it into CI. It catches
          divergence \u2014 *when it is\nintroduced* \u2014 before it breaks a deploy
          or a gate, across two check groups\n(`--group image | kolla | all`).\n\n**Image
          version pins** \u2014 follows an image's version down the chain\nrelease
          `base.yml` \u2192 rendered manager `images.yml` \u2192 role defaults:\n\n-
          `release_vs_manager` \u2014 the rendered manager `images.yml` disagrees
          with the release pin\n- `role_shadows` \u2014 a role-default tag shadows
          the release pin (LIVE = the role default actually deploys; DORMANT = the
          release pin wins)\n- `role_unpinned` \u2014 a role-default tag with no release
          pin\n- `image_orphan` \u2014 a manager image alias is emitted but consumed
          by no role or playbook\n\n**Kolla container-image toolchain** \u2014 divergence
          between the OSISM consumer repos\n(`defaults`, `release`, `generics`, the
          container-image repos) and upstream\n`openstack/kolla` / `openstack/kolla-ansible`:\n\n-
          `kolla_enablement_orphan` \u2014 OSISM enables a service upstream no longer
          defines\n- `kolla_orphan_config` \u2014 dead companion vars / image blocks
          for an orphaned service\n- `kolla_secrets_orphan` \u2014 an OSISM kolla
          secret with no upstream counterpart\n- `kolla_enablement_build` \u2014 an
          enabled, upstream-buildable service missing from the build set\n- `kolla_version_chain_upstream`
          \u2014 an upstream `docker/` service with no version-pin line\n- `kolla_version_chain_inner`
          \u2014 a referenced version pin that resolves to nothing (inert pin)\n-
          `kolla_inventory` \u2014 an upstream inventory group missing from the OSISM
          `50/51-kolla` files\n\nThe framework lands first (empty registry, flags
          nothing on its own), then the\nplugins in lifecycle order, each reviewable
          in isolation. Sources resolve\nremotely (GitHub) by default, so the detector
          runs anywhere including CI;\n`--base-dir` reads local checkouts. An allowlist
          marks intentional exceptions\n(each requiring a reason); entries that match
          nothing are reported stale, so\nsuppressions can't silently rot. Exit codes:
          0 clean / 1 drift or stale / 2 input\nerror. Full reference: `docs/check-drift-image.md`,
          `docs/check-drift-kolla.md`.\n\n## Commits (each independently green \u2014
          unit tests pass at every commit)\n\n- `drift: add plugin framework and driver`\n-
          `drift: add image-drift plugins`\n- `drift: add kolla-drift plugins`\n-
          `drift: add drift-detection docs`\n- `drift: wire tox and Zuul CI`\n\n##
          CI\n\n- **`release-tox-drift-test`** \u2014 the unit suite (`tox -e drift-test`,
          233 tests).\n  Runs in **check** (gates this PR) and periodic-daily.\n-
          **`release-tox-drift`** \u2014 the detector (`tox -e drift`, `check-drift.py\n
          \ --group all`). Runs **periodic-daily only**: it reads live upstream state,
          so\n  it's unfit as a PR gate. It exits non-zero when drift exists, so the
          daily job\n  goes **red** with the report in its log.\n\n## What this finds
          in practice\n\nRun against the live OSISM repos today, the detector reports
          **20 actionable\nitems, 41 allowlisted, 0 stale** \u2014 a real backlog,
          now surfaced in one place:\n\n- **image pins** \u2014 1 rendered-vs-release
          disagreement (`ara_server`), 3 LIVE\n  role-default shadows that actually
          deploy (`dnsmasq`, `scaphandre`, `stepca`),\n  9 DORMANT lags, 4 unpinned
          tags (`httpd`, `httpd_data`, `kepler`,\n  `thanos_sidecar`), and 3 orphaned
          manager aliases (`nginx`, `osism_netbox`,\n  `registry`).\n- **kolla toolchain**
          \u2014 clean today: every finding matches an allowlist entry and\n  the
          baseline is green.\n\nThe kolla checks have already driven a wave of cleanups
          across the consumer repos,\ntaking the kolla actionable-drift count from
          81 to 0 (all merged):\n\n- osism/defaults#291\n- osism/container-image-kolla-ansible#924\n-
          osism/release#2551\n- osism/container-images-kolla#745\n- osism/generics#599\n-
          osism/generics#601\n\nIt self-audits its allowlist too: an entry that no
          longer suppresses real drift is\na hard error (exit 1), so suppressions
          can't silently rot.\n\nBecause the detector is daily-only, this output does
          not appear in this PR's\nchecks. Full `release-tox-drift` report against
          current upstream:\n\n<details>\n<summary><code>check-drift.py --group all</code>
          \u2014 current output</summary>\n\n```\nChecks follow an image's version
          pin: release base.yml \u2192 rendered manager images.yml \u2192 role defaults.\n\nrelease_vs_manager
          \u2014 1 image tags in the rendered manager images.yml\ndisagree with the
          release base.yml pins:\n\n    ara_server\n\n  Fix: re-render environments/manager/images.yml
          from the current release,\n       or allowlist the entry if the divergence
          is intentional (e.g. a\n       rolling-release image pinned to 'latest').\n
          \ Refs: release/latest/base.yml\n        testbed/environments/manager/images.yml\n\nrole_shadows
          \u2014 9 DORMANT \u2014 overridden by the rendered images.yml; the\nrelease
          pin wins at deploy:\n\n    adminer_tag (4.7 \u2192 5.4.2)   roles/adminer/defaults/main.yml\n
          \   ara_server_mariadb_tag (11.8.3 \u2192 11.8.4)   roles/manager/defaults/main.yml\n
          \   manager_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/manager/defaults/main.yml\n
          \   vault_tag (1.21.0 \u2192 1.21.4)   roles/manager/defaults/main.yml\n
          \   netbox_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/netbox/defaults/main.yml\n
          \   postgres_tag (16.10-alpine \u2192 16.11-alpine)   roles/netbox/defaults/main.yml\n
          \   nexus_tag (3.82.0 \u2192 3.93.1)   roles/nexus/defaults/main.yml\n    phpmyadmin_tag
          (5.2 \u2192 5.2.3)   roles/phpmyadmin/defaults/main.yml\n    traefik_tag
          (v3.4.4 \u2192 v3.5.0)   roles/traefik/defaults/main.yml\n\n  Fix: lower
          priority; sync when convenient.\n  Refs: release/latest/base.yml\n\nrole_shadows
          \u2014 3 LIVE \u2014 no images.yml override; the role default is what\nactually
          deploys:\n\n    dnsmasq_tag (2.90 \u2192 2.91)   roles/dnsmasq/defaults/main.yml\n
          \   scaphandre_tag (1.0.0 \u2192 1.0.2)   roles/scaphandre/defaults/main.yml\n
          \   stepca_tag (0.28.4 \u2192 0.30.2)   roles/stepca/defaults/main.yml\n\n
          \ Fix: add `<alias>_tag`/`<alias>_image` to the manager render template\n
          \      (images.yml.j2) so the latest/base.yml pin governs the deployed\n
          \      version.\n  Refs: release/latest/base.yml\n\nrole_unpinned \u2014
          4 <alias>_tag pins in role defaults with no release base.yml\npin:\n\n    httpd_tag
          (alpine, no release pin)   roles/httpd/defaults/main.yml\n    httpd_data_tag
          (latest, no release pin)   roles/httpd/defaults/main.yml\n    kepler_tag
          (v0.11.2, no release pin)   roles/kepler/defaults/main.yml\n    thanos_sidecar_tag
          (v0.32.5, no release pin)   roles/thanos_sidecar/defaults/main.yml\n\n  Fix:
          add a pin to release base.yml (and wire <alias>_tag into the manager\n       render
          template) to make it release-managed, or allowlist it if the\n       image
          is intentionally role-managed.\n  Refs: release/latest/base.yml\n\nimage_orphan
          \u2014 3 image vars emitted by the manager images.yml that no role\nor playbook
          consumes:\n\n    nginx, osism_netbox, registry\n\n  Fix: remove the orphaned
          <alias>_tag/<alias>_image (and its etc/images.yml\n       + role-default
          remnants), or allowlist it if the image is genuinely\n       consumed in
          a form this scan misses \u2014 it flags an alias only when no\n       literal
          {{ <alias>_image }} reference is found in the scanned\n       roles/playbooks.\n
          \ Refs: ansible-collection-services/roles/, ansible-playbooks-manager/playbooks/\n
          \       generics/environments/manager/images.yml\n\nSummary: 20 to act on,
          41 allowlisted, 0 stale allowlist entries (61 total)\n```\n</details>\n\n###
          How to read it\n- **DORMANT (role_shadows)** \u2014 the release pin already
          governs at deploy; role\n  defaults just lag. Low priority.\n- **LIVE (role_shadows)**
          \u2014 `dnsmasq`, `scaphandre`, `stepca`: the role default\n  actually deploys;
          fix by wiring the alias into `images.yml.j2`.\n- **role_unpinned** \u2014
          `httpd*`, `kepler`, `thanos_sidecar`: no release pin.\n- **image_orphan**
          \u2014 `nginx`, `osism_netbox`, `registry`: emitted by the manager\n  template,
          consumed nowhere.\n- **kolla group** \u2014 no actionable findings today;
          matches are allowlisted and the\n  baseline is clean.\n\n> The detector
          runs unauthenticated against GitHub (no token support in\n> `source.py`),
          same as generics' existing daily job. A heavy run can occasionally\n> hit
          API rate limits and fail without a report; adding `GITHUB_TOKEN` support
          is a\n> sensible follow-up.\n"
        change_url: https://github.com/osism/release/pull/2561
        commit_id: d21d3d04334bc1fae00a164455753a392ba9cd3a
        patchset: d21d3d04334bc1fae00a164455753a392ba9cd3a
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/release
          name: osism/release
          short_name: release
        src_dir: src/github.com/osism/release
        topic: null
      buildset: 7800996997684a368987c6b49f3401f4
      buildset_refs:
      - branch: main
        change: '2561'
        change_message: "Add drift-detection tooling (check-drift) to the release
          repo\n\n## What\n\nAdds `check-drift.py`, a plugin-based drift detector
          for OSISM's image and\nversion wiring, and wires it into CI. It catches
          divergence \u2014 *when it is\nintroduced* \u2014 before it breaks a deploy
          or a gate, across two check groups\n(`--group image | kolla | all`).\n\n**Image
          version pins** \u2014 follows an image's version down the chain\nrelease
          `base.yml` \u2192 rendered manager `images.yml` \u2192 role defaults:\n\n-
          `release_vs_manager` \u2014 the rendered manager `images.yml` disagrees
          with the release pin\n- `role_shadows` \u2014 a role-default tag shadows
          the release pin (LIVE = the role default actually deploys; DORMANT = the
          release pin wins)\n- `role_unpinned` \u2014 a role-default tag with no release
          pin\n- `image_orphan` \u2014 a manager image alias is emitted but consumed
          by no role or playbook\n\n**Kolla container-image toolchain** \u2014 divergence
          between the OSISM consumer repos\n(`defaults`, `release`, `generics`, the
          container-image repos) and upstream\n`openstack/kolla` / `openstack/kolla-ansible`:\n\n-
          `kolla_enablement_orphan` \u2014 OSISM enables a service upstream no longer
          defines\n- `kolla_orphan_config` \u2014 dead companion vars / image blocks
          for an orphaned service\n- `kolla_secrets_orphan` \u2014 an OSISM kolla
          secret with no upstream counterpart\n- `kolla_enablement_build` \u2014 an
          enabled, upstream-buildable service missing from the build set\n- `kolla_version_chain_upstream`
          \u2014 an upstream `docker/` service with no version-pin line\n- `kolla_version_chain_inner`
          \u2014 a referenced version pin that resolves to nothing (inert pin)\n-
          `kolla_inventory` \u2014 an upstream inventory group missing from the OSISM
          `50/51-kolla` files\n\nThe framework lands first (empty registry, flags
          nothing on its own), then the\nplugins in lifecycle order, each reviewable
          in isolation. Sources resolve\nremotely (GitHub) by default, so the detector
          runs anywhere including CI;\n`--base-dir` reads local checkouts. An allowlist
          marks intentional exceptions\n(each requiring a reason); entries that match
          nothing are reported stale, so\nsuppressions can't silently rot. Exit codes:
          0 clean / 1 drift or stale / 2 input\nerror. Full reference: `docs/check-drift-image.md`,
          `docs/check-drift-kolla.md`.\n\n## Commits (each independently green \u2014
          unit tests pass at every commit)\n\n- `drift: add plugin framework and driver`\n-
          `drift: add image-drift plugins`\n- `drift: add kolla-drift plugins`\n-
          `drift: add drift-detection docs`\n- `drift: wire tox and Zuul CI`\n\n##
          CI\n\n- **`release-tox-drift-test`** \u2014 the unit suite (`tox -e drift-test`,
          233 tests).\n  Runs in **check** (gates this PR) and periodic-daily.\n-
          **`release-tox-drift`** \u2014 the detector (`tox -e drift`, `check-drift.py\n
          \ --group all`). Runs **periodic-daily only**: it reads live upstream state,
          so\n  it's unfit as a PR gate. It exits non-zero when drift exists, so the
          daily job\n  goes **red** with the report in its log.\n\n## What this finds
          in practice\n\nRun against the live OSISM repos today, the detector reports
          **20 actionable\nitems, 41 allowlisted, 0 stale** \u2014 a real backlog,
          now surfaced in one place:\n\n- **image pins** \u2014 1 rendered-vs-release
          disagreement (`ara_server`), 3 LIVE\n  role-default shadows that actually
          deploy (`dnsmasq`, `scaphandre`, `stepca`),\n  9 DORMANT lags, 4 unpinned
          tags (`httpd`, `httpd_data`, `kepler`,\n  `thanos_sidecar`), and 3 orphaned
          manager aliases (`nginx`, `osism_netbox`,\n  `registry`).\n- **kolla toolchain**
          \u2014 clean today: every finding matches an allowlist entry and\n  the
          baseline is green.\n\nThe kolla checks have already driven a wave of cleanups
          across the consumer repos,\ntaking the kolla actionable-drift count from
          81 to 0 (all merged):\n\n- osism/defaults#291\n- osism/container-image-kolla-ansible#924\n-
          osism/release#2551\n- osism/container-images-kolla#745\n- osism/generics#599\n-
          osism/generics#601\n\nIt self-audits its allowlist too: an entry that no
          longer suppresses real drift is\na hard error (exit 1), so suppressions
          can't silently rot.\n\nBecause the detector is daily-only, this output does
          not appear in this PR's\nchecks. Full `release-tox-drift` report against
          current upstream:\n\n<details>\n<summary><code>check-drift.py --group all</code>
          \u2014 current output</summary>\n\n```\nChecks follow an image's version
          pin: release base.yml \u2192 rendered manager images.yml \u2192 role defaults.\n\nrelease_vs_manager
          \u2014 1 image tags in the rendered manager images.yml\ndisagree with the
          release base.yml pins:\n\n    ara_server\n\n  Fix: re-render environments/manager/images.yml
          from the current release,\n       or allowlist the entry if the divergence
          is intentional (e.g. a\n       rolling-release image pinned to 'latest').\n
          \ Refs: release/latest/base.yml\n        testbed/environments/manager/images.yml\n\nrole_shadows
          \u2014 9 DORMANT \u2014 overridden by the rendered images.yml; the\nrelease
          pin wins at deploy:\n\n    adminer_tag (4.7 \u2192 5.4.2)   roles/adminer/defaults/main.yml\n
          \   ara_server_mariadb_tag (11.8.3 \u2192 11.8.4)   roles/manager/defaults/main.yml\n
          \   manager_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/manager/defaults/main.yml\n
          \   vault_tag (1.21.0 \u2192 1.21.4)   roles/manager/defaults/main.yml\n
          \   netbox_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/netbox/defaults/main.yml\n
          \   postgres_tag (16.10-alpine \u2192 16.11-alpine)   roles/netbox/defaults/main.yml\n
          \   nexus_tag (3.82.0 \u2192 3.93.1)   roles/nexus/defaults/main.yml\n    phpmyadmin_tag
          (5.2 \u2192 5.2.3)   roles/phpmyadmin/defaults/main.yml\n    traefik_tag
          (v3.4.4 \u2192 v3.5.0)   roles/traefik/defaults/main.yml\n\n  Fix: lower
          priority; sync when convenient.\n  Refs: release/latest/base.yml\n\nrole_shadows
          \u2014 3 LIVE \u2014 no images.yml override; the role default is what\nactually
          deploys:\n\n    dnsmasq_tag (2.90 \u2192 2.91)   roles/dnsmasq/defaults/main.yml\n
          \   scaphandre_tag (1.0.0 \u2192 1.0.2)   roles/scaphandre/defaults/main.yml\n
          \   stepca_tag (0.28.4 \u2192 0.30.2)   roles/stepca/defaults/main.yml\n\n
          \ Fix: add `<alias>_tag`/`<alias>_image` to the manager render template\n
          \      (images.yml.j2) so the latest/base.yml pin governs the deployed\n
          \      version.\n  Refs: release/latest/base.yml\n\nrole_unpinned \u2014
          4 <alias>_tag pins in role defaults with no release base.yml\npin:\n\n    httpd_tag
          (alpine, no release pin)   roles/httpd/defaults/main.yml\n    httpd_data_tag
          (latest, no release pin)   roles/httpd/defaults/main.yml\n    kepler_tag
          (v0.11.2, no release pin)   roles/kepler/defaults/main.yml\n    thanos_sidecar_tag
          (v0.32.5, no release pin)   roles/thanos_sidecar/defaults/main.yml\n\n  Fix:
          add a pin to release base.yml (and wire <alias>_tag into the manager\n       render
          template) to make it release-managed, or allowlist it if the\n       image
          is intentionally role-managed.\n  Refs: release/latest/base.yml\n\nimage_orphan
          \u2014 3 image vars emitted by the manager images.yml that no role\nor playbook
          consumes:\n\n    nginx, osism_netbox, registry\n\n  Fix: remove the orphaned
          <alias>_tag/<alias>_image (and its etc/images.yml\n       + role-default
          remnants), or allowlist it if the image is genuinely\n       consumed in
          a form this scan misses \u2014 it flags an alias only when no\n       literal
          {{ <alias>_image }} reference is found in the scanned\n       roles/playbooks.\n
          \ Refs: ansible-collection-services/roles/, ansible-playbooks-manager/playbooks/\n
          \       generics/environments/manager/images.yml\n\nSummary: 20 to act on,
          41 allowlisted, 0 stale allowlist entries (61 total)\n```\n</details>\n\n###
          How to read it\n- **DORMANT (role_shadows)** \u2014 the release pin already
          governs at deploy; role\n  defaults just lag. Low priority.\n- **LIVE (role_shadows)**
          \u2014 `dnsmasq`, `scaphandre`, `stepca`: the role default\n  actually deploys;
          fix by wiring the alias into `images.yml.j2`.\n- **role_unpinned** \u2014
          `httpd*`, `kepler`, `thanos_sidecar`: no release pin.\n- **image_orphan**
          \u2014 `nginx`, `osism_netbox`, `registry`: emitted by the manager\n  template,
          consumed nowhere.\n- **kolla group** \u2014 no actionable findings today;
          matches are allowlisted and the\n  baseline is clean.\n\n> The detector
          runs unauthenticated against GitHub (no token support in\n> `source.py`),
          same as generics' existing daily job. A heavy run can occasionally\n> hit
          API rate limits and fail without a report; adding `GITHUB_TOKEN` support
          is a\n> sensible follow-up.\n"
        change_url: https://github.com/osism/release/pull/2561
        commit_id: d21d3d04334bc1fae00a164455753a392ba9cd3a
        patchset: d21d3d04334bc1fae00a164455753a392ba9cd3a
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/release
          name: osism/release
          short_name: release
        src_dir: src/github.com/osism/release
        topic: null
      change: '2561'
      change_message: "Add drift-detection tooling (check-drift) to the release repo\n\n##
        What\n\nAdds `check-drift.py`, a plugin-based drift detector for OSISM's image
        and\nversion wiring, and wires it into CI. It catches divergence \u2014 *when
        it is\nintroduced* \u2014 before it breaks a deploy or a gate, across two
        check groups\n(`--group image | kolla | all`).\n\n**Image version pins** \u2014
        follows an image's version down the chain\nrelease `base.yml` \u2192 rendered
        manager `images.yml` \u2192 role defaults:\n\n- `release_vs_manager` \u2014
        the rendered manager `images.yml` disagrees with the release pin\n- `role_shadows`
        \u2014 a role-default tag shadows the release pin (LIVE = the role default
        actually deploys; DORMANT = the release pin wins)\n- `role_unpinned` \u2014
        a role-default tag with no release pin\n- `image_orphan` \u2014 a manager
        image alias is emitted but consumed by no role or playbook\n\n**Kolla container-image
        toolchain** \u2014 divergence between the OSISM consumer repos\n(`defaults`,
        `release`, `generics`, the container-image repos) and upstream\n`openstack/kolla`
        / `openstack/kolla-ansible`:\n\n- `kolla_enablement_orphan` \u2014 OSISM enables
        a service upstream no longer defines\n- `kolla_orphan_config` \u2014 dead
        companion vars / image blocks for an orphaned service\n- `kolla_secrets_orphan`
        \u2014 an OSISM kolla secret with no upstream counterpart\n- `kolla_enablement_build`
        \u2014 an enabled, upstream-buildable service missing from the build set\n-
        `kolla_version_chain_upstream` \u2014 an upstream `docker/` service with no
        version-pin line\n- `kolla_version_chain_inner` \u2014 a referenced version
        pin that resolves to nothing (inert pin)\n- `kolla_inventory` \u2014 an upstream
        inventory group missing from the OSISM `50/51-kolla` files\n\nThe framework
        lands first (empty registry, flags nothing on its own), then the\nplugins
        in lifecycle order, each reviewable in isolation. Sources resolve\nremotely
        (GitHub) by default, so the detector runs anywhere including CI;\n`--base-dir`
        reads local checkouts. An allowlist marks intentional exceptions\n(each requiring
        a reason); entries that match nothing are reported stale, so\nsuppressions
        can't silently rot. Exit codes: 0 clean / 1 drift or stale / 2 input\nerror.
        Full reference: `docs/check-drift-image.md`, `docs/check-drift-kolla.md`.\n\n##
        Commits (each independently green \u2014 unit tests pass at every commit)\n\n-
        `drift: add plugin framework and driver`\n- `drift: add image-drift plugins`\n-
        `drift: add kolla-drift plugins`\n- `drift: add drift-detection docs`\n- `drift:
        wire tox and Zuul CI`\n\n## CI\n\n- **`release-tox-drift-test`** \u2014 the
        unit suite (`tox -e drift-test`, 233 tests).\n  Runs in **check** (gates this
        PR) and periodic-daily.\n- **`release-tox-drift`** \u2014 the detector (`tox
        -e drift`, `check-drift.py\n  --group all`). Runs **periodic-daily only**:
        it reads live upstream state, so\n  it's unfit as a PR gate. It exits non-zero
        when drift exists, so the daily job\n  goes **red** with the report in its
        log.\n\n## What this finds in practice\n\nRun against the live OSISM repos
        today, the detector reports **20 actionable\nitems, 41 allowlisted, 0 stale**
        \u2014 a real backlog, now surfaced in one place:\n\n- **image pins** \u2014
        1 rendered-vs-release disagreement (`ara_server`), 3 LIVE\n  role-default
        shadows that actually deploy (`dnsmasq`, `scaphandre`, `stepca`),\n  9 DORMANT
        lags, 4 unpinned tags (`httpd`, `httpd_data`, `kepler`,\n  `thanos_sidecar`),
        and 3 orphaned manager aliases (`nginx`, `osism_netbox`,\n  `registry`).\n-
        **kolla toolchain** \u2014 clean today: every finding matches an allowlist
        entry and\n  the baseline is green.\n\nThe kolla checks have already driven
        a wave of cleanups across the consumer repos,\ntaking the kolla actionable-drift
        count from 81 to 0 (all merged):\n\n- osism/defaults#291\n- osism/container-image-kolla-ansible#924\n-
        osism/release#2551\n- osism/container-images-kolla#745\n- osism/generics#599\n-
        osism/generics#601\n\nIt self-audits its allowlist too: an entry that no longer
        suppresses real drift is\na hard error (exit 1), so suppressions can't silently
        rot.\n\nBecause the detector is daily-only, this output does not appear in
        this PR's\nchecks. Full `release-tox-drift` report against current upstream:\n\n<details>\n<summary><code>check-drift.py
        --group all</code> \u2014 current output</summary>\n\n```\nChecks follow an
        image's version pin: release base.yml \u2192 rendered manager images.yml \u2192
        role defaults.\n\nrelease_vs_manager \u2014 1 image tags in the rendered manager
        images.yml\ndisagree with the release base.yml pins:\n\n    ara_server\n\n
        \ Fix: re-render environments/manager/images.yml from the current release,\n
        \      or allowlist the entry if the divergence is intentional (e.g. a\n       rolling-release
        image pinned to 'latest').\n  Refs: release/latest/base.yml\n        testbed/environments/manager/images.yml\n\nrole_shadows
        \u2014 9 DORMANT \u2014 overridden by the rendered images.yml; the\nrelease
        pin wins at deploy:\n\n    adminer_tag (4.7 \u2192 5.4.2)   roles/adminer/defaults/main.yml\n
        \   ara_server_mariadb_tag (11.8.3 \u2192 11.8.4)   roles/manager/defaults/main.yml\n
        \   manager_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/manager/defaults/main.yml\n
        \   vault_tag (1.21.0 \u2192 1.21.4)   roles/manager/defaults/main.yml\n    netbox_redis_tag
        (7.4.6-alpine \u2192 7.4.7-alpine)   roles/netbox/defaults/main.yml\n    postgres_tag
        (16.10-alpine \u2192 16.11-alpine)   roles/netbox/defaults/main.yml\n    nexus_tag
        (3.82.0 \u2192 3.93.1)   roles/nexus/defaults/main.yml\n    phpmyadmin_tag
        (5.2 \u2192 5.2.3)   roles/phpmyadmin/defaults/main.yml\n    traefik_tag (v3.4.4
        \u2192 v3.5.0)   roles/traefik/defaults/main.yml\n\n  Fix: lower priority;
        sync when convenient.\n  Refs: release/latest/base.yml\n\nrole_shadows \u2014
        3 LIVE \u2014 no images.yml override; the role default is what\nactually deploys:\n\n
        \   dnsmasq_tag (2.90 \u2192 2.91)   roles/dnsmasq/defaults/main.yml\n    scaphandre_tag
        (1.0.0 \u2192 1.0.2)   roles/scaphandre/defaults/main.yml\n    stepca_tag
        (0.28.4 \u2192 0.30.2)   roles/stepca/defaults/main.yml\n\n  Fix: add `<alias>_tag`/`<alias>_image`
        to the manager render template\n       (images.yml.j2) so the latest/base.yml
        pin governs the deployed\n       version.\n  Refs: release/latest/base.yml\n\nrole_unpinned
        \u2014 4 <alias>_tag pins in role defaults with no release base.yml\npin:\n\n
        \   httpd_tag (alpine, no release pin)   roles/httpd/defaults/main.yml\n    httpd_data_tag
        (latest, no release pin)   roles/httpd/defaults/main.yml\n    kepler_tag (v0.11.2,
        no release pin)   roles/kepler/defaults/main.yml\n    thanos_sidecar_tag (v0.32.5,
        no release pin)   roles/thanos_sidecar/defaults/main.yml\n\n  Fix: add a pin
        to release base.yml (and wire <alias>_tag into the manager\n       render
        template) to make it release-managed, or allowlist it if the\n       image
        is intentionally role-managed.\n  Refs: release/latest/base.yml\n\nimage_orphan
        \u2014 3 image vars emitted by the manager images.yml that no role\nor playbook
        consumes:\n\n    nginx, osism_netbox, registry\n\n  Fix: remove the orphaned
        <alias>_tag/<alias>_image (and its etc/images.yml\n       + role-default remnants),
        or allowlist it if the image is genuinely\n       consumed in a form this
        scan misses \u2014 it flags an alias only when no\n       literal {{ <alias>_image
        }} reference is found in the scanned\n       roles/playbooks.\n  Refs: ansible-collection-services/roles/,
        ansible-playbooks-manager/playbooks/\n        generics/environments/manager/images.yml\n\nSummary:
        20 to act on, 41 allowlisted, 0 stale allowlist entries (61 total)\n```\n</details>\n\n###
        How to read it\n- **DORMANT (role_shadows)** \u2014 the release pin already
        governs at deploy; role\n  defaults just lag. Low priority.\n- **LIVE (role_shadows)**
        \u2014 `dnsmasq`, `scaphandre`, `stepca`: the role default\n  actually deploys;
        fix by wiring the alias into `images.yml.j2`.\n- **role_unpinned** \u2014
        `httpd*`, `kepler`, `thanos_sidecar`: no release pin.\n- **image_orphan**
        \u2014 `nginx`, `osism_netbox`, `registry`: emitted by the manager\n  template,
        consumed nowhere.\n- **kolla group** \u2014 no actionable findings today;
        matches are allowlisted and the\n  baseline is clean.\n\n> The detector runs
        unauthenticated against GitHub (no token support in\n> `source.py`), same
        as generics' existing daily job. A heavy run can occasionally\n> hit API rate
        limits and fail without a report; adding `GITHUB_TOKEN` support is a\n> sensible
        follow-up.\n"
      change_url: https://github.com/osism/release/pull/2561
      child_jobs: []
      commit_id: d21d3d04334bc1fae00a164455753a392ba9cd3a
      event_id: 97401740-75e0-11f1-8e7b-8271d34f5c6a
      executor:
        hostname: 521e9411259a
        inventory_file: /var/lib/zuul/builds/0ff9054e3ed84ab19cf9652ce0ffc356/ansible/inventory.yaml
        log_root: /var/lib/zuul/builds/0ff9054e3ed84ab19cf9652ce0ffc356/work/logs
        result_data_file: /var/lib/zuul/builds/0ff9054e3ed84ab19cf9652ce0ffc356/work/results.json
        src_root: /var/lib/zuul/builds/0ff9054e3ed84ab19cf9652ce0ffc356/work/src
        work_root: /var/lib/zuul/builds/0ff9054e3ed84ab19cf9652ce0ffc356/work
      include_vars: []
      items:
      - branch: main
        change: '2561'
        change_message: "Add drift-detection tooling (check-drift) to the release
          repo\n\n## What\n\nAdds `check-drift.py`, a plugin-based drift detector
          for OSISM's image and\nversion wiring, and wires it into CI. It catches
          divergence \u2014 *when it is\nintroduced* \u2014 before it breaks a deploy
          or a gate, across two check groups\n(`--group image | kolla | all`).\n\n**Image
          version pins** \u2014 follows an image's version down the chain\nrelease
          `base.yml` \u2192 rendered manager `images.yml` \u2192 role defaults:\n\n-
          `release_vs_manager` \u2014 the rendered manager `images.yml` disagrees
          with the release pin\n- `role_shadows` \u2014 a role-default tag shadows
          the release pin (LIVE = the role default actually deploys; DORMANT = the
          release pin wins)\n- `role_unpinned` \u2014 a role-default tag with no release
          pin\n- `image_orphan` \u2014 a manager image alias is emitted but consumed
          by no role or playbook\n\n**Kolla container-image toolchain** \u2014 divergence
          between the OSISM consumer repos\n(`defaults`, `release`, `generics`, the
          container-image repos) and upstream\n`openstack/kolla` / `openstack/kolla-ansible`:\n\n-
          `kolla_enablement_orphan` \u2014 OSISM enables a service upstream no longer
          defines\n- `kolla_orphan_config` \u2014 dead companion vars / image blocks
          for an orphaned service\n- `kolla_secrets_orphan` \u2014 an OSISM kolla
          secret with no upstream counterpart\n- `kolla_enablement_build` \u2014 an
          enabled, upstream-buildable service missing from the build set\n- `kolla_version_chain_upstream`
          \u2014 an upstream `docker/` service with no version-pin line\n- `kolla_version_chain_inner`
          \u2014 a referenced version pin that resolves to nothing (inert pin)\n-
          `kolla_inventory` \u2014 an upstream inventory group missing from the OSISM
          `50/51-kolla` files\n\nThe framework lands first (empty registry, flags
          nothing on its own), then the\nplugins in lifecycle order, each reviewable
          in isolation. Sources resolve\nremotely (GitHub) by default, so the detector
          runs anywhere including CI;\n`--base-dir` reads local checkouts. An allowlist
          marks intentional exceptions\n(each requiring a reason); entries that match
          nothing are reported stale, so\nsuppressions can't silently rot. Exit codes:
          0 clean / 1 drift or stale / 2 input\nerror. Full reference: `docs/check-drift-image.md`,
          `docs/check-drift-kolla.md`.\n\n## Commits (each independently green \u2014
          unit tests pass at every commit)\n\n- `drift: add plugin framework and driver`\n-
          `drift: add image-drift plugins`\n- `drift: add kolla-drift plugins`\n-
          `drift: add drift-detection docs`\n- `drift: wire tox and Zuul CI`\n\n##
          CI\n\n- **`release-tox-drift-test`** \u2014 the unit suite (`tox -e drift-test`,
          233 tests).\n  Runs in **check** (gates this PR) and periodic-daily.\n-
          **`release-tox-drift`** \u2014 the detector (`tox -e drift`, `check-drift.py\n
          \ --group all`). Runs **periodic-daily only**: it reads live upstream state,
          so\n  it's unfit as a PR gate. It exits non-zero when drift exists, so the
          daily job\n  goes **red** with the report in its log.\n\n## What this finds
          in practice\n\nRun against the live OSISM repos today, the detector reports
          **20 actionable\nitems, 41 allowlisted, 0 stale** \u2014 a real backlog,
          now surfaced in one place:\n\n- **image pins** \u2014 1 rendered-vs-release
          disagreement (`ara_server`), 3 LIVE\n  role-default shadows that actually
          deploy (`dnsmasq`, `scaphandre`, `stepca`),\n  9 DORMANT lags, 4 unpinned
          tags (`httpd`, `httpd_data`, `kepler`,\n  `thanos_sidecar`), and 3 orphaned
          manager aliases (`nginx`, `osism_netbox`,\n  `registry`).\n- **kolla toolchain**
          \u2014 clean today: every finding matches an allowlist entry and\n  the
          baseline is green.\n\nThe kolla checks have already driven a wave of cleanups
          across the consumer repos,\ntaking the kolla actionable-drift count from
          81 to 0 (all merged):\n\n- osism/defaults#291\n- osism/container-image-kolla-ansible#924\n-
          osism/release#2551\n- osism/container-images-kolla#745\n- osism/generics#599\n-
          osism/generics#601\n\nIt self-audits its allowlist too: an entry that no
          longer suppresses real drift is\na hard error (exit 1), so suppressions
          can't silently rot.\n\nBecause the detector is daily-only, this output does
          not appear in this PR's\nchecks. Full `release-tox-drift` report against
          current upstream:\n\n<details>\n<summary><code>check-drift.py --group all</code>
          \u2014 current output</summary>\n\n```\nChecks follow an image's version
          pin: release base.yml \u2192 rendered manager images.yml \u2192 role defaults.\n\nrelease_vs_manager
          \u2014 1 image tags in the rendered manager images.yml\ndisagree with the
          release base.yml pins:\n\n    ara_server\n\n  Fix: re-render environments/manager/images.yml
          from the current release,\n       or allowlist the entry if the divergence
          is intentional (e.g. a\n       rolling-release image pinned to 'latest').\n
          \ Refs: release/latest/base.yml\n        testbed/environments/manager/images.yml\n\nrole_shadows
          \u2014 9 DORMANT \u2014 overridden by the rendered images.yml; the\nrelease
          pin wins at deploy:\n\n    adminer_tag (4.7 \u2192 5.4.2)   roles/adminer/defaults/main.yml\n
          \   ara_server_mariadb_tag (11.8.3 \u2192 11.8.4)   roles/manager/defaults/main.yml\n
          \   manager_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/manager/defaults/main.yml\n
          \   vault_tag (1.21.0 \u2192 1.21.4)   roles/manager/defaults/main.yml\n
          \   netbox_redis_tag (7.4.6-alpine \u2192 7.4.7-alpine)   roles/netbox/defaults/main.yml\n
          \   postgres_tag (16.10-alpine \u2192 16.11-alpine)   roles/netbox/defaults/main.yml\n
          \   nexus_tag (3.82.0 \u2192 3.93.1)   roles/nexus/defaults/main.yml\n    phpmyadmin_tag
          (5.2 \u2192 5.2.3)   roles/phpmyadmin/defaults/main.yml\n    traefik_tag
          (v3.4.4 \u2192 v3.5.0)   roles/traefik/defaults/main.yml\n\n  Fix: lower
          priority; sync when convenient.\n  Refs: release/latest/base.yml\n\nrole_shadows
          \u2014 3 LIVE \u2014 no images.yml override; the role default is what\nactually
          deploys:\n\n    dnsmasq_tag (2.90 \u2192 2.91)   roles/dnsmasq/defaults/main.yml\n
          \   scaphandre_tag (1.0.0 \u2192 1.0.2)   roles/scaphandre/defaults/main.yml\n
          \   stepca_tag (0.28.4 \u2192 0.30.2)   roles/stepca/defaults/main.yml\n\n
          \ Fix: add `<alias>_tag`/`<alias>_image` to the manager render template\n
          \      (images.yml.j2) so the latest/base.yml pin governs the deployed\n
          \      version.\n  Refs: release/latest/base.yml\n\nrole_unpinned \u2014
          4 <alias>_tag pins in role defaults with no release base.yml\npin:\n\n    httpd_tag
          (alpine, no release pin)   roles/httpd/defaults/main.yml\n    httpd_data_tag
          (latest, no release pin)   roles/httpd/defaults/main.yml\n    kepler_tag
          (v0.11.2, no release pin)   roles/kepler/defaults/main.yml\n    thanos_sidecar_tag
          (v0.32.5, no release pin)   roles/thanos_sidecar/defaults/main.yml\n\n  Fix:
          add a pin to release base.yml (and wire <alias>_tag into the manager\n       render
          template) to make it release-managed, or allowlist it if the\n       image
          is intentionally role-managed.\n  Refs: release/latest/base.yml\n\nimage_orphan
          \u2014 3 image vars emitted by the manager images.yml that no role\nor playbook
          consumes:\n\n    nginx, osism_netbox, registry\n\n  Fix: remove the orphaned
          <alias>_tag/<alias>_image (and its etc/images.yml\n       + role-default
          remnants), or allowlist it if the image is genuinely\n       consumed in
          a form this scan misses \u2014 it flags an alias only when no\n       literal
          {{ <alias>_image }} reference is found in the scanned\n       roles/playbooks.\n
          \ Refs: ansible-collection-services/roles/, ansible-playbooks-manager/playbooks/\n
          \       generics/environments/manager/images.yml\n\nSummary: 20 to act on,
          41 allowlisted, 0 stale allowlist entries (61 total)\n```\n</details>\n\n###
          How to read it\n- **DORMANT (role_shadows)** \u2014 the release pin already
          governs at deploy; role\n  defaults just lag. Low priority.\n- **LIVE (role_shadows)**
          \u2014 `dnsmasq`, `scaphandre`, `stepca`: the role default\n  actually deploys;
          fix by wiring the alias into `images.yml.j2`.\n- **role_unpinned** \u2014
          `httpd*`, `kepler`, `thanos_sidecar`: no release pin.\n- **image_orphan**
          \u2014 `nginx`, `osism_netbox`, `registry`: emitted by the manager\n  template,
          consumed nowhere.\n- **kolla group** \u2014 no actionable findings today;
          matches are allowlisted and the\n  baseline is clean.\n\n> The detector
          runs unauthenticated against GitHub (no token support in\n> `source.py`),
          same as generics' existing daily job. A heavy run can occasionally\n> hit
          API rate limits and fail without a report; adding `GITHUB_TOKEN` support
          is a\n> sensible follow-up.\n"
        change_url: https://github.com/osism/release/pull/2561
        commit_id: d21d3d04334bc1fae00a164455753a392ba9cd3a
        patchset: d21d3d04334bc1fae00a164455753a392ba9cd3a
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/release
          name: osism/release
          short_name: release
          src_dir: src/github.com/osism/release
        topic: null
      job: python-black
      jobtags: []
      max_attempts: 3
      message: QWRkIGRyaWZ0LWRldGVjdGlvbiB0b29saW5nIChjaGVjay1kcmlmdCkgdG8gdGhlIHJlbGVhc2UgcmVwbwoKIyMgV2hhdAoKQWRkcyBgY2hlY2stZHJpZnQucHlgLCBhIHBsdWdpbi1iYXNlZCBkcmlmdCBkZXRlY3RvciBmb3IgT1NJU00ncyBpbWFnZSBhbmQKdmVyc2lvbiB3aXJpbmcsIGFuZCB3aXJlcyBpdCBpbnRvIENJLiBJdCBjYXRjaGVzIGRpdmVyZ2VuY2Ug4oCUICp3aGVuIGl0IGlzCmludHJvZHVjZWQqIOKAlCBiZWZvcmUgaXQgYnJlYWtzIGEgZGVwbG95IG9yIGEgZ2F0ZSwgYWNyb3NzIHR3byBjaGVjayBncm91cHMKKGAtLWdyb3VwIGltYWdlIHwga29sbGEgfCBhbGxgKS4KCioqSW1hZ2UgdmVyc2lvbiBwaW5zKiog4oCUIGZvbGxvd3MgYW4gaW1hZ2UncyB2ZXJzaW9uIGRvd24gdGhlIGNoYWluCnJlbGVhc2UgYGJhc2UueW1sYCDihpIgcmVuZGVyZWQgbWFuYWdlciBgaW1hZ2VzLnltbGAg4oaSIHJvbGUgZGVmYXVsdHM6CgotIGByZWxlYXNlX3ZzX21hbmFnZXJgIOKAlCB0aGUgcmVuZGVyZWQgbWFuYWdlciBgaW1hZ2VzLnltbGAgZGlzYWdyZWVzIHdpdGggdGhlIHJlbGVhc2UgcGluCi0gYHJvbGVfc2hhZG93c2Ag4oCUIGEgcm9sZS1kZWZhdWx0IHRhZyBzaGFkb3dzIHRoZSByZWxlYXNlIHBpbiAoTElWRSA9IHRoZSByb2xlIGRlZmF1bHQgYWN0dWFsbHkgZGVwbG95czsgRE9STUFOVCA9IHRoZSByZWxlYXNlIHBpbiB3aW5zKQotIGByb2xlX3VucGlubmVkYCDigJQgYSByb2xlLWRlZmF1bHQgdGFnIHdpdGggbm8gcmVsZWFzZSBwaW4KLSBgaW1hZ2Vfb3JwaGFuYCDigJQgYSBtYW5hZ2VyIGltYWdlIGFsaWFzIGlzIGVtaXR0ZWQgYnV0IGNvbnN1bWVkIGJ5IG5vIHJvbGUgb3IgcGxheWJvb2sKCioqS29sbGEgY29udGFpbmVyLWltYWdlIHRvb2xjaGFpbioqIOKAlCBkaXZlcmdlbmNlIGJldHdlZW4gdGhlIE9TSVNNIGNvbnN1bWVyIHJlcG9zCihgZGVmYXVsdHNgLCBgcmVsZWFzZWAsIGBnZW5lcmljc2AsIHRoZSBjb250YWluZXItaW1hZ2UgcmVwb3MpIGFuZCB1cHN0cmVhbQpgb3BlbnN0YWNrL2tvbGxhYCAvIGBvcGVuc3RhY2sva29sbGEtYW5zaWJsZWA6CgotIGBrb2xsYV9lbmFibGVtZW50X29ycGhhbmAg4oCUIE9TSVNNIGVuYWJsZXMgYSBzZXJ2aWNlIHVwc3RyZWFtIG5vIGxvbmdlciBkZWZpbmVzCi0gYGtvbGxhX29ycGhhbl9jb25maWdgIOKAlCBkZWFkIGNvbXBhbmlvbiB2YXJzIC8gaW1hZ2UgYmxvY2tzIGZvciBhbiBvcnBoYW5lZCBzZXJ2aWNlCi0gYGtvbGxhX3NlY3JldHNfb3JwaGFuYCDigJQgYW4gT1NJU00ga29sbGEgc2VjcmV0IHdpdGggbm8gdXBzdHJlYW0gY291bnRlcnBhcnQKLSBga29sbGFfZW5hYmxlbWVudF9idWlsZGAg4oCUIGFuIGVuYWJsZWQsIHVwc3RyZWFtLWJ1aWxkYWJsZSBzZXJ2aWNlIG1pc3NpbmcgZnJvbSB0aGUgYnVpbGQgc2V0Ci0gYGtvbGxhX3ZlcnNpb25fY2hhaW5fdXBzdHJlYW1gIOKAlCBhbiB1cHN0cmVhbSBgZG9ja2VyL2Agc2VydmljZSB3aXRoIG5vIHZlcnNpb24tcGluIGxpbmUKLSBga29sbGFfdmVyc2lvbl9jaGFpbl9pbm5lcmAg4oCUIGEgcmVmZXJlbmNlZCB2ZXJzaW9uIHBpbiB0aGF0IHJlc29sdmVzIHRvIG5vdGhpbmcgKGluZXJ0IHBpbikKLSBga29sbGFfaW52ZW50b3J5YCDigJQgYW4gdXBzdHJlYW0gaW52ZW50b3J5IGdyb3VwIG1pc3NpbmcgZnJvbSB0aGUgT1NJU00gYDUwLzUxLWtvbGxhYCBmaWxlcwoKVGhlIGZyYW1ld29yayBsYW5kcyBmaXJzdCAoZW1wdHkgcmVnaXN0cnksIGZsYWdzIG5vdGhpbmcgb24gaXRzIG93biksIHRoZW4gdGhlCnBsdWdpbnMgaW4gbGlmZWN5Y2xlIG9yZGVyLCBlYWNoIHJldmlld2FibGUgaW4gaXNvbGF0aW9uLiBTb3VyY2VzIHJlc29sdmUKcmVtb3RlbHkgKEdpdEh1YikgYnkgZGVmYXVsdCwgc28gdGhlIGRldGVjdG9yIHJ1bnMgYW55d2hlcmUgaW5jbHVkaW5nIENJOwpgLS1iYXNlLWRpcmAgcmVhZHMgbG9jYWwgY2hlY2tvdXRzLiBBbiBhbGxvd2xpc3QgbWFya3MgaW50ZW50aW9uYWwgZXhjZXB0aW9ucwooZWFjaCByZXF1aXJpbmcgYSByZWFzb24pOyBlbnRyaWVzIHRoYXQgbWF0Y2ggbm90aGluZyBhcmUgcmVwb3J0ZWQgc3RhbGUsIHNvCnN1cHByZXNzaW9ucyBjYW4ndCBzaWxlbnRseSByb3QuIEV4aXQgY29kZXM6IDAgY2xlYW4gLyAxIGRyaWZ0IG9yIHN0YWxlIC8gMiBpbnB1dAplcnJvci4gRnVsbCByZWZlcmVuY2U6IGBkb2NzL2NoZWNrLWRyaWZ0LWltYWdlLm1kYCwgYGRvY3MvY2hlY2stZHJpZnQta29sbGEubWRgLgoKIyMgQ29tbWl0cyAoZWFjaCBpbmRlcGVuZGVudGx5IGdyZWVuIOKAlCB1bml0IHRlc3RzIHBhc3MgYXQgZXZlcnkgY29tbWl0KQoKLSBgZHJpZnQ6IGFkZCBwbHVnaW4gZnJhbWV3b3JrIGFuZCBkcml2ZXJgCi0gYGRyaWZ0OiBhZGQgaW1hZ2UtZHJpZnQgcGx1Z2luc2AKLSBgZHJpZnQ6IGFkZCBrb2xsYS1kcmlmdCBwbHVnaW5zYAotIGBkcmlmdDogYWRkIGRyaWZ0LWRldGVjdGlvbiBkb2NzYAotIGBkcmlmdDogd2lyZSB0b3ggYW5kIFp1dWwgQ0lgCgojIyBDSQoKLSAqKmByZWxlYXNlLXRveC1kcmlmdC10ZXN0YCoqIOKAlCB0aGUgdW5pdCBzdWl0ZSAoYHRveCAtZSBkcmlmdC10ZXN0YCwgMjMzIHRlc3RzKS4KICBSdW5zIGluICoqY2hlY2sqKiAoZ2F0ZXMgdGhpcyBQUikgYW5kIHBlcmlvZGljLWRhaWx5LgotICoqYHJlbGVhc2UtdG94LWRyaWZ0YCoqIOKAlCB0aGUgZGV0ZWN0b3IgKGB0b3ggLWUgZHJpZnRgLCBgY2hlY2stZHJpZnQucHkKICAtLWdyb3VwIGFsbGApLiBSdW5zICoqcGVyaW9kaWMtZGFpbHkgb25seSoqOiBpdCByZWFkcyBsaXZlIHVwc3RyZWFtIHN0YXRlLCBzbwogIGl0J3MgdW5maXQgYXMgYSBQUiBnYXRlLiBJdCBleGl0cyBub24temVybyB3aGVuIGRyaWZ0IGV4aXN0cywgc28gdGhlIGRhaWx5IGpvYgogIGdvZXMgKipyZWQqKiB3aXRoIHRoZSByZXBvcnQgaW4gaXRzIGxvZy4KCiMjIFdoYXQgdGhpcyBmaW5kcyBpbiBwcmFjdGljZQoKUnVuIGFnYWluc3QgdGhlIGxpdmUgT1NJU00gcmVwb3MgdG9kYXksIHRoZSBkZXRlY3RvciByZXBvcnRzICoqMjAgYWN0aW9uYWJsZQppdGVtcywgNDEgYWxsb3dsaXN0ZWQsIDAgc3RhbGUqKiDigJQgYSByZWFsIGJhY2tsb2csIG5vdyBzdXJmYWNlZCBpbiBvbmUgcGxhY2U6CgotICoqaW1hZ2UgcGlucyoqIOKAlCAxIHJlbmRlcmVkLXZzLXJlbGVhc2UgZGlzYWdyZWVtZW50IChgYXJhX3NlcnZlcmApLCAzIExJVkUKICByb2xlLWRlZmF1bHQgc2hhZG93cyB0aGF0IGFjdHVhbGx5IGRlcGxveSAoYGRuc21hc3FgLCBgc2NhcGhhbmRyZWAsIGBzdGVwY2FgKSwKICA5IERPUk1BTlQgbGFncywgNCB1bnBpbm5lZCB0YWdzIChgaHR0cGRgLCBgaHR0cGRfZGF0YWAsIGBrZXBsZXJgLAogIGB0aGFub3Nfc2lkZWNhcmApLCBhbmQgMyBvcnBoYW5lZCBtYW5hZ2VyIGFsaWFzZXMgKGBuZ2lueGAsIGBvc2lzbV9uZXRib3hgLAogIGByZWdpc3RyeWApLgotICoqa29sbGEgdG9vbGNoYWluKiog4oCUIGNsZWFuIHRvZGF5OiBldmVyeSBmaW5kaW5nIG1hdGNoZXMgYW4gYWxsb3dsaXN0IGVudHJ5IGFuZAogIHRoZSBiYXNlbGluZSBpcyBncmVlbi4KClRoZSBrb2xsYSBjaGVja3MgaGF2ZSBhbHJlYWR5IGRyaXZlbiBhIHdhdmUgb2YgY2xlYW51cHMgYWNyb3NzIHRoZSBjb25zdW1lciByZXBvcywKdGFraW5nIHRoZSBrb2xsYSBhY3Rpb25hYmxlLWRyaWZ0IGNvdW50IGZyb20gODEgdG8gMCAoYWxsIG1lcmdlZCk6CgotIG9zaXNtL2RlZmF1bHRzIzI5MQotIG9zaXNtL2NvbnRhaW5lci1pbWFnZS1rb2xsYS1hbnNpYmxlIzkyNAotIG9zaXNtL3JlbGVhc2UjMjU1MQotIG9zaXNtL2NvbnRhaW5lci1pbWFnZXMta29sbGEjNzQ1Ci0gb3Npc20vZ2VuZXJpY3MjNTk5Ci0gb3Npc20vZ2VuZXJpY3MjNjAxCgpJdCBzZWxmLWF1ZGl0cyBpdHMgYWxsb3dsaXN0IHRvbzogYW4gZW50cnkgdGhhdCBubyBsb25nZXIgc3VwcHJlc3NlcyByZWFsIGRyaWZ0IGlzCmEgaGFyZCBlcnJvciAoZXhpdCAxKSwgc28gc3VwcHJlc3Npb25zIGNhbid0IHNpbGVudGx5IHJvdC4KCkJlY2F1c2UgdGhlIGRldGVjdG9yIGlzIGRhaWx5LW9ubHksIHRoaXMgb3V0cHV0IGRvZXMgbm90IGFwcGVhciBpbiB0aGlzIFBSJ3MKY2hlY2tzLiBGdWxsIGByZWxlYXNlLXRveC1kcmlmdGAgcmVwb3J0IGFnYWluc3QgY3VycmVudCB1cHN0cmVhbToKCjxkZXRhaWxzPgo8c3VtbWFyeT48Y29kZT5jaGVjay1kcmlmdC5weSAtLWdyb3VwIGFsbDwvY29kZT4g4oCUIGN1cnJlbnQgb3V0cHV0PC9zdW1tYXJ5PgoKYGBgCkNoZWNrcyBmb2xsb3cgYW4gaW1hZ2UncyB2ZXJzaW9uIHBpbjogcmVsZWFzZSBiYXNlLnltbCDihpIgcmVuZGVyZWQgbWFuYWdlciBpbWFnZXMueW1sIOKGkiByb2xlIGRlZmF1bHRzLgoKcmVsZWFzZV92c19tYW5hZ2VyIOKAlCAxIGltYWdlIHRhZ3MgaW4gdGhlIHJlbmRlcmVkIG1hbmFnZXIgaW1hZ2VzLnltbApkaXNhZ3JlZSB3aXRoIHRoZSByZWxlYXNlIGJhc2UueW1sIHBpbnM6CgogICAgYXJhX3NlcnZlcgoKICBGaXg6IHJlLXJlbmRlciBlbnZpcm9ubWVudHMvbWFuYWdlci9pbWFnZXMueW1sIGZyb20gdGhlIGN1cnJlbnQgcmVsZWFzZSwKICAgICAgIG9yIGFsbG93bGlzdCB0aGUgZW50cnkgaWYgdGhlIGRpdmVyZ2VuY2UgaXMgaW50ZW50aW9uYWwgKGUuZy4gYQogICAgICAgcm9sbGluZy1yZWxlYXNlIGltYWdlIHBpbm5lZCB0byAnbGF0ZXN0JykuCiAgUmVmczogcmVsZWFzZS9sYXRlc3QvYmFzZS55bWwKICAgICAgICB0ZXN0YmVkL2Vudmlyb25tZW50cy9tYW5hZ2VyL2ltYWdlcy55bWwKCnJvbGVfc2hhZG93cyDigJQgOSBET1JNQU5UIOKAlCBvdmVycmlkZGVuIGJ5IHRoZSByZW5kZXJlZCBpbWFnZXMueW1sOyB0aGUKcmVsZWFzZSBwaW4gd2lucyBhdCBkZXBsb3k6CgogICAgYWRtaW5lcl90YWcgKDQuNyDihpIgNS40LjIpICAgcm9sZXMvYWRtaW5lci9kZWZhdWx0cy9tYWluLnltbAogICAgYXJhX3NlcnZlcl9tYXJpYWRiX3RhZyAoMTEuOC4zIOKGkiAxMS44LjQpICAgcm9sZXMvbWFuYWdlci9kZWZhdWx0cy9tYWluLnltbAogICAgbWFuYWdlcl9yZWRpc190YWcgKDcuNC42LWFscGluZSDihpIgNy40LjctYWxwaW5lKSAgIHJvbGVzL21hbmFnZXIvZGVmYXVsdHMvbWFpbi55bWwKICAgIHZhdWx0X3RhZyAoMS4yMS4wIOKGkiAxLjIxLjQpICAgcm9sZXMvbWFuYWdlci9kZWZhdWx0cy9tYWluLnltbAogICAgbmV0Ym94X3JlZGlzX3RhZyAoNy40LjYtYWxwaW5lIOKGkiA3LjQuNy1hbHBpbmUpICAgcm9sZXMvbmV0Ym94L2RlZmF1bHRzL21haW4ueW1sCiAgICBwb3N0Z3Jlc190YWcgKDE2LjEwLWFscGluZSDihpIgMTYuMTEtYWxwaW5lKSAgIHJvbGVzL25ldGJveC9kZWZhdWx0cy9tYWluLnltbAogICAgbmV4dXNfdGFnICgzLjgyLjAg4oaSIDMuOTMuMSkgICByb2xlcy9uZXh1cy9kZWZhdWx0cy9tYWluLnltbAogICAgcGhwbXlhZG1pbl90YWcgKDUuMiDihpIgNS4yLjMpICAgcm9sZXMvcGhwbXlhZG1pbi9kZWZhdWx0cy9tYWluLnltbAogICAgdHJhZWZpa190YWcgKHYzLjQuNCDihpIgdjMuNS4wKSAgIHJvbGVzL3RyYWVmaWsvZGVmYXVsdHMvbWFpbi55bWwKCiAgRml4OiBsb3dlciBwcmlvcml0eTsgc3luYyB3aGVuIGNvbnZlbmllbnQuCiAgUmVmczogcmVsZWFzZS9sYXRlc3QvYmFzZS55bWwKCnJvbGVfc2hhZG93cyDigJQgMyBMSVZFIOKAlCBubyBpbWFnZXMueW1sIG92ZXJyaWRlOyB0aGUgcm9sZSBkZWZhdWx0IGlzIHdoYXQKYWN0dWFsbHkgZGVwbG95czoKCiAgICBkbnNtYXNxX3RhZyAoMi45MCDihpIgMi45MSkgICByb2xlcy9kbnNtYXNxL2RlZmF1bHRzL21haW4ueW1sCiAgICBzY2FwaGFuZHJlX3RhZyAoMS4wLjAg4oaSIDEuMC4yKSAgIHJvbGVzL3NjYXBoYW5kcmUvZGVmYXVsdHMvbWFpbi55bWwKICAgIHN0ZXBjYV90YWcgKDAuMjguNCDihpIgMC4zMC4yKSAgIHJvbGVzL3N0ZXBjYS9kZWZhdWx0cy9tYWluLnltbAoKICBGaXg6IGFkZCBgPGFsaWFzPl90YWdgL2A8YWxpYXM+X2ltYWdlYCB0byB0aGUgbWFuYWdlciByZW5kZXIgdGVtcGxhdGUKICAgICAgIChpbWFnZXMueW1sLmoyKSBzbyB0aGUgbGF0ZXN0L2Jhc2UueW1sIHBpbiBnb3Zlcm5zIHRoZSBkZXBsb3llZAogICAgICAgdmVyc2lvbi4KICBSZWZzOiByZWxlYXNlL2xhdGVzdC9iYXNlLnltbAoKcm9sZV91bnBpbm5lZCDigJQgNCA8YWxpYXM+X3RhZyBwaW5zIGluIHJvbGUgZGVmYXVsdHMgd2l0aCBubyByZWxlYXNlIGJhc2UueW1sCnBpbjoKCiAgICBodHRwZF90YWcgKGFscGluZSwgbm8gcmVsZWFzZSBwaW4pICAgcm9sZXMvaHR0cGQvZGVmYXVsdHMvbWFpbi55bWwKICAgIGh0dHBkX2RhdGFfdGFnIChsYXRlc3QsIG5vIHJlbGVhc2UgcGluKSAgIHJvbGVzL2h0dHBkL2RlZmF1bHRzL21haW4ueW1sCiAgICBrZXBsZXJfdGFnICh2MC4xMS4yLCBubyByZWxlYXNlIHBpbikgICByb2xlcy9rZXBsZXIvZGVmYXVsdHMvbWFpbi55bWwKICAgIHRoYW5vc19zaWRlY2FyX3RhZyAodjAuMzIuNSwgbm8gcmVsZWFzZSBwaW4pICAgcm9sZXMvdGhhbm9zX3NpZGVjYXIvZGVmYXVsdHMvbWFpbi55bWwKCiAgRml4OiBhZGQgYSBwaW4gdG8gcmVsZWFzZSBiYXNlLnltbCAoYW5kIHdpcmUgPGFsaWFzPl90YWcgaW50byB0aGUgbWFuYWdlcgogICAgICAgcmVuZGVyIHRlbXBsYXRlKSB0byBtYWtlIGl0IHJlbGVhc2UtbWFuYWdlZCwgb3IgYWxsb3dsaXN0IGl0IGlmIHRoZQogICAgICAgaW1hZ2UgaXMgaW50ZW50aW9uYWxseSByb2xlLW1hbmFnZWQuCiAgUmVmczogcmVsZWFzZS9sYXRlc3QvYmFzZS55bWwKCmltYWdlX29ycGhhbiDigJQgMyBpbWFnZSB2YXJzIGVtaXR0ZWQgYnkgdGhlIG1hbmFnZXIgaW1hZ2VzLnltbCB0aGF0IG5vIHJvbGUKb3IgcGxheWJvb2sgY29uc3VtZXM6CgogICAgbmdpbngsIG9zaXNtX25ldGJveCwgcmVnaXN0cnkKCiAgRml4OiByZW1vdmUgdGhlIG9ycGhhbmVkIDxhbGlhcz5fdGFnLzxhbGlhcz5faW1hZ2UgKGFuZCBpdHMgZXRjL2ltYWdlcy55bWwKICAgICAgICsgcm9sZS1kZWZhdWx0IHJlbW5hbnRzKSwgb3IgYWxsb3dsaXN0IGl0IGlmIHRoZSBpbWFnZSBpcyBnZW51aW5lbHkKICAgICAgIGNvbnN1bWVkIGluIGEgZm9ybSB0aGlzIHNjYW4gbWlzc2VzIOKAlCBpdCBmbGFncyBhbiBhbGlhcyBvbmx5IHdoZW4gbm8KICAgICAgIGxpdGVyYWwge3sgPGFsaWFzPl9pbWFnZSB9fSByZWZlcmVuY2UgaXMgZm91bmQgaW4gdGhlIHNjYW5uZWQKICAgICAgIHJvbGVzL3BsYXlib29rcy4KICBSZWZzOiBhbnNpYmxlLWNvbGxlY3Rpb24tc2VydmljZXMvcm9sZXMvLCBhbnNpYmxlLXBsYXlib29rcy1tYW5hZ2VyL3BsYXlib29rcy8KICAgICAgICBnZW5lcmljcy9lbnZpcm9ubWVudHMvbWFuYWdlci9pbWFnZXMueW1sCgpTdW1tYXJ5OiAyMCB0byBhY3Qgb24sIDQxIGFsbG93bGlzdGVkLCAwIHN0YWxlIGFsbG93bGlzdCBlbnRyaWVzICg2MSB0b3RhbCkKYGBgCjwvZGV0YWlscz4KCiMjIyBIb3cgdG8gcmVhZCBpdAotICoqRE9STUFOVCAocm9sZV9zaGFkb3dzKSoqIOKAlCB0aGUgcmVsZWFzZSBwaW4gYWxyZWFkeSBnb3Zlcm5zIGF0IGRlcGxveTsgcm9sZQogIGRlZmF1bHRzIGp1c3QgbGFnLiBMb3cgcHJpb3JpdHkuCi0gKipMSVZFIChyb2xlX3NoYWRvd3MpKiog4oCUIGBkbnNtYXNxYCwgYHNjYXBoYW5kcmVgLCBgc3RlcGNhYDogdGhlIHJvbGUgZGVmYXVsdAogIGFjdHVhbGx5IGRlcGxveXM7IGZpeCBieSB3aXJpbmcgdGhlIGFsaWFzIGludG8gYGltYWdlcy55bWwuajJgLgotICoqcm9sZV91bnBpbm5lZCoqIOKAlCBgaHR0cGQqYCwgYGtlcGxlcmAsIGB0aGFub3Nfc2lkZWNhcmA6IG5vIHJlbGVhc2UgcGluLgotICoqaW1hZ2Vfb3JwaGFuKiog4oCUIGBuZ2lueGAsIGBvc2lzbV9uZXRib3hgLCBgcmVnaXN0cnlgOiBlbWl0dGVkIGJ5IHRoZSBtYW5hZ2VyCiAgdGVtcGxhdGUsIGNvbnN1bWVkIG5vd2hlcmUuCi0gKiprb2xsYSBncm91cCoqIOKAlCBubyBhY3Rpb25hYmxlIGZpbmRpbmdzIHRvZGF5OyBtYXRjaGVzIGFyZSBhbGxvd2xpc3RlZCBhbmQgdGhlCiAgYmFzZWxpbmUgaXMgY2xlYW4uCgo+IFRoZSBkZXRlY3RvciBydW5zIHVuYXV0aGVudGljYXRlZCBhZ2FpbnN0IEdpdEh1YiAobm8gdG9rZW4gc3VwcG9ydCBpbgo+IGBzb3VyY2UucHlgKSwgc2FtZSBhcyBnZW5lcmljcycgZXhpc3RpbmcgZGFpbHkgam9iLiBBIGhlYXZ5IHJ1biBjYW4gb2NjYXNpb25hbGx5Cj4gaGl0IEFQSSByYXRlIGxpbWl0cyBhbmQgZmFpbCB3aXRob3V0IGEgcmVwb3J0OyBhZGRpbmcgYEdJVEhVQl9UT0tFTmAgc3VwcG9ydCBpcyBhCj4gc2Vuc2libGUgZm9sbG93LXVwLgo=
      patchset: d21d3d04334bc1fae00a164455753a392ba9cd3a
      pipeline: check
      playbook_context:
        playbook_projects:
          trusted/project_0/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: 71b711c422c10c242a8f785dc6d41ed9d12e9cd3
          trusted/project_1/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 242732586e2cba04d04867d3e302888c766bc231
          trusted/project_2/github.com/osism/zuul-jobs:
            canonical_name: github.com/osism/zuul-jobs
            checkout: main
            commit: 1d71e58c93442038e695a5d883d57a603182c7ae
          untrusted/project_0/github.com/osism/zuul-jobs:
            canonical_name: github.com/osism/zuul-jobs
            checkout: main
            commit: 1d71e58c93442038e695a5d883d57a603182c7ae
          untrusted/project_1/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: 71b711c422c10c242a8f785dc6d41ed9d12e9cd3
          untrusted/project_2/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 242732586e2cba04d04867d3e302888c766bc231
        playbooks:
        - path: untrusted/project_0/github.com/osism/zuul-jobs/playbooks/python-black/run.yml
          roles:
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/playbook_0/role_0/zuul-config
            link_target: untrusted/project_1/github.com/osism/zuul-config
            role_path: ansible/playbook_0/role_0/zuul-config/roles
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/playbook_0/role_1/openinfra-zuul-jobs
            link_target: untrusted/project_2/github.com/osism/openinfra-zuul-jobs
            role_path: ansible/playbook_0/role_1/openinfra-zuul-jobs/roles
          - checkout: main
            checkout_description: playbook branch
            link_name: ansible/playbook_0/role_2/zuul-jobs
            link_target: untrusted/project_0/github.com/osism/zuul-jobs
            role_path: ansible/playbook_0/role_2/zuul-jobs/roles
      post_review: false
      post_timeout: 1800
      pre_timeout: null
      project:
        canonical_hostname: github.com
        canonical_name: github.com/osism/release
        name: osism/release
        short_name: release
        src_dir: src/github.com/osism/release
      projects:
        github.com/osism/release:
          canonical_hostname: github.com
          canonical_name: github.com/osism/release
          checkout: main
          checkout_description: zuul branch
          commit: 0ce2ac079957c444d4ea924646187e3818ba09f4
          name: osism/release
          required: false
          short_name: release
          src_dir: src/github.com/osism/release
      ref: refs/pull/2561/head
      resources: {}
      tenant: osism
      timeout: 1800
      topic: null
      voting: true
