From 76cedc3329734f28c08bb2ddf8129312b27f9caa Mon Sep 17 00:00:00 2001 From: Marco Molteni Date: Fri, 6 Dec 2024 13:10:08 +0100 Subject: [PATCH 1/5] minor cleanups --- CHANGELOG.md | 6 ++-- Taskfile.yml | 4 ++- cmd/cogito/main.go | 21 +++++++------ cmd/cogito/main_test.go | 70 ++++++++++++++++++++--------------------- 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9fce37..b3b68400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `source.log_level`. - The README was wrongly still mentioning `silent` as a way to silent the logging. Actually `silent` was remapped to `info` since [v0.8.2](#v082---2022-11-18). - - Starting from this version, levels `silent` and `off` will cause an error. + - Starting from this version, levels `silent` and `off` will cause an error. - The only supported log levels are `debug`, `info`, `warn`, `error`. The default level, `info`, is recommended for normal operation. ### Fixed @@ -40,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v0.11.0] - 2023-09-22 -### Added +### Added - Retry GitHub API HTTP request when rate limited or when transient server error: - New generic and customizable `retry` package, usable with any API (not GitHub specific). @@ -107,7 +107,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Minor breaking change - `source.log_level`. If you were previously silencing the logging with level `silent`, it will now be interpreted as invalid and automatically remapped to `info`. If you really want no logging, the log level to use is `off`. - Note that there is no reason to disable logging, since it is not visible in the build log in the UI, and the default of `info` helps to, for example, know which version of cogito is being used. + Note that there is no reason to disable logging, since it is not visible in the build log in the UI, and the default of `info` helps to, for example, know which version of cogito is being used. ## [v0.8.1] - 2022-09-07 diff --git a/Taskfile.yml b/Taskfile.yml index 71b98fe6..80e885cd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -44,7 +44,9 @@ tasks: - golangci-lint run ./... test:env: - desc: "Run what is passed on the command-line with a shell environment containing the secrets needed for the integration tests. Example: task test:env -- go test -count=1 -run 'TestFooIntegration' ./pkg/update" + desc: | + Run what is passed on the command-line with a shell environment containing the secrets needed for the integration tests. + Example: task test:env -- go test -count=1 -run 'TestFooIntegration' ./pkg/update" cmds: - '{{ .CLI_ARGS }}' env: &test-env diff --git a/cmd/cogito/main.go b/cmd/cogito/main.go index 6655d776..0e797211 100644 --- a/cmd/cogito/main.go +++ b/cmd/cogito/main.go @@ -15,24 +15,25 @@ import ( ) func main() { - // The "Concourse resource protocol" expects: - // - stdin, stdout and command-line arguments for the protocol itself - // - stderr for logging - // See: https://concourse-ci.org/implementing-resource-types.html if err := mainErr(os.Stdin, os.Stdout, os.Stderr, os.Args); err != nil { fmt.Fprintf(os.Stderr, "cogito: error: %s\n", err) os.Exit(1) } } -func mainErr(in io.Reader, out io.Writer, logOut io.Writer, args []string) error { +// The "Concourse resource protocol" expects: +// - stdin, stdout and command-line arguments for the protocol itself +// - stderr for logging +// +// See: https://concourse-ci.org/implementing-resource-types.html +func mainErr(stdin io.Reader, stdout io.Writer, stderr io.Writer, args []string) error { cmd := path.Base(args[0]) validCmds := sets.From("check", "in", "out") if !validCmds.Contains(cmd) { return fmt.Errorf("invoked as '%s'; want: one of %v", cmd, validCmds) } - input, err := io.ReadAll(in) + input, err := io.ReadAll(stdin) if err != nil { return fmt.Errorf("reading stdin: %s", err) } @@ -52,7 +53,7 @@ func mainErr(in io.Reader, out io.Writer, logOut io.Writer, args []string) error return a } log := slog.New(slog.NewTextHandler( - logOut, + stderr, &slog.HandlerOptions{ Level: level, ReplaceAttr: removeTime, @@ -61,12 +62,12 @@ func mainErr(in io.Reader, out io.Writer, logOut io.Writer, args []string) error switch cmd { case "check": - return cogito.Check(log, input, out, args[1:]) + return cogito.Check(log, input, stdout, args[1:]) case "in": - return cogito.Get(log, input, out, args[1:]) + return cogito.Get(log, input, stdout, args[1:]) case "out": putter := cogito.NewPutter(log) - return cogito.Put(log, input, out, args[1:], putter) + return cogito.Put(log, input, stdout, args[1:], putter) default: return fmt.Errorf("cli wiring error; please report") } diff --git a/cmd/cogito/main_test.go b/cmd/cogito/main_test.go index 1a49049f..2df7dc83 100644 --- a/cmd/cogito/main_test.go +++ b/cmd/cogito/main_test.go @@ -21,7 +21,7 @@ import ( ) func TestRunCheckSuccess(t *testing.T) { - in := strings.NewReader(` + stdin := strings.NewReader(` { "source": { "owner": "the-owner", @@ -30,16 +30,16 @@ func TestRunCheckSuccess(t *testing.T) { "log_level": "debug" } }`) - var out bytes.Buffer - var logOut bytes.Buffer + var stdout bytes.Buffer + var stderr bytes.Buffer - err := mainErr(in, &out, &logOut, []string{"check"}) + err := mainErr(stdin, &stdout, &stderr, []string{"check"}) - assert.NilError(t, err, "\nout: %s\nlogOut: %s", out.String(), logOut.String()) + assert.NilError(t, err, "\nstdout: %s\nstderr: %s", stdout.String(), stderr.String()) } func TestRunGetSuccess(t *testing.T) { - in := strings.NewReader(` + stdin := strings.NewReader(` { "source": { "owner": "the-owner", @@ -49,12 +49,12 @@ func TestRunGetSuccess(t *testing.T) { }, "version": {"ref": "pizza"} }`) - var out bytes.Buffer - var logOut bytes.Buffer + var stdout bytes.Buffer + var stderr bytes.Buffer - err := mainErr(in, &out, &logOut, []string{"in", "dummy-dir"}) + err := mainErr(stdin, &stdout, &stderr, []string{"in", "dummy-dir"}) - assert.NilError(t, err, "\nout: %s\nlogOut: %s", out.String(), logOut.String()) + assert.NilError(t, err, "\nstdout: %s\nstderr: %s", stdout.String(), stderr.String()) } func TestRunPutSuccess(t *testing.T) { @@ -69,7 +69,7 @@ func TestRunPutSuccess(t *testing.T) { chatReply := googlechat.MessageReply{} var gchatUrl *url.URL googleChatSpy := testhelp.SpyHttpServer(&chatMsg, chatReply, &gchatUrl, http.StatusOK) - in := bytes.NewReader(testhelp.ToJSON(t, cogito.PutRequest{ + stdin := bytes.NewReader(testhelp.ToJSON(t, cogito.PutRequest{ Source: cogito.Source{ Owner: "the-owner", Repo: "the-repo", @@ -80,14 +80,14 @@ func TestRunPutSuccess(t *testing.T) { }, Params: cogito.PutParams{State: wantState}, })) - var out bytes.Buffer - var logOut bytes.Buffer + var stdout bytes.Buffer + var stderr bytes.Buffer inputDir := testhelp.MakeGitRepoFromTestdata(t, "../../cogito/testdata/one-repo/a-repo", testhelp.HttpsRemote(gitHubSpyURL.Host, "the-owner", "the-repo"), "dummySHA", wantGitRef) - err = mainErr(in, &out, &logOut, []string{"out", inputDir}) + err = mainErr(stdin, &stdout, &stderr, []string{"out", inputDir}) - assert.NilError(t, err, "\nout: %s\nlogOut: %s", out.String(), logOut.String()) + assert.NilError(t, err, "\nstdout: %s\nstderr: %s", stdout.String(), stderr.String()) // gitHubSpy.Close() // Avoid races before the following asserts. assert.Equal(t, ghReq.State, string(wantState)) @@ -104,7 +104,7 @@ func TestRunPutSuccessIntegration(t *testing.T) { gitHubCfg := testhelp.GitHubSecretsOrFail(t) googleChatCfg := testhelp.GoogleChatSecretsOrFail(t) - in := bytes.NewReader(testhelp.ToJSON(t, cogito.PutRequest{ + stdin := bytes.NewReader(testhelp.ToJSON(t, cogito.PutRequest{ Source: cogito.Source{ Owner: gitHubCfg.Owner, Repo: gitHubCfg.Repo, @@ -114,8 +114,8 @@ func TestRunPutSuccessIntegration(t *testing.T) { }, Params: cogito.PutParams{State: cogito.StateError}, })) - var out bytes.Buffer - var logOut bytes.Buffer + var stdout bytes.Buffer + var stderr bytes.Buffer inputDir := testhelp.MakeGitRepoFromTestdata(t, "../../cogito/testdata/one-repo/a-repo", testhelp.HttpsRemote(github.GhDefaultHostname, gitHubCfg.Owner, gitHubCfg.Repo), gitHubCfg.SHA, "ref: refs/heads/a-branch-FIXME") @@ -125,12 +125,12 @@ func TestRunPutSuccessIntegration(t *testing.T) { t.Setenv("BUILD_TEAM_NAME", "the-test-team") t.Setenv("BUILD_NAME", "42") - err := mainErr(in, &out, &logOut, []string{"out", inputDir}) + err := mainErr(stdin, &stdout, &stderr, []string{"out", inputDir}) - assert.NilError(t, err, "\nout:\n%s\nlogOut:\n%s", out.String(), logOut.String()) - assert.Assert(t, cmp.Contains(logOut.String(), + assert.NilError(t, err, "\nstdout:\n%s\nstderr:\n%s", stdout.String(), stderr.String()) + assert.Assert(t, cmp.Contains(stderr.String(), `level=INFO msg="commit status posted successfully" name=cogito.put name=ghCommitStatus state=error`)) - assert.Assert(t, cmp.Contains(logOut.String(), + assert.Assert(t, cmp.Contains(stderr.String(), `level=INFO msg="state posted successfully to chat" name=cogito.put name=gChat state=error`)) } @@ -138,14 +138,14 @@ func TestRunFailure(t *testing.T) { type testCase struct { name string args []string - in string + stdin string wantErr string } test := func(t *testing.T, tc testCase) { - in := strings.NewReader(tc.in) + stdin := strings.NewReader(tc.stdin) - err := mainErr(in, nil, io.Discard, tc.args) + err := mainErr(stdin, nil, io.Discard, tc.args) assert.ErrorContains(t, err, tc.wantErr) } @@ -159,16 +159,16 @@ func TestRunFailure(t *testing.T) { { name: "check, wrong stdin", args: []string{"check"}, - in: ` + stdin: ` { - "fruit": "banana" + "fruit": "banana" }`, wantErr: `check: parsing request: json: unknown field "fruit"`, }, { name: "peeking for log_level", args: []string{"check"}, - in: "", + stdin: "", wantErr: "peeking into JSON for log_level: unexpected end of JSON input", }, } @@ -182,28 +182,28 @@ func TestRunFailure(t *testing.T) { } func TestRunSystemFailure(t *testing.T) { - in := iotest.ErrReader(errors.New("test read error")) + stdin := iotest.ErrReader(errors.New("test read error")) - err := mainErr(in, nil, io.Discard, []string{"check"}) + err := mainErr(stdin, nil, io.Discard, []string{"check"}) assert.ErrorContains(t, err, "test read error") } func TestRunPrintsBuildInformation(t *testing.T) { - in := strings.NewReader(` + stdin := strings.NewReader(` { "source": { "owner": "the-owner", "repo": "the-repo", "access_token": "the-secret" - } + } }`) - var logBuf bytes.Buffer + var stderr bytes.Buffer wantLog := "This is the Cogito GitHub status resource. unknown" - err := mainErr(in, io.Discard, &logBuf, []string{"check"}) + err := mainErr(stdin, io.Discard, &stderr, []string{"check"}) assert.NilError(t, err) - haveLog := logBuf.String() + haveLog := stderr.String() assert.Assert(t, strings.Contains(haveLog, wantLog), "\nhave: %s\nwant: %s", haveLog, wantLog) From b313299f9989da7c7417997c52470bb8d8187bf0 Mon Sep 17 00:00:00 2001 From: Marco Molteni Date: Fri, 6 Dec 2024 13:10:08 +0100 Subject: [PATCH 2/5] Update to Go 1.23 --- CONTRIBUTING.md | 4 ++-- Dockerfile | 2 +- go.mod | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a48a77ee..c5769fb8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,9 +14,9 @@ In case of doubts about how to tackle testing something, feel free to ask. ## Required -* Go, version >= 1.22 +* Go, version >= 1.23 * Docker, version >= 20 -* [Task], version >= 3.27 +* [Task], version >= 3.40 ## Optional diff --git a/Dockerfile b/Dockerfile index 287502f5..9e78f035 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine as builder +FROM golang:1.23-alpine AS builder ARG BUILD_INFO diff --git a/go.mod b/go.mod index b61e843a..636f8374 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Pix4D/cogito -go 1.22 +go 1.23 require ( dario.cat/mergo v1.0.0 From 201072f9424bfa00f309bd783a8b37ff1f1f1662 Mon Sep 17 00:00:00 2001 From: Marco Molteni Date: Fri, 6 Dec 2024 13:10:08 +0100 Subject: [PATCH 3/5] ci: update deps --- .github/workflows/ci.yml | 4 ++-- Taskfile.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7a7fd77..4a92706a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,8 @@ on: [push] name: ci env: - go-version: 1.22.x - task-version: v3.28.0 + go-version: 1.23.x + task-version: v3.40.0 jobs: all: diff --git a/Taskfile.yml b/Taskfile.yml index 80e885cd..6d67b5dc 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -27,8 +27,8 @@ vars: "version": {"ref": "pizza"} } # - GOLANGCI_VERSION: v1.54.2 - GOTESTSUM_VERSION: v1.10.1 + GOLANGCI_VERSION: v1.62.2 + GOTESTSUM_VERSION: v1.12.0 tasks: From fdcf48b130f91b739dc2580d9cd40fc75622c573 Mon Sep 17 00:00:00 2001 From: Marco Molteni Date: Tue, 17 Dec 2024 10:32:07 +0100 Subject: [PATCH 4/5] ci: update actions --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a92706a..77acbfe8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ env.go-version }} - name: Install Task run: go install github.com/go-task/task/v3/cmd/task@${{ env.task-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # By default, actions/checkout will persist the GITHUB_TOKEN, so that further # steps in the job can perform authenticated git commands (that is: WRITE to From 2743c082bfd7e788f6d8a8eba5c8fc467e83c49a Mon Sep 17 00:00:00 2001 From: Marco Molteni Date: Fri, 6 Dec 2024 13:10:08 +0100 Subject: [PATCH 5/5] Add source.omit_target_url Closes #159 --- CHANGELOG.md | 1 + README.md | 4 ++++ cogito/ghcommitsink.go | 3 +++ cogito/protocol.go | 3 ++- cogito/protocol_test.go | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b68400..e958ba79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Optional `source.omit_target_url`. If set to true, will omit the GitHub Commit status API `target_url` (see README). - pkg/sets: add Intersect() and Add() methods. ## [v0.12.1] - 2024-04-03 diff --git a/README.md b/README.md index 34f10175..61aae8ef 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,10 @@ With reference to the [GitHub Commit status API], the `POST` parameters (`state` GitHub hostname. This allows to post commit statuses to repositories hosted by GitHub Enterprise (GHE) instances. For example: github.mycompany.org will be expanded by cogito to https://github.mycompany.org/api/v3 \ Default: `github.com` +- `omit_target_url`:\ + If set to true, will omit the GitHub Commit status API `target_url` (the URL to the build on Concourse).\n + Default: `false`. + - `log_url`. **DEPRECATED, no-op, will be removed**\ A Google Hangout Chat webhook. Useful to obtain logging for the `check` step for Concourse < v7.x diff --git a/cogito/ghcommitsink.go b/cogito/ghcommitsink.go index 94079667..df33a4ba 100644 --- a/cogito/ghcommitsink.go +++ b/cogito/ghcommitsink.go @@ -54,6 +54,9 @@ func (sink GitHubCommitStatusSink) Send() error { "state", ghState, "owner", sink.Request.Source.Owner, "repo", sink.Request.Source.Repo, "git-ref", sink.GitRef, "context", context, "buildURL", buildURL, "description", description) + if sink.Request.Source.OmitTargetURL { + buildURL = "" + } if err := commitStatus.Add(sink.GitRef, ghState, buildURL, description); err != nil { return err } diff --git a/cogito/protocol.go b/cogito/protocol.go index 12d66a51..85aa9deb 100644 --- a/cogito/protocol.go +++ b/cogito/protocol.go @@ -173,6 +173,7 @@ type Source struct { LogLevel string `json:"log_level"` LogUrl string `json:"log_url"` // DEPRECATED ContextPrefix string `json:"context_prefix"` + OmitTargetURL bool `json:"omit_target_url"` ChatAppendSummary bool `json:"chat_append_summary"` ChatNotifyOnStates []BuildState `json:"chat_notify_on_states"` Sinks []string `json:"sinks"` @@ -189,6 +190,7 @@ func (src Source) String() string { fmt.Fprintf(&bld, "gchat_webhook: %s\n", redact(src.GChatWebHook)) fmt.Fprintf(&bld, "log_level: %s\n", src.LogLevel) fmt.Fprintf(&bld, "context_prefix: %s\n", src.ContextPrefix) + fmt.Fprintf(&bld, "omit_target_url: %t\n", src.OmitTargetURL) fmt.Fprintf(&bld, "chat_append_summary: %t\n", src.ChatAppendSummary) fmt.Fprintf(&bld, "chat_notify_on_states: %s\n", src.ChatNotifyOnStates) // Last one: no newline. @@ -253,7 +255,6 @@ func (src *Source) Validate() error { if src.GChatWebHook == "" { mandatory = append(mandatory, "gchat_webhook") } - } if len(mandatory) > 0 { diff --git a/cogito/protocol_test.go b/cogito/protocol_test.go index 289848b5..5ff00e83 100644 --- a/cogito/protocol_test.go +++ b/cogito/protocol_test.go @@ -219,6 +219,7 @@ access_token: ***REDACTED*** gchat_webhook: ***REDACTED*** log_level: debug context_prefix: the-prefix +omit_target_url: false chat_append_summary: true chat_notify_on_states: [success failure] sinks: []` @@ -239,6 +240,7 @@ access_token: gchat_webhook: log_level: context_prefix: +omit_target_url: false chat_append_summary: false chat_notify_on_states: [] sinks: []`