From f29bea3c1fc156ee21ff9019792d2a5efad04c6a Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 29 Mar 2026 17:53:05 +0200 Subject: [PATCH] Enhance CI pipeline and documentation - Update Drone CI configuration to include triggers for push and tag events. - Improve test coverage commands and add coverage checks. - Introduce release notes generation and packaging steps in the CI pipeline. - Add comprehensive release documentation and a checklist for the release process. - Implement tests for GELF logging functionality. - Update dependencies in go.mod and go.sum for better compatibility. - Add scripts for checking coverage and generating release notes. --- .drone.yml | 57 ++++++++++- AGENTS.md | 67 ++++++++++--- README.md | 100 ++++++++++++++++++- docs/DEFINITION_OF_DONE.md | 16 ++- docs/RELEASING.md | 115 ++++++++++++++++++++++ gelflogger_test.go | 158 ++++++++++++++++++++++++++++++ go.mod | 4 + go.sum | 9 ++ scripts/check-coverage.sh | 32 ++++++ scripts/generate-release-notes.sh | 70 +++++++++++++ 10 files changed, 606 insertions(+), 22 deletions(-) create mode 100644 docs/RELEASING.md create mode 100644 gelflogger_test.go create mode 100755 scripts/check-coverage.sh create mode 100755 scripts/generate-release-notes.sh diff --git a/.drone.yml b/.drone.yml index 4e96d51..d6e446f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,11 +2,62 @@ kind: pipeline type: docker name: go-lib/gelf +trigger: + event: + - push + - tag + ref: + include: + - refs/heads/** + - refs/tags/v* + steps: - name: test - image: golang:1.20.1 + image: golang:1.25.8 commands: - go get ./... - - go test ./... + - go vet ./... + - mkdir -p .build + - go test -v -coverprofile .build/coverage.out ./... + - go tool cover -func .build/coverage.out | tee .build/coverage.txt + - bash scripts/check-coverage.sh .build/coverage.out 80 - go install golang.org/x/vuln/cmd/govulncheck@latest - - govulncheck -v ./... \ No newline at end of file + - govulncheck -json ./... > .build/vulncheck.json + +- name: release-notes + image: golang:1.25.8 + commands: + - bash scripts/generate-release-notes.sh + when: + event: + - tag + status: + - success + +- name: package + image: golang:1.25.8 + commands: + - tar czf .build/sources.tar.gz --exclude=.build --exclude=.git --exclude=.drone.yml . + when: + event: + - tag + status: + - success + +- name: release + image: plugins/gitea-release + settings: + base_url: https://scm.yoorie.de + api_key: + from_secret: gitea_token + files: + - .build/coverage.txt + - .build/sources.tar.gz + - .build/release-notes.md + title: ${DRONE_TAG} + note_from_file: .build/release-notes.md + when: + event: + - tag + status: + - success \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 32fd1c9..a5418ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,50 +1,85 @@ # AGENTS.md ## Purpose -This file defines the default working agreement for AI coding agents and contributors in this repository. + +This file defines the default working agreement for AI coding agents +and contributors in this repository. ## Documentation Language -Project language is English. All documentation, issues, pull requests, commit messages, and code comments should be written in English. + +Project language is English. All documentation, issues, pull requests, +commit messages, and code comments should be written in English. + All newly created documentation in this project must be written in English. -All project documentation files must be stored under `docs/`, except `README.md` and `AGENTS.md`. + +All project documentation files must be stored under `docs/`, except +`README.md` and `AGENTS.md`. ## Working Principles + 1. Keep changes minimal and focused on the requested task. -2. Preserve existing public APIs unless a breaking change is explicitly requested. + +2. Preserve existing public APIs unless a breaking change is explicitly + requested. + 3. Prefer clear, maintainable code over clever shortcuts. + 4. Do not modify unrelated files. + 5. Never add secrets, credentials, or tokens to the repository. ## Testing Expectations + 1. Add or update tests for behavior changes. + 2. Keep tests deterministic and fast. + 3. Prefer table-driven tests where they improve readability. + 4. Run relevant tests locally before finishing changes. -5. For Go projects, use `github.com/smartystreets/goconvey` as the standard test library. + +5. For Go projects, use `github.com/smartystreets/goconvey` as the + standard test library. ## Build Artifacts and Reports + 1. Builder logs and generated reports must be created under `.build/`. + 2. The `.build/` directory must be excluded from version control via `.gitignore`. ## Git and Script Standards + 1. Shell scripts (`*.sh`) must use LF line endings. -2. Shell scripts committed to the repository must be executable in Git index (mode `100755`). -3. When adding a new shell script, set execute permissions before commit: `git add --chmod=+x path/to/script.sh`. + +2. Shell scripts committed to the repository must be executable in Git + index (mode `100755`). + +3. When adding a new shell script, set execute permissions before + commit: `git add --chmod=+x path/to/script.sh`. ## Git Bash Execution Defaults + 1. Repository maintenance scripts are executed with Git Bash shell on Windows. + 2. Default repository root is `~/git`. + 3. The repository root can be overridden via `GO_GIT_ROOT`. + 4. The standards repository defaults to `$ROOT_PATH/project-standards`. + 5. The standards repository path can be overridden via `GO_PROJECT_STANDARDS`. ## Definition of Done (DoD) -### Purpose -The Definition of Done defines the minimum quality bar for every completed change in this repository. +### DoD Purpose + +The Definition of Done defines the minimum quality bar for every +completed change in this repository. ### Mandatory Criteria + 1. Tests + - Every code change is covered by tests where applicable. - New functionality includes new tests. - Bug fixes include at least one regression test. @@ -52,37 +87,47 @@ The Definition of Done defines the minimum quality bar for every completed chang - Automated test coverage is at least 80%. 1. Functional documentation + - Implemented functionality is documented. - Public API-relevant changes are reflected in README and/or docs. 1. Documentation standards + - Documentation is written in English. - Documentation files are placed under `docs/`. - Exceptions: `README.md` and `AGENTS.md` remain at repository root. ### Technical Completion Criteria + 1. Build and test status + - The project builds successfully. - Relevant test commands run successfully. 1. No unresolved critical issues + - No new blocking errors are introduced. -- Known non-blocking warnings are acceptable only if unrelated to the change or documented. +- Known non-blocking warnings are acceptable only if unrelated to the + change or documented. 1. SonarQube status + - No SonarQube errors are present. 1. Documentation structure + - Links to moved or newly added docs are valid. - Documentation structure remains consistent with project rules. ### Review Checklist (Quick) + - [ ] Change is implemented and meets acceptance criteria. - [ ] Tests were added/updated and pass. - [ ] Go tests use `github.com/smartystreets/goconvey`. - [ ] Automated test coverage is at least 80%. - [ ] Functionality is documented. - [ ] Documentation is in English. -- [ ] Documentation is located under `docs/` (except `README.md` and `AGENTS.md`). +- [ ] Documentation is located under `docs/` (except `README.md` and + `AGENTS.md`). - [ ] No SonarQube errors are present. - [ ] No critical regressions found. diff --git a/README.md b/README.md index 89d09dc..94e7b69 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,102 @@ -
+# Go GELF Logging Library -# Go graylog logging library +![yoorie favicon](https://www.yoorie.de/img/favicon_32.png) -[![Build Status](https://drone.yoorie.de/api/badges/go-lib/gelf/status.svg)](https://drone.yoorie.de/go-lib/gelf) +[![Build Status][build-badge]][build-link] -## Documentation +Minimal helper package to send application logs to Graylog via GELF +while still writing to the standard Go logger. -Is missed so far and will be created soon. +## Overview + +This module wraps [gopkg.in/aphistic/golf.v0][golf] and exposes a +small API for common log levels. + +Behavior summary: + +- If GELF is configured with `SetDefaultConfig`, messages are sent to + Graylog over UDP. +- Messages are also written to the standard Go logger (`log` package). +- If GELF is not configured, logging still works locally via the + standard logger. + +## Installation + +```bash +go get scm.yoorie.de/go-lib/gelf +``` + +## Quick Start + +```go +package main + +import ( + "scm.yoorie.de/go-lib/gelf" +) + +func main() { + gelf.SetDefaultConfig("graylog.example.local", 12201, + map[string]interface{}{ + "service": "billing-api", + "env": "prod", + }, + ) + + gelf.Info("service started") + gelf.Infof("listening on %s", ":8080") + gelf.Alert("database latency is high") +} +``` + +## API + +### Configuration + +- `SetDefaultConfig(host string, port int, attrs map[string]interface{})` + +Creates and configures the default GELF logger. Attributes in +`attrs` are attached as default fields to all GELF messages. + +### Logging Functions + +- `Debug(msg string)` +- `Debugf(format string, va ...interface{})` +- `Info(msg string)` +- `Infof(format string, va ...interface{})` +- `Alert(msg string)` +- `Alertf(format string, va ...interface{})` +- `Fatal(msg string)` +- `Fatalf(format string, va ...interface{})` + +## Notes + +- `Fatal` and `Fatalf` terminate the program by calling `log.Fatalf`. +- Graylog delivery uses UDP (`udp://host:port`). +- Calling `SetDefaultConfig` again replaces the previous client. + +## Development + +Run tests: + +```bash +go test ./... +``` + +Module information is defined in [go.mod](go.mod). + +## Additional Documentation + +- [Definition of Done](docs/DEFINITION_OF_DONE.md) +- [Releasing](docs/RELEASING.md) + +## License + +See [LICENSE](LICENSE). --- Copyright © 2023 yoorie.de + +[build-badge]: https://drone.yoorie.de/api/badges/go-lib/gelf/status.svg +[build-link]: https://drone.yoorie.de/go-lib/gelf +[golf]: https://gopkg.in/aphistic/golf.v0 diff --git a/docs/DEFINITION_OF_DONE.md b/docs/DEFINITION_OF_DONE.md index a47d72f..9487d48 100644 --- a/docs/DEFINITION_OF_DONE.md +++ b/docs/DEFINITION_OF_DONE.md @@ -2,11 +2,13 @@ ## Purpose -This Definition of Done defines the minimum quality bar for every completed change in this repository. +This Definition of Done defines the minimum quality bar for every +completed change in this repository. ## Mandatory Criteria 1. Tests + - Every code change is covered by tests where applicable. - New functionality includes new tests. - Bug fixes include at least one regression test. @@ -14,10 +16,12 @@ This Definition of Done defines the minimum quality bar for every completed chan - Automated test coverage is at least 80%. 1. Functional documentation + - Implemented functionality is documented. - Public API-relevant changes are reflected in README and/or docs. 1. Documentation standards + - Documentation is written in English. - Documentation files are placed under `docs/`. - Exceptions: `README.md` and `AGENTS.md` remain at repository root. @@ -25,17 +29,22 @@ This Definition of Done defines the minimum quality bar for every completed chan ## Technical Completion Criteria 1. Build and test status + - The project builds successfully. - Relevant test commands run successfully. 1. No unresolved critical issues + - No new blocking errors are introduced. -- Known non-blocking warnings are acceptable only if unrelated to the change or documented. +- Known non-blocking warnings are acceptable only if unrelated to the + change or documented. 1. SonarQube status + - No SonarQube errors are present. 1. Documentation links and structure + - Links to moved or newly added docs are valid. - Documentation structure remains consistent with project rules. @@ -47,6 +56,7 @@ This Definition of Done defines the minimum quality bar for every completed chan - [ ] Automated test coverage is at least 80%. - [ ] Functionality is documented. - [ ] Documentation is in English. -- [ ] Documentation is located under `docs/` (except `README.md` and `AGENTS.md`). +- [ ] Documentation is located under `docs/` (except `README.md` and + `AGENTS.md`). - [ ] No SonarQube errors are present. - [ ] No critical regressions found. diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..bd3e45b --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,115 @@ +# Releasing go-lib/gelf + +This document describes the process for creating a release of the +`go-lib/gelf` library. + +## Overview + +Releases in this project are managed via Git tags. When you push a +tag matching the pattern `v*` (for example, `v0.2.0`), the Drone +pipeline automatically: + +1. Runs quality checks (tests, coverage gate, vet, vulnerability scan) +2. Generates release notes from commits +3. Creates a source archive (`sources.tar.gz`) +4. Publishes a release to Gitea with attached artifacts + +## Versioning + +This library follows semantic versioning: `MAJOR.MINOR.PATCH` +(for example, `v1.2.3`). + +- `v1.0.0`: breaking API changes +- `v1.1.0`: backward-compatible features +- `v1.0.1`: backward-compatible fixes + +## Prerequisites + +Before creating a release, ensure: + +1. Working tree is clean (`git status`) +2. Tests pass locally (`go test ./...`) +3. Coverage is at least 80% +4. Dependencies are tidy (`go mod tidy`) +5. Documentation is up to date (README/docs) + +## Create a Release + +### 1. Prepare (optional) + +Update docs, examples, or changelog if needed: + +```bash +git add README.md docs/ +git commit -m "docs: prepare release" +``` + +### 2. Create tag + +```bash +git tag -a v0.2.0 -m "Release v0.2.0" +``` + +### 3. Push tag + +```bash +git push origin v0.2.0 +``` + +### 4. Verify pipeline and release + +After pushing the tag, verify in Drone and Gitea: + +- Pipeline succeeded for tag build +- Gitea release exists with artifacts: + - `.build/coverage.txt` + - `.build/sources.tar.gz` + - `.build/release-notes.md` + +## Install Released Version + +```bash +# latest +go get scm.yoorie.de/go-lib/gelf + +# specific +go get scm.yoorie.de/go-lib/gelf@v0.2.0 +``` + +## Troubleshooting + +### Coverage below threshold + +Run locally and inspect uncovered code: + +```bash +go test -v -coverprofile .build/coverage.out ./... +go tool cover -func .build/coverage.out +``` + +### Release step fails + +Common causes: + +- Missing Drone secret `gitea_token` +- Tag does not match `v*` +- Earlier pipeline step failed + +### Wrong tag pushed + +```bash +git tag -d v0.2.0 +git push origin :refs/tags/v0.2.0 +``` + +Then create and push the corrected tag. + +## Release Checklist + +- [ ] Tests pass locally +- [ ] Coverage is at least 80% +- [ ] `go vet ./...` passes +- [ ] `govulncheck ./...` is clean or reviewed +- [ ] Tag `vX.Y.Z` created and pushed +- [ ] Drone pipeline succeeded +- [ ] Gitea release contains expected artifacts diff --git a/gelflogger_test.go b/gelflogger_test.go new file mode 100644 index 0000000..703cde0 --- /dev/null +++ b/gelflogger_test.go @@ -0,0 +1,158 @@ +package gelf + +import ( + "bytes" + "log" + "os" + "os/exec" + "testing" + + . "github.com/smartystreets/goconvey/convey" + golf "gopkg.in/aphistic/golf.v0" +) + +func TestSetDefaultConfig(t *testing.T) { + Convey("SetDefaultConfig with empty host keeps GELF client disabled", t, func() { + originalClient := c + defer func() { c = originalClient }() + + c = nil + SetDefaultConfig("", 12201, map[string]interface{}{"service": "test"}) + + So(c, ShouldBeNil) + }) + + Convey("SetDefaultConfig with host initializes GELF client", t, func() { + originalClient := c + defer func() { + if c != nil { + c.Close() + } + c = originalClient + }() + + c = nil + SetDefaultConfig("127.0.0.1", 12201, map[string]interface{}{"service": "test"}) + + So(c, ShouldNotBeNil) + }) +} + +func TestLoggingWithoutGelfClient(t *testing.T) { + Convey("Logging functions write to standard logger when GELF client is nil", t, func() { + originalClient := c + defer func() { c = originalClient }() + c = nil + + buffer, restore := captureStandardLogger() + defer restore() + + Debug("debug message") + Debugf("debugf %d", 42) + Info("info message") + Infof("infof %s", "ok") + Alert("alert message") + Alertf("alertf %d", 7) + + output := buffer.String() + So(output, ShouldContainSubstring, "debug message") + So(output, ShouldContainSubstring, "debugf 42") + So(output, ShouldContainSubstring, "info message") + So(output, ShouldContainSubstring, "infof ok") + So(output, ShouldContainSubstring, "Alert: alert message") + So(output, ShouldContainSubstring, "Alert: alertf 7") + }) +} + +func TestLoggingWithGelfClient(t *testing.T) { + Convey("Logging functions execute without panic when GELF client is configured", t, func() { + originalClient := c + defer func() { + if c != nil { + c.Close() + } + c = originalClient + }() + + client, err := golf.NewClient() + So(err, ShouldBeNil) + c = client + So(c.Dial("udp://127.0.0.1:12201"), ShouldBeNil) + + logger, err := c.NewLogger() + So(err, ShouldBeNil) + golf.DefaultLogger(logger) + + buffer, restore := captureStandardLogger() + defer restore() + + So(func() { Debug("debug with gelf") }, ShouldNotPanic) + So(func() { Debugf("debugf %d", 1) }, ShouldNotPanic) + So(func() { Info("info with gelf") }, ShouldNotPanic) + So(func() { Infof("infof %d", 2) }, ShouldNotPanic) + So(func() { Alert("alert with gelf") }, ShouldNotPanic) + So(func() { Alertf("alertf %d", 3) }, ShouldNotPanic) + + output := buffer.String() + So(output, ShouldContainSubstring, "debug with gelf") + So(output, ShouldContainSubstring, "Alert: alert with gelf") + }) +} + +func TestFatalExits(t *testing.T) { + Convey("Fatal exits with non-zero status and logs message", t, func() { + cmd := exec.Command(os.Args[0], "-test.run=TestFatalHelperProcess") + cmd.Env = append(os.Environ(), "GO_WANT_FATAL_HELPER=1", "FATAL_MODE=plain", "FATAL_WITH_GELF=1") + + output, err := cmd.CombinedOutput() + So(err, ShouldNotBeNil) + So(string(output), ShouldContainSubstring, "Fatal: fatal message") + }) +} + +func TestFatalfExits(t *testing.T) { + Convey("Fatalf exits with non-zero status and logs formatted message", t, func() { + cmd := exec.Command(os.Args[0], "-test.run=TestFatalHelperProcess") + cmd.Env = append(os.Environ(), "GO_WANT_FATAL_HELPER=1", "FATAL_MODE=format", "FATAL_WITH_GELF=1") + + output, err := cmd.CombinedOutput() + So(err, ShouldNotBeNil) + So(string(output), ShouldContainSubstring, "Fatal: formatted 9") + }) +} + +func TestFatalHelperProcess(t *testing.T) { + if os.Getenv("GO_WANT_FATAL_HELPER") != "1" { + return + } + + log.SetFlags(0) + mode := os.Getenv("FATAL_MODE") + + if os.Getenv("FATAL_WITH_GELF") == "1" { + SetDefaultConfig("127.0.0.1", 12201, map[string]interface{}{"service": "test"}) + } + + if mode == "format" { + Fatalf("formatted %d", 9) + } + + Fatal("fatal message") +} + +func captureStandardLogger() (*bytes.Buffer, func()) { + var buffer bytes.Buffer + originalWriter := log.Writer() + originalFlags := log.Flags() + originalPrefix := log.Prefix() + + log.SetOutput(&buffer) + log.SetFlags(0) + log.SetPrefix("") + + return &buffer, func() { + log.SetOutput(originalWriter) + log.SetFlags(originalFlags) + log.SetPrefix(originalPrefix) + } +} diff --git a/go.mod b/go.mod index e85fb55..951f0d0 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,9 @@ require gopkg.in/aphistic/golf.v0 v0.0.0-20180712155816-02c07f170c5a require ( github.com/aphistic/sweet v0.3.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/onsi/gomega v1.27.2 // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/smartystreets/goconvey v1.8.1 // indirect ) diff --git a/go.sum b/go.sum index dc950f7..4038af0 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,11 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= @@ -18,6 +22,10 @@ github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY= github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -30,6 +38,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= gopkg.in/aphistic/golf.v0 v0.0.0-20180712155816-02c07f170c5a h1:34vqlRjuZiE9c8eHsuZ9nn+GbcimFpvGUEmW+vyfhG8= diff --git a/scripts/check-coverage.sh b/scripts/check-coverage.sh new file mode 100755 index 0000000..cf7e777 --- /dev/null +++ b/scripts/check-coverage.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Check test coverage against minimum threshold. + +set -euo pipefail + +COVERAGE_FILE="${1:-.build/coverage.out}" +MIN_COVERAGE="${2:-80}" + +if [ ! -f "$COVERAGE_FILE" ]; then + echo "Error: coverage file not found: $COVERAGE_FILE" + exit 1 +fi + +# Extract total coverage percentage from go tool cover output. +COVERAGE=$(go tool cover -func "$COVERAGE_FILE" | awk '/^total:/ { match($0, /[0-9.]+%/); print substr($0, RSTART, RLENGTH-1) }') + +if [ -z "$COVERAGE" ]; then + echo "Error: failed to parse coverage from $COVERAGE_FILE" + exit 1 +fi + +echo "Total coverage: ${COVERAGE}%" + +# Compare as integer part to keep shell arithmetic simple and portable. +COVERAGE_INT=${COVERAGE%.*} + +if [ "$COVERAGE_INT" -lt "$MIN_COVERAGE" ]; then + echo "Coverage ${COVERAGE}% is below minimum ${MIN_COVERAGE}%" + exit 1 +fi + +echo "Coverage check passed" diff --git a/scripts/generate-release-notes.sh b/scripts/generate-release-notes.sh new file mode 100755 index 0000000..b0b471f --- /dev/null +++ b/scripts/generate-release-notes.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Generate release notes from commits since the previous tag. + +set -euo pipefail + +CURRENT_TAG="${1:-${DRONE_TAG:-}}" +OUTPUT_FILE="${2:-.build/release-notes.md}" + +if [ -z "$CURRENT_TAG" ]; then + echo "Error: current tag not provided" + exit 1 +fi + +PREVIOUS_TAG=$(git describe --tags --abbrev=0 "$CURRENT_TAG"^ 2>/dev/null || echo "") + +mkdir -p "$(dirname "$OUTPUT_FILE")" + +{ + echo "# Release $CURRENT_TAG" + echo + + if [ -n "$PREVIOUS_TAG" ]; then + echo "Changes since $PREVIOUS_TAG:" + echo + + git log "$PREVIOUS_TAG..$CURRENT_TAG" --pretty=format:"%s" | while read -r commit; do + if [[ $commit =~ ^([a-z]+)(\(.+\))?:\ (.+)$ ]]; then + TYPE="${BASH_REMATCH[1]}" + SCOPE="${BASH_REMATCH[2]}" + MESSAGE="${BASH_REMATCH[3]}" + + case "$TYPE" in + feat) + echo "- Feature${SCOPE}: $MESSAGE" ;; + fix) + echo "- Fix${SCOPE}: $MESSAGE" ;; + docs) + echo "- Docs${SCOPE}: $MESSAGE" ;; + ci) + echo "- CI${SCOPE}: $MESSAGE" ;; + test) + echo "- Test${SCOPE}: $MESSAGE" ;; + refactor) + echo "- Refactor${SCOPE}: $MESSAGE" ;; + perf) + echo "- Performance${SCOPE}: $MESSAGE" ;; + *) + echo "- $commit" ;; + esac + else + echo "- $commit" + fi + done + else + echo "Initial release" + echo + git log "$CURRENT_TAG" --pretty=format:"- %s" | head -20 + fi + + echo + echo "---" + if [ -n "$PREVIOUS_TAG" ]; then + echo "See all changes: https://scm.yoorie.de/git/go-lib/gelf/compare/$PREVIOUS_TAG...$CURRENT_TAG" + else + echo "See all changes: https://scm.yoorie.de/git/go-lib/gelf/releases/tag/$CURRENT_TAG" + fi +} > "$OUTPUT_FILE" + +echo "Release notes generated: $OUTPUT_FILE" +cat "$OUTPUT_FILE"