Upstream Monitor

Upstream release discovery and catalog API for Hummingbird. Application code lives in upstream-monitor/. Project definitions belong to the separate upstream-monitor-projects repository.

Features

  • Release discovery from Git, GitHub, GitLab, Anitya, and HTTP sources
  • Version filtering, release streams, lifecycle dates, and Git provenance
  • Catalog API and web interface
  • File, PostgreSQL, or S3 storage
  • Git catalog reconciliation and optional NATS JetStream events

Prerequisites

  • Go 1.27 or newer and a C compiler for local builds and tests
  • Podman for container builds
  • A project catalog and writable file storage, PostgreSQL, or S3 for results
  • Deployment managed credentials for authenticated upstreams and API operations

Build and Deployment

From the tools repository root:

podman build -t localhost/upstream-monitor:dev upstream-monitor/

Konflux builds ARM64 and AMD64 images as upstream-monitor--tools and releases them to quay.io/hummingbird-ci/upstream-monitor. The image runs as UID 65532 and contains these commands:

Command Purpose
upstream-monitor Poll upstreams and persist results, the default entry point
upstream-monitor-api Serve the catalog API and web interface
upstream-monitor-enricher Resolve optional Git provenance
upstream-monitor-project-reconciler Reconcile Git definitions into PostgreSQL

The infrastructure repository owns deployments, storage, messaging, credentials, and image pins. Register the Konflux component before building, and pin the released image digest in infrastructure and the catalog validator. Tools does not deploy the service or bundle a project catalog.

Usage

Validate a catalog

From the tools repository root:

podman run --rm --pull=never --network=none --read-only \
  -e PROJECTS_DIR=/projects \
  -v "$PWD/upstream-monitor/examples/file-layout/config/projects:/projects:ro" \
  localhost/upstream-monitor:dev validate

Replace the mounted directory with a checkout of the deployment catalog to validate it. Validation does not contact upstreams or write to a database. An empty or missing directory validates zero projects, so check the reported count before treating validation as successful.

Run locally

From the application directory, start the monitor:

cd upstream-monitor
PROJECTS_DIR=examples/file-layout/config/projects \
  go run ./cmd/upstream-monitor

In another terminal, from the same directory, start the API:

PROJECTS_DIR=examples/file-layout/config/projects \
  go run ./cmd/upstream-monitor-api

Both processes must use the same project and results storage. File results default to observations/, with provider cache data under cache/. Without project definitions, the monitor has nothing to check.

For an upstream check without persisting results:

go run ./cmd/upstream-monitor dry-run \
  --projects-dir /path/to/projects --project example-widget

dry-run contacts the upstream and can consume API quota. It resolves credentials through source.credential_ref, not the monitor’s GitHub App configuration.

Configuration

Variable Default Purpose
PROJECT_STORAGE_BACKEND file Definitions: file, postgres, or s3
RESULTS_STORAGE_BACKEND file Results: file, postgres, or s3
PROJECT_MUTATIONS auto Definition writes: auto, enabled, or disabled
PROJECTS_DIR config/projects External project JSON directory
POSTGRES_DSN empty Required when either backend is PostgreSQL
S3_BUCKET empty Required when either backend is S3
S3_PREFIX empty Unique prefix for each installation sharing a bucket
S3_REGION us-east-1 AWS region
S3_ENDPOINT empty Optional S3 compatible endpoint
S3_USE_PATH_STYLE false Enable path style S3 addressing
OBSERVATIONS_DIR observations File results directory
CACHE_DIR cache File backend provider cache
LISTEN_ADDR :8080 API listen address
POLL_INTERVAL 5m Scheduler interval and default project check interval
UPSTREAM_TIMEOUT 5m Maximum duration of an upstream check
MAX_CONCURRENT_CHECKS 4 Concurrent checks per worker
MAX_CONCURRENT_GIT_FETCHES 1 Concurrent uncached Git fetches per worker
ENRICHMENT_ENABLED false Include provenance in API responses

File results support a single writer. Use PostgreSQL or S3 for shared worker coordination. S3 must support conditional writes. Do not expire observation or coordination objects through bucket lifecycle policies.

Use GITHUB_TOKEN for token based GitHub access. For a GitHub App, set GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID, and GITHUB_APP_PRIVATE_KEY_FILE together. A project’s source.credential_ref names an environment variable containing a credential, never the credential itself. Keep secrets outside Git and container images.

See configuration defaults and environment overrides for remaining cache, lease, retry, and concurrency settings.

Project Definitions

Each JSON filename must match its id. IDs start with a lowercase letter, contain only lowercase letters, digits, and hyphens, and have at most 128 characters. Do not supply uid, which the service assigns.

{
  "id": "example-widget",
  "name": "Example widget",
  "homepage": "https://example.org/widget",
  "source": {
    "type": "github-releases",
    "location": "example/widget"
  },
  "version_policy": {
    "scheme": "semantic",
    "include_prereleases": false
  },
  "stream_policy": {
    "granularity": "minor"
  },
  "check_policy": {
    "mode": "fixed",
    "interval": "15m"
  }
}
Source type Location
git-tags Git remote URL
github-releases GitHub owner/repository
gitlab-releases, gitlab-tags GitLab numeric project ID or namespace path
anitya Numeric Anitya project ID
http-regex HTTP(S) URL with a source.extract regular expression

Set source.base_url for a separate GitLab or Anitya instance, and source.tag_pattern to select a tag family. Upstream destinations are not host allowlisted, so infrastructure must enforce the required network egress policy.

The default version scheme is semantic. Other choices are calendar, rpm, pep440 (python), and lexical (generic). Use transforms to normalize versions, and version_filters and prerelease_filters to control release selection. Streams group releases by major or minor, with optional semantic numeric component buckets.

Optional lifecycle modes are latest-release, latest-n-lines, date-based, endoflife-date, and manual, with per stream overrides. Fixed check policies use interval. Adaptive policies back off between min_interval and max_interval, resetting after a new release. The maximum interval is also the worst polling detection delay.

Catalog Reconciliation

The project reconciler validates an atomic git-sync checkout and replaces the PostgreSQL definition set. Supply POSTGRES_DSN through the environment:

PROJECT_STORAGE_BACKEND=postgres \
PROJECT_MUTATIONS=disabled \
PROJECT_CHECKOUT_LINK=/git-sync/current \
PROJECT_CHECKOUT_SUBPATH=projects \
  go run ./cmd/upstream-monitor-project-reconciler

Run from upstream-monitor/. The projects subpath matches the separate catalog repository. Set PROJECT_MUTATIONS=disabled on every process using that catalog. Workers and API replicas read its database copy.

Invalid revisions leave the last valid catalog active. Reconciliation removes definitions absent from Git. Verify completeness and back up the database before enabling it. Git cannot restore deleted observation history.

The monitor initializes database schemas. The API does not. Schema and image rollouts belong to infrastructure, not API startup.

API and Authentication

Open / for the catalog browser. The generated API schema is available at /openapi.json and /openapi.yaml. /docs redirects to the JSON schema.

Endpoint Purpose
/healthz Process health
/readyz Project store readability, not proof of successful polling
/v1/status Aggregate and per project check status
/v1/project-reconciliation Attempted and active Git revisions and reconciliation errors
/v1/projects Project search and pagination

Use last_success_at and project errors to assess freshness. Follow X-Next-Cursor for paginated reads. Definition updates and deletes require the current ETag in If-Match. A queued scan retains observations, while rescan clears that project’s snapshot and release history before checking.

Catalog reads are public. Definition reads, writes, inline checks, and scan requests require authentication and Cedar authorization. PROJECT_MUTATIONS=auto disables file definition writes but permits supported PostgreSQL and S3 writes.

API_AUTH_MODE Required configuration
disabled Default. Authenticated operations are unavailable.
static-token API_AUTH_TOKEN, API_AUTH_PRINCIPAL, API_AUTH_ROLES
kubernetes-tokenreview API_KUBERNETES_AUDIENCE and a role mapping file
oidc API_OIDC_ISSUER, API_OIDC_AUDIENCE, and a role mapping file
trusted-proxy API_TRUSTED_PROXY_CIDRS and a role mapping file
insecure Local development only. Never use in production.

API_AUTH_ROLE_MAPPINGS_FILE maps subjects and groups to Cedar roles:

{
  "subjects": {
    "system:serviceaccount:ci:release-bot": ["ProjectChecker"]
  },
  "groups": {
    "editors": ["ProjectEditor"]
  }
}

The default policy grants definition reads to ProjectReader, inline checks to ProjectChecker, editing and scan operations to ProjectEditor, and all actions to ProjectAdmin. Use API_CEDAR_POLICY_FILE to replace that policy.

TokenReview requires a mounted reviewer token and permission to create tokenreviews.authentication.k8s.io. Trust proxy identity headers only from an isolated authenticating proxy that callers cannot bypass. Remove inbound identity headers at the proxy, and prefer loopback CIDRs for a sidecar rather than trusting a whole cluster network.

Project status

Successful checks return to scheduled. waiting and succeeded are accepted for compatibility with older stored records:

stateDiagram-v2
    [*] --> queued : definition due now
    [*] --> scheduled : definition with future check
    [*] --> waiting : load legacy record
    [*] --> succeeded : load older successful record
    waiting --> queued : restart with due check
    waiting --> scheduled : restart with future check
    succeeded --> queued : next check claimed
    scheduled --> queued : check due, scan, or definition changed
    queued --> processing : worker starts
    queued --> scheduled : repository lease held elsewhere
    processing --> scheduled : snapshot persisted
    processing --> failed : check or persistence error
    failed --> queued : retry or definition changed
    processing --> queued : recover interrupted file worker

Records include the project identity, definition fingerprint, pending scan request, queue/start/finish/next check times, last_success_at, failure count, last error, and release/stream counts.

Events and Monitoring

Set NATS_URL to enable JetStream publishing and NATS_CREDS_FILE for mounted credentials. Default subjects are hummingbird.upstream.version.v1 and hummingbird.upstream.stream.v1. Set NATS_MANAGE_STREAM=false if infrastructure provisions the stream. Delivery is at least once, so consumers must deduplicate using event_key. Publication failures retain events for retry.

Logs are JSON on stdout by default, configured through LOG_LEVEL and LOG_FORMAT. Set OTEL_METRICS_EXPORTER=otlp with standard OpenTelemetry endpoint settings to export metrics. Unset exporter configuration does not connect to a local collector.

Monitor time since the last successful check, check failures, GitHub quota, and outbox depth and oldest pending age. Quiet projects remain healthy when checks succeed and adaptive intervals grow.

Development

Run from upstream-monitor/:

go vet ./...
go test ./...
go build ./cmd/...
go run ./cmd/upstream-monitor validate --projects-dir examples/file-layout/config/projects

The GitLab job also rejects empty fixture directories. Container smoke tests mount the examples and check API health through the K8s test runner.

Integration tests require disposable services. Set TEST_POSTGRES_DSN for PostgreSQL storage, TEST_POSTGRES_RECONCILE_DSN for a separate catalog replacement database, TEST_S3_ENDPOINT and TEST_S3_BUCKET for S3, or NATS_CLUSTER_TEST_URL for a JetStream cluster with three stream replicas. Never point these tests at production services.

License

See the repository LICENSE.