From 7d256a999f92840a241768bde71c367829d866a3 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 15:55:17 +1100 Subject: [PATCH 1/7] Add version subcommand and Makefile semver bump targets - var version injected at build time via -X main.version=$(VERSION) - VERSION derives from 'git describe --tags' so built binaries show the exact tag (or tag+commits+sha for dirty/untagged builds) - 'make patch/minor/major' reads the latest vX.Y.Z tag and creates the next one; starts from v0.0.0 if no tags exist yet --- Makefile | 25 +++++++++++++++++++++++-- main.go | 10 ++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0459ade..e44aeb7 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ BINARY := node-lookup -GOFLAGS := -ldflags="-s -w" +VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +GOFLAGS := -ldflags="-s -w -X main.version=$(VERSION)" -.PHONY: all build test lint clean install +.PHONY: all build test lint clean install patch minor major all: build @@ -19,3 +20,23 @@ clean: install: go install $(GOFLAGS) ./... + +# Bump helpers — reads the latest semver tag and creates the next one. +# If no tag exists yet, starts from v0.0.0. +_LATEST := $(shell git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' | head -1) +_BASE := $(if $(_LATEST),$(_LATEST),v0.0.0) +_MAJ := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f1) +_MIN := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f2) +_PAT := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f3) + +patch: + @NEW=v$(_MAJ).$(_MIN).$(shell expr $(_PAT) + 1); \ + git tag $$NEW && echo "Tagged $$NEW" + +minor: + @NEW=v$(_MAJ).$(shell expr $(_MIN) + 1).0; \ + git tag $$NEW && echo "Tagged $$NEW" + +major: + @NEW=v$(shell expr $(_MAJ) + 1).0.0; \ + git tag $$NEW && echo "Tagged $$NEW" diff --git a/main.go b/main.go index 652207a..b6ff5a1 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,8 @@ const ( appName = "node-lookup" ) +var version = "dev" + // config holds all configurable values. Fields map 1:1 to config file keys, // env vars (NODE_LOOKUP_*), and (where applicable) CLI flags. type config struct { @@ -397,6 +399,14 @@ func main() { configCmd.AddCommand(configInitCmd, configShowCmd) rootCmd.AddCommand(configCmd) + versionCmd := &cobra.Command{ + Use: "version", + Short: "Print the version", + Run: func(cmd *cobra.Command, args []string) { fmt.Println(version) }, + SilenceUsage: true, + } + rootCmd.AddCommand(versionCmd) + if err := rootCmd.Execute(); err != nil { os.Exit(1) } -- 2.47.3 From 677016ca529dd03671582fcb00a84eac52d784d6 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 15:55:46 +1100 Subject: [PATCH 2/7] Auto-push tag and create Gitea release on make patch/minor/major --- Makefile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index e44aeb7..5812135 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ BINARY := node-lookup VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) GOFLAGS := -ldflags="-s -w -X main.version=$(VERSION)" -.PHONY: all build test lint clean install patch minor major +.PHONY: all build test lint clean install patch minor major _release all: build @@ -31,12 +31,16 @@ _PAT := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f3) patch: @NEW=v$(_MAJ).$(_MIN).$(shell expr $(_PAT) + 1); \ - git tag $$NEW && echo "Tagged $$NEW" + git tag $$NEW && echo "Tagged $$NEW" && $(MAKE) _release TAG=$$NEW minor: @NEW=v$(_MAJ).$(shell expr $(_MIN) + 1).0; \ - git tag $$NEW && echo "Tagged $$NEW" + git tag $$NEW && echo "Tagged $$NEW" && $(MAKE) _release TAG=$$NEW major: @NEW=v$(shell expr $(_MAJ) + 1).0.0; \ - git tag $$NEW && echo "Tagged $$NEW" + git tag $$NEW && echo "Tagged $$NEW" && $(MAKE) _release TAG=$$NEW + +_release: + git push origin $(TAG) + tea releases create --tag $(TAG) --title $(TAG) -- 2.47.3 From 03abf1b976211c4b0ef193c3723ee1ad6e31636f Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 15:59:34 +1100 Subject: [PATCH 3/7] Add Woodpecker CI pipeline to build and release on tag --- .woodpecker.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .woodpecker.yaml diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..c0e6375 --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,25 @@ +when: + - event: release + +steps: + - name: test + image: golang:latest + commands: + - go test ./... + + - name: build + image: golang:latest + commands: + - VERSION=${CI_COMMIT_TAG} + - go build -ldflags="-s -w -X main.version=${VERSION}" -o node-lookup ./... + depends_on: [test] + + - name: release + image: woodpeckerci/plugin-gitea-release + settings: + api_key: + from_secret: GITEA_TOKEN + base_url: https://git.unkin.net + files: node-lookup + title: ${CI_COMMIT_TAG} + depends_on: [build] -- 2.47.3 From c8df10984181b378f3fc9cb9b28a03b25c240f7f Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 16:01:38 +1100 Subject: [PATCH 4/7] Add unit-test and lint woodpecker pipelines for pull requests Also move .woodpecker.yaml into .woodpecker/release.yaml for consistency. --- .woodpecker/lint.yaml | 8 ++++++++ .woodpecker/pre-commit.yaml | 8 ++++++++ .woodpecker.yaml => .woodpecker/release.yaml | 0 .woodpecker/unit-tests.yaml | 8 ++++++++ 4 files changed, 24 insertions(+) create mode 100644 .woodpecker/lint.yaml create mode 100644 .woodpecker/pre-commit.yaml rename .woodpecker.yaml => .woodpecker/release.yaml (100%) create mode 100644 .woodpecker/unit-tests.yaml diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml new file mode 100644 index 0000000..ab1e667 --- /dev/null +++ b/.woodpecker/lint.yaml @@ -0,0 +1,8 @@ +when: + - event: pull_request + +steps: + - name: lint + image: golangci/golangci-lint:latest + commands: + - golangci-lint run ./... diff --git a/.woodpecker/pre-commit.yaml b/.woodpecker/pre-commit.yaml new file mode 100644 index 0000000..75d1fca --- /dev/null +++ b/.woodpecker/pre-commit.yaml @@ -0,0 +1,8 @@ +when: + - event: pull_request + +steps: + - name: pre-commit + image: git.unkin.net/unkin/almalinux9-base:20260308 + commands: + - uvx pre-commit run --all-files diff --git a/.woodpecker.yaml b/.woodpecker/release.yaml similarity index 100% rename from .woodpecker.yaml rename to .woodpecker/release.yaml diff --git a/.woodpecker/unit-tests.yaml b/.woodpecker/unit-tests.yaml new file mode 100644 index 0000000..88e875a --- /dev/null +++ b/.woodpecker/unit-tests.yaml @@ -0,0 +1,8 @@ +when: + - event: pull_request + +steps: + - name: unit-tests + image: golang:latest + commands: + - go test -v -race ./... -- 2.47.3 From 16072917f057863f4dc7973159c10f79ceaa39f1 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 16:03:16 +1100 Subject: [PATCH 5/7] feat: Add environment secret and kubernetes serviceAccount to release pipeline --- .woodpecker/release.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml index c0e6375..5bb6f28 100644 --- a/.woodpecker/release.yaml +++ b/.woodpecker/release.yaml @@ -22,4 +22,10 @@ steps: base_url: https://git.unkin.net files: node-lookup title: ${CI_COMMIT_TAG} + environment: + DRONECI_PASSWORD: + from_secret: DRONECI_PASSWORD + backend_options: + kubernetes: + serviceAccountName: default depends_on: [build] -- 2.47.3 From 0ba9a3da09da3fce8cfb0f4fefddd68914d6c073 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 17:35:42 +1100 Subject: [PATCH 6/7] feat: use gobuilder image for pre-commit - add golang on top of base image - https://git.unkin.net/unkin/packer-images/pulls/44 --- .woodpecker/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pre-commit.yaml b/.woodpecker/pre-commit.yaml index 75d1fca..4ed7b15 100644 --- a/.woodpecker/pre-commit.yaml +++ b/.woodpecker/pre-commit.yaml @@ -3,6 +3,6 @@ when: steps: - name: pre-commit - image: git.unkin.net/unkin/almalinux9-base:20260308 + image: git.unkin.net/unkin/almalinux9-gobuilder:20260325 commands: - uvx pre-commit run --all-files -- 2.47.3 From c4cdfd2cc11b518804fb8302cadacc761fdef0e5 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 25 Mar 2026 17:40:40 +1100 Subject: [PATCH 7/7] Fix all golangci-lint errcheck and unused warnings - Wrap defer resp.Body.Close() to capture error - Ignore json.Unmarshal error in valueAny (best-effort) - Check enc.Encode and os.Stdout.Write return values - Check json.NewEncoder.Encode and w.Write in test helpers - Check os.MkdirAll and os.WriteFile errors in tests - Check first writeDefaultConfig() call in AlreadyExists test - Remove unused mustMarshal helper --- main.go | 8 ++++---- main_test.go | 40 +++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/main.go b/main.go index b6ff5a1..1595493 100644 --- a/main.go +++ b/main.go @@ -150,7 +150,7 @@ func queryPuppetDB(puppetDBURL, query string) ([]fact, error) { if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) @@ -176,7 +176,7 @@ func valueString(raw json.RawMessage) string { func valueAny(raw json.RawMessage) interface{} { var v interface{} - json.Unmarshal(raw, &v) + _ = json.Unmarshal(raw, &v) return v } @@ -280,7 +280,7 @@ func run(cfg config, nodeName, factName, match, partialMatch string, showRole, n enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") enc.SetEscapeHTML(false) - enc.Encode(hostFactMap) + _ = enc.Encode(hostFactMap) case count: values := stdinLines @@ -299,7 +299,7 @@ func run(cfg config, nodeName, factName, match, partialMatch string, showRole, n "all": map[string]interface{}{"hosts": hosts}, } b, _ := yaml.Marshal(inventory) - os.Stdout.Write(b) + _, _ = os.Stdout.Write(b) case nodeOnly: for _, line := range returnData { diff --git a/main_test.go b/main_test.go index 49cc8fe..ca6df71 100644 --- a/main_test.go +++ b/main_test.go @@ -12,19 +12,11 @@ import ( // ---- helpers ---------------------------------------------------------------- -func mustMarshal(v interface{}) []byte { - b, err := json.Marshal(v) - if err != nil { - panic(err) - } - return b -} - func newTestServer(t *testing.T, facts []fact) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(facts) + _ = json.NewEncoder(w).Encode(facts) })) } @@ -183,7 +175,7 @@ func TestQueryPuppetDB_HTTPError(t *testing.T) { func TestQueryPuppetDB_BadJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("not json")) + _, _ = w.Write([]byte("not json")) })) defer srv.Close() @@ -243,8 +235,12 @@ func TestLoadConfig_FileOverride(t *testing.T) { t.Setenv("NODE_LOOKUP_ROLE_FACT", "") cfgDir := filepath.Join(dir, appName) - os.MkdirAll(cfgDir, 0o755) - os.WriteFile(filepath.Join(cfgDir, configFileName), []byte("puppetdb_url: http://file:8080/facts\nrole_fact: file_role\n"), 0o644) + if err := os.MkdirAll(cfgDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(cfgDir, configFileName), []byte("puppetdb_url: http://file:8080/facts\nrole_fact: file_role\n"), 0o644); err != nil { + t.Fatal(err) + } cfg, err := loadConfig() if err != nil { @@ -265,8 +261,12 @@ func TestLoadConfig_EnvOverridesFile(t *testing.T) { t.Setenv("NODE_LOOKUP_ROLE_FACT", "") cfgDir := filepath.Join(dir, appName) - os.MkdirAll(cfgDir, 0o755) - os.WriteFile(filepath.Join(cfgDir, configFileName), []byte("puppetdb_url: http://file:8080/facts\n"), 0o644) + if err := os.MkdirAll(cfgDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(cfgDir, configFileName), []byte("puppetdb_url: http://file:8080/facts\n"), 0o644); err != nil { + t.Fatal(err) + } cfg, err := loadConfig() if err != nil { @@ -284,8 +284,12 @@ func TestLoadConfig_InvalidYAML(t *testing.T) { t.Setenv("NODE_LOOKUP_ROLE_FACT", "") cfgDir := filepath.Join(dir, appName) - os.MkdirAll(cfgDir, 0o755) - os.WriteFile(filepath.Join(cfgDir, configFileName), []byte(":\tinvalid: yaml:\n"), 0o644) + if err := os.MkdirAll(cfgDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(cfgDir, configFileName), []byte(":\tinvalid: yaml:\n"), 0o644); err != nil { + t.Fatal(err) + } _, err := loadConfig() if err == nil { @@ -315,7 +319,9 @@ func TestWriteDefaultConfig_AlreadyExists(t *testing.T) { dir := t.TempDir() t.Setenv("XDG_CONFIG_HOME", dir) - writeDefaultConfig() + if err := writeDefaultConfig(); err != nil { + t.Fatal(err) + } err := writeDefaultConfig() if err == nil { t.Fatal("expected error when config already exists") -- 2.47.3