Clarify delivery exit statuses by failure phase

This commit is contained in:
2026-11-01 19:26:30 +00:00
parent 7b5c1285a2
commit 6bed1cd691
4 changed files with 97 additions and 12 deletions

View File

@@ -0,0 +1,71 @@
package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"go.maximumdirect.net/releasekit/cmd/releasekit/internal/cli"
)
func TestDeliveryFailureStatusesRespectPhase(t *testing.T) {
for _, test := range []struct {
name, rejectedPath, state string
cancelWrite bool
status int
}{
{name: "preflight authorization", rejectedPath: "/api/v1/repos/owner/example", state: "not_attempted", status: 2},
{name: "later authorization", rejectedPath: "/api/v1/repos/owner/example/tags/v1.2.3", state: "not_attempted", status: 1},
{name: "canceled unresolved write", cancelWrite: true, state: "unknown", status: 3},
} {
t.Run(test.name, func(t *testing.T) {
repository := newCLIRepository(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var commit string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == test.rejectedPath {
http.Error(w, "rejected credential", http.StatusUnauthorized)
return
}
switch r.URL.Path {
case "/api/v1/repos/owner/example", "/api/v1/repositories/1":
_ = json.NewEncoder(w).Encode(map[string]any{"id": 1, "name": "example", "full_name": "owner/example", "owner": map[string]string{"login": "owner"}})
case "/api/v1/repos/owner/example/tags/v1.2.3":
_ = json.NewEncoder(w).Encode(map[string]any{"commit": map[string]string{"sha": commit}})
case "/api/v1/repos/owner/example/releases/tags/v1.2.3":
http.NotFound(w, r)
case "/api/v1/repos/owner/example/releases":
if r.Method != http.MethodPost || !test.cancelWrite {
t.Errorf("unexpected release write: %s", r.Method)
}
cancel()
http.Error(w, "unacknowledged write", http.StatusServiceUnavailable)
default:
t.Errorf("unexpected request: %s", r.URL.Path)
http.NotFound(w, r)
}
}))
defer server.Close()
writeDeliveryProfile(t, repository, cli.Settings{
Product: "Example Product",
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
})
commit = cliGitOutput(t, repository, "rev-parse", "HEAD")
cliGitRun(t, repository, "tag", "v1.2.3")
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
var stdout, stderr bytes.Buffer
status := run(ctx, []string{"deliver", "--repo", repository, "v1.2.3"}, &stdout, &stderr)
if status != test.status || !strings.Contains(stderr.String(), "state="+test.state) {
t.Fatalf("status=%d stderr=%q, want status %d and state %s", status, stderr.String(), test.status, test.state)
}
if test.cancelWrite && !strings.Contains(stderr.String(), context.Canceled.Error()) {
t.Fatalf("cancellation cause missing: %s", stderr.String())
}
})
}
}

View File

@@ -208,10 +208,14 @@ first and then run `deliver --assets DIR` with the token available. The ordinary
`deliver` command does not download application dependencies or change the `deliver` command does not download application dependencies or change the
checkout. checkout.
The command returns 0 for complete delivery, 1 for an unavailable preflight, The command returns 0 for complete delivery, 1 for an unavailable preflight or
cancellation, known delivery failure, conflict, or partial delivery, 2 for a known delivery failure, conflict, or partial delivery, and 2 for usage,
usage, profile, forge-selection, credential, known permission deficiency, or profile, forge-selection, local credential-loading, or classified preflight
inaccessible-destination errors, and 3 when a remote write remains uncertain. configuration/access errors. A remote write whose outcome remains uncertain
returns 3, including when cancellation prevents reconciliation. Cancellation
without an unresolved write returns 1, including during preflight or
preparation. Later authorization failures follow
the delivery outcome: known failure returns 1; an unresolved write returns 3.
The result lists matching, uploaded, pending, conflicting, and uncertain assets The result lists matching, uploaded, pending, conflicting, and uncertain assets
as disjoint current observations; as disjoint current observations;
early failures may leave some declared assets unreported. On failure, the CLI early failures may leave some declared assets unreported. On failure, the CLI
@@ -258,11 +262,12 @@ tag arguments. The event ID may identify an annotated tag object; see the
[delivery guide](consumers/delivery.md#woodpecker-tag-workflows) for resolution [delivery guide](consumers/delivery.md#woodpecker-tag-workflows) for resolution
and workflow preparation. The CLI does not read and workflow preparation. The CLI does not read
provider-specific environment variable names. Checkout and dependency provider-specific environment variable names. Checkout and dependency
preparation failures, unavailable preflight, and cancellation return status 1; preparation failures, unavailable preflight, and cancellation during those
usage, profile, forge-selection, credential, known permission deficiency, steps return status 1. Usage, profile, forge-selection, local credential-loading,
inaccessible-destination, and invalid-version errors return status 2. Final classified preflight configuration/access, and invalid-version errors return
delivery uses the same statuses as `deliver`: 0 for complete, 1 for known status 2. Final delivery uses the same statuses as `deliver`: 0 for complete,
failure, conflict, or partial delivery, and 3 for uncertain remote state. 1 for known failure, conflict, or partial delivery, and 3 for uncertain remote
state, including when cancellation prevents reconciliation.
The maintained module-based tag workflow verifies and checks out the event tag The maintained module-based tag workflow verifies and checks out the event tag
before resolving the consumer module. It builds the ReleaseKit version already before resolving the consumer module. It builds the ReleaseKit version already

View File

@@ -26,8 +26,11 @@ selection and introduces authenticated, read-only credential preflight.
establish release-write or asset-write permission. establish release-write or asset-write permission.
- The CLI keeps `RELEASEKIT_GITEA_TOKEN` as its default token environment - The CLI keeps `RELEASEKIT_GITEA_TOKEN` as its default token environment
setting and preserves configured environment/file precedence. Selection, setting and preserves configured environment/file precedence. Selection,
credential, and inaccessible-destination errors return status 2; unavailable local credential-loading, and classified preflight configuration/access
verification and cancellation return status 1. errors return status 2; unavailable preflight and cancellation during
preflight/preparation return status 1. Later authorization failures follow
delivery outcomes. An unresolved write returns status 3, including when
cancellation prevents reconciliation; known delivery failures return 1.
- Binary `ci-deliver` checks credentials before application dependency - Binary `ci-deliver` checks credentials before application dependency
downloads and checks again during delivery. The automatic check now precedes downloads and checks again during delivery. The automatic check now precedes
full local artifact size and digest verification; those checks still finish full local artifact size and digest verification; those checks still finish

View File

@@ -121,7 +121,13 @@ require a new public package restriction or a broader dependency policy.
## 3. Qualify phase-specific CLI exit-status documentation — P3 ## 3. Qualify phase-specific CLI exit-status documentation — P3
Status: open. Status: complete.
**Completed:** Qualified the CLI reference and unreleased v0.6.0 note by phase:
local loading/classified preflight errors use status 2; later known failures
use 1; unresolved writes retain 3 even when canceled. Added a narrow CLI
regression for preflight versus later authorization failure and cancellation
after an attempted page write. Focused CLI preflight/recovery tests passed.
**Evidence:** [CLI exit statuses](../cli.md), **Evidence:** [CLI exit statuses](../cli.md),
[v0.6.0 release note](../releases/v0.6.0.md), [v0.6.0 release note](../releases/v0.6.0.md),