Skip to content

Commit 3c0404e

Browse files
committed
build: stamp charts and bake into the binary the proper version
1 parent d67db69 commit 3c0404e

10 files changed

Lines changed: 153 additions & 106 deletions

File tree

.config/goreleaser.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ project_name: clawmachine
44

55
before:
66
hooks:
7+
- bash .config/scripts/stamp-version.sh {{ .Version }}
78
- mise run charts
89

910
builds:

.config/mise.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ description = "Run go vet"
6868
depends = ["charts"]
6969
run = "cd control-plane && go vet ./..."
7070

71-
[tasks."release:image-contract"]
72-
description = "Validate bot image refs match Goreleaser image outputs"
73-
run = "bash .config/scripts/check-image-contract.sh"
74-
7571
[tasks.clean]
7672
description = "Remove build artifacts"
7773
run = "cd control-plane && rm -rf internal/service/charts/ clawmachine && echo '✓ Build artifacts cleaned'"

.config/scripts/check-image-contract.sh

Lines changed: 0 additions & 92 deletions
This file was deleted.

.config/scripts/stamp-version.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ver="${1:?Usage: stamp-version.sh <version>}"
5+
6+
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
7+
charts_dir="${repo_root}/control-plane/charts"
8+
9+
# Strip leading 'v' if present
10+
ver="${ver#v}"
11+
12+
echo "Stamping version ${ver} into chart files..."
13+
14+
# ── Chart.yaml: stamp 'version' for all charts ──
15+
for chart in clawmachine openclaw picoclaw ironclaw busybox; do
16+
file="${charts_dir}/${chart}/Chart.yaml"
17+
awk -v ver="$ver" '
18+
/^version:/ { $0 = "version: " ver }
19+
{ print }
20+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
21+
done
22+
23+
# ── Chart.yaml: stamp 'appVersion' only for clawmachine ──
24+
file="${charts_dir}/clawmachine/Chart.yaml"
25+
awk -v ver="$ver" '
26+
/^appVersion:/ { $0 = "appVersion: \"" ver "\"" }
27+
{ print }
28+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
29+
30+
# ── values.yaml: stamp image.tag and backup.image.tag ──
31+
#
32+
# Uses an awk state machine to target only:
33+
# - top-level image.tag (indent 0 → indent 2)
34+
# - backup.image.tag (indent 0 → indent 2 → indent 4)
35+
# while leaving other nested image blocks untouched (e.g. postgresql.image).
36+
for chart in openclaw picoclaw ironclaw busybox; do
37+
file="${charts_dir}/${chart}/values.yaml"
38+
awk -v ver="$ver" '
39+
/^[a-zA-Z]/ {
40+
section = $0; sub(/:.*/, "", section)
41+
in_sub = 0
42+
}
43+
section == "image" && /^ tag:/ {
44+
$0 = " tag: \"" ver "\""
45+
}
46+
section == "backup" {
47+
if (/^ image:/ && !in_sub) {
48+
in_sub = 1
49+
} else if (in_sub && /^ tag:/) {
50+
$0 = " tag: \"" ver "\""
51+
} else if (in_sub && /^ [a-zA-Z]/) {
52+
in_sub = 0
53+
}
54+
}
55+
{ print }
56+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
57+
done
58+
59+
echo "✓ Version ${ver} stamped"

.github/workflows/ci.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ jobs:
3232
- name: golangci-lint
3333
run: mise run lint
3434

35-
- name: release image contract
36-
run: mise run release:image-contract
37-
3835
- name: go test
3936
run: mise run test:ci
4037

.github/workflows/release.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ jobs:
4040
version="${GITHUB_REF_NAME#v}"
4141
echo "value=${version}" >> "$GITHUB_OUTPUT"
4242
43-
- name: Validate release image contract
44-
run: mise run release:image-contract
45-
env:
46-
EXPECTED_RELEASE_TAG: ${{ steps.version.outputs.value }}
47-
4843
- name: Run GoReleaser (artifacts only)
4944
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6
5045
with:
@@ -141,6 +136,7 @@ jobs:
141136
- name: Build release binary for clawmachine image
142137
if: matrix.image == 'ghcr.io/zackerydev/theclawmachine'
143138
run: |
139+
bash .config/scripts/stamp-version.sh "${{ steps.version.outputs.value }}"
144140
mise run charts
145141
mkdir -p "linux/${{ matrix.goarch }}"
146142
cd control-plane
@@ -234,6 +230,9 @@ jobs:
234230
git config user.name "github-actions[bot]"
235231
git config user.email "github-actions[bot]@users.noreply.github.com"
236232
233+
- name: Stamp chart versions
234+
run: bash .config/scripts/stamp-version.sh "${GITHUB_REF_NAME#v}"
235+
237236
- name: Package charts
238237
run: |
239238
mkdir -p .cr-release-packages

control-plane/cmd/clawmachine/version.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/spf13/cobra"
1111
"github.com/zackerydev/clawmachine/control-plane/internal/service"
12+
versionutil "github.com/zackerydev/clawmachine/control-plane/internal/version"
1213
"helm.sh/helm/v4/pkg/chart/loader"
1314
chartv2 "helm.sh/helm/v4/pkg/chart/v2"
1415
)
@@ -99,6 +100,8 @@ type botImageRef struct {
99100
}
100101

101102
func resolveBotImageRefs() ([]botImageRef, error) {
103+
runtimeTag, _ := versionutil.NormalizeRuntimeImageTag(version)
104+
102105
refs := make([]botImageRef, 0, len(botChartSpecs))
103106
for _, spec := range botChartSpecs {
104107
archive, err := service.GetEmbeddedChart(spec.kind)
@@ -119,6 +122,9 @@ func resolveBotImageRefs() ([]botImageRef, error) {
119122
if err != nil {
120123
return nil, fmt.Errorf("resolving image ref for %s: %w", spec.name, err)
121124
}
125+
if runtimeTag != "" {
126+
tag = runtimeTag
127+
}
122128
refs = append(refs, botImageRef{name: spec.name, repository: repo, tag: tag})
123129
}
124130
return refs, nil

control-plane/cmd/clawmachine/version_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func TestVersionCommand_Default(t *testing.T) {
3535

3636
func TestVersionCommand_All(t *testing.T) {
3737
origVersion := version
38-
version = "0.1.0"
38+
// Use a version different from chart defaults to verify runtime tag override.
39+
version = "9.9.9"
3940
defer func() { version = origVersion }()
4041

4142
cmd := newVersionCmd()
@@ -51,7 +52,7 @@ func TestVersionCommand_All(t *testing.T) {
5152
got := out.String()
5253
for _, want := range []string{
5354
"clawmachine",
54-
"v0.1.0",
55+
"v9.9.9",
5556
"bot images (canonical repo:tag):",
5657
"openclaw:",
5758
"picoclaw:",
@@ -67,6 +68,19 @@ func TestVersionCommand_All(t *testing.T) {
6768
}
6869
}
6970

71+
// Verify runtime tag override: all bot images should use the runtime version,
72+
// not the hardcoded chart default.
73+
for _, want := range []string{
74+
"ghcr.io/zackerydev/openclaw:9.9.9",
75+
"ghcr.io/zackerydev/picoclaw:9.9.9",
76+
"ghcr.io/zackerydev/ironclaw:9.9.9",
77+
"ghcr.io/zackerydev/theclawmachine-toolbox:9.9.9",
78+
} {
79+
if !strings.Contains(got, want) {
80+
t.Fatalf("output missing runtime-tagged image %q:\n%s", want, got)
81+
}
82+
}
83+
7084
shaPattern := regexp.MustCompile(`sha256:[a-f0-9]{64}`)
7185
matches := shaPattern.FindAllString(got, -1)
7286
if len(matches) < 3 {

control-plane/internal/handler/helm.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,13 @@ func ensureRuntimeImageTag(values map[string]any, runtimeVersion string, force b
11691169
return
11701170
}
11711171

1172+
stampImageTag(values, tag, force)
1173+
if backup, ok := values["backup"].(map[string]any); ok {
1174+
stampImageTag(backup, tag, force)
1175+
}
1176+
}
1177+
1178+
func stampImageTag(values map[string]any, tag string, force bool) {
11721179
image, _ := values["image"].(map[string]any)
11731180
if image == nil {
11741181
image = make(map[string]any)

control-plane/internal/handler/helm_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,3 +2401,63 @@ func TestIsHTMX(t *testing.T) {
24012401
})
24022402
}
24032403
}
2404+
2405+
func TestEnsureRuntimeImageTag_BackupImage(t *testing.T) {
2406+
t.Run("stamps backup.image.tag when force", func(t *testing.T) {
2407+
values := map[string]any{
2408+
"image": map[string]any{"repository": "ghcr.io/example", "tag": "old"},
2409+
"backup": map[string]any{
2410+
"enabled": true,
2411+
"image": map[string]any{"repository": "ghcr.io/backup", "tag": "old"},
2412+
},
2413+
}
2414+
ensureRuntimeImageTag(values, "1.2.3", true)
2415+
2416+
image := values["image"].(map[string]any)
2417+
if image["tag"] != "1.2.3" {
2418+
t.Fatalf("image.tag = %v, want 1.2.3", image["tag"])
2419+
}
2420+
2421+
backup := values["backup"].(map[string]any)
2422+
backupImage := backup["image"].(map[string]any)
2423+
if backupImage["tag"] != "1.2.3" {
2424+
t.Fatalf("backup.image.tag = %v, want 1.2.3", backupImage["tag"])
2425+
}
2426+
})
2427+
2428+
t.Run("respects force=false for backup tag", func(t *testing.T) {
2429+
values := map[string]any{
2430+
"image": map[string]any{"tag": "existing"},
2431+
"backup": map[string]any{
2432+
"image": map[string]any{"tag": "existing"},
2433+
},
2434+
}
2435+
ensureRuntimeImageTag(values, "1.2.3", false)
2436+
2437+
image := values["image"].(map[string]any)
2438+
if image["tag"] != "existing" {
2439+
t.Fatalf("image.tag = %v, want existing", image["tag"])
2440+
}
2441+
2442+
backup := values["backup"].(map[string]any)
2443+
backupImage := backup["image"].(map[string]any)
2444+
if backupImage["tag"] != "existing" {
2445+
t.Fatalf("backup.image.tag = %v, want existing", backupImage["tag"])
2446+
}
2447+
})
2448+
2449+
t.Run("no backup section is a no-op", func(t *testing.T) {
2450+
values := map[string]any{
2451+
"image": map[string]any{},
2452+
}
2453+
ensureRuntimeImageTag(values, "1.2.3", true)
2454+
2455+
image := values["image"].(map[string]any)
2456+
if image["tag"] != "1.2.3" {
2457+
t.Fatalf("image.tag = %v, want 1.2.3", image["tag"])
2458+
}
2459+
if _, ok := values["backup"]; ok {
2460+
t.Fatal("backup section should not be created")
2461+
}
2462+
})
2463+
}

0 commit comments

Comments
 (0)