fix(storage): migrate GitHub artifact upload from deprecated v1-v3 API to v4#2612
fix(storage): migrate GitHub artifact upload from deprecated v1-v3 API to v4#2612kiwamizamurai wants to merge 4 commits into
Conversation
…I to v4
The old implementation used the internal `_apis/pipelines/workflows/{runID}/artifacts?api-version=6.0-preview`
endpoint (via ACTIONS_RUNTIME_URL), which GitHub deprecated and removed, causing HTTP 400 errors.
Replace with the Artifacts v4 Twirp/JSON protocol (ACTIONS_RESULTS_URL based):
1. POST CreateArtifact → receive signed_upload_url
2. PUT file contents to the signed Azure Blob URL
3. POST FinalizeArtifact to commit the upload
Closes diggerhq#1702
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
hey @kiwamizamurai , any chance you could link me to a repo where you tested this? or some screenshots |
|
@s1ntaxe770r sorry I don't have it because I was working on it for long time ago.... |
|
ahh, gotcha. Mind testing this out? This looks good but i would need to be certain it woks as intended |
… upload
The new v4 artifact upload code in plan_storage.go reads ACTIONS_RESULTS_URL,
but GitHub Actions does not pass this internal env var to shell `run:` steps -
only to Node.js JavaScript actions. Without this passthrough, the Go binary
sees the variable as empty and aborts with:
Error storing artifact file: ACTIONS_RESULTS_URL is not set
The existing actions/github-script step already exports the v1-v3 vars
(ACTIONS_RUNTIME_URL, ACTIONS_RUNTIME_TOKEN); this adds ACTIONS_RESULTS_URL
alongside them so the Go binary can reach it via os.Getenv.
…vars The Artifacts v4 Twirp API rejects requests with HTTP 401 "invalid auth token" when workflow_run_backend_id is set to GITHUB_RUN_ID. The official @actions/artifact v2 package does NOT use those env vars; it parses the JWT in ACTIONS_RUNTIME_TOKEN and reads the "scp" claim, which contains a scope of the form "Actions.Results:<workflow_run_backend_id>:<workflow_job_run_backend_id>". Without this derivation the IDs in the request body do not match the IDs the backend tied to the bearer token, so authorization fails before authentication proceeds. Adds a base64-decoded JWT-payload parser (no signature verification - the token is already trusted by virtue of being the runner's own token) and uses the extracted IDs in CreateArtifact and FinalizeArtifact.
|
@s1ntaxe770r Got it tested end-to-end. Spinning up a real workflow surfaced two more bugs that the original commit didn't account for, so I've pushed two follow-up commits on top of the v4 migration: 1. GitHub Actions only injects the runtime service env vars ( action.yml already had a 2. After fixing #1, ``` The IDs the backend ties to the bearer token are baked into the token itself, so they have to be extracted from there. Added a small base64-decode-only JWT payload parser (no signature check — it's the runner's own token). Verified end-to-end on a hosted runner: ``` All three Twirp calls succeed (CreateArtifact → signed-URL PUT → FinalizeArtifact). Test repo PR for evidence: kiwamizamurai/digger_tutorial#4 PR description updated to cover all three pieces. Ready for another look 🙏 |
Adds black-box tests for the two pieces of new behavior introduced by the v1-v3 → v4 migration: - extractArtifactBackendIDs: pure-function coverage of the JWT-payload parser, including malformed segments, missing scope, and empty/wrong- shaped Actions.Results scope values. - StorePlanFile: integration coverage of the 3-step Twirp + Azure-Blob upload flow against an httptest.Server, asserting request method, path, Authorization/Content-Type/x-ms-blob-* headers, and JSON body shape; plus negative paths for missing env vars, malformed token, server errors at each step, missing/empty/wrong-typed signed_upload_url, trailing slash on ACTIONS_RESULTS_URL, and empty file contents. No production-code changes — the existing API is testable as-is via t.Setenv and a loopback httptest.Server. Coverage: extractArtifactBackendIDs 100%, StorePlanFile 100%, doRequest 75% (remaining uncovered branches are http.NewRequest failure paths unreachable from well-formed calls).
|
Awesome i would try this out myself , do you have any steps to reproduce myself? |
|
No? Maybe you can just refer my above pr? |
Summary
Fixes #1702
The old
StorePlanFileimplementation called_apis/pipelines/workflows/{runID}/artifacts?api-version=6.0-preview(viaACTIONS_RUNTIME_URL), the deprecated Artifacts v1-v3 internal API. GitHub has removed this endpoint, causing HTTP 400 errors for anyone usingupload-plan-destination: github.This PR migrates the upload path to the Artifacts v4 Twirp/JSON protocol used by
@actions/artifactv2+, the same protocol thatactions/upload-artifact@v4uses internally:POST {ACTIONS_RESULTS_URL}/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact→ receivesigned_upload_urlPUTfile contents to the signed Azure Blob URL (x-ms-blob-type: BlockBlob)POST .../FinalizeArtifactto commit the uploadThe download path (
DownloadLatestPlans,PlanExists,RetrievePlan) already uses the stable GitHub REST API (go-github) and is unchanged.Changes
This PR contains three commits, each addressing a distinct piece needed to make v4 upload work:
1.
libs/storage/plan_storage.go— replace v1-v3 calls with v4 Twirp/JSONThe download path is unchanged. Only
StorePlanFileis rewritten.2.
action.yml— exposeACTIONS_RESULTS_URLto the digger CLIGitHub Actions does not inject the runtime service env vars (
ACTIONS_RUNTIME_TOKEN,ACTIONS_RESULTS_URL, etc.) into shellrun:steps — they are only injected into Node.js JavaScript actions. The digger CLI is launched from arun:step, so it sees these variables as empty.action.yml already had a Node.js step that re-exports the v1-v3 vars (
ACTIONS_RUNTIME_URL,ACTIONS_RUNTIME_TOKEN,ACTIONS_CACHE_URL) viacore.exportVariable()for exactly this reason. This PR addsACTIONS_RESULTS_URLto that list (one-line change). Without this, the Go code aborts immediately withACTIONS_RESULTS_URL is not set.3.
libs/storage/plan_storage.go— derive backend IDs from the JWT, not from env varsThe Twirp service rejects requests with
HTTP 401 "invalid auth token"whenworkflow_run_backend_idis set toGITHUB_RUN_ID. The official@actions/artifactv2 package does not useGITHUB_RUN_ID/GITHUB_RUN_ATTEMPT— it parses the JWT inACTIONS_RUNTIME_TOKENand reads thescpclaim, which has the form:The IDs the backend ties to the bearer token are baked into the token itself, so they must be extracted from the JWT — not guessed from env vars. This PR adds a small base64-decode-only JWT payload parser (no signature verification — the token is the runner's own) and uses the extracted IDs in
CreateArtifactandFinalizeArtifact.Test plan
End-to-end verified with a real PR on a GitHub-hosted runner: kiwamizamurai/digger_tutorial#4
Successful run output:
All three Twirp steps succeeded (
CreateArtifact→ signed-URLPUT→FinalizeArtifact).go build ./libs/storage/...passesCreateArtifactreturnssigned_upload_urlPUTsucceedsFinalizeArtifactcommits the artifact