Skip to main content

report_project_versions

Report which task template version each configured project is actually on.

Projects are deployed independently, so no single workflow run or git tag describes what every project holds. This script reads each project's state from the hub and compares it against the config file the deployment workflow reads.

Task templates are the only part of this with real versioning: the hub assigns a monotonic integer per owner/slug, and a project's latest task definition names the one it is rendered from. Template variables have no version of their own, so they are compared by a digest of their resolved values.

Example usage: python -m bitfount.runners.report_project_versions report \ task_templates/project-config-production-public.yaml --username myuser \ --password mypass --json-output report-bitfount.json

Render one markdown table from the per-account reports:

python -m bitfount.runners.report_project_versions summarise \ report-bitfount.json report-bitfount-testing.json

Module​

Functions​

fail_on_drift​

def fail_on_drift(*json_files: str) ‑> None:

Exit non-zero if any reported project disagrees with the config.

Shares DRIFT_STATUSES with the report itself, so the workflow gate and the table cannot disagree about what counts.

Arguments

  • json_files: The JSON reports written by report.

latest_template_version​

def latest_template_version(    task_owner: str,    task_slug: str,    hub: BitfountHub,    hub_url: str,    cache: dict[str, int | None],) ‑> int | None:

The newest version of a task template, or None if it cannot be read.

Asking per project would repeat the same lookup for every project sharing a template, so results are cached by owner/slug for the run.

Arguments

  • task_owner: The template owner.
  • task_slug: The template slug.
  • hub: The hub connection object.
  • hub_url: The hub URL.
  • cache: The per-run cache, keyed by owner/slug.

Returns The newest version, or None if the template could not be read.

report​

def report(    config_file: str,    username: str,    password: str | None = None,    project_id: str | None = None,    task_slug: str | None = None,    trial_name: str | None = None,    json_output: str | None = None,    git_ref: str | None = None,    fail_on_drift: bool = False,) ‑> None:

Report what each configured project is actually on.

Arguments

  • config_file: Path to the project configuration file.
  • username: The username to authenticate as. Only projects this user owns are reported; the others belong to another account's run.
  • password: Optional password. Falls back to the device code flow.
  • project_id: Only report this project id.
  • task_slug: Only report projects using this task slug.
  • trial_name: Only report projects whose trial_name variable is this.
  • json_output: Optional path to write the report to as JSON, for a later step to render or post.
  • git_ref: The git ref the config was read from, recorded in the report as the provenance of the desired state.
  • fail_on_drift: Exit non-zero if any reported project has drifted.

summarise​

def summarise(*json_files: str, markdown_output: str | None = None) ‑> None:

Render one markdown table from the per-account JSON reports.

Arguments

  • json_files: The JSON reports written by report.
  • markdown_output: Optional path to write the markdown to. Written to stdout either way, so a workflow can append it to the job summary.

variables_digest​

def variables_digest(variables: dict[str, Any]) ‑> str:

A short, stable digest of a set of template variables.

Template variables carry no version, so a digest of their resolved values is the only way to say whether two sets are the same one. Keys are sorted so that a reordering of the config does not read as a change.

Arguments

  • variables: The variable names and values.

Returns The first few hex characters of the digest, or empty for no variables.

Classes​

ProjectVersionRow​

class ProjectVersionRow(    project_id: str,    project_owner: str,    desired_task_slug: str,    desired_template_version: str,    desired_variables_digest: str,    actual_task_slug: str | None = None,    actual_template_version: int | None = None,    latest_template_version: int | None = None,    actual_variables_digest: str | None = None,    status: str = 'unreadable',    detail: str = '',):

One project's desired and actual state, as reported.

Variables​

  • static actual_task_slug : str | None
  • static actual_template_version : int | None
  • static actual_variables_digest : str | None
  • static desired_task_slug : str
  • static desired_template_version : str
  • static desired_variables_digest : str
  • static detail : str
  • static latest_template_version : int | None
  • static project_id : str
  • static project_owner : str
  • static status : str
  • actual_label : str - The slug@version label for what the project is actually on.

ReportStatus​

class ReportStatus(*args, **kwds):

What the hub says about a project relative to the config.

Ancestors​

Variables​

  • static BEHIND_LATEST
  • static MATCHES
  • static NO_TASK_DEFINITION
  • static PINNED_BEHIND
  • static TEMPLATE_DIFFERS
  • static UNREADABLE
  • static VARIABLES_DIFFER

VersionReport​

class VersionReport(    account: str,    config_file: str,    git_ref: str | None = None,    rows: list[ProjectVersionRow] = [],):

Every row gathered for one account.

Variables​

  • static account : str
  • static config_file : str
  • static git_ref : str | None

Methods​


drifted​

def drifted(self) ‑> list[ProjectVersionRow]:

The rows a reader needs to act on.

A pinned-behind row is where the config puts it, so it is reported in the table but is not counted as drift.