all:
  children:
    cephs:
      hosts:
        instance: null
    computes:
      hosts:
        instance: null
    controllers:
      hosts:
        instance: null
    zuul_unreachable:
      hosts: {}
  hosts:
    instance:
      ansible_connection: ssh
      ansible_host: 162.253.55.208
      ansible_port: 22
      ansible_python_interpreter: auto
      ansible_user: zuul
      atmosphere_image_prefix: harbor.atmosphere.dev/
      atmosphere_network_backend: ovn
      barbican_helm_values:
        pod:
          replicas:
            api: 1
      ceph_conf_overrides:
      - option: mon allow pool size one
        section: global
        value: true
      - option: osd crush chooseleaf type
        section: global
        value: 0
      - option: auth allow insecure global id reclaim
        section: mon
        value: false
      ceph_csi_rbd_helm_values:
        provisioner:
          replicaCount: 1
      ceph_fsid: 4837cbf8-4f90-4300-b3f6-726c9b9f89b4
      ceph_osd_devices:
      - /dev/ceph-{{ inventory_hostname_short }}-osd0/data
      - /dev/ceph-{{ inventory_hostname_short }}-osd1/data
      - /dev/ceph-{{ inventory_hostname_short }}-osd2/data
      cilium_helm_values:
        operator:
          replicas: 1
      cinder_helm_values:
        conf:
          ceph:
            pools:
              backup:
                replication: 1
              cinder.volumes:
                replication: 1
          cinder:
            DEFAULT:
              osapi_volume_workers: 2
        pod:
          replicas:
            api: 1
            scheduler: 1
      cluster_issuer_type: self-signed
      coredns_helm_values:
        replicaCount: 1
      csi_driver: local-path-provisioner
      glance_helm_values:
        conf:
          glance:
            DEFAULT:
              workers: 2
            glance_store:
              rbd_store_replication: 1
        pod:
          replicas:
            api: 1
      glance_images:
      - container_format: bare
        disk_format: raw
        is_public: true
        min_disk: 1
        name: cirros
        url: http://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img
      heat_helm_values:
        conf:
          heat:
            DEFAULT:
              num_engine_workers: 2
            heat_api:
              workers: 2
            heat_api_cfn:
              workers: 2
            heat_api_cloudwatch:
              workers: 2
        pod:
          replicas:
            api: 1
            cfn: 1
            cloudwatch: 1
            engine: 1
      horizon_helm_values:
        pod:
          replicas:
            server: 1
      ingress_nginx_helm_values:
        controller:
          config:
            worker-processes: 2
      keystone_helm_values:
        pod:
          replicas:
            api: 1
      kube_vip_address: 172.17.0.100
      kube_vip_interface: '{{ ansible_facts[''default_ipv4''].interface }}'
      kubernetes_hostname: '{{ ansible_facts[''default_ipv4''].address }}'
      kubernetes_keepalived_interface: br-mgmt
      magnum_helm_values:
        conf:
          magnum:
            api:
              workers: 2
            conductor:
              workers: 2
        pod:
          replicas:
            api: 1
            conductor: 1
      magnum_image_disk_format: qcow2
      magnum_images: '[ {{ _magnum_images[-1] }} ]'
      manila_helm_values:
        conf:
          manila:
            DEFAULT:
              osapi_share_workers: 2
        pod:
          replicas:
            api: 1
            scheduler: 1
      molecule_install_collection_siblings: true
      molecule_scenario: aio
      neutron_helm_values:
        conf:
          neutron:
            DEFAULT:
              api_workers: 2
              metadata_workers: 2
              rpc_workers: 2
        pod:
          replicas:
            rpc_server: 1
            server: 1
      nodepool:
        az: yul2
        cloud: public
        external_id: f20c77d9-8b25-4d1c-bbc3-d2882c5262ca
        host_id: 3cc3064f3f299d3d37bf8eb0728708985d63c3ec9e6e6eb729dd744b
        interface_ip: 162.253.55.208
        label: ubuntu-jammy-16
        node_properties: {}
        private_ipv4: 162.253.55.208
        private_ipv6: null
        provider: yul1
        public_ipv4: 162.253.55.208
        public_ipv6: 2604:e100:1:0:f816:3eff:febd:479d
        region: ca-ymq-1
        slot: null
      nova_helm_values:
        conf:
          nova:
            DEFAULT:
              metadata_workers: 2
              osapi_compute_workers: 2
            conductor:
              workers: 2
            scheduler:
              workers: 2
        pod:
          replicas:
            api_metadata: 1
            conductor: 1
            novncproxy: 1
            osapi: 1
            scheduler: 1
            spiceproxy: 1
      octavia_helm_values:
        conf:
          octavia:
            controller_worker:
              workers: 2
          octavia_api_uwsgi:
            uwsgi:
              processes: 2
        pod:
          replicas:
            api: 1
            housekeeping: 1
            worker: 1
      ovn_helm_values:
        conf:
          auto_bridge_add:
            br-ex: null
        pod:
          replicas:
            ovn_northd: 1
            ovn_ovsdb_nb: 1
            ovn_ovsdb_sb: 1
      percona_xtradb_cluster_spec:
        allowUnsafeConfigurations: true
        haproxy:
          size: 1
        pxc:
          size: 1
      placement_helm_values:
        conf:
          placement_api_uwsgi:
            uwsgi:
              processes: 2
        pod:
          replicas:
            api: 1
      rook_ceph_cluster_radosgw_spec:
        dataPool:
          failureDomain: osd
        gateway:
          instances: 1
        metadataPool:
          failureDomain: osd
      senlin_helm_values:
        pod:
          replicas:
            api: 1
            conductor: 1
            engine: 1
            health_manager: 1
      staffeln_helm_values:
        pod:
          replicas:
            api: 1
            conductor: 1
      valkey_helm_values:
        replica:
          replicaCount: 1
      zuul_node:
        az: yul2
        cloud: public
        external_id: f20c77d9-8b25-4d1c-bbc3-d2882c5262ca
        host_id: 3cc3064f3f299d3d37bf8eb0728708985d63c3ec9e6e6eb729dd744b
        interface_ip: 162.253.55.208
        label: ubuntu-jammy-16
        node_properties: {}
        private_ipv4: 162.253.55.208
        private_ipv6: null
        provider: yul1
        public_ipv4: 162.253.55.208
        public_ipv6: 2604:e100:1:0:f816:3eff:febd:479d
        region: ca-ymq-1
        slot: null
        uuid: null
  vars:
    atmosphere_image_prefix: harbor.atmosphere.dev/
    atmosphere_network_backend: ovn
    barbican_helm_values:
      pod:
        replicas:
          api: 1
    ceph_conf_overrides:
    - option: mon allow pool size one
      section: global
      value: true
    - option: osd crush chooseleaf type
      section: global
      value: 0
    - option: auth allow insecure global id reclaim
      section: mon
      value: false
    ceph_csi_rbd_helm_values:
      provisioner:
        replicaCount: 1
    ceph_fsid: 4837cbf8-4f90-4300-b3f6-726c9b9f89b4
    ceph_osd_devices:
    - /dev/ceph-{{ inventory_hostname_short }}-osd0/data
    - /dev/ceph-{{ inventory_hostname_short }}-osd1/data
    - /dev/ceph-{{ inventory_hostname_short }}-osd2/data
    cilium_helm_values:
      operator:
        replicas: 1
    cinder_helm_values:
      conf:
        ceph:
          pools:
            backup:
              replication: 1
            cinder.volumes:
              replication: 1
        cinder:
          DEFAULT:
            osapi_volume_workers: 2
      pod:
        replicas:
          api: 1
          scheduler: 1
    cluster_issuer_type: self-signed
    coredns_helm_values:
      replicaCount: 1
    csi_driver: local-path-provisioner
    glance_helm_values:
      conf:
        glance:
          DEFAULT:
            workers: 2
          glance_store:
            rbd_store_replication: 1
      pod:
        replicas:
          api: 1
    glance_images:
    - container_format: bare
      disk_format: raw
      is_public: true
      min_disk: 1
      name: cirros
      url: http://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img
    heat_helm_values:
      conf:
        heat:
          DEFAULT:
            num_engine_workers: 2
          heat_api:
            workers: 2
          heat_api_cfn:
            workers: 2
          heat_api_cloudwatch:
            workers: 2
      pod:
        replicas:
          api: 1
          cfn: 1
          cloudwatch: 1
          engine: 1
    horizon_helm_values:
      pod:
        replicas:
          server: 1
    ingress_nginx_helm_values:
      controller:
        config:
          worker-processes: 2
    keystone_helm_values:
      pod:
        replicas:
          api: 1
    kube_vip_address: 172.17.0.100
    kube_vip_interface: '{{ ansible_facts[''default_ipv4''].interface }}'
    kubernetes_hostname: '{{ ansible_facts[''default_ipv4''].address }}'
    kubernetes_keepalived_interface: br-mgmt
    magnum_helm_values:
      conf:
        magnum:
          api:
            workers: 2
          conductor:
            workers: 2
      pod:
        replicas:
          api: 1
          conductor: 1
    magnum_image_disk_format: qcow2
    magnum_images: '[ {{ _magnum_images[-1] }} ]'
    manila_helm_values:
      conf:
        manila:
          DEFAULT:
            osapi_share_workers: 2
      pod:
        replicas:
          api: 1
          scheduler: 1
    molecule_install_collection_siblings: true
    molecule_scenario: aio
    neutron_helm_values:
      conf:
        neutron:
          DEFAULT:
            api_workers: 2
            metadata_workers: 2
            rpc_workers: 2
      pod:
        replicas:
          rpc_server: 1
          server: 1
    nova_helm_values:
      conf:
        nova:
          DEFAULT:
            metadata_workers: 2
            osapi_compute_workers: 2
          conductor:
            workers: 2
          scheduler:
            workers: 2
      pod:
        replicas:
          api_metadata: 1
          conductor: 1
          novncproxy: 1
          osapi: 1
          scheduler: 1
          spiceproxy: 1
    octavia_helm_values:
      conf:
        octavia:
          controller_worker:
            workers: 2
        octavia_api_uwsgi:
          uwsgi:
            processes: 2
      pod:
        replicas:
          api: 1
          housekeeping: 1
          worker: 1
    ovn_helm_values:
      conf:
        auto_bridge_add:
          br-ex: null
      pod:
        replicas:
          ovn_northd: 1
          ovn_ovsdb_nb: 1
          ovn_ovsdb_sb: 1
    percona_xtradb_cluster_spec:
      allowUnsafeConfigurations: true
      haproxy:
        size: 1
      pxc:
        size: 1
    placement_helm_values:
      conf:
        placement_api_uwsgi:
          uwsgi:
            processes: 2
      pod:
        replicas:
          api: 1
    rook_ceph_cluster_radosgw_spec:
      dataPool:
        failureDomain: osd
      gateway:
        instances: 1
      metadataPool:
        failureDomain: osd
    senlin_helm_values:
      pod:
        replicas:
          api: 1
          conductor: 1
          engine: 1
          health_manager: 1
    staffeln_helm_values:
      pod:
        replicas:
          api: 1
          conductor: 1
    valkey_helm_values:
      replica:
        replicaCount: 1
    zuul:
      _inheritance_path:
      - '<Job base explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: vexxhost/zuul-config/zuul.d/jobs.yaml@main#1>'
      - '<Job molecule explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: vexxhost/zuul-jobs/zuul.d/ansible-jobs.yaml@main#1>'
      - '<Job atmosphere-molecule explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: vexxhost/atmosphere-zuul-jobs/zuul.d/jobs.yaml@main#1>'
      - '<Job atmosphere-molecule-aio explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: vexxhost/atmosphere-zuul-jobs/zuul.d/jobs.yaml@main#75>'
      - '<Job atmosphere-molecule-aio-ovn explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: vexxhost/atmosphere-zuul-jobs/zuul.d/jobs.yaml@main#269>'
      - '<Job atmosphere-molecule-aio-ovn explicit: None implied: None source: vexxhost/atmosphere-zuul-jobs/zuul.d/project-templates.yaml@main#1>'
      ansible_version: '9'
      attempts: 1
      branch: main
      build: f53defdacdf341b98c76930040f26e20
      build_refs:
      - branch: main
        change: '4420'
        change_message: "feat: publish release manifests and deploy from them\n\nToday
          a release resolves image tags at release time, after CI has run: 88 of the
          232 entries in `roles/defaults/vars/main.yml` are plain tags, and `cmd/pinimages`
          resolves them into the Galaxy collection, silently skipping any it cannot
          resolve (it logs 94 errors on `main` today). The digests a release ships
          are only recorded inside the Galaxy tarball, and mirroring for air-gapped
          installs goes through the `image_manifest` role, whose registry rewrite
          keeps only the last path component, so `rook/ceph` and `ceph/ceph` both
          become `<registry>/ceph`.\n\nThis PR makes a digest-pinned **release manifest**
          the source of truth for which images a commit or release deploys.\n\n**Publishing**\n-
          The `build` job already resolved every image on each push; it now publishes
          the result as an OCI artifact to `ghcr.io/vexxhost/atmosphere-release`,
          tagged with the commit SHA and the branch (`main`, `stable-2025.2`, ...).\n-
          `release.yaml` promotes the released commit's manifest to the release tag
          (no tag is resolved again), attaches it and `atmosphere-release` binaries
          (linux/darwin, amd64/arm64) to the GitHub release, and pins the Galaxy collection
          from that same manifest.\n\n**Deploying**\n- `atmosphere_release_manifest`
          (OCI reference or file path) deploys exactly a manifest's images. Kubernetes
          control plane images fall back to the tag when another `atmosphere_kubernetes_version`
          is deployed.\n- `atmosphere_image_mirrors` redirects images per registry,
          namespace or repository (most specific rule wins); `atmosphere_image_prefix`
          still applies to everything no rule matches, so Harbor proxy projects with
          custom names now work too.\n- Resolution happens in the rendered references,
          so it works with any Kubernetes and container runtime. `atmosphere_image_overrides`
          are applied last, unchanged.\n\n**`atmosphere-release` command** (`cmd/atmosphere-release`,
          replaces `cmd/pinimages`)\n- `generate [--write-vars]`, `push`, `promote`,
          `show`, `diff`, `check`\n- `mirror` to a registry, or `--to-dir` / `--from-dir`
          for disconnected sites, in `nested`, `flat` (`quay.io--ceph--ceph`) or `single-repo`
          layouts. Images are copied with every platform so digests never change,
          each digest is verified, kubeadm's images stay under one prefix with their
          original names, the manifest artifact itself is copied with its digest intact,
          and the `atmosphere_release_manifest` / `atmosphere_image_mirrors` vars
          are written out.\n\nThe `image_manifest` role keeps working but now warns
          that it is deprecated.\n\n### Compatibility\n\nDeployments that set none
          of the new variables get byte-identical `atmosphere_images`: I rendered
          the `defaults` role through `ansible-playbook` before and after this change
          with no prefix, with a Harbor prefix, and with prefix + overrides, on `main`
          (231 images) and on `stable/zed` (216 images); all outputs are identical.
          `write-vars` produces the same vars file as `pinimages` except that Ceph
          is now pinned too (`pinimages` could not resolve its `{{ ceph_version }}`
          template).\n\n### Testing\n\n- Go: `internal/releasemanifest` tests cover
          generation, `write-vars`, push/fetch/promote, every mirror layout with multi-arch
          images in in-memory registries, directory export/import, digest-mismatch
          detection, registries with ports and retry on rate limits.\n- Python: the
          `resolve_images` filter and the Go resolver run the same cases from `internal/releasemanifest/testdata/resolve.yaml`;
          the lookup is tested against a local HTTP registry with token auth and blob
          redirects (credentials must not reach storage) and tampered content. A new
          `python-test` CI job runs these (the existing `tests/unit` suite is not
          run in CI and has a stale magnum expectation, so the job only runs the new
          tests).\n- End to end against a local CNCF Distribution registry with real
          images from `registry.k8s.io`, `ghcr.io` and Docker Hub: `generate`, `push`,
          `promote`, `show`, nested mirror, export \u2192 flat import, `check`, `diff`,
          plus deploying the mirrored manifest through the lookup.\n- `generate` against
          the real registries resolves all 231 images on `main` and `stable/2025.2`.\n\n###
          Backports\n\n`go.mod` and `go.sum` are untouched; every dependency is already
          present at the same version on all stable branches.\n\n- `stable/2025.2`
          cherry-picks cleanly and passes everything above.\n- `stable/2025.1` back
          to `stable/zed` conflict in one hunk of `roles/defaults/vars/main.yml`:
          the conflict context includes `main`'s `_atmosphere_storage` block, which
          those branches don't have. Resolve it by keeping the new `atmosphere_images`
          block and dropping the `_atmosphere_storage` lines. I did this on `stable/zed`
          and the Go tests, Python tests and the old/new rendering comparison all
          pass.\n\n### Before merging / follow-ups\n\n- After the first push publishes
          `ghcr.io/vexxhost/atmosphere-release`, make the package public and set retention
          (keep release tags; commit tags can be pruned).\n- Docker Hub's anonymous
          rate limit can still fail `generate` after its retries; previously this
          silently produced unpinned images.\n- `atmosphere-zuul-jobs` (separate repo):
          generate a manifest in the molecule pre-run and pass `atmosphere_release_manifest`,
          so CI deploys exactly what gets published.\n- `harbor.atmosphere.dev` has
          no proxy project for `packages.vexxhost.com` (used by `prometheus_smartctl_exporter`).\n-
          The docs build with `-W` already fails on `main` because of existing warnings
          in `admin/monitoring.rst` and `arch/images.rst`; the new page adds none.\n"
        change_url: https://github.com/vexxhost/atmosphere/pull/4420
        commit_id: b7d6551b75ede1377cbc82be0ebc3011809bed0b
        patchset: b7d6551b75ede1377cbc82be0ebc3011809bed0b
        project:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/atmosphere
          name: vexxhost/atmosphere
          short_name: atmosphere
          src_dir: src/github.com/vexxhost/atmosphere
        src_dir: src/github.com/vexxhost/atmosphere
        topic: null
      buildset: e26feb6437e741019276e4d311f710c3
      buildset_refs:
      - branch: main
        change: '4420'
        change_message: "feat: publish release manifests and deploy from them\n\nToday
          a release resolves image tags at release time, after CI has run: 88 of the
          232 entries in `roles/defaults/vars/main.yml` are plain tags, and `cmd/pinimages`
          resolves them into the Galaxy collection, silently skipping any it cannot
          resolve (it logs 94 errors on `main` today). The digests a release ships
          are only recorded inside the Galaxy tarball, and mirroring for air-gapped
          installs goes through the `image_manifest` role, whose registry rewrite
          keeps only the last path component, so `rook/ceph` and `ceph/ceph` both
          become `<registry>/ceph`.\n\nThis PR makes a digest-pinned **release manifest**
          the source of truth for which images a commit or release deploys.\n\n**Publishing**\n-
          The `build` job already resolved every image on each push; it now publishes
          the result as an OCI artifact to `ghcr.io/vexxhost/atmosphere-release`,
          tagged with the commit SHA and the branch (`main`, `stable-2025.2`, ...).\n-
          `release.yaml` promotes the released commit's manifest to the release tag
          (no tag is resolved again), attaches it and `atmosphere-release` binaries
          (linux/darwin, amd64/arm64) to the GitHub release, and pins the Galaxy collection
          from that same manifest.\n\n**Deploying**\n- `atmosphere_release_manifest`
          (OCI reference or file path) deploys exactly a manifest's images. Kubernetes
          control plane images fall back to the tag when another `atmosphere_kubernetes_version`
          is deployed.\n- `atmosphere_image_mirrors` redirects images per registry,
          namespace or repository (most specific rule wins); `atmosphere_image_prefix`
          still applies to everything no rule matches, so Harbor proxy projects with
          custom names now work too.\n- Resolution happens in the rendered references,
          so it works with any Kubernetes and container runtime. `atmosphere_image_overrides`
          are applied last, unchanged.\n\n**`atmosphere-release` command** (`cmd/atmosphere-release`,
          replaces `cmd/pinimages`)\n- `generate [--write-vars]`, `push`, `promote`,
          `show`, `diff`, `check`\n- `mirror` to a registry, or `--to-dir` / `--from-dir`
          for disconnected sites, in `nested`, `flat` (`quay.io--ceph--ceph`) or `single-repo`
          layouts. Images are copied with every platform so digests never change,
          each digest is verified, kubeadm's images stay under one prefix with their
          original names, the manifest artifact itself is copied with its digest intact,
          and the `atmosphere_release_manifest` / `atmosphere_image_mirrors` vars
          are written out.\n\nThe `image_manifest` role keeps working but now warns
          that it is deprecated.\n\n### Compatibility\n\nDeployments that set none
          of the new variables get byte-identical `atmosphere_images`: I rendered
          the `defaults` role through `ansible-playbook` before and after this change
          with no prefix, with a Harbor prefix, and with prefix + overrides, on `main`
          (231 images) and on `stable/zed` (216 images); all outputs are identical.
          `write-vars` produces the same vars file as `pinimages` except that Ceph
          is now pinned too (`pinimages` could not resolve its `{{ ceph_version }}`
          template).\n\n### Testing\n\n- Go: `internal/releasemanifest` tests cover
          generation, `write-vars`, push/fetch/promote, every mirror layout with multi-arch
          images in in-memory registries, directory export/import, digest-mismatch
          detection, registries with ports and retry on rate limits.\n- Python: the
          `resolve_images` filter and the Go resolver run the same cases from `internal/releasemanifest/testdata/resolve.yaml`;
          the lookup is tested against a local HTTP registry with token auth and blob
          redirects (credentials must not reach storage) and tampered content. A new
          `python-test` CI job runs these (the existing `tests/unit` suite is not
          run in CI and has a stale magnum expectation, so the job only runs the new
          tests).\n- End to end against a local CNCF Distribution registry with real
          images from `registry.k8s.io`, `ghcr.io` and Docker Hub: `generate`, `push`,
          `promote`, `show`, nested mirror, export \u2192 flat import, `check`, `diff`,
          plus deploying the mirrored manifest through the lookup.\n- `generate` against
          the real registries resolves all 231 images on `main` and `stable/2025.2`.\n\n###
          Backports\n\n`go.mod` and `go.sum` are untouched; every dependency is already
          present at the same version on all stable branches.\n\n- `stable/2025.2`
          cherry-picks cleanly and passes everything above.\n- `stable/2025.1` back
          to `stable/zed` conflict in one hunk of `roles/defaults/vars/main.yml`:
          the conflict context includes `main`'s `_atmosphere_storage` block, which
          those branches don't have. Resolve it by keeping the new `atmosphere_images`
          block and dropping the `_atmosphere_storage` lines. I did this on `stable/zed`
          and the Go tests, Python tests and the old/new rendering comparison all
          pass.\n\n### Before merging / follow-ups\n\n- After the first push publishes
          `ghcr.io/vexxhost/atmosphere-release`, make the package public and set retention
          (keep release tags; commit tags can be pruned).\n- Docker Hub's anonymous
          rate limit can still fail `generate` after its retries; previously this
          silently produced unpinned images.\n- `atmosphere-zuul-jobs` (separate repo):
          generate a manifest in the molecule pre-run and pass `atmosphere_release_manifest`,
          so CI deploys exactly what gets published.\n- `harbor.atmosphere.dev` has
          no proxy project for `packages.vexxhost.com` (used by `prometheus_smartctl_exporter`).\n-
          The docs build with `-W` already fails on `main` because of existing warnings
          in `admin/monitoring.rst` and `arch/images.rst`; the new page adds none.\n"
        change_url: https://github.com/vexxhost/atmosphere/pull/4420
        commit_id: b7d6551b75ede1377cbc82be0ebc3011809bed0b
        patchset: b7d6551b75ede1377cbc82be0ebc3011809bed0b
        project:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/atmosphere
          name: vexxhost/atmosphere
          short_name: atmosphere
          src_dir: src/github.com/vexxhost/atmosphere
        src_dir: src/github.com/vexxhost/atmosphere
        topic: null
      change: '4420'
      change_message: "feat: publish release manifests and deploy from them\n\nToday
        a release resolves image tags at release time, after CI has run: 88 of the
        232 entries in `roles/defaults/vars/main.yml` are plain tags, and `cmd/pinimages`
        resolves them into the Galaxy collection, silently skipping any it cannot
        resolve (it logs 94 errors on `main` today). The digests a release ships are
        only recorded inside the Galaxy tarball, and mirroring for air-gapped installs
        goes through the `image_manifest` role, whose registry rewrite keeps only
        the last path component, so `rook/ceph` and `ceph/ceph` both become `<registry>/ceph`.\n\nThis
        PR makes a digest-pinned **release manifest** the source of truth for which
        images a commit or release deploys.\n\n**Publishing**\n- The `build` job already
        resolved every image on each push; it now publishes the result as an OCI artifact
        to `ghcr.io/vexxhost/atmosphere-release`, tagged with the commit SHA and the
        branch (`main`, `stable-2025.2`, ...).\n- `release.yaml` promotes the released
        commit's manifest to the release tag (no tag is resolved again), attaches
        it and `atmosphere-release` binaries (linux/darwin, amd64/arm64) to the GitHub
        release, and pins the Galaxy collection from that same manifest.\n\n**Deploying**\n-
        `atmosphere_release_manifest` (OCI reference or file path) deploys exactly
        a manifest's images. Kubernetes control plane images fall back to the tag
        when another `atmosphere_kubernetes_version` is deployed.\n- `atmosphere_image_mirrors`
        redirects images per registry, namespace or repository (most specific rule
        wins); `atmosphere_image_prefix` still applies to everything no rule matches,
        so Harbor proxy projects with custom names now work too.\n- Resolution happens
        in the rendered references, so it works with any Kubernetes and container
        runtime. `atmosphere_image_overrides` are applied last, unchanged.\n\n**`atmosphere-release`
        command** (`cmd/atmosphere-release`, replaces `cmd/pinimages`)\n- `generate
        [--write-vars]`, `push`, `promote`, `show`, `diff`, `check`\n- `mirror` to
        a registry, or `--to-dir` / `--from-dir` for disconnected sites, in `nested`,
        `flat` (`quay.io--ceph--ceph`) or `single-repo` layouts. Images are copied
        with every platform so digests never change, each digest is verified, kubeadm's
        images stay under one prefix with their original names, the manifest artifact
        itself is copied with its digest intact, and the `atmosphere_release_manifest`
        / `atmosphere_image_mirrors` vars are written out.\n\nThe `image_manifest`
        role keeps working but now warns that it is deprecated.\n\n### Compatibility\n\nDeployments
        that set none of the new variables get byte-identical `atmosphere_images`:
        I rendered the `defaults` role through `ansible-playbook` before and after
        this change with no prefix, with a Harbor prefix, and with prefix + overrides,
        on `main` (231 images) and on `stable/zed` (216 images); all outputs are identical.
        `write-vars` produces the same vars file as `pinimages` except that Ceph is
        now pinned too (`pinimages` could not resolve its `{{ ceph_version }}` template).\n\n###
        Testing\n\n- Go: `internal/releasemanifest` tests cover generation, `write-vars`,
        push/fetch/promote, every mirror layout with multi-arch images in in-memory
        registries, directory export/import, digest-mismatch detection, registries
        with ports and retry on rate limits.\n- Python: the `resolve_images` filter
        and the Go resolver run the same cases from `internal/releasemanifest/testdata/resolve.yaml`;
        the lookup is tested against a local HTTP registry with token auth and blob
        redirects (credentials must not reach storage) and tampered content. A new
        `python-test` CI job runs these (the existing `tests/unit` suite is not run
        in CI and has a stale magnum expectation, so the job only runs the new tests).\n-
        End to end against a local CNCF Distribution registry with real images from
        `registry.k8s.io`, `ghcr.io` and Docker Hub: `generate`, `push`, `promote`,
        `show`, nested mirror, export \u2192 flat import, `check`, `diff`, plus deploying
        the mirrored manifest through the lookup.\n- `generate` against the real registries
        resolves all 231 images on `main` and `stable/2025.2`.\n\n### Backports\n\n`go.mod`
        and `go.sum` are untouched; every dependency is already present at the same
        version on all stable branches.\n\n- `stable/2025.2` cherry-picks cleanly
        and passes everything above.\n- `stable/2025.1` back to `stable/zed` conflict
        in one hunk of `roles/defaults/vars/main.yml`: the conflict context includes
        `main`'s `_atmosphere_storage` block, which those branches don't have. Resolve
        it by keeping the new `atmosphere_images` block and dropping the `_atmosphere_storage`
        lines. I did this on `stable/zed` and the Go tests, Python tests and the old/new
        rendering comparison all pass.\n\n### Before merging / follow-ups\n\n- After
        the first push publishes `ghcr.io/vexxhost/atmosphere-release`, make the package
        public and set retention (keep release tags; commit tags can be pruned).\n-
        Docker Hub's anonymous rate limit can still fail `generate` after its retries;
        previously this silently produced unpinned images.\n- `atmosphere-zuul-jobs`
        (separate repo): generate a manifest in the molecule pre-run and pass `atmosphere_release_manifest`,
        so CI deploys exactly what gets published.\n- `harbor.atmosphere.dev` has
        no proxy project for `packages.vexxhost.com` (used by `prometheus_smartctl_exporter`).\n-
        The docs build with `-W` already fails on `main` because of existing warnings
        in `admin/monitoring.rst` and `arch/images.rst`; the new page adds none.\n"
      change_url: https://github.com/vexxhost/atmosphere/pull/4420
      child_jobs: []
      commit_id: b7d6551b75ede1377cbc82be0ebc3011809bed0b
      event_id: e3908d20-c93f-11f1-80a5-94fd0942060c
      executor:
        hostname: 2d72f0692154
        inventory_file: /var/lib/zuul/builds/f53defdacdf341b98c76930040f26e20/ansible/inventory.yaml
        log_root: /var/lib/zuul/builds/f53defdacdf341b98c76930040f26e20/work/logs
        result_data_file: /var/lib/zuul/builds/f53defdacdf341b98c76930040f26e20/work/results.json
        src_root: /var/lib/zuul/builds/f53defdacdf341b98c76930040f26e20/work/src
        work_root: /var/lib/zuul/builds/f53defdacdf341b98c76930040f26e20/work
      include_vars: []
      items:
      - branch: main
        change: '4420'
        change_message: "feat: publish release manifests and deploy from them\n\nToday
          a release resolves image tags at release time, after CI has run: 88 of the
          232 entries in `roles/defaults/vars/main.yml` are plain tags, and `cmd/pinimages`
          resolves them into the Galaxy collection, silently skipping any it cannot
          resolve (it logs 94 errors on `main` today). The digests a release ships
          are only recorded inside the Galaxy tarball, and mirroring for air-gapped
          installs goes through the `image_manifest` role, whose registry rewrite
          keeps only the last path component, so `rook/ceph` and `ceph/ceph` both
          become `<registry>/ceph`.\n\nThis PR makes a digest-pinned **release manifest**
          the source of truth for which images a commit or release deploys.\n\n**Publishing**\n-
          The `build` job already resolved every image on each push; it now publishes
          the result as an OCI artifact to `ghcr.io/vexxhost/atmosphere-release`,
          tagged with the commit SHA and the branch (`main`, `stable-2025.2`, ...).\n-
          `release.yaml` promotes the released commit's manifest to the release tag
          (no tag is resolved again), attaches it and `atmosphere-release` binaries
          (linux/darwin, amd64/arm64) to the GitHub release, and pins the Galaxy collection
          from that same manifest.\n\n**Deploying**\n- `atmosphere_release_manifest`
          (OCI reference or file path) deploys exactly a manifest's images. Kubernetes
          control plane images fall back to the tag when another `atmosphere_kubernetes_version`
          is deployed.\n- `atmosphere_image_mirrors` redirects images per registry,
          namespace or repository (most specific rule wins); `atmosphere_image_prefix`
          still applies to everything no rule matches, so Harbor proxy projects with
          custom names now work too.\n- Resolution happens in the rendered references,
          so it works with any Kubernetes and container runtime. `atmosphere_image_overrides`
          are applied last, unchanged.\n\n**`atmosphere-release` command** (`cmd/atmosphere-release`,
          replaces `cmd/pinimages`)\n- `generate [--write-vars]`, `push`, `promote`,
          `show`, `diff`, `check`\n- `mirror` to a registry, or `--to-dir` / `--from-dir`
          for disconnected sites, in `nested`, `flat` (`quay.io--ceph--ceph`) or `single-repo`
          layouts. Images are copied with every platform so digests never change,
          each digest is verified, kubeadm's images stay under one prefix with their
          original names, the manifest artifact itself is copied with its digest intact,
          and the `atmosphere_release_manifest` / `atmosphere_image_mirrors` vars
          are written out.\n\nThe `image_manifest` role keeps working but now warns
          that it is deprecated.\n\n### Compatibility\n\nDeployments that set none
          of the new variables get byte-identical `atmosphere_images`: I rendered
          the `defaults` role through `ansible-playbook` before and after this change
          with no prefix, with a Harbor prefix, and with prefix + overrides, on `main`
          (231 images) and on `stable/zed` (216 images); all outputs are identical.
          `write-vars` produces the same vars file as `pinimages` except that Ceph
          is now pinned too (`pinimages` could not resolve its `{{ ceph_version }}`
          template).\n\n### Testing\n\n- Go: `internal/releasemanifest` tests cover
          generation, `write-vars`, push/fetch/promote, every mirror layout with multi-arch
          images in in-memory registries, directory export/import, digest-mismatch
          detection, registries with ports and retry on rate limits.\n- Python: the
          `resolve_images` filter and the Go resolver run the same cases from `internal/releasemanifest/testdata/resolve.yaml`;
          the lookup is tested against a local HTTP registry with token auth and blob
          redirects (credentials must not reach storage) and tampered content. A new
          `python-test` CI job runs these (the existing `tests/unit` suite is not
          run in CI and has a stale magnum expectation, so the job only runs the new
          tests).\n- End to end against a local CNCF Distribution registry with real
          images from `registry.k8s.io`, `ghcr.io` and Docker Hub: `generate`, `push`,
          `promote`, `show`, nested mirror, export \u2192 flat import, `check`, `diff`,
          plus deploying the mirrored manifest through the lookup.\n- `generate` against
          the real registries resolves all 231 images on `main` and `stable/2025.2`.\n\n###
          Backports\n\n`go.mod` and `go.sum` are untouched; every dependency is already
          present at the same version on all stable branches.\n\n- `stable/2025.2`
          cherry-picks cleanly and passes everything above.\n- `stable/2025.1` back
          to `stable/zed` conflict in one hunk of `roles/defaults/vars/main.yml`:
          the conflict context includes `main`'s `_atmosphere_storage` block, which
          those branches don't have. Resolve it by keeping the new `atmosphere_images`
          block and dropping the `_atmosphere_storage` lines. I did this on `stable/zed`
          and the Go tests, Python tests and the old/new rendering comparison all
          pass.\n\n### Before merging / follow-ups\n\n- After the first push publishes
          `ghcr.io/vexxhost/atmosphere-release`, make the package public and set retention
          (keep release tags; commit tags can be pruned).\n- Docker Hub's anonymous
          rate limit can still fail `generate` after its retries; previously this
          silently produced unpinned images.\n- `atmosphere-zuul-jobs` (separate repo):
          generate a manifest in the molecule pre-run and pass `atmosphere_release_manifest`,
          so CI deploys exactly what gets published.\n- `harbor.atmosphere.dev` has
          no proxy project for `packages.vexxhost.com` (used by `prometheus_smartctl_exporter`).\n-
          The docs build with `-W` already fails on `main` because of existing warnings
          in `admin/monitoring.rst` and `arch/images.rst`; the new page adds none.\n"
        change_url: https://github.com/vexxhost/atmosphere/pull/4420
        commit_id: b7d6551b75ede1377cbc82be0ebc3011809bed0b
        patchset: b7d6551b75ede1377cbc82be0ebc3011809bed0b
        project:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/atmosphere
          name: vexxhost/atmosphere
          short_name: atmosphere
          src_dir: src/github.com/vexxhost/atmosphere
        topic: null
      job: atmosphere-molecule-aio-ovn
      jobtags: []
      max_attempts: 3
      message: ZmVhdDogcHVibGlzaCByZWxlYXNlIG1hbmlmZXN0cyBhbmQgZGVwbG95IGZyb20gdGhlbQoKVG9kYXkgYSByZWxlYXNlIHJlc29sdmVzIGltYWdlIHRhZ3MgYXQgcmVsZWFzZSB0aW1lLCBhZnRlciBDSSBoYXMgcnVuOiA4OCBvZiB0aGUgMjMyIGVudHJpZXMgaW4gYHJvbGVzL2RlZmF1bHRzL3ZhcnMvbWFpbi55bWxgIGFyZSBwbGFpbiB0YWdzLCBhbmQgYGNtZC9waW5pbWFnZXNgIHJlc29sdmVzIHRoZW0gaW50byB0aGUgR2FsYXh5IGNvbGxlY3Rpb24sIHNpbGVudGx5IHNraXBwaW5nIGFueSBpdCBjYW5ub3QgcmVzb2x2ZSAoaXQgbG9ncyA5NCBlcnJvcnMgb24gYG1haW5gIHRvZGF5KS4gVGhlIGRpZ2VzdHMgYSByZWxlYXNlIHNoaXBzIGFyZSBvbmx5IHJlY29yZGVkIGluc2lkZSB0aGUgR2FsYXh5IHRhcmJhbGwsIGFuZCBtaXJyb3JpbmcgZm9yIGFpci1nYXBwZWQgaW5zdGFsbHMgZ29lcyB0aHJvdWdoIHRoZSBgaW1hZ2VfbWFuaWZlc3RgIHJvbGUsIHdob3NlIHJlZ2lzdHJ5IHJld3JpdGUga2VlcHMgb25seSB0aGUgbGFzdCBwYXRoIGNvbXBvbmVudCwgc28gYHJvb2svY2VwaGAgYW5kIGBjZXBoL2NlcGhgIGJvdGggYmVjb21lIGA8cmVnaXN0cnk+L2NlcGhgLgoKVGhpcyBQUiBtYWtlcyBhIGRpZ2VzdC1waW5uZWQgKipyZWxlYXNlIG1hbmlmZXN0KiogdGhlIHNvdXJjZSBvZiB0cnV0aCBmb3Igd2hpY2ggaW1hZ2VzIGEgY29tbWl0IG9yIHJlbGVhc2UgZGVwbG95cy4KCioqUHVibGlzaGluZyoqCi0gVGhlIGBidWlsZGAgam9iIGFscmVhZHkgcmVzb2x2ZWQgZXZlcnkgaW1hZ2Ugb24gZWFjaCBwdXNoOyBpdCBub3cgcHVibGlzaGVzIHRoZSByZXN1bHQgYXMgYW4gT0NJIGFydGlmYWN0IHRvIGBnaGNyLmlvL3ZleHhob3N0L2F0bW9zcGhlcmUtcmVsZWFzZWAsIHRhZ2dlZCB3aXRoIHRoZSBjb21taXQgU0hBIGFuZCB0aGUgYnJhbmNoIChgbWFpbmAsIGBzdGFibGUtMjAyNS4yYCwgLi4uKS4KLSBgcmVsZWFzZS55YW1sYCBwcm9tb3RlcyB0aGUgcmVsZWFzZWQgY29tbWl0J3MgbWFuaWZlc3QgdG8gdGhlIHJlbGVhc2UgdGFnIChubyB0YWcgaXMgcmVzb2x2ZWQgYWdhaW4pLCBhdHRhY2hlcyBpdCBhbmQgYGF0bW9zcGhlcmUtcmVsZWFzZWAgYmluYXJpZXMgKGxpbnV4L2RhcndpbiwgYW1kNjQvYXJtNjQpIHRvIHRoZSBHaXRIdWIgcmVsZWFzZSwgYW5kIHBpbnMgdGhlIEdhbGF4eSBjb2xsZWN0aW9uIGZyb20gdGhhdCBzYW1lIG1hbmlmZXN0LgoKKipEZXBsb3lpbmcqKgotIGBhdG1vc3BoZXJlX3JlbGVhc2VfbWFuaWZlc3RgIChPQ0kgcmVmZXJlbmNlIG9yIGZpbGUgcGF0aCkgZGVwbG95cyBleGFjdGx5IGEgbWFuaWZlc3QncyBpbWFnZXMuIEt1YmVybmV0ZXMgY29udHJvbCBwbGFuZSBpbWFnZXMgZmFsbCBiYWNrIHRvIHRoZSB0YWcgd2hlbiBhbm90aGVyIGBhdG1vc3BoZXJlX2t1YmVybmV0ZXNfdmVyc2lvbmAgaXMgZGVwbG95ZWQuCi0gYGF0bW9zcGhlcmVfaW1hZ2VfbWlycm9yc2AgcmVkaXJlY3RzIGltYWdlcyBwZXIgcmVnaXN0cnksIG5hbWVzcGFjZSBvciByZXBvc2l0b3J5IChtb3N0IHNwZWNpZmljIHJ1bGUgd2lucyk7IGBhdG1vc3BoZXJlX2ltYWdlX3ByZWZpeGAgc3RpbGwgYXBwbGllcyB0byBldmVyeXRoaW5nIG5vIHJ1bGUgbWF0Y2hlcywgc28gSGFyYm9yIHByb3h5IHByb2plY3RzIHdpdGggY3VzdG9tIG5hbWVzIG5vdyB3b3JrIHRvby4KLSBSZXNvbHV0aW9uIGhhcHBlbnMgaW4gdGhlIHJlbmRlcmVkIHJlZmVyZW5jZXMsIHNvIGl0IHdvcmtzIHdpdGggYW55IEt1YmVybmV0ZXMgYW5kIGNvbnRhaW5lciBydW50aW1lLiBgYXRtb3NwaGVyZV9pbWFnZV9vdmVycmlkZXNgIGFyZSBhcHBsaWVkIGxhc3QsIHVuY2hhbmdlZC4KCioqYGF0bW9zcGhlcmUtcmVsZWFzZWAgY29tbWFuZCoqIChgY21kL2F0bW9zcGhlcmUtcmVsZWFzZWAsIHJlcGxhY2VzIGBjbWQvcGluaW1hZ2VzYCkKLSBgZ2VuZXJhdGUgWy0td3JpdGUtdmFyc11gLCBgcHVzaGAsIGBwcm9tb3RlYCwgYHNob3dgLCBgZGlmZmAsIGBjaGVja2AKLSBgbWlycm9yYCB0byBhIHJlZ2lzdHJ5LCBvciBgLS10by1kaXJgIC8gYC0tZnJvbS1kaXJgIGZvciBkaXNjb25uZWN0ZWQgc2l0ZXMsIGluIGBuZXN0ZWRgLCBgZmxhdGAgKGBxdWF5LmlvLS1jZXBoLS1jZXBoYCkgb3IgYHNpbmdsZS1yZXBvYCBsYXlvdXRzLiBJbWFnZXMgYXJlIGNvcGllZCB3aXRoIGV2ZXJ5IHBsYXRmb3JtIHNvIGRpZ2VzdHMgbmV2ZXIgY2hhbmdlLCBlYWNoIGRpZ2VzdCBpcyB2ZXJpZmllZCwga3ViZWFkbSdzIGltYWdlcyBzdGF5IHVuZGVyIG9uZSBwcmVmaXggd2l0aCB0aGVpciBvcmlnaW5hbCBuYW1lcywgdGhlIG1hbmlmZXN0IGFydGlmYWN0IGl0c2VsZiBpcyBjb3BpZWQgd2l0aCBpdHMgZGlnZXN0IGludGFjdCwgYW5kIHRoZSBgYXRtb3NwaGVyZV9yZWxlYXNlX21hbmlmZXN0YCAvIGBhdG1vc3BoZXJlX2ltYWdlX21pcnJvcnNgIHZhcnMgYXJlIHdyaXR0ZW4gb3V0LgoKVGhlIGBpbWFnZV9tYW5pZmVzdGAgcm9sZSBrZWVwcyB3b3JraW5nIGJ1dCBub3cgd2FybnMgdGhhdCBpdCBpcyBkZXByZWNhdGVkLgoKIyMjIENvbXBhdGliaWxpdHkKCkRlcGxveW1lbnRzIHRoYXQgc2V0IG5vbmUgb2YgdGhlIG5ldyB2YXJpYWJsZXMgZ2V0IGJ5dGUtaWRlbnRpY2FsIGBhdG1vc3BoZXJlX2ltYWdlc2A6IEkgcmVuZGVyZWQgdGhlIGBkZWZhdWx0c2Agcm9sZSB0aHJvdWdoIGBhbnNpYmxlLXBsYXlib29rYCBiZWZvcmUgYW5kIGFmdGVyIHRoaXMgY2hhbmdlIHdpdGggbm8gcHJlZml4LCB3aXRoIGEgSGFyYm9yIHByZWZpeCwgYW5kIHdpdGggcHJlZml4ICsgb3ZlcnJpZGVzLCBvbiBgbWFpbmAgKDIzMSBpbWFnZXMpIGFuZCBvbiBgc3RhYmxlL3plZGAgKDIxNiBpbWFnZXMpOyBhbGwgb3V0cHV0cyBhcmUgaWRlbnRpY2FsLiBgd3JpdGUtdmFyc2AgcHJvZHVjZXMgdGhlIHNhbWUgdmFycyBmaWxlIGFzIGBwaW5pbWFnZXNgIGV4Y2VwdCB0aGF0IENlcGggaXMgbm93IHBpbm5lZCB0b28gKGBwaW5pbWFnZXNgIGNvdWxkIG5vdCByZXNvbHZlIGl0cyBge3sgY2VwaF92ZXJzaW9uIH19YCB0ZW1wbGF0ZSkuCgojIyMgVGVzdGluZwoKLSBHbzogYGludGVybmFsL3JlbGVhc2VtYW5pZmVzdGAgdGVzdHMgY292ZXIgZ2VuZXJhdGlvbiwgYHdyaXRlLXZhcnNgLCBwdXNoL2ZldGNoL3Byb21vdGUsIGV2ZXJ5IG1pcnJvciBsYXlvdXQgd2l0aCBtdWx0aS1hcmNoIGltYWdlcyBpbiBpbi1tZW1vcnkgcmVnaXN0cmllcywgZGlyZWN0b3J5IGV4cG9ydC9pbXBvcnQsIGRpZ2VzdC1taXNtYXRjaCBkZXRlY3Rpb24sIHJlZ2lzdHJpZXMgd2l0aCBwb3J0cyBhbmQgcmV0cnkgb24gcmF0ZSBsaW1pdHMuCi0gUHl0aG9uOiB0aGUgYHJlc29sdmVfaW1hZ2VzYCBmaWx0ZXIgYW5kIHRoZSBHbyByZXNvbHZlciBydW4gdGhlIHNhbWUgY2FzZXMgZnJvbSBgaW50ZXJuYWwvcmVsZWFzZW1hbmlmZXN0L3Rlc3RkYXRhL3Jlc29sdmUueWFtbGA7IHRoZSBsb29rdXAgaXMgdGVzdGVkIGFnYWluc3QgYSBsb2NhbCBIVFRQIHJlZ2lzdHJ5IHdpdGggdG9rZW4gYXV0aCBhbmQgYmxvYiByZWRpcmVjdHMgKGNyZWRlbnRpYWxzIG11c3Qgbm90IHJlYWNoIHN0b3JhZ2UpIGFuZCB0YW1wZXJlZCBjb250ZW50LiBBIG5ldyBgcHl0aG9uLXRlc3RgIENJIGpvYiBydW5zIHRoZXNlICh0aGUgZXhpc3RpbmcgYHRlc3RzL3VuaXRgIHN1aXRlIGlzIG5vdCBydW4gaW4gQ0kgYW5kIGhhcyBhIHN0YWxlIG1hZ251bSBleHBlY3RhdGlvbiwgc28gdGhlIGpvYiBvbmx5IHJ1bnMgdGhlIG5ldyB0ZXN0cykuCi0gRW5kIHRvIGVuZCBhZ2FpbnN0IGEgbG9jYWwgQ05DRiBEaXN0cmlidXRpb24gcmVnaXN0cnkgd2l0aCByZWFsIGltYWdlcyBmcm9tIGByZWdpc3RyeS5rOHMuaW9gLCBgZ2hjci5pb2AgYW5kIERvY2tlciBIdWI6IGBnZW5lcmF0ZWAsIGBwdXNoYCwgYHByb21vdGVgLCBgc2hvd2AsIG5lc3RlZCBtaXJyb3IsIGV4cG9ydCDihpIgZmxhdCBpbXBvcnQsIGBjaGVja2AsIGBkaWZmYCwgcGx1cyBkZXBsb3lpbmcgdGhlIG1pcnJvcmVkIG1hbmlmZXN0IHRocm91Z2ggdGhlIGxvb2t1cC4KLSBgZ2VuZXJhdGVgIGFnYWluc3QgdGhlIHJlYWwgcmVnaXN0cmllcyByZXNvbHZlcyBhbGwgMjMxIGltYWdlcyBvbiBgbWFpbmAgYW5kIGBzdGFibGUvMjAyNS4yYC4KCiMjIyBCYWNrcG9ydHMKCmBnby5tb2RgIGFuZCBgZ28uc3VtYCBhcmUgdW50b3VjaGVkOyBldmVyeSBkZXBlbmRlbmN5IGlzIGFscmVhZHkgcHJlc2VudCBhdCB0aGUgc2FtZSB2ZXJzaW9uIG9uIGFsbCBzdGFibGUgYnJhbmNoZXMuCgotIGBzdGFibGUvMjAyNS4yYCBjaGVycnktcGlja3MgY2xlYW5seSBhbmQgcGFzc2VzIGV2ZXJ5dGhpbmcgYWJvdmUuCi0gYHN0YWJsZS8yMDI1LjFgIGJhY2sgdG8gYHN0YWJsZS96ZWRgIGNvbmZsaWN0IGluIG9uZSBodW5rIG9mIGByb2xlcy9kZWZhdWx0cy92YXJzL21haW4ueW1sYDogdGhlIGNvbmZsaWN0IGNvbnRleHQgaW5jbHVkZXMgYG1haW5gJ3MgYF9hdG1vc3BoZXJlX3N0b3JhZ2VgIGJsb2NrLCB3aGljaCB0aG9zZSBicmFuY2hlcyBkb24ndCBoYXZlLiBSZXNvbHZlIGl0IGJ5IGtlZXBpbmcgdGhlIG5ldyBgYXRtb3NwaGVyZV9pbWFnZXNgIGJsb2NrIGFuZCBkcm9wcGluZyB0aGUgYF9hdG1vc3BoZXJlX3N0b3JhZ2VgIGxpbmVzLiBJIGRpZCB0aGlzIG9uIGBzdGFibGUvemVkYCBhbmQgdGhlIEdvIHRlc3RzLCBQeXRob24gdGVzdHMgYW5kIHRoZSBvbGQvbmV3IHJlbmRlcmluZyBjb21wYXJpc29uIGFsbCBwYXNzLgoKIyMjIEJlZm9yZSBtZXJnaW5nIC8gZm9sbG93LXVwcwoKLSBBZnRlciB0aGUgZmlyc3QgcHVzaCBwdWJsaXNoZXMgYGdoY3IuaW8vdmV4eGhvc3QvYXRtb3NwaGVyZS1yZWxlYXNlYCwgbWFrZSB0aGUgcGFja2FnZSBwdWJsaWMgYW5kIHNldCByZXRlbnRpb24gKGtlZXAgcmVsZWFzZSB0YWdzOyBjb21taXQgdGFncyBjYW4gYmUgcHJ1bmVkKS4KLSBEb2NrZXIgSHViJ3MgYW5vbnltb3VzIHJhdGUgbGltaXQgY2FuIHN0aWxsIGZhaWwgYGdlbmVyYXRlYCBhZnRlciBpdHMgcmV0cmllczsgcHJldmlvdXNseSB0aGlzIHNpbGVudGx5IHByb2R1Y2VkIHVucGlubmVkIGltYWdlcy4KLSBgYXRtb3NwaGVyZS16dXVsLWpvYnNgIChzZXBhcmF0ZSByZXBvKTogZ2VuZXJhdGUgYSBtYW5pZmVzdCBpbiB0aGUgbW9sZWN1bGUgcHJlLXJ1biBhbmQgcGFzcyBgYXRtb3NwaGVyZV9yZWxlYXNlX21hbmlmZXN0YCwgc28gQ0kgZGVwbG95cyBleGFjdGx5IHdoYXQgZ2V0cyBwdWJsaXNoZWQuCi0gYGhhcmJvci5hdG1vc3BoZXJlLmRldmAgaGFzIG5vIHByb3h5IHByb2plY3QgZm9yIGBwYWNrYWdlcy52ZXh4aG9zdC5jb21gICh1c2VkIGJ5IGBwcm9tZXRoZXVzX3NtYXJ0Y3RsX2V4cG9ydGVyYCkuCi0gVGhlIGRvY3MgYnVpbGQgd2l0aCBgLVdgIGFscmVhZHkgZmFpbHMgb24gYG1haW5gIGJlY2F1c2Ugb2YgZXhpc3Rpbmcgd2FybmluZ3MgaW4gYGFkbWluL21vbml0b3JpbmcucnN0YCBhbmQgYGFyY2gvaW1hZ2VzLnJzdGA7IHRoZSBuZXcgcGFnZSBhZGRzIG5vbmUuCg==
      patchset: b7d6551b75ede1377cbc82be0ebc3011809bed0b
      pipeline: check
      playbook_context:
        playbook_projects:
          trusted/project_0/github.com/vexxhost/zuul-config:
            canonical_name: github.com/vexxhost/zuul-config
            checkout: main
            commit: 298983cd1253e6833abdb49d87d912527e0e6597
          trusted/project_1/opendev.org/zuul/zuul-jobs:
            canonical_name: opendev.org/zuul/zuul-jobs
            checkout: master
            commit: be52461cd48abccbf95b13a58b2551ac673d8ad7
          trusted/project_2/github.com/vexxhost/zuul-jobs:
            canonical_name: github.com/vexxhost/zuul-jobs
            checkout: main
            commit: c286bf94224b5660b8e49e9a058289e61062a9a0
          untrusted/project_0/github.com/vexxhost/zuul-jobs:
            canonical_name: github.com/vexxhost/zuul-jobs
            checkout: main
            commit: c286bf94224b5660b8e49e9a058289e61062a9a0
          untrusted/project_1/github.com/vexxhost/zuul-config:
            canonical_name: github.com/vexxhost/zuul-config
            checkout: main
            commit: 298983cd1253e6833abdb49d87d912527e0e6597
          untrusted/project_2/opendev.org/zuul/zuul-jobs:
            canonical_name: opendev.org/zuul/zuul-jobs
            checkout: master
            commit: be52461cd48abccbf95b13a58b2551ac673d8ad7
          untrusted/project_3/github.com/vexxhost/atmosphere-zuul-jobs:
            canonical_name: github.com/vexxhost/atmosphere-zuul-jobs
            checkout: main
            commit: a05c8ee068edf95d57873f78e89ff5b01c9098ce
          untrusted/project_4/opendev.org/openstack/openstack-helm:
            canonical_name: opendev.org/openstack/openstack-helm
            checkout: master
            commit: 484fb97d16b8f2724f2e7a8151ed26b673a019c6
        playbooks:
        - path: untrusted/project_0/github.com/vexxhost/zuul-jobs/playbooks/molecule/run.yaml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/playbook_0/role_1/zuul-jobs
            link_target: untrusted/project_2/opendev.org/zuul/zuul-jobs
            role_path: ansible/playbook_0/role_1/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/vexxhost/zuul-jobs
            role_path: ansible/playbook_0/role_2/zuul-jobs/roles
        post_playbooks:
        - path: untrusted/project_3/github.com/vexxhost/atmosphere-zuul-jobs/playbooks/molecule/post.yml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/post_playbook_0/role_1/openstack-helm
            link_target: untrusted/project_4/opendev.org/openstack/openstack-helm
            role_path: ansible/post_playbook_0/role_1/openstack-helm/roles
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/post_playbook_0/role_3/zuul-jobs
            link_target: untrusted/project_2/opendev.org/zuul/zuul-jobs
            role_path: ansible/post_playbook_0/role_3/zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/post_playbook_0/role_4/zuul-jobs
            link_target: untrusted/project_0/github.com/vexxhost/zuul-jobs
            role_path: ansible/post_playbook_0/role_4/zuul-jobs/roles
        - path: trusted/project_0/github.com/vexxhost/zuul-config/playbooks/base/post.yaml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/post_playbook_1/role_1/zuul-jobs
            link_target: trusted/project_1/opendev.org/zuul/zuul-jobs
            role_path: ansible/post_playbook_1/role_1/zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/post_playbook_1/role_2/zuul-jobs
            link_target: trusted/project_2/github.com/vexxhost/zuul-jobs
            role_path: ansible/post_playbook_1/role_2/zuul-jobs/roles
        - path: trusted/project_0/github.com/vexxhost/zuul-config/playbooks/base/post-logs.yaml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/post_playbook_2/role_1/zuul-jobs
            link_target: trusted/project_1/opendev.org/zuul/zuul-jobs
            role_path: ansible/post_playbook_2/role_1/zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/post_playbook_2/role_2/zuul-jobs
            link_target: trusted/project_2/github.com/vexxhost/zuul-jobs
            role_path: ansible/post_playbook_2/role_2/zuul-jobs/roles
        pre_playbooks:
        - path: trusted/project_0/github.com/vexxhost/zuul-config/playbooks/base/pre.yaml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/pre_playbook_0/role_1/zuul-jobs
            link_target: trusted/project_1/opendev.org/zuul/zuul-jobs
            role_path: ansible/pre_playbook_0/role_1/zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/pre_playbook_0/role_2/zuul-jobs
            link_target: trusted/project_2/github.com/vexxhost/zuul-jobs
            role_path: ansible/pre_playbook_0/role_2/zuul-jobs/roles
        - path: untrusted/project_0/github.com/vexxhost/zuul-jobs/playbooks/molecule/pre.yaml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/pre_playbook_1/role_1/zuul-jobs
            link_target: untrusted/project_2/opendev.org/zuul/zuul-jobs
            role_path: ansible/pre_playbook_1/role_1/zuul-jobs/roles
          - checkout: main
            checkout_description: playbook branch
            link_name: ansible/pre_playbook_1/role_2/zuul-jobs
            link_target: untrusted/project_0/github.com/vexxhost/zuul-jobs
            role_path: ansible/pre_playbook_1/role_2/zuul-jobs/roles
        - path: untrusted/project_3/github.com/vexxhost/atmosphere-zuul-jobs/playbooks/molecule/pre.yml
          roles:
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/pre_playbook_2/role_1/openstack-helm
            link_target: untrusted/project_4/opendev.org/openstack/openstack-helm
            role_path: ansible/pre_playbook_2/role_1/openstack-helm/roles
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/pre_playbook_2/role_3/zuul-jobs
            link_target: untrusted/project_2/opendev.org/zuul/zuul-jobs
            role_path: ansible/pre_playbook_2/role_3/zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/pre_playbook_2/role_4/zuul-jobs
            link_target: untrusted/project_0/github.com/vexxhost/zuul-jobs
            role_path: ansible/pre_playbook_2/role_4/zuul-jobs/roles
      post_review: false
      post_timeout: null
      pre_timeout: null
      project:
        canonical_hostname: github.com
        canonical_name: github.com/vexxhost/atmosphere
        name: vexxhost/atmosphere
        short_name: atmosphere
        src_dir: src/github.com/vexxhost/atmosphere
      projects:
        github.com/ansible-collections/ansible.netcommon:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/ansible.netcommon
          checkout: main
          checkout_description: zuul branch
          commit: a929db70577e5630c32b36690fe58362f8c17443
          name: ansible-collections/ansible.netcommon
          required: true
          short_name: ansible.netcommon
          src_dir: src/github.com/ansible-collections/ansible.netcommon
        github.com/ansible-collections/ansible.posix:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/ansible.posix
          checkout: main
          checkout_description: zuul branch
          commit: d5699a3fcfca765b32f94b68a592139dc58d0301
          name: ansible-collections/ansible.posix
          required: true
          short_name: ansible.posix
          src_dir: src/github.com/ansible-collections/ansible.posix
        github.com/ansible-collections/ansible.utils:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/ansible.utils
          checkout: main
          checkout_description: zuul branch
          commit: 1f3e1bdfe2ba8904d397222dd6f90911d6f2402b
          name: ansible-collections/ansible.utils
          required: true
          short_name: ansible.utils
          src_dir: src/github.com/ansible-collections/ansible.utils
        github.com/ansible-collections/community.crypto:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/community.crypto
          checkout: main
          checkout_description: zuul branch
          commit: 051f8716048743eb7b9f9a875d851b97731bbf90
          name: ansible-collections/community.crypto
          required: true
          short_name: community.crypto
          src_dir: src/github.com/ansible-collections/community.crypto
        github.com/ansible-collections/community.general:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/community.general
          checkout: main
          checkout_description: zuul branch
          commit: 3299b62bb02549f2ab858361caea863ff07e921a
          name: ansible-collections/community.general
          required: true
          short_name: community.general
          src_dir: src/github.com/ansible-collections/community.general
        github.com/ansible-collections/community.mysql:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/community.mysql
          checkout: main
          checkout_description: zuul branch
          commit: 4cf7a3176fbd07be43459f1366e8066381df6d97
          name: ansible-collections/community.mysql
          required: true
          short_name: community.mysql
          src_dir: src/github.com/ansible-collections/community.mysql
        github.com/ansible-collections/kubernetes.core:
          canonical_hostname: github.com
          canonical_name: github.com/ansible-collections/kubernetes.core
          checkout: main
          checkout_description: zuul branch
          commit: ebb0d7f087413013eb27e275c7d638e6838e95c0
          name: ansible-collections/kubernetes.core
          required: true
          short_name: kubernetes.core
          src_dir: src/github.com/ansible-collections/kubernetes.core
        github.com/vexxhost/ansible-collection-ceph:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/ansible-collection-ceph
          checkout: main
          checkout_description: zuul branch
          commit: 44a694a80e3179c28d75180baa1a56426f987257
          name: vexxhost/ansible-collection-ceph
          required: true
          short_name: ansible-collection-ceph
          src_dir: src/github.com/vexxhost/ansible-collection-ceph
        github.com/vexxhost/ansible-collection-containers:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/ansible-collection-containers
          checkout: main
          checkout_description: zuul branch
          commit: d22923ebd40c0fd5a345318c1db90d125fb5140f
          name: vexxhost/ansible-collection-containers
          required: true
          short_name: ansible-collection-containers
          src_dir: src/github.com/vexxhost/ansible-collection-containers
        github.com/vexxhost/ansible-collection-kubernetes:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/ansible-collection-kubernetes
          checkout: main
          checkout_description: zuul branch
          commit: bb3fd0fde58a5b158605523e708645eb2a763886
          name: vexxhost/ansible-collection-kubernetes
          required: true
          short_name: ansible-collection-kubernetes
          src_dir: src/github.com/vexxhost/ansible-collection-kubernetes
        github.com/vexxhost/atmosphere:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/atmosphere
          checkout: main
          checkout_description: zuul branch
          commit: b7d6551b75ede1377cbc82be0ebc3011809bed0b
          name: vexxhost/atmosphere
          required: false
          short_name: atmosphere
          src_dir: src/github.com/vexxhost/atmosphere
        github.com/vexxhost/atmosphere.common:
          canonical_hostname: github.com
          canonical_name: github.com/vexxhost/atmosphere.common
          checkout: main
          checkout_description: zuul branch
          commit: 0317937da7abc1979d331af8b8f7b640b80d7e75
          name: vexxhost/atmosphere.common
          required: true
          short_name: atmosphere.common
          src_dir: src/github.com/vexxhost/atmosphere.common
        opendev.org/openstack/ansible-collections-openstack:
          canonical_hostname: opendev.org
          canonical_name: opendev.org/openstack/ansible-collections-openstack
          checkout: master
          checkout_description: project default branch
          commit: da4a211b2d1dd147dead0e3db63a373b43b3c09e
          name: openstack/ansible-collections-openstack
          required: true
          short_name: ansible-collections-openstack
          src_dir: src/opendev.org/openstack/ansible-collections-openstack
      ref: refs/pull/4420/head
      resources: {}
      tenant: oss
      timeout: 7200
      topic: null
      voting: true
