From 6fb3f01e05ee905169ec2b2db929ca345d7db470 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Thu, 30 Jul 2026 12:59:40 +0530 Subject: [PATCH 1/3] Add CI to auto-bump linuxaid-cli version/checksums in LinuxAid On every GitHub tag push, after goreleaser publishes the release, a new job downloads the release's checksums.txt and opens a PR against Obmondo/LinuxAid bumping common::system::openvox::linuxaid_cli::version and the per-arch checksums maps in common.yaml, architectures/armv7l.yaml, and architectures/aarch64.yaml, keeping the newest 4 versions in each. Requires a LINUXAID_REPO_TOKEN repo secret (a token with contents and pull-request write access to Obmondo/LinuxAid) to push the branch and open the PR. --- .github/workflows/release.yaml | 61 ++++++++++++++++++++++ scripts/bump-linuxaid-hiera.py | 94 ++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100755 scripts/bump-linuxaid-hiera.py diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6756603..590a54f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,3 +26,64 @@ jobs: args: release --clean -f .goreleaser-github.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + bump-linuxaid: + needs: goreleaser + runs-on: ubuntu-latest + steps: + - name: Checkout linuxaid-cli + uses: actions/checkout@v5.0.0 + + - name: Compute version + id: version + run: | + echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Download release checksums + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download "${{ steps.version.outputs.tag }}" \ + --repo Obmondo/linuxaid-cli \ + --pattern "linuxaid-cli_${{ steps.version.outputs.version }}_checksums.txt" \ + --output checksums.txt + + - name: Checkout LinuxAid + uses: actions/checkout@v5.0.0 + with: + repository: Obmondo/LinuxAid + token: ${{ secrets.LINUXAID_REPO_TOKEN }} + path: LinuxAid + + - name: Bump version and checksums in LinuxAid hiera data + run: | + python3 scripts/bump-linuxaid-hiera.py LinuxAid "${{ steps.version.outputs.version }}" checksums.txt + + - name: Open PR against LinuxAid + env: + GH_TOKEN: ${{ secrets.LINUXAID_REPO_TOKEN }} + working-directory: LinuxAid + run: | + VERSION="${{ steps.version.outputs.version }}" + BRANCH="bump-linuxaid-cli-${VERSION}" + + if ! git diff --quiet; then + git config user.name "obmondo-bot" + git config user.email "bot@obmondo.com" + git checkout -B "${BRANCH}" + git add modules/enableit/common/data/common.yaml \ + modules/enableit/common/data/architectures/armv7l.yaml \ + modules/enableit/common/data/architectures/aarch64.yaml + git commit -m "Bump linuxaid-cli to ${VERSION}" \ + -m "Automated bump triggered by Obmondo/linuxaid-cli release v${VERSION}. Checksums taken from the release's checksums.txt. Keeps the newest 4 versions in each hiera checksums map." + git push --force-with-lease origin "${BRANCH}" + gh pr create \ + --repo Obmondo/LinuxAid \ + --title "Bump linuxaid-cli to ${VERSION}" \ + --body "Automated bump triggered by [Obmondo/linuxaid-cli v${VERSION}](https://github.com/Obmondo/linuxaid-cli/releases/tag/v${VERSION})." \ + --head "${BRANCH}" \ + || echo "PR create failed or already exists for ${BRANCH}, check manually." + else + echo "No changes detected, skipping PR (already up to date)." + fi diff --git a/scripts/bump-linuxaid-hiera.py b/scripts/bump-linuxaid-hiera.py new file mode 100755 index 0000000..9f3c81c --- /dev/null +++ b/scripts/bump-linuxaid-hiera.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Bump the linuxaid-cli version/checksums pinned in the LinuxAid Puppet control repo. + +Usage: bump-linuxaid-hiera.py + +Updates, per architecture: + - modules/enableit/common/data/common.yaml (amd64, also holds the version pin) + - modules/enableit/common/data/architectures/armv7l.yaml (armv7) + - modules/enableit/common/data/architectures/aarch64.yaml (arm64) + +Keeps only the newest KEEP_VERSIONS checksum entries per file. +""" +import re +import sys +from pathlib import Path + +KEEP_VERSIONS = 4 + +VERSION_KEY = "common::system::openvox::linuxaid_cli::version" +CHECKSUMS_KEY = "common::system::openvox::linuxaid_cli::checksums" + +# path (relative to repo root) -> release asset arch suffix +FILES = { + "modules/enableit/common/data/common.yaml": "amd64", + "modules/enableit/common/data/architectures/armv7l.yaml": "armv7", + "modules/enableit/common/data/architectures/aarch64.yaml": "arm64", +} + + +def parse_checksums(text): + sums = {} + for line in text.splitlines(): + line = line.strip() + if not line: + continue + sha, name = line.split(None, 1) + sums[name] = sha + return sums + + +def sha_for_arch(sums, arch, checksums_path): + suffix = f"_linux_{arch}.tar.gz" + for name, sha in sums.items(): + if name.endswith(suffix): + return sha + raise SystemExit(f"no checksum ending in {suffix!r} found in {checksums_path}") + + +def bump_checksums_block(text, version, sha, keep=KEEP_VERSIONS): + pattern = re.compile( + r"(?P
^" + re.escape(CHECKSUMS_KEY) + r":\n)" + r"(?P(?:^ \d+\.\d+\.\d+: [0-9a-f]+\n)+)", + re.MULTILINE, + ) + m = pattern.search(text) + if not m: + raise SystemExit(f"could not find {CHECKSUMS_KEY!r} block") + + entries = re.findall(r"^ (\d+\.\d+\.\d+): ([0-9a-f]+)\n", m.group("entries"), re.MULTILINE) + entries = [(v, h) for v, h in entries if v != version] + entries.insert(0, (version, sha)) + entries = entries[:keep] + + new_block = m.group("header") + "".join(f" {v}: {h}\n" for v, h in entries) + return text[: m.start()] + new_block + text[m.end() :] + + +def bump_version_line(text, version): + pattern = re.compile(r"^(" + re.escape(VERSION_KEY) + r": ).*$", re.MULTILINE) + if not pattern.search(text): + return text + return pattern.sub(lambda m: m.group(1) + version, text, count=1) + + +def main(): + if len(sys.argv) != 4: + print(__doc__, file=sys.stderr) + sys.exit(1) + + repo_root, version, checksums_path = Path(sys.argv[1]), sys.argv[2], sys.argv[3] + sums = parse_checksums(Path(checksums_path).read_text()) + + for rel_path, arch in FILES.items(): + path = repo_root / rel_path + text = path.read_text() + sha = sha_for_arch(sums, arch, checksums_path) + text = bump_checksums_block(text, version, sha) + text = bump_version_line(text, version) + path.write_text(text) + print(f"updated {rel_path} ({arch} -> {sha})") + + +if __name__ == "__main__": + main() From fe6eb8f94e5965a8216f8e93aa22306948d14f1b Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Thu, 30 Jul 2026 13:32:57 +0530 Subject: [PATCH 2/3] Add workflow_dispatch to test bump-linuxaid without a new release Lets bump-linuxaid be run manually against an already-published tag (e.g. v1.8.0) to validate the checkout/download/bump/PR pipeline and LINUXAID_REPO_TOKEN without waiting for or cutting a real release. --- .github/workflows/release.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 590a54f..633430c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,12 +4,18 @@ on: push: tags: - "v*" + workflow_dispatch: + inputs: + tag: + description: "Existing linuxaid-cli release tag to (re)bump LinuxAid against, e.g. v1.8.0. Lets bump-linuxaid be tested without cutting a new release." + required: true permissions: contents: write jobs: goreleaser: + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - name: Checkout @@ -29,6 +35,7 @@ jobs: bump-linuxaid: needs: goreleaser + if: always() && (needs.goreleaser.result == 'success' || github.event_name == 'workflow_dispatch') runs-on: ubuntu-latest steps: - name: Checkout linuxaid-cli @@ -37,8 +44,9 @@ jobs: - name: Compute version id: version run: | - echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" - echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + TAG="${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - name: Download release checksums env: From 9ebc947778aada9d29e40f16fc2ef7f3cc67cc0c Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Thu, 30 Jul 2026 14:08:40 +0530 Subject: [PATCH 3/3] Rewrite hiera-bump script in Go instead of Python Replaces scripts/bump-linuxaid-hiera.py with scripts/bump-linuxaid-hiera (package main), matching this repo's existing Go tooling instead of adding a Python dependency. Same behavior: targeted regex edits (not a full YAML parse/re-encode) so every other line and comment in the hiera files stays byte-for-byte untouched, keeping the newest 4 checksum entries per file. The workflow now sets up Go in the bump-linuxaid job and runs the program via `go run` instead of `python3`. --- .github/workflows/release.yaml | 7 +- scripts/bump-linuxaid-hiera.py | 94 --------------- scripts/bump-linuxaid-hiera/main.go | 171 ++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 95 deletions(-) delete mode 100755 scripts/bump-linuxaid-hiera.py create mode 100644 scripts/bump-linuxaid-hiera/main.go diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 633430c..bdc7392 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -41,6 +41,11 @@ jobs: - name: Checkout linuxaid-cli uses: actions/checkout@v5.0.0 + - name: Set up Go + uses: actions/setup-go@v6.0.0 + with: + go-version-file: go.mod + - name: Compute version id: version run: | @@ -66,7 +71,7 @@ jobs: - name: Bump version and checksums in LinuxAid hiera data run: | - python3 scripts/bump-linuxaid-hiera.py LinuxAid "${{ steps.version.outputs.version }}" checksums.txt + go run ./scripts/bump-linuxaid-hiera LinuxAid "${{ steps.version.outputs.version }}" checksums.txt - name: Open PR against LinuxAid env: diff --git a/scripts/bump-linuxaid-hiera.py b/scripts/bump-linuxaid-hiera.py deleted file mode 100755 index 9f3c81c..0000000 --- a/scripts/bump-linuxaid-hiera.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -"""Bump the linuxaid-cli version/checksums pinned in the LinuxAid Puppet control repo. - -Usage: bump-linuxaid-hiera.py - -Updates, per architecture: - - modules/enableit/common/data/common.yaml (amd64, also holds the version pin) - - modules/enableit/common/data/architectures/armv7l.yaml (armv7) - - modules/enableit/common/data/architectures/aarch64.yaml (arm64) - -Keeps only the newest KEEP_VERSIONS checksum entries per file. -""" -import re -import sys -from pathlib import Path - -KEEP_VERSIONS = 4 - -VERSION_KEY = "common::system::openvox::linuxaid_cli::version" -CHECKSUMS_KEY = "common::system::openvox::linuxaid_cli::checksums" - -# path (relative to repo root) -> release asset arch suffix -FILES = { - "modules/enableit/common/data/common.yaml": "amd64", - "modules/enableit/common/data/architectures/armv7l.yaml": "armv7", - "modules/enableit/common/data/architectures/aarch64.yaml": "arm64", -} - - -def parse_checksums(text): - sums = {} - for line in text.splitlines(): - line = line.strip() - if not line: - continue - sha, name = line.split(None, 1) - sums[name] = sha - return sums - - -def sha_for_arch(sums, arch, checksums_path): - suffix = f"_linux_{arch}.tar.gz" - for name, sha in sums.items(): - if name.endswith(suffix): - return sha - raise SystemExit(f"no checksum ending in {suffix!r} found in {checksums_path}") - - -def bump_checksums_block(text, version, sha, keep=KEEP_VERSIONS): - pattern = re.compile( - r"(?P
^" + re.escape(CHECKSUMS_KEY) + r":\n)" - r"(?P(?:^ \d+\.\d+\.\d+: [0-9a-f]+\n)+)", - re.MULTILINE, - ) - m = pattern.search(text) - if not m: - raise SystemExit(f"could not find {CHECKSUMS_KEY!r} block") - - entries = re.findall(r"^ (\d+\.\d+\.\d+): ([0-9a-f]+)\n", m.group("entries"), re.MULTILINE) - entries = [(v, h) for v, h in entries if v != version] - entries.insert(0, (version, sha)) - entries = entries[:keep] - - new_block = m.group("header") + "".join(f" {v}: {h}\n" for v, h in entries) - return text[: m.start()] + new_block + text[m.end() :] - - -def bump_version_line(text, version): - pattern = re.compile(r"^(" + re.escape(VERSION_KEY) + r": ).*$", re.MULTILINE) - if not pattern.search(text): - return text - return pattern.sub(lambda m: m.group(1) + version, text, count=1) - - -def main(): - if len(sys.argv) != 4: - print(__doc__, file=sys.stderr) - sys.exit(1) - - repo_root, version, checksums_path = Path(sys.argv[1]), sys.argv[2], sys.argv[3] - sums = parse_checksums(Path(checksums_path).read_text()) - - for rel_path, arch in FILES.items(): - path = repo_root / rel_path - text = path.read_text() - sha = sha_for_arch(sums, arch, checksums_path) - text = bump_checksums_block(text, version, sha) - text = bump_version_line(text, version) - path.write_text(text) - print(f"updated {rel_path} ({arch} -> {sha})") - - -if __name__ == "__main__": - main() diff --git a/scripts/bump-linuxaid-hiera/main.go b/scripts/bump-linuxaid-hiera/main.go new file mode 100644 index 0000000..73c151e --- /dev/null +++ b/scripts/bump-linuxaid-hiera/main.go @@ -0,0 +1,171 @@ +// Command bump-linuxaid-hiera bumps the linuxaid-cli version/checksums pinned +// in the LinuxAid Puppet control repo's hiera data. +// +// Usage: bump-linuxaid-hiera +// +// Updates, per architecture: +// - modules/enableit/common/data/common.yaml (amd64, also holds the version pin) +// - modules/enableit/common/data/architectures/armv7l.yaml (armv7) +// - modules/enableit/common/data/architectures/aarch64.yaml (arm64) +// +// Edits are done with targeted regexes rather than a full YAML +// parse/re-encode, so every other line and comment in these files is left +// byte-for-byte untouched. +// +// Keeps only the newest keepVersions checksum entries per file. +package main + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "regexp" + "sort" + "strings" +) + +const keepVersions = 4 + +const ( + versionKey = "common::system::openvox::linuxaid_cli::version" + checksumsKey = "common::system::openvox::linuxaid_cli::checksums" +) + +// files maps each hiera file (relative to the LinuxAid repo root) to the +// release asset architecture suffix it tracks. +var files = map[string]string{ + "modules/enableit/common/data/common.yaml": "amd64", + "modules/enableit/common/data/architectures/armv7l.yaml": "armv7", + "modules/enableit/common/data/architectures/aarch64.yaml": "arm64", +} + +var ( + checksumsBlockRe = regexp.MustCompile( + `(?m)(^` + regexp.QuoteMeta(checksumsKey) + `:\n)((?:^ \d+\.\d+\.\d+: [0-9a-f]+\n)+)`, + ) + entryLineRe = regexp.MustCompile(`(?m)^ (\d+\.\d+\.\d+): ([0-9a-f]+)\n`) + versionLineRe = regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(versionKey) + `: .*$`) +) + +func parseChecksums(path string) (map[string]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + sums := make(map[string]string) + scanner := bufio.NewScanner(f) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 2 { + continue + } + sums[fields[1]] = fields[0] + } + return sums, scanner.Err() +} + +func shaForArch(sums map[string]string, arch, checksumsPath string) (string, error) { + suffix := fmt.Sprintf("_linux_%s.tar.gz", arch) + for name, sha := range sums { + if strings.HasSuffix(name, suffix) { + return sha, nil + } + } + return "", fmt.Errorf("no checksum ending in %q found in %s", suffix, checksumsPath) +} + +type checksumEntry struct { + version string + sha string +} + +func bumpChecksumsBlock(text, version, sha string) (string, error) { + loc := checksumsBlockRe.FindStringSubmatchIndex(text) + if loc == nil { + return "", fmt.Errorf("could not find %q block", checksumsKey) + } + header := text[loc[2]:loc[3]] + entriesBlock := text[loc[4]:loc[5]] + + var entries []checksumEntry + for _, m := range entryLineRe.FindAllStringSubmatch(entriesBlock, -1) { + if m[1] == version { + continue + } + entries = append(entries, checksumEntry{version: m[1], sha: m[2]}) + } + entries = append([]checksumEntry{{version: version, sha: sha}}, entries...) + if len(entries) > keepVersions { + entries = entries[:keepVersions] + } + + var b strings.Builder + b.WriteString(header) + for _, e := range entries { + fmt.Fprintf(&b, " %s: %s\n", e.version, e.sha) + } + + return text[:loc[0]] + b.String() + text[loc[1]:], nil +} + +func bumpVersionLine(text, version string) string { + return versionLineRe.ReplaceAllStringFunc(text, func(string) string { + return versionKey + ": " + version + }) +} + +func run(repoRoot, version, checksumsPath string) error { + sums, err := parseChecksums(checksumsPath) + if err != nil { + return err + } + + relPaths := make([]string, 0, len(files)) + for p := range files { + relPaths = append(relPaths, p) + } + sort.Strings(relPaths) + + for _, relPath := range relPaths { + arch := files[relPath] + fullPath := filepath.Join(repoRoot, relPath) + + data, err := os.ReadFile(fullPath) + if err != nil { + return err + } + + sha, err := shaForArch(sums, arch, checksumsPath) + if err != nil { + return err + } + + text, err := bumpChecksumsBlock(string(data), version, sha) + if err != nil { + return fmt.Errorf("%s: %w", relPath, err) + } + text = bumpVersionLine(text, version) + + if err := os.WriteFile(fullPath, []byte(text), 0o644); err != nil { + return err + } + fmt.Printf("updated %s (%s -> %s)\n", relPath, arch, sha) + } + + return nil +} + +func main() { + if len(os.Args) != 4 { + fmt.Fprintln(os.Stderr, "usage: bump-linuxaid-hiera ") + os.Exit(1) + } + + if err := run(os.Args[1], os.Args[2], os.Args[3]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +}