Metadata-Version: 2.5
Name: releaseprobe
Version: 1.1.1
Summary: Find release notes, URLs and metadata from container labels and tags.
Project-URL: Homepage, https://github.com/dkarv/releaseprobe
Project-URL: Repository, https://github.com/dkarv/releaseprobe
Project-URL: Issues, https://github.com/dkarv/releaseprobe/issues
Author-email: David Punzet <d.punzet@testbirds.de>
License-Expression: MIT
License-File: LICENSE
Keywords: container,docker,labels,oci,release-notes
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: packaging>=24
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# releaseprobe

Find out whether a newer version of a container image is available, and what
changed. Inspired by [Renovate's docker datasource](https://docs.renovatebot.com/modules/datasource/docker/):

- Reads [OCI image labels](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
  (`org.opencontainers.image.*`), preferring them over the deprecated
  [label-schema.org](http://label-schema.org/rc1/) labels (`org.label-schema.*`).
  Manifest (list) annotations with the same keys take precedence, since
  unlike labels they can't be inherited from the base image.
- Talks to the image's registry directly (Docker Hub, GHCR, Quay, or any
  registry implementing the [Docker Registry HTTP API v2](https://distribution.github.io/distribution/spec/api/))
  to list tags and fetch labels - no docker daemon required.
- If the image's source label points at GitHub, fetches the real release
  notes for every version between the current and the latest tag. Without a
  source label, the repository is taken from the image's BuildKit build
  provenance (`vcs:source`) if it has one, and for GHCR images
  `ghcr.io/<owner>/<repo>` is tried as `github.com/<owner>/<repo>` last.

## Installation

```bash
pip install releaseprobe
```

## Usage

### Check for a newer version

```bash
releaseprobe check grafana/grafana:11.2.0
```

```
registry-1.docker.io/grafana/grafana:11.2.0: 11.2.0 -> 13.2.1

## 11.2.1
https://github.com/grafana/grafana/releases/tag/v11.2.1

### Features and enhancements
...
```

If the image is already at the latest version, it says so and exits; add
`--json` for machine-readable output (`current_version`, `latest_version`,
`has_update`, `release_notes`).

Set a `GITHUB_TOKEN` environment variable to avoid GitHub's low unauthenticated
rate limit when checking images with many releases.

Pass `-v` (INFO) or `-vv` (DEBUG) to log progress and diagnostics - registry
and GitHub requests, version comparisons, label parsing - to stderr, useful
when debugging a failed or unexpected check remotely.

#### Floating tags (`:latest`, `:stable`, ...)

A floating tag carries no version of its own, so there is nothing to compare
against the registry's other tags. Pass the *currently installed* image's
labels with `--current-labels` so releaseprobe can read the real version
(`org.opencontainers.image.version`) out of them instead:

```bash
docker inspect --format '{{json .Config.Labels}}' my-homeassistant-container \
  | releaseprobe check homeassistant/home-assistant:stable --current-labels -
```

A path to a JSON file works too (`--current-labels labels.json`). Without
`--current-labels`, a floating tag fails with an error rather than silently
comparing against the wrong version.

Many images don't set a version label themselves and inherit their base
image's instead (e.g. `ollama/ollama` is labeled `24.04`, from
`ubuntu:24.04`). A labeled version that none of the repository's tags carry
is therefore ignored. For such images - and those without any version
label - pass the installed image's manifest digests with `--current-digest`
(repeatable); releaseprobe then looks for the newest version tag that points
to one of them:

```bash
docker image inspect --format '{{index .RepoDigests 0}}' ollama/ollama:latest
# ollama/ollama@sha256:926d40fd...
releaseprobe check ollama/ollama:latest --current-labels labels.json \
  --current-digest ollama/ollama@sha256:926d40fd...
```

Only the newest 25 version tags are looked up, so an install older than that
is still reported as an unknown version.

Images built straight from a branch, without any version tags, can't be
compared by version at all. If the installed image (via `--current-labels`)
and the image the tag points to now both carry an
`org.opencontainers.image.revision` label, releaseprobe compares those git
revisions instead: the versions are reported as short commit hashes, and for
GitHub sources the commits in between (from GitHub's compare API) become the
release notes. If the revisions are the same but the digests (when given)
differ, the label is assumed to come from the base image and the version is
reported as unknown.

**Limitations:**

- Only public/anonymous registry access is supported - no credentials for
  private images or registries yet.
- Tags are compared as versions using a simple `X.Y[.Z...]` pattern (PEP 440
  under the hood, so `v1.2.3` works too); tags that don't look like a
  version - `latest`, digests, build numbers - are ignored, flavored tags like
  `1.2.3-alpine` are only compared with tags of the same flavor, and
  pre-releases are never suggested as "latest".
- Real release notes are only fetched for GitHub-hosted sources; other hosts
  fall back to just the source URL.

### Interpret labels you already have

For offline use (no network access), `releaseprobe labels` interprets a JSON
object of labels directly, e.g. straight from `docker inspect`:

```bash
docker inspect --format '{{json .Config.Labels}}' ghcr.io/org/app:1.2.3 \
  | releaseprobe labels
```

```
version:       1.2.3
source:        https://github.com/org/app
url:           -
documentation: -
revision:      abc123
release notes: https://github.com/org/app
```

A path to a JSON file works too (`releaseprobe labels labels.json`); omit the
path or pass `-` to read from stdin. Add `--json` for machine-readable output.

## Library usage

```python
from releaseprobe import check_for_update, probe_labels

result = check_for_update("grafana/grafana:11.2.0")
result.has_update  # True
result.latest_version  # "13.2.1"
result.release_notes  # list[ReleaseNote], oldest first

info = probe_labels({"org.opencontainers.image.source": "https://github.com/org/app"})
info.release_notes_url()  # "https://github.com/org/app"
```

If the installed version of a floating-tag image can't be determined,
`UnknownVersionError` carries what is known about the installed image:

```python
from releaseprobe import UnknownVersionError, check_for_update, repository_name

try:
    check_for_update("ghcr.io/org/app:latest", labels, current_digests=digests)
except UnknownVersionError as err:
    err.installed_version  # "latest (sha256:0123456789ab)"
    err.reason  # "floating tag 'latest', no version label, ..."
    err.current_info.release_notes_url()  # from the installed image's labels

# The key check_for_update's tags_cache uses, for any image reference:
repository_name("nginx")  # "registry-1.docker.io/library/nginx"
```

Every public function logs progress and diagnostics through the standard
`logging` module, under logger names rooted at `releaseprobe` (e.g.
`releaseprobe.registry`) - configure those as usual to see them. To route the
logs elsewhere instead, e.g. into your own application's logging setup, pass
a `logger`:

```python
import logging

logger = logging.getLogger("myapp.releaseprobe")
result = check_for_update("grafana/grafana:11.2.0", logger=logger)
```

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

pytest
ruff check .
mypy
```

### Dev container

A [dev container](.devcontainer/devcontainer.json) is included so you can try
releaseprobe in VS Code: open the repo and choose "Reopen in Container"
(needs the Dev Containers extension). It gives you a Python 3.12 environment
with the project installed in editable mode.

`releaseprobe check <image>` works straight away in the container - it only
needs outbound network access, not a docker daemon. To try the offline
`labels` command against a real image instead, inspect it from your host and
drop the labels into the workspace (they land in the container too, since the
workspace folder is mounted in):

```bash
# on your host
docker inspect --format '{{json .Config.Labels}}' ghcr.io/grafana/grafana:11.2.0 > labels.json
```

```bash
# inside the dev container
releaseprobe labels labels.json
```

## Releasing

Bump the `__version__` string in `src/releaseprobe/__init__.py` and merge to
`main`. The CI pipeline detects the version bump, tags the commit, creates a
GitHub release with auto-generated notes, and publishes the package to PyPI.

## License

MIT, see [LICENSE](LICENSE).
