Skip to content

Commit e572edc

Browse files
committed
Cover release auxiliary assets
1 parent 2fe4f1d commit e572edc

11 files changed

Lines changed: 97 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- Added `openexit release-manifest` and `openexit verify-release` for schema-backed release artifact manifests and offline binary/checksum verification.
4242
- Added a release installer script that detects the local platform, verifies the selected artifact against `SHA256SUMS` and `RELEASE_MANIFEST.json`, and is smoke-tested by the release gate.
4343
- Added `openexit completion` plus release completion assets for Bash, Zsh, Fish, and PowerShell.
44+
- Extended release manifests and `SHA256SUMS` to cover installer and shell-completion assets as well as platform binaries.
4445
- Added `make release-check` and wired the draft-release workflow to run the full release gate before packaging artifacts.
4546
- Updated push CI to run the same release readiness gate, including smoke pipelines and bundle verification.
4647
- Updated CI and release workflows to Node.js 24-native GitHub Actions.

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ LDFLAGS := -s -w -X github.com/RamazanKara/openexit/internal/version.Version=$(V
77
GOFILES := $(shell find . -name '*.go' -not -path './bin/*' -not -path './dist/*')
88
PLATFORMS ?= linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64
99
RELEASE_MANIFEST ?= RELEASE_MANIFEST.json
10+
RELEASE_ASSETS ?= install.sh openexit.bash _openexit openexit.fish openexit.ps1
1011
GOLANGCI_LINT_VERSION ?= v2.12.2
1112
GOLANGCI_LINT ?= bin/golangci-lint
1213
EXAMPLE_INPUT ?= examples/datadog-to-grafana/input/datadog-fixture.json
@@ -128,14 +129,14 @@ release-dist:
128129
echo "building $$out"; \
129130
CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o "$$out" ./cmd/openexit; \
130131
done
131-
cd dist && sha256sum openexit_* > SHA256SUMS
132-
$(GO) run -trimpath -ldflags "$(LDFLAGS)" ./cmd/openexit release-manifest --dist dist --out dist/$(RELEASE_MANIFEST) $(foreach target,$(PLATFORMS),--platform $(target))
133132
cp scripts/install.sh dist/install.sh
134133
chmod +x dist/install.sh
135134
$(GO) run -trimpath -ldflags "$(LDFLAGS)" ./cmd/openexit completion bash > dist/openexit.bash
136135
$(GO) run -trimpath -ldflags "$(LDFLAGS)" ./cmd/openexit completion zsh > dist/_openexit
137136
$(GO) run -trimpath -ldflags "$(LDFLAGS)" ./cmd/openexit completion fish > dist/openexit.fish
138137
$(GO) run -trimpath -ldflags "$(LDFLAGS)" ./cmd/openexit completion powershell > dist/openexit.ps1
138+
$(GO) run -trimpath -ldflags "$(LDFLAGS)" ./cmd/openexit release-manifest --dist dist --out dist/$(RELEASE_MANIFEST) $(foreach target,$(PLATFORMS),--platform $(target)) $(foreach asset,$(RELEASE_ASSETS),--asset $(asset))
139+
cd dist && sha256sum openexit_* $(RELEASE_ASSETS) > SHA256SUMS
139140

140141
install-smoke: release-dist
141142
tmp=$$(mktemp -d); \
@@ -151,7 +152,7 @@ release-check: verify release-dist install-smoke
151152
@test -s dist/_openexit
152153
@test -s dist/openexit.fish
153154
@test -s dist/openexit.ps1
154-
@expected=$$(printf '%s\n' $(PLATFORMS) | wc -w | tr -d ' '); \
155+
@expected=$$(printf '%s\n' $(PLATFORMS) $(RELEASE_ASSETS) | wc -w | tr -d ' '); \
155156
actual=$$(wc -l < dist/SHA256SUMS | tr -d ' '); \
156157
if [ "$$actual" != "$$expected" ]; then \
157158
echo "expected $$expected release checksums, got $$actual"; \

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ Included in the current implementation:
136136
- Validation report with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, Forgejo migration, identity realm/client, edge VCL/HAProxy/Coraza, and LiteLLM/vLLM candidate checks, YAML/JSON parsing, evidence ref checks, secret scan, and optional `promtool`/`kubeconform` checks.
137137
- Evidence bundle export with README, checksums, and a schema-backed machine-readable manifest.
138138
- Offline evidence bundle verification for manifest schema, checksums, digest/size metadata, and archive path safety.
139-
- Release artifact manifest generation with per-binary OS/architecture metadata, size, and SHA-256 digests.
140-
- Offline release artifact verification against `RELEASE_MANIFEST.json` and optional `SHA256SUMS`.
139+
- Release artifact manifest generation with binary OS/architecture metadata, auxiliary asset metadata, sizes, and SHA-256 digests.
140+
- Offline release artifact verification for binaries and auxiliary assets against `RELEASE_MANIFEST.json` and optional `SHA256SUMS`.
141141
- Release installer script that selects the current platform binary and verifies it before installation.
142142
- Shell completion generation for Bash, Zsh, Fish, and PowerShell, including release-provided completion assets.
143143
- Evidence bundle path-safety checks that reject symlinks in exported project sections.

docs/cli.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ The checked-in Datadog example can be refreshed with `make example VERSION=0.1.0
3535

3636
`openexit verify-bundle <bundle.zip>` verifies an exported bundle without requiring the original project directory. It checks archive path safety, required bundle files, manifest schema validity, manifest file size/digest metadata, and `checksums.txt`. Use `--json` to feed the verification report into a handover gate.
3737

38-
`openexit release-manifest --dist dist --out dist/RELEASE_MANIFEST.json` writes a machine-readable manifest for release binaries. It records the stamped release version, commit, build date, generation time, and each expected OS/architecture artifact with file name, relative path, size, and SHA-256 digest. The manifest shape is published as `schemas/openexit.release-manifest.schema.json`.
38+
`openexit release-manifest --dist dist --out dist/RELEASE_MANIFEST.json` writes a machine-readable manifest for release artifacts. It records the stamped release version, commit, build date, generation time, each expected OS/architecture binary, and auxiliary assets such as `install.sh` and shell completions with file name, type, relative path, size, and SHA-256 digest. The manifest shape is published as `schemas/openexit.release-manifest.schema.json`.
3939

40-
`openexit verify-release dist/RELEASE_MANIFEST.json --dist dist --require-checksums` verifies release binaries after download or before publishing. It validates the manifest schema, rejects unsafe artifact paths, recomputes file sizes and SHA-256 digests, and verifies `SHA256SUMS` when required. Use repeatable `--artifact <name>` to verify only the current platform artifact after download, or `--json` to feed the verification report into a release gate.
40+
`openexit verify-release dist/RELEASE_MANIFEST.json --dist dist --require-checksums` verifies release artifacts after download or before publishing. It validates the manifest schema, rejects unsafe artifact paths, recomputes file sizes and SHA-256 digests, and verifies `SHA256SUMS` when required. Use repeatable `--artifact <name>` to verify only the current platform artifact after download, or `--json` to feed the verification report into a release gate.
4141

4242
`openexit completion bash|zsh|fish|powershell` prints shell completion scripts. Release artifacts also include `openexit.bash`, `_openexit`, `openexit.fish`, and `openexit.ps1` so package managers and manual installs can wire completion without rebuilding from source.
4343

docs/release.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Release-blocking v0.1 requirements:
2020
- Validation engine with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, Forgejo migration, identity realm/client, edge VCL/HAProxy/Coraza, and LiteLLM/vLLM candidate checks, YAML/JSON parsing, evidence refs, secret scan, optional promtool, and optional kubeconform.
2121
- Evidence bundle export with checksums, a schema-backed machine-readable manifest, and OpenExit version metadata.
2222
- Offline evidence bundle verification for archive path safety, manifest schema, manifest digests, and checksums.
23-
- Machine-readable release manifest generation and offline release artifact verification for OS/architecture binaries and `SHA256SUMS`.
23+
- Machine-readable release manifest generation and offline release artifact verification for OS/architecture binaries, auxiliary release assets, and `SHA256SUMS`.
2424
- Verified release installer for Linux/macOS `amd64` and `arm64` downloads.
2525
- Shell completion generation and release completion assets for Bash, Zsh, Fish, and PowerShell.
2626
- No-op AI assist and optional external assist behind explicit opt-in.
@@ -43,7 +43,7 @@ The AI provider path is complete for local fixture assessment workflows and incl
4343
- [ ] `make verify VERSION=0.1.0` passes, including CLI smoke pipelines.
4444
- [ ] `make lint` runs `gofmt`, `golangci-lint`, and `go vet`.
4545
- [ ] `make release-dist VERSION=0.1.0` produces binaries, `dist/SHA256SUMS`, `dist/RELEASE_MANIFEST.json`, `dist/install.sh`, and completion assets `openexit.bash`, `_openexit`, `openexit.fish`, and `openexit.ps1`.
46-
- [ ] `openexit verify-release dist/RELEASE_MANIFEST.json --dist dist --require-checksums` passes and fails when a release binary is tampered with.
46+
- [ ] `openexit verify-release dist/RELEASE_MANIFEST.json --dist dist --require-checksums` passes and covers binaries, `install.sh`, and completion assets; it fails when a release artifact is tampered with.
4747
- [ ] `OPENEXIT_VERSION=0.1.0 OPENEXIT_BASE_URL=$PWD/dist BIN_DIR=$(mktemp -d)/bin sh scripts/install.sh` installs a verified local release binary and `openexit version` reports `0.1.0`.
4848
- [ ] `make example VERSION=0.1.0-dev` refreshes `examples/datadog-to-grafana/output/` and exports `examples/datadog-to-grafana/openexit-example.zip`.
4949
- [ ] Datadog definition-of-done pipeline passes:
@@ -73,7 +73,7 @@ The AI provider path is complete for local fixture assessment workflows and incl
7373
- [ ] Exported bundle README includes version, commit, build date, bundle timestamp, and candidate warning.
7474
- [ ] Exported bundle `manifest.json` includes build metadata, project source/target, validation totals, and per-file SHA-256 digests, and validates against `schemas/openexit.evidence-bundle.schema.json`.
7575
- [ ] `openexit verify-bundle <zip>` passes for exported bundles and fails when an archived file is tampered with.
76-
- [ ] Release manifest validates against `schemas/openexit.release-manifest.schema.json` and includes version, commit, build date, generation time, OS/architecture metadata, sizes, and SHA-256 digests.
76+
- [ ] Release manifest validates against `schemas/openexit.release-manifest.schema.json` and includes version, commit, build date, generation time, artifact type, binary OS/architecture metadata, sizes, and SHA-256 digests.
7777
- [ ] Export refuses symlinks in exported project sections, including when `--force` is used.
7878
- [ ] No credentials, tokens, passwords, or private keys are present in fixtures, generated files, docs, or bundles.
7979
- [ ] Draft release notes have been reviewed.

docs/schemas.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ OpenExit schemas live under `schemas/` and mirror the typed Go manifests. Releas
44

55
Evidence bundle exports include `openexit-evidence/manifest.json`, which follows `schemas/openexit.evidence-bundle.schema.json`. The manifest records OpenExit build metadata, project source/target metadata, validation totals, and SHA-256 digests for exported project files so downstream review tooling can verify a bundle without parsing every human-readable report first.
66

7+
Release builds include `RELEASE_MANIFEST.json`, which follows `schemas/openexit.release-manifest.schema.json`. The manifest records OpenExit build metadata and every release artifact that should be covered by `SHA256SUMS`: platform binaries use `type: binary` with `os` and `arch`, while installer and shell-completion files use `type: asset`.
8+
79
Project manifests must use one of the supported source/target pairs: Datadog to Grafana LGTM, GitHub Enterprise to Forgejo, Okta/Auth0 to Keycloak/Zitadel, Cloudflare/Akamai to Varnish/HAProxy/Coraza, or OpenAI/Anthropic to vLLM/LiteLLM.
810

911
Inventory dashboards can include optional `dataSources` and `templateVariables` fields so assessment can flag Grafana mapping risk. Datadog fixture and live collectors populate `metrics` from captured dashboard and monitor queries, including referenced tag keys where available. The live Datadog collector also populates `integrations` from the Datadog v2 Integrations API when accessible. SLOs can include optional `sli`, `burnRateMonitorIds`, and `dashboardRefs` fields. The top-level inventory `volumes` section records whether log and trace volume assumptions are known.

internal/app/command.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,7 @@ func writeBundleVerification(w io.Writer, report *openexport.VerificationReport)
929929

930930
func newReleaseManifestCommand() *cobra.Command {
931931
var distDir, out, manifestVersion, commit, date string
932-
var platforms []string
932+
var platforms, assets []string
933933
cmd := &cobra.Command{
934934
Use: "release-manifest",
935935
Short: "Write a machine-readable OpenExit release artifact manifest",
@@ -952,6 +952,7 @@ func newReleaseManifestCommand() *cobra.Command {
952952
Commit: commit,
953953
Date: date,
954954
Platforms: platforms,
955+
Assets: assets,
955956
})
956957
if err != nil {
957958
return err
@@ -970,6 +971,7 @@ func newReleaseManifestCommand() *cobra.Command {
970971
cmd.Flags().StringVar(&commit, "commit", "", "Release commit; defaults to the stamped CLI commit")
971972
cmd.Flags().StringVar(&date, "date", "", "Release build date; defaults to the stamped CLI date")
972973
cmd.Flags().StringArrayVar(&platforms, "platform", nil, "Release target os/arch; repeatable and defaults to the standard release matrix")
974+
cmd.Flags().StringArrayVar(&assets, "asset", nil, "Additional non-binary release asset path under --dist; repeatable")
973975
return cmd
974976
}
975977

internal/app/e2e_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ func TestReleaseManifestAndVerifyCommands(t *testing.T) {
133133
t.Fatalf("write artifact %s: %v", name, err)
134134
}
135135
}
136+
if err := os.WriteFile(filepath.Join(distDir, "install.sh"), []byte("#!/bin/sh\n"), 0o755); err != nil {
137+
t.Fatalf("write install asset: %v", err)
138+
}
136139
manifestPath := filepath.Join(distDir, openrelease.DefaultManifest)
137140

138141
args := []string{
@@ -146,11 +149,12 @@ func TestReleaseManifestAndVerifyCommands(t *testing.T) {
146149
for _, platform := range platforms {
147150
args = append(args, "--platform", platform)
148151
}
152+
args = append(args, "--asset", "install.sh")
149153
out, err := executeForTestWithOutput(args...)
150154
if err != nil {
151155
t.Fatalf("openexit release-manifest failed: %v\n%s", err, out)
152156
}
153-
if !strings.Contains(out, "artifacts: 2") {
157+
if !strings.Contains(out, "artifacts: 3") {
154158
t.Fatalf("expected release-manifest artifact count, got:\n%s", out)
155159
}
156160
manifestData, err := os.ReadFile(manifestPath)
@@ -172,7 +176,7 @@ func TestReleaseManifestAndVerifyCommands(t *testing.T) {
172176
if err != nil {
173177
t.Fatalf("openexit verify-release failed: %v\n%s", err, out)
174178
}
175-
for _, marker := range []string{"status: passed", "build: version=9.9.9-test", "artifacts: 2", "checksums: 2"} {
179+
for _, marker := range []string{"status: passed", "build: version=9.9.9-test", "artifacts: 3", "checksums: 3"} {
176180
if !strings.Contains(out, marker) {
177181
t.Fatalf("expected verify-release marker %q, got:\n%s", marker, out)
178182
}
@@ -194,7 +198,7 @@ func TestReleaseManifestAndVerifyCommands(t *testing.T) {
194198
if err := json.Unmarshal([]byte(jsonOut), &report); err != nil {
195199
t.Fatalf("decode verify-release JSON: %v\n%s", err, jsonOut)
196200
}
197-
if report.Status != "passed" || len(report.Artifacts) != 2 || report.ChecksumEntries != 2 {
201+
if report.Status != "passed" || len(report.Artifacts) != 3 || report.ChecksumEntries != 3 {
198202
t.Fatalf("unexpected verify-release JSON report: %+v", report)
199203
}
200204
}

internal/release/manifest.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const (
2424
Kind = "ReleaseManifest"
2525
DefaultManifest = "RELEASE_MANIFEST.json"
2626
ChecksumFileName = "SHA256SUMS"
27+
ArtifactBinary = "binary"
28+
ArtifactAsset = "asset"
2729
)
2830

2931
var DefaultPlatforms = []string{
@@ -50,8 +52,9 @@ type Build struct {
5052

5153
type Artifact struct {
5254
Name string `json:"name"`
53-
OS string `json:"os"`
54-
Arch string `json:"arch"`
55+
Type string `json:"type"`
56+
OS string `json:"os,omitempty"`
57+
Arch string `json:"arch,omitempty"`
5558
Path string `json:"path"`
5659
Size int64 `json:"size"`
5760
SHA256 string `json:"sha256"`
@@ -63,6 +66,7 @@ type ManifestOptions struct {
6366
Commit string
6467
Date string
6568
Platforms []string
69+
Assets []string
6670
GeneratedAt time.Time
6771
}
6872

@@ -86,6 +90,7 @@ type VerificationReport struct {
8690

8791
type ArtifactVerification struct {
8892
Name string `json:"name"`
93+
Type string `json:"type"`
8994
OS string `json:"os"`
9095
Arch string `json:"arch"`
9196
Path string `json:"path"`
@@ -142,13 +147,34 @@ func Generate(opts ManifestOptions) (*Manifest, error) {
142147
}
143148
manifest.Artifacts = append(manifest.Artifacts, Artifact{
144149
Name: name,
150+
Type: ArtifactBinary,
145151
OS: osName,
146152
Arch: arch,
147153
Path: name,
148154
Size: info.Size(),
149155
SHA256: digest,
150156
})
151157
}
158+
for _, asset := range opts.Assets {
159+
asset = strings.TrimSpace(asset)
160+
if asset == "" {
161+
return nil, errors.New("asset path is required")
162+
}
163+
if err := safeArtifactPath(asset); err != nil {
164+
return nil, fmt.Errorf("asset %s: %w", asset, err)
165+
}
166+
info, digest, err := fileDigest(filepath.Join(distDir, filepath.FromSlash(asset)))
167+
if err != nil {
168+
return nil, fmt.Errorf("%s: %w", asset, err)
169+
}
170+
manifest.Artifacts = append(manifest.Artifacts, Artifact{
171+
Name: path.Base(asset),
172+
Type: ArtifactAsset,
173+
Path: asset,
174+
Size: info.Size(),
175+
SHA256: digest,
176+
})
177+
}
152178
sort.Slice(manifest.Artifacts, func(i, j int) bool {
153179
return manifest.Artifacts[i].Path < manifest.Artifacts[j].Path
154180
})
@@ -211,6 +237,7 @@ func Verify(opts VerifyOptions) (*VerificationReport, error) {
211237
}
212238
result := ArtifactVerification{
213239
Name: artifact.Name,
240+
Type: artifact.Type,
214241
OS: artifact.OS,
215242
Arch: artifact.Arch,
216243
Path: artifact.Path,
@@ -224,6 +251,12 @@ func Verify(opts VerifyOptions) (*VerificationReport, error) {
224251
if artifact.Name == "" {
225252
resultError(&result, "name is required")
226253
}
254+
if artifact.Type == "" {
255+
resultError(&result, "type is required")
256+
}
257+
if artifact.Type == ArtifactBinary && (artifact.OS == "" || artifact.Arch == "") {
258+
resultError(&result, "binary artifact requires os and arch")
259+
}
227260
if _, exists := seen[artifact.Path]; exists {
228261
resultError(&result, "duplicate artifact path")
229262
}

0 commit comments

Comments
 (0)