Skip to content

Image manifest

The image manifest is usually stored at .github/image_manifest.yaml in the deployment repos. It is the config that bump-images reads to decide which manifests an incoming image touches, how the change lands, and who was allowed to send it.

It can be dry-run against a canned payload with odp-releaser test bump-images, and statically checked with odp-releaser validate image-manifest.

Example image manifest

Example image_manifest.yaml with documentation can be generated via the CLI.

$ odp-releaser generate-config image-manifest
# Configuration for image manifests, mapping image names to their update configurations.
# Default settings applied to every image config; a config's own value replaces the default
defaults:
  # Full repo names (owner/name) allowed to trigger bumps; None disables the check
  allowed_source_repos:
  - gulfofmaine/Neracoos-1-Buoy-App
  - ioos/buoy_retriever
  # Users and teams allowed to trigger bumps; None disables the check
  allowed_actors:
    # GitHub usernames, compared case-insensitively against the payload's source actor
    users:
    - abkfenris
    # GitHub teams as org/team-slug entries. Membership is checked with the source org's reporter app credentials (REPORTER_APPS / REPORTER_APP_ID / REPORTER_APP_PRIVATE_KEY), so that app must also be granted the organization Members: read permission
    teams:
    - gulfofmaine/deployers
  # Default GitHub environment name used when reporting deployments back to source repos (`report-deployment`). Overridable per image config; unset falls back to this deploy repo's owner/name slug
  environment: staging
  # Default URL reported as the deployment's 'View deployment' link. May reference `{new_tag}`, `{git_sha}`, and `{digest}`. Overridable per image config; unset falls back to the bump commit or pull request URL
  environment_url: https://staging.neracoos.org
  # Default comment posted back on source pull requests when images are bumped. Overridable per image config, field by field; unset falls back to the built-in templates
  comment:
    # Comment body posted once the bump has landed -- immediately for a commit-mode bump, or when the bump pull request merges. Unset inherits the defaults-level value, then the built-in template
    deployed: '`{image_name}` `{new_tag}` deployed to `{environment}`'
  # GitHub usernames requested as reviewers on bump pull requests
  reviewers:
  - abkfenris
  # GitHub team slugs (no org prefix) requested as reviewers on bump pull requests. The bump-images workflow detects this key and mints its app token with organization Members: read (the ci app must be granted the permission)
  team_reviewers:
  - deployers
# Mapping of image names to their configurations
images:
  gmri/neracoos-mariners-dashboard:
    # Configuration for an image, specifying which manifests to update and how.
  -
    # List of GitHub events for these manifests. Only these events will trigger updates. If `None`, all events trigger updates.
    events:
    - publish
    # Full repo names (owner/name) allowed to trigger this config. Replaces the defaults-level list; unset inherits it. A config whose resolved list rejects the payload's repo is skipped
    allowed_source_repos:
    - gulfofmaine/Neracoos-1-Buoy-App
    # Users and teams allowed to trigger this config. Replaces the defaults-level setting; unset inherits it. A config whose resolved actors reject the payload's actor is skipped. Use YAML merge keys (<<: *anchor) to share allowlists between configs
    allowed_actors:
      users:
      - abkfenris
    # GitHub environment name reported back to the source repo for this config's bumps (`report-deployment`). Overrides the defaults-level environment; unset falls back to the deploy repo's owner/name slug
    environment: production
    # URL reported as the deployment's 'View deployment' link, e.g. where this config's app runs. May reference `{new_tag}`, `{git_sha}`, and `{digest}`. Overrides the defaults-level environment_url; unset falls back to the bump commit or pull request URL
    environment_url: https://mariners.neracoos.org
    # Whether to commit the change directly or open a pull request for review
    update_mode: pull_request
    # GitHub usernames requested as reviewers when this config's bump opens a pull request. Replaces the defaults-level list; unset inherits it, [] requests none
    reviewers:
    - abkfenris
    # GitHub team slugs (no org prefix) requested as reviewers on the bump pull request. Replaces the defaults-level list. The bump-images workflow detects this key and mints its app token with organization Members: read
    team_reviewers:
    - mariners
    # The image name the manifests under this config actually deploy from, when it differs from the payload's image_name -- e.g. an ECR pull-through cache path such as '123456789.dkr.ecr.us-east-1.amazonaws.com/docker-hub/gmri/sea-eagle-brown-3crs' mirroring upstream 'gmri/sea-eagle-brown-3crs'. Used as the Helm dagster shorthand's image.repository selector and in `set` templating via `{deployed_image}`; kustomize's own `newName` already carries the mirror, so this does not change which `images:` entry kustomize matches. Per-config only
    deployed_as: 
      123456789.dkr.ecr.us-east-1.amazonaws.com/docker-hub/gmri/neracoos-mariners-dashboard
    # List of Kustomize manifests to set for the image
    kustomize_manifests:
    - ../apps/mariners/kustomization.yaml
    # List of Helm values files to update for the image
    helm_charts:
      # Helm manifest configuration with Dagster user deployments chart layout shorthand.
    -
      # Relative path to the Helm values file
      path: ../apps/sea-eagle/values.yaml
      # When true, update the image.tag of every entry in the top-level 'deployments' list whose image.repository matches the released image (Dagster user-deployments chart layout)
      dagster_user_code: true
    # List of generic YAML or JSON manifests updated via set paths
    file_manifests:
      # A generic YAML or JSON manifest updated purely via ``set`` paths.

      # Unlike the kustomize and helm manifests there is no implicit image field
      # to update, so a bare-string form carries no useful information; the mapping
      # form with an explicit ``set`` is required.
    -
      # Relative path to the file manifest
      path: ../apps/config/deployment.json
      # Mapping of yamlpath expressions to templated values. Values may reference `{new_tag}`, `{git_sha}`, `{digest}`, `{payload}`, and `{deployed_image}` (the config's `deployed_as`, or the payload's image name when it declares none)
      set:
        /spec/template/spec/containers[0]/image: gmri/example@{digest}
  - events:
    - push
    # Comment posted back on the source pull request for this config's bumps. Each field is inherited from the defaults-level setting independently, so overriding one template keeps the other
    comment:
      deployed: '`{image_name}` `{new_tag}` is live on dev'
    update_mode: commit
    deployed_as: ghcr.io/gulfofmaine/neracoos-mariners-dashboard-dev
    # Whether odp-releaser copies the payload's image to deployed_as before the bump lands. Unset or false means declare-only: the correct setting for ECR pull-through and any other registry-native replication, where there is nothing to copy because the cache populates itself on the next pull. Overrides the defaults-level value; unset inherits it, then falls back to false
    sync: true
    kustomize_manifests:
      # Kustomize manifest configuration. Updates the image overrides and set fields.
    -
      # Relative path to the Kustomize manifest
      path: apps/mariners-dev/kustomization.yaml
      # Whether the kustomize images entry pins the tag (newTag) or the immutable digest (digest)
      pin: digest
    helm_charts:
    - path: ../apps/sea-eagle/values.yaml
      dagster_user_code: true
    file_manifests:
    - path: ../apps/config/deployment.json
      set:
        /spec/template/spec/containers[0]/image: gmri/example@{digest}

Manifests

For each of the manifest types, the set key takes a dictionary of yamlpath selectors and templated values to update.

Templated set values:

The values are templated with parts of the client payload.

Example values

  • new_tag - 7c8d9e0
  • git_sha - 7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
  • digest - sha256:2a4b6c8d0e1f3a5b7c9d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b
  • payload - {"image_name":"ghcr.io/ioos/buoy_retriever_s3_timeseries","digest":"sha256:2a4b6c8d0e1f3a5b7c9d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b","tag":"7c8d9e0","git_sha":"7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d","image_ref":"ghcr.io/ioos/buoy_retriever_s3_timeseries@sha256:2a4b6c8d0e1f3a5b7c9d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b","source":{"event":"push","ref":"main","url":"https://github.com/ioos/buoy_retriever/pull/142","run_url":"https://github.com/ioos/buoy_retriever/actions/runs/1200000002","actor":"abkfenris","release":null,"pr":{"number":142,"title":"Add hourly S3 timeseries ingest","url":"https://github.com/ioos/buoy_retriever/pull/142"}},"repo":"ioos/buoy_retriever"}

One additional placeholder isn't in that list because it isn't part of the payload: {deployed_image}, the name this config's manifests actually deploy from (deployed_as when set, otherwise the payload's own image_name). See Syncing images.

KustomizeManifest pydantic-model

Bases: BaseModel

Kustomize manifest configuration. Updates the image overrides and set fields.

Fields:

Validators:

  • coerce_path_string
path pydantic-field
path: Path

Relative path to the Kustomize manifest

pin pydantic-field
pin: Literal['tag', 'digest']

Whether the kustomize images entry pins the tag (newTag) or the immutable digest (digest)

__get_pydantic_json_schema__ classmethod
__get_pydantic_json_schema__(core_schema: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue

Also accept the bare-path shorthand coerce_path_string allows.

model_json_schema() only sees the mapping form, so the generated schema in schemas/ (used for editor completion and check-jsonschema) would flag the documented - ./kustomization.yaml shorthand as an error. Declaring the union here keeps the published schema honest about what the model really validates.

Source code in src/odp_releaser/schemas/manifest_config.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@classmethod
# pylint sees BaseModel's hook as taking no arguments, so an override with
# pydantic's documented signature reads as arguments-differ.
def __get_pydantic_json_schema__(  # pylint: disable=arguments-differ
    cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
    """Also accept the bare-path shorthand ``coerce_path_string`` allows.

    ``model_json_schema()`` only sees the mapping form, so the generated
    schema in ``schemas/`` (used for editor completion and
    ``check-jsonschema``) would flag the documented
    ``- ./kustomization.yaml`` shorthand as an error. Declaring the union
    here keeps the published schema honest about what the model really
    validates.
    """
    return {
        "anyOf": [
            handler(core_schema),
            {
                "type": "string",
                "description": (
                    "Relative path to the Kustomize manifest, shorthand "
                    "for a mapping with only `path` set"
                ),
            },
        ]
    }

HelmManifest pydantic-model

Bases: BaseModel

Helm manifest configuration with Dagster user deployments chart layout shorthand.

Fields:

dagster_user_code pydantic-field
dagster_user_code: bool

When true, update the image.tag of every entry in the top-level 'deployments' list whose image.repository matches the released image (Dagster user-deployments chart layout)

path pydantic-field
path: Path

Relative path to the Helm values file

FileManifest pydantic-model

Bases: BaseModel

A generic YAML or JSON manifest updated purely via set paths.

Unlike the kustomize and helm manifests there is no implicit image field to update, so a bare-string form carries no useful information; the mapping form with an explicit set is required.

Fields:

path pydantic-field
path: Path

Relative path to the file manifest

Deploying from a mirrored registry

A config whose manifests deploy from a mirror rather than the payload's own image name declares that with deployed_as, and can have odp-releaser push the image there with sync: true. Both settings, how each manifest engine consumes them, and the credentials a sync needs have their own page: Syncing images.

Pull request comments

When the reporter app has been granted Pull requests: Read and write (see GitHub App), a bump also comments on the source pull request saying which image landed and where. There are two templates, because a bump has two states worth distinguishing:

  • staged — a pull_request-mode bump has opened a pull request in the deploy repo but nothing is live yet.
  • deployed — the bump has landed: immediately for update_mode: commit, or when the bump pull request merges, at which point report-merged.yml rewrites the staged comment in place.

Each comment is keyed to one (deploy repo, image, environment) triple, so a rerun edits the same comment rather than adding another, and sibling bumps never overwrite each other on the same pull request.

Both templates are inherited field by field — a config that overrides only staged keeps the defaults-level deployed, and an unset field falls back to the built-in template. That differs from every other setting, where a config's value replaces the default wholesale, because the two templates describe different states and are meant to be set independently.

Only push events carry a source pull request (that's the only event odp-releaser notify resolves one for), so nothing is posted for release or workflow_dispatch dispatches; odp-releaser validate image-manifest warns when a config asks for a comment it could never post. Set enabled: false to turn commenting off, or a template to "" to post nothing in that one state.

The built-in templates

These are what you get when neither level sets a template — and a useful starting point for an override:

comment:
  staged: |
    ### `{image_name}` staged for `{environment}`

    Tag `{new_tag}` is staged in [`{deploy_repo}`]({bump_url}) and is waiting on review before it deploys.

    <sub>Bumped by [odp-releaser]({run_url}) from `{git_sha}`.</sub>
  deployed: |
    ### `{image_name}` deployed to `{environment}`

    Tag `{new_tag}` landed in [`{deploy_repo}`]({bump_url}).

    <sub>Bumped by [odp-releaser]({run_url}) from `{git_sha}`.</sub>

Rendered against a real bump, the deployed one comes out as:

from odp_releaser.bump_image_tester import EventType, load_client_payload
from odp_releaser.comment_body import CommentState, build_context, render_comment
from odp_releaser.schemas.manifest_config import DEFAULT_DEPLOYED_TEMPLATE

payload = load_client_payload(EventType.push)
context = build_context(
    payload,
    deploy_repo="gulfofmaine/deploy-repo",
    environment="production",
    environment_url=None,
    update_mode="commit",
    bump_url="https://github.com/gulfofmaine/deploy-repo/commit/9f8e7d6",
    run_url="https://github.com/gulfofmaine/deploy-repo/actions/runs/123",
    state=CommentState.deployed,
)
print(render_comment(DEFAULT_DEPLOYED_TEMPLATE, context))
### `ghcr.io/ioos/buoy_retriever_s3_timeseries` deployed to `production`

Tag `7c8d9e0` landed in [`gulfofmaine/deploy-repo`](https://github.com/gulfofmaine/deploy-repo/commit/9f8e7d6).

<sub>Bumped by [odp-releaser](https://github.com/gulfofmaine/deploy-repo/actions/runs/123) from `7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d`.</sub>

<!-- odp-releaser:comment key=gulfofmaine/deploy-repo|ghcr.io/ioos/buoy_retriever_s3_timeseries|production -->

That trailing HTML comment is the marker described above — it is invisible on the rendered pull request, and it is how a rerun finds the comment to update.

Comment placeholders

Comment templates are rendered with str.format, like set values — but against their own, larger vocabulary, since a comment can reference deploy-side facts that have no business being templated into a manifest:

  • {image_name}
  • {image_ref}
  • {new_tag}
  • {digest}
  • {git_sha}
  • {source_repo}
  • {source_url}
  • {actor}
  • {deploy_repo}
  • {environment}
  • {environment_url}
  • {update_mode}
  • {bump_url}
  • {run_url}
  • {state}

Because rendering goes through str.format, a literal brace has to be doubled: write {{ for { and }} for }. This matters for markdown that contains a Helm or Go template, or a ${{ ... }} expression:

comment:
  deployed: |
    `{image_name}` is now `{new_tag}` in [{deploy_repo}]({bump_url}).

    Pinned with `{{{{ .Values.image.tag }}}}`.

An unknown placeholder or a stray single brace is reported by odp-releaser validate image-manifest before a release, rather than failing mid-bump.

CommentConfig pydantic-model

Bases: BaseModel

Comment posted back on the source pull request when this image is bumped.

Every field is optional and inherited independently: an unset field takes the defaults-level value, then the built-in template. See :func:resolve_comment_config.

Fields:

  • enabled (bool | None)
  • staged (str | None)
  • deployed (str | None)
deployed pydantic-field
deployed: str | None

Comment body posted once the bump has landed -- immediately for a commit-mode bump, or when the bump pull request merges. Unset inherits the defaults-level value, then the built-in template

enabled pydantic-field
enabled: bool | None

Whether to comment on the source pull request for this config's bumps. Unset inherits the defaults-level value, which itself defaults to true

staged pydantic-field
staged: str | None

Comment body posted while a pull_request-mode bump is open and awaiting review. May reference the comment placeholders (see the docs); escape literal braces as {{/}}. Unset inherits the defaults-level value, then the built-in template

Validating

The config is read by pydantic models with the default extra="ignore", so a typo'd key (kustomize_manifest: instead of kustomize_manifests:) is silently dropped rather than rejected. Nothing about that runtime path tells the author their key never took effect. Worse, the checks that would catch a typo'd or malformed value — a yamlpath selector that never resolves, a {sha} placeholder that should have been {git_sha}, a manifest path that doesn't exist — otherwise only run partway through a real bump, in CI, against a real payload, often after earlier manifests in the same run have already been written.

odp-releaser validate image-manifest runs all of those checks statically and offline: no GitHub API calls, no writes, just the config file(s) on disk. It's meant to run in CI on a config repo, as a pre-commit hook, or by hand before a config change is merged.

odp-releaser validate image-manifest
odp-releaser validate image-manifest .github/image_manifest.yaml

A clean file prints a one-line ✓ <path> to stdout. Problems are printed to stderr as path:line: severity: message, one per line, colored by severity. The command exits 1 if any file has errors, or — with --strict — if any file has warnings, so a repo that wants warnings to block CI can opt in:

odp-releaser validate image-manifest --strict

image-manifest also accepts --no-check-files, for a repo where the manifests an image_manifest.yaml points at (Kustomize/Helm/plain YAML files) don't live in the same checkout the validator is run against:

odp-releaser validate image-manifest --no-check-files

Relaxing validation

There is no way to switch off one individual check by name (#47 tracks whether there should be). What exists instead:

  • Severity. Warnings never fail a run unless you pass --strict, and bump-images never fails on a warning at all. A check whose correctness depends on something the validator can't see — cluster state, another process — is a warning for exactly this reason, so the usual answer to "this finding doesn't apply to us" is to not run --strict.
  • --no-check-files. Skips every check that has to read a referenced manifest: selector resolution, the kustomize pin and newName checks, the Helm dagster check, and the engine backstop. Coarse, but it is the one flag that turns off a whole family.
  • pre-commit. The hooks are ordinary pre-commit hooks, so a consumer repo can skip one for a single commit (SKIP=validate-image-manifest git commit), narrow what it runs against with files:/exclude:, or pass flags through with args: [--no-check-files].

If a check is wrong for a legitimate config rather than merely unwanted, that's worth reporting as a bug — a rule that a correct config cannot satisfy is a defect in the rule, not something to suppress.

What is checked

Every check exists because a config that is shaped correctly can still mean something the runtime code mishandles. The tables below list each check's consequence at bump time — that's the reason it exists.

Errors (always fail the run):

Check Consequence if not caught
Unknown key anywhere in the config The key is silently dropped by pydantic; the setting the author intended never takes effect
Schema validation failure (wrong type, missing required field, ...) bump-images never gets a validated config to run against at all
images key isn't a valid image name (empty, whitespace, uppercase, contains @/:) Can never equal a real payload's image_name, so this config can never match a bump
allowed_source_repos entry isn't an owner/name pair Can never equal the payload's repo, so this entry can never match
allowed_actors.teams entry isn't an org/team-slug pair The team-membership check hard-exits the whole run
team_reviewers entry has an org/ prefix GitHub's "request review from teams" API expects a bare slug; the request fails
A set selector is not a valid yamlpath bump-images fails trying to apply the same selector
A set selector does not resolve against its target manifest Processor.get_nodes(..., mustexist=True) raises at bump time
A templated value uses a positional ({0}, {}), attribute/index ({payload.foo}), or unknown placeholder str.format(**kwargs) raises KeyError/ValueError at bump time
A templated value fails to actually format against a real payload Same failure, confirmed against real value_format_kwargs()
A comment template uses an unknown placeholder, or a single stray {/} odp-releaser comment can't render it; literal braces must be doubled ({{/}}). Comment templates have their own vocabulary, so a set-only placeholder like {payload} is wrong here
kustomize_manifests[].pin: tag but /images[name=...]/newTag doesn't exist bump-images sets it with mustexist=True, which raises
dagster_user_code: true but no matching /deployments[image.repository=...] entry bump-images sets its tag with mustexist=True, which raises
Referenced manifest file can't be read or parsed (unless --no-check-files) bump-images fails the same way when it tries to load the file mid-run
deployed_as isn't a valid image name (empty, whitespace, uppercase, contains @/:) Used as the Helm dagster shorthand's image.repository selector and in set templating via {deployed_image}, so it must be a plain image name like a real payload's image_name
sync: true (directly, or inherited from defaults.sync) with no deployed_as set There is nothing for odp-releaser to copy the payload's image to
The real manifest engine raises on this manifest, even though every check above passed bump-images fails the same way mid-run. This is the backstop: rather than predicting the engines, the validator runs them against the manifest's actual text, so a failure mode the checks above don't model is still caught

Warnings (reported; fail the run only with --strict):

Check Consequence if not caught
allowed_source_repos is explicitly [] Denies every source repository, probably not what was intended
allowed_actors is present but both users and teams are empty Denies every actor
A config has none of kustomize_manifests, helm_charts, file_manifests The config is a silent no-op
reviewers/team_reviewers set but update_mode resolves to commit Only pull_request mode ever requests reviewers; these are never used
A templated value has no placeholder at all bump-images can never change this value
kustomize_manifests[].pin: digest but no /images[name=...] entry exists yet Set with mustexist=False, which won't create the missing entry
Both newTag and digest set on the same kustomize image entry Kustomize prefers digest; bumping the tag has no visible effect
The same resolved manifest path is targeted more than once for one event bump-images applies all of them, redundantly
A comment is configured for an event that never carries a source pull request (anything but push) There is nothing to comment on, so the comment can never be posted
comment.staged is set but update_mode resolves to commit A direct commit is reported as deployed immediately, so only comment.deployed is ever used
Multiple configs matching the same event disagree on update_mode or a resolved setting (environment, environment_url, reviewers, team_reviewers, comment) bump-images warns and silently uses the first config's value
deployed_as is set to the same value as this image's own images: key Redundant: ImageConfig.deployed_name already falls back to the images: key when deployed_as is unset, so this declares nothing new
A kustomize manifest's /images[name=...]/newName is set but no deployed_as declares it sync and the Helm dagster shorthand can't see this mirror
A kustomize manifest's /images[name=...] entry has no newName, but deployed_as is set Kustomize renders the upstream image rather than the mirror the config declares — which is correct if the mirror is a node-level registry mirror or pull-through cache, and wrong otherwise. Only the cluster knows which, so this reports rather than blocks
A kustomize manifest's /images[name=...]/newName disagrees with deployed_as The manifest and the config name different registries. Usually a mistake, so the deploy pulls the wrong image — but legitimate if something else populates newName's registry, so this reports rather than blocks
A file_manifests set value hard-codes the upstream image name while deployed_as is set on the same config Almost certainly meant to reference {deployed_image} instead — otherwise the wrong registry gets written
Two configs writing the same resolved manifest path resolve different deployed_as values The manifest can only agree with one mirror; at least one config is wrong about what it actually deploys

bump-images pre-flight

Before bump-images writes any manifest, it runs the same semantic checks against exactly the configs it has already selected for this event (after filtering by image name, event, and authorization) — see _preflight in src/odp_releaser/bump_images.py. If that turns up any error, bump-images prints them all and exits before writing anything; a problem in, say, the third of five manifests can no longer surface as a mid-run traceback after the first two were already written and left half-applied.

Warnings from the pre-flight are logged, not fatal — they describe configs that work but probably don't do what their author meant, which isn't a reason to block a release. odp-releaser validate image-manifest --strict is where warnings are meant to block, in CI on the config repo itself.

API Reference

ManifestConfig pydantic-model

Bases: BaseModel

Configuration for image manifests, mapping image names to their update configurations.

Fields:

  • defaults (ConfigDefaults)
  • images (dict[str, list[ImageConfig]])

defaults pydantic-field

defaults: ConfigDefaults

Default settings applied to every image config; a config's own value replaces the default

images pydantic-field

images: dict[str, list[ImageConfig]]

Mapping of image names to manifests to update

generate_yaml classmethod

generate_yaml() -> str

Render the bundled :data:EXAMPLE_MANIFEST as commented YAML.

Source code in src/odp_releaser/schemas/manifest_config.py
585
586
587
588
@classmethod
def generate_yaml(cls) -> str:
    """Render the bundled :data:`EXAMPLE_MANIFEST` as commented YAML."""
    return example_yaml(EXAMPLE_MANIFEST)

ImageConfig pydantic-model

Bases: BaseModel

Configuration for an image, specifying which manifests to update and how.

Fields:

  • events (list[Literal['push', 'publish', 'workflow_dispatch', 'release']] | None)
  • allowed_source_repos (list[str] | None)
  • allowed_actors (AllowedActors | None)
  • environment (str | None)
  • environment_url (str | None)
  • comment (CommentConfig | None)
  • update_mode (Literal['commit', 'pull_request'])
  • reviewers (list[str] | None)
  • team_reviewers (list[str] | None)
  • deployed_as (str | None)
  • sync (bool | None)
  • kustomize_manifests (list[KustomizeManifest])
  • helm_charts (list[HelmManifest])
  • file_manifests (list[FileManifest])

allowed_actors pydantic-field

allowed_actors: AllowedActors | None

Users and teams allowed to trigger this config. Replaces the defaults-level setting; unset inherits it. A config whose resolved actors reject the payload's actor is skipped. Use YAML merge keys (<<: *anchor) to share allowlists between configs

allowed_source_repos pydantic-field

allowed_source_repos: list[str] | None

Full repo names (owner/name) allowed to trigger this config. Replaces the defaults-level list; unset inherits it. A config whose resolved list rejects the payload's repo is skipped

comment pydantic-field

comment: CommentConfig | None

Comment posted back on the source pull request for this config's bumps. Each field is inherited from the defaults-level setting independently, so overriding one template keeps the other

deployed_as pydantic-field

deployed_as: str | None

The image name the manifests under this config actually deploy from, when it differs from the payload's image_name -- e.g. an ECR pull-through cache path such as '123456789.dkr.ecr.us-east-1.amazonaws.com/docker-hub/gmri/sea-eagle-brown-3crs' mirroring upstream 'gmri/sea-eagle-brown-3crs'. Used as the Helm dagster shorthand's image.repository selector and in set templating via {deployed_image}; kustomize's own newName already carries the mirror, so this does not change which images: entry kustomize matches. Per-config only

environment pydantic-field

environment: str | None

GitHub environment name reported back to the source repo for this config's bumps (report-deployment). Overrides the defaults-level environment; unset falls back to the deploy repo's owner/name slug

environment_url pydantic-field

environment_url: str | None

URL reported as the deployment's 'View deployment' link, e.g. where this config's app runs. May reference {new_tag}, {git_sha}, and {digest}. Overrides the defaults-level environment_url; unset falls back to the bump commit or pull request URL

events pydantic-field

events: list[Literal['push', 'publish', 'workflow_dispatch', 'release']] | None

List of GitHub events for these manifests. Only these events will trigger updates. If None, all events trigger updates.

file_manifests pydantic-field

file_manifests: list[FileManifest]

List of generic YAML or JSON manifests updated via set paths

helm_charts pydantic-field

helm_charts: list[HelmManifest]

List of Helm values files to update for the image

kustomize_manifests pydantic-field

kustomize_manifests: list[KustomizeManifest]

List of Kustomize manifests to set for the image

reviewers pydantic-field

reviewers: list[str] | None

GitHub usernames requested as reviewers when this config's bump opens a pull request. Replaces the defaults-level list; unset inherits it, [] requests none

sync pydantic-field

sync: bool | None

Whether odp-releaser copies the payload's image to deployed_as before the bump lands. Unset or false means declare-only: the correct setting for ECR pull-through and any other registry-native replication, where there is nothing to copy because the cache populates itself on the next pull. Overrides the defaults-level value; unset inherits it, then falls back to false

team_reviewers pydantic-field

team_reviewers: list[str] | None

GitHub team slugs (no org prefix) requested as reviewers on the bump pull request. Replaces the defaults-level list. The bump-images workflow detects this key and mints its app token with organization Members: read

update_mode pydantic-field

update_mode: Literal['commit', 'pull_request']

Whether to commit the change directly or open a pull request for review

deployed_name

deployed_name(upstream: str) -> str

deployed_as when set, otherwise upstream.

Resolved per config, not across configs: unlike other per-config settings, this is deliberately never routed through bump_images._resolve_config_setting, whose first-wins-and-warn behaviour would silently pick one of two configs' registries.

Source code in src/odp_releaser/schemas/manifest_config.py
386
387
388
389
390
391
392
393
394
def deployed_name(self, upstream: str) -> str:
    """``deployed_as`` when set, otherwise ``upstream``.

    Resolved per config, not across configs: unlike other per-config
    settings, this is deliberately never routed through
    ``bump_images._resolve_config_setting``, whose first-wins-and-warn
    behaviour would silently pick one of two configs' registries.
    """
    return self.deployed_as or upstream

matches_event

matches_event(event: str) -> bool

Whether this config applies to event; events: None matches every event.

Source code in src/odp_releaser/schemas/manifest_config.py
376
377
378
379
380
381
382
383
384
def matches_event(self, event: str) -> bool:
    """Whether this config applies to ``event``; ``events: None`` matches every event."""
    events = self.events
    if events is None:
        return True
    # mypy is happy with this; pylint's pydantic plugin misinfers the field
    # type once it is read off `self`, so the membership test reads as
    # invalid to it. The equivalent module-level function did not trip it.
    return event in events  # pylint: disable=unsupported-membership-test