Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DevinoSolutions/artifact

Drop-in replacements for actions/upload-artifact and actions/download-artifact that store artifacts on Devino's own MinIO (https://storage.devino.ca, bucket gh-artifacts) instead of GitHub's metered artifact storage.

permissions:
  id-token: write        # required: the action authenticates with the job's OIDC token

steps:
  - uses: DevinoSolutions/artifact/upload@v1
    with:
      name: playwright-report
      path: playwright-report/
      retention-days: 7

  - uses: DevinoSolutions/artifact/download@v1
    with:
      pattern: release-evidence-*
      merge-multiple: true
      path: release-evidence

Why

The org is on GitHub's free plan (500 MB of artifact storage, hard-capped by a $0 Actions budget). Once the quota is crossed every upload-artifact step is rejected. Self-hosted runners do not help: the upstream action always writes to GitHub's blob store. This action writes to devino instead.

How it works

  1. The job's GitHub OIDC token (audience storage.devino.ca) is exchanged with MinIO STS (AssumeRoleWithWebIdentity) for one-hour credentials. MinIO picks the policy named after the token's repository_owner_id claim, so only workflows owned by DevinoSolutions get access; tokens from any other owner map to no policy and are refused.

  2. A pinned mc (MinIO client) is fetched from storage.devino.ca/tools/ and cached in the runner tool cache. Two fallbacks follow the org mirror: dl.min.io, and the release assets of the archived github.com/minio/mc repository. Whichever source answers, the bytes are checked against the per-platform sha256 pinned in lib/main.py (MC_SHA256) before the binary is installed; a mismatch is never installed and the next source is tried. If every source fails, the error names each URL with its own reason.

    dl.min.io has answered 410 Gone since 2026-09-11/12 — MinIO archived the client and stopped serving those files. The org mirror is populated and is normally the source that answers, so the third entry exists for the case where the mirror itself is down: with the secondary retired there is no longer anything behind it. Re-pin MC_SHA256 from each release's .sha256sum asset whenever MC_VERSION changes.

  3. Upload: matched files are packed into one .tgz whose root mirrors upstream semantics (a single directory uploads its contents; several paths share their least common ancestor), then copied to gh-artifacts/<owner>/<repo>/<run_id>/<name>.tgz. Large files are uploaded as 16 MiB multipart chunks, which keeps each request under Cloudflare's 100 MB limit.

  4. Download: fetches by name, by pattern (with merge-multiple), or all artifacts of the run, and extracts safely into path.

Retention is enforced by bucket lifecycle rules: retention-days is rounded up to 1/3/5/7/14/30/90 days and stored as an object tag (values above 90 are capped at 90). When retention-days is not set the artifact expires after 14 days, which matches how the org's test reports and screenshots are used.

Transient failures

Every mc call is retried when it fails for a transport reason — connection reset, refused or timed out, TLS handshake timeout, DNS failure, or a 5xx from the endpoint. Five attempts, exponential backoff (2, 4, 8, 16 s) with +/- 25% jitter. Both verbs are safe to repeat: cp writes a whole object under a key derived from the run, and ls is read-only.

A real 404 (NoSuchKey, "object does not exist") and a rejected credential are never retried — they are deterministic answers, so retrying only delays the report. The download step also words its error after the real cause: only a genuine 404 says Artifact not found. Anything else leads with mc's own message, so a network failure is not mistaken for an upload that never happened.

Inputs

upload: name, path, if-no-files-found, retention-days, compression-level, include-hidden-files, overwrite (accepted, always overwrites). Outputs: artifact-id, artifact-url, artifact-digest.

download: name, path, pattern, merge-multiple, run-id, repository, github-token (ignored). Output: download-path.

Both accept endpoint, bucket, access-key, secret-key to bypass OIDC.

Finding an artifact afterwards

Every step writes the s3:// location to the job summary. Fetch it with:

mc cp devino/gh-artifacts/<owner>/<repo>/<run_id>/<name>.tgz .

or browse https://minio-console.devino.ca.

Runner requirements

Python 3.8+ and bash (all GitHub-hosted images and the ubuntu-devino ARC image qualify). Supported platforms: Linux x64/arm64, macOS x64/arm64, Windows x64.

About

Drop-in replacement for actions/upload-artifact and actions/download-artifact backed by Devino's self-hosted MinIO (storage.devino.ca) via GitHub OIDC

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages