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..4ed7b15 --- /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-gobuilder:20260325 + commands: + - uvx pre-commit run --all-files diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml new file mode 100644 index 0000000..5bb6f28 --- /dev/null +++ b/.woodpecker/release.yaml @@ -0,0 +1,31 @@ +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} + environment: + DRONECI_PASSWORD: + from_secret: DRONECI_PASSWORD + backend_options: + kubernetes: + serviceAccountName: default + depends_on: [build] 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 ./... diff --git a/Makefile b/Makefile index 0459ade..5812135 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 _release all: build @@ -19,3 +20,27 @@ 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" && $(MAKE) _release TAG=$$NEW + +minor: + @NEW=v$(_MAJ).$(shell expr $(_MIN) + 1).0; \ + 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" && $(MAKE) _release TAG=$$NEW + +_release: + git push origin $(TAG) + tea releases create --tag $(TAG) --title $(TAG) diff --git a/main.go b/main.go index 234febc..ca3b0b7 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 { @@ -148,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) @@ -174,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 } @@ -298,7 +300,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 @@ -317,7 +319,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 { @@ -419,6 +421,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) } diff --git a/main_test.go b/main_test.go index d22424a..3ea6d45 100644 --- a/main_test.go +++ b/main_test.go @@ -13,19 +13,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) })) } @@ -184,7 +176,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() @@ -244,8 +236,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 { @@ -266,8 +262,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 { @@ -285,8 +285,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 { @@ -316,7 +320,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")