Skip to content

Commit

Permalink
cogito: add support for configuring Github API endpoint
Browse files Browse the repository at this point in the history
- adds support for Github Enterprise API endpoints
  • Loading branch information
aliculPix4D committed Oct 18, 2023
1 parent 3d091a4 commit 4aef045
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 102 deletions.
5 changes: 2 additions & 3 deletions cmd/cogito/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/hashicorp/go-hclog"

"github.com/Pix4D/cogito/cogito"
"github.com/Pix4D/cogito/github"
"github.com/Pix4D/cogito/sets"
)

Expand Down Expand Up @@ -56,8 +55,8 @@ func mainErr(in io.Reader, out io.Writer, logOut io.Writer, args []string) error
case "in":
return cogito.Get(log, input, out, args[1:])
case "out":
putter := cogito.NewPutter(github.API, log)
return cogito.Put(input, out, args[1:], putter)
putter := cogito.NewPutter(log)
return cogito.Put(log, input, out, args[1:], putter)
default:
return fmt.Errorf("cli wiring error; please report")
}
Expand Down
4 changes: 1 addition & 3 deletions cogito/ghcommitsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const (
// GitHubCommitStatusSink is an implementation of [Sinker] for the Cogito resource.
type GitHubCommitStatusSink struct {
Log hclog.Logger
GhAPI string
GitRef string
Request PutRequest
}
Expand All @@ -42,7 +41,7 @@ func (sink GitHubCommitStatusSink) Send() error {
context := ghMakeContext(sink.Request)

target := &github.Target{
Server: sink.GhAPI,
Server: sink.Request.Source.GithubApiEndpoint,
Retry: retry.Retry{
FirstDelay: retryFirstDelay,
BackoffLimit: retryBackoffLimit,
Expand All @@ -58,7 +57,6 @@ 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 err := commitStatus.Add(sink.GitRef, ghState, buildURL, description); err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cogito/ghcommitsink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func TestSinkGitHubCommitStatusSendSuccess(t *testing.T) {
ts := testhelp.SpyHttpServer(&ghReq, nil, &URL, http.StatusCreated)
sink := cogito.GitHubCommitStatusSink{
Log: hclog.NewNullLogger(),
GhAPI: ts.URL,
GitRef: wantGitRef,
Request: cogito.PutRequest{
Source: cogito.Source{GithubApiEndpoint: ts.URL},
Params: cogito.PutParams{State: wantState},
Env: cogito.Environment{BuildJobName: jobName},
},
Expand All @@ -50,9 +50,9 @@ func TestSinkGitHubCommitStatusSendFailure(t *testing.T) {
defer ts.Close()
sink := cogito.GitHubCommitStatusSink{
Log: hclog.NewNullLogger(),
GhAPI: ts.URL,
GitRef: "deadbeefdeadbeef",
Request: cogito.PutRequest{
Source: cogito.Source{GithubApiEndpoint: ts.URL},
Params: cogito.PutParams{State: cogito.StatePending},
},
}
Expand Down
12 changes: 9 additions & 3 deletions cogito/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"strings"

"github.com/Pix4D/cogito/github"
"github.com/Pix4D/cogito/sets"
)

Expand Down Expand Up @@ -165,14 +166,14 @@ type Source struct {
//
// Optional
//
GithubApiEndpoint string `json:"github_api_endpoint"`
GChatWebHook string `json:"gchat_webhook"` // SENSITIVE
LogLevel string `json:"log_level"`
LogUrl string `json:"log_url"` // DEPRECATED
ContextPrefix string `json:"context_prefix"`
ChatAppendSummary bool `json:"chat_append_summary"`
ChatNotifyOnStates []BuildState `json:"chat_notify_on_states"`
Sinks []string `json:"sinks"`
GithubApiEndpoint string `json:"github_api_endpoint"`
}

// String renders Source, redacting the sensitive fields.
Expand All @@ -181,15 +182,15 @@ func (src Source) String() string {

fmt.Fprintf(&bld, "owner: %s\n", src.Owner)
fmt.Fprintf(&bld, "repo: %s\n", src.Repo)
fmt.Fprintf(&bld, "github_api_endpoint: %s\n", src.GithubApiEndpoint)
fmt.Fprintf(&bld, "access_token: %s\n", redact(src.AccessToken))
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, "chat_append_summary: %t\n", src.ChatAppendSummary)
fmt.Fprintf(&bld, "chat_notify_on_states: %s\n", src.ChatNotifyOnStates)
// Last one: no newline.
fmt.Fprintf(&bld, "sinks: %s\n", src.Sinks)
fmt.Fprintf(&bld, "github_api_endpoint: %s", src.GithubApiEndpoint)
fmt.Fprintf(&bld, "sinks: %s", src.Sinks)

return bld.String()
}
Expand Down Expand Up @@ -257,7 +258,9 @@ func (src *Source) Validate() error {
return fmt.Errorf("source: missing keys: %s", strings.Join(mandatory, ", "))
}

//
// Validate optional fields.
//
if src.GithubApiEndpoint != "" {
u, err := url.ParseRequestURI(src.GithubApiEndpoint)
if err != nil || u.Host == "" {
Expand All @@ -274,6 +277,9 @@ func (src *Source) Validate() error {
if len(src.ChatNotifyOnStates) == 0 {
src.ChatNotifyOnStates = defaultNotifyStates
}
if len(src.GithubApiEndpoint) == 0 {
src.GithubApiEndpoint = github.API
}

return nil
}
Expand Down
9 changes: 5 additions & 4 deletions cogito/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func TestSourcePrintLogRedaction(t *testing.T) {
source := cogito.Source{
Owner: "the-owner",
Repo: "the-repo",
GithubApiEndpoint: "dummy-api",
AccessToken: "sensitive-the-access-token",
GChatWebHook: "sensitive-gchat-webhook",
LogLevel: "debug",
Expand All @@ -224,14 +225,14 @@ func TestSourcePrintLogRedaction(t *testing.T) {
t.Run("fmt.Print redacts fields", func(t *testing.T) {
want := `owner: the-owner
repo: the-repo
github_api_endpoint: dummy-api
access_token: ***REDACTED***
gchat_webhook: ***REDACTED***
log_level: debug
context_prefix: the-prefix
chat_append_summary: true
chat_notify_on_states: [success failure]
sinks: []
github_api_endpoint: `
sinks: []`

have := fmt.Sprint(source)

Expand All @@ -244,14 +245,14 @@ github_api_endpoint: `
}
want := `owner: the-owner
repo:
github_api_endpoint:
access_token:
gchat_webhook:
log_level:
context_prefix:
chat_append_summary: false
chat_notify_on_states: []
sinks: []
github_api_endpoint: `
sinks: []`

have := fmt.Sprint(input)

Expand Down
4 changes: 3 additions & 1 deletion cogito/put.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"io"
"strings"

"github.com/hashicorp/go-hclog"
)

// Putter represents the put step of a Concourse resource.
Expand Down Expand Up @@ -38,7 +40,7 @@ type Sinker interface {
// Additionally, the script may emit metadata as a list of key-value pairs. This data is
// intended for public consumption and will make it upstream, intended to be shown on the
// build's page.
func Put(input []byte, out io.Writer, args []string, putter Putter) error {
func Put(log hclog.Logger, input []byte, out io.Writer, args []string, putter Putter) error {
if err := putter.LoadConfiguration(input, args); err != nil {
return fmt.Errorf("put: %s", err)
}
Expand Down
80 changes: 15 additions & 65 deletions cogito/put_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cogito_test
import (
"errors"
"fmt"
"github.com/Pix4D/cogito/github"
"io"
"path/filepath"
"testing"
Expand Down Expand Up @@ -65,7 +64,7 @@ func (ms MockSinker) Send() error {
func TestPutSuccess(t *testing.T) {
putter := MockPutter{sinkers: []cogito.Sinker{MockSinker{}}}

err := cogito.Put(nil, nil, nil, putter)
err := cogito.Put(hclog.NewNullLogger(), nil, nil, nil, putter)

assert.NilError(t, err)
}
Expand All @@ -78,7 +77,7 @@ func TestPutFailure(t *testing.T) {
}

test := func(t *testing.T, tc testCase) {
err := cogito.Put(nil, nil, nil, tc.putter)
err := cogito.Put(hclog.NewNullLogger(), nil, nil, nil, tc.putter)

assert.ErrorContains(t, err, tc.wantErr)
}
Expand Down Expand Up @@ -124,7 +123,7 @@ func TestPutFailure(t *testing.T) {

func TestPutterLoadConfigurationSuccess(t *testing.T) {
in := testhelp.ToJSON(t, basePutRequest)
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.LoadConfiguration(in, []string{"dummy-dir"})

Expand All @@ -143,7 +142,7 @@ func TestPutterLoadConfigurationSinksOverrideSuccess(t *testing.T) {
},
"params": {"sinks": ["gchat"]}
}`)
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())
inputDir := []string{""}
err := putter.LoadConfiguration(in, inputDir)
assert.NilError(t, err)
Expand All @@ -154,22 +153,6 @@ func TestPutterLoadConfigurationSinksOverrideSuccess(t *testing.T) {
assert.NilError(t, err)
}

func TestPutterLoadConfigurationGhApiEndpointOverrideSuccess(t *testing.T) {
in := []byte(`
{
"source": {
"owner": "the-owner",
"repo": "the-repo",
"access_token": "the-token",
"github_api_endpoint": "https://ghe.company.com"
}
}`)
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
inputDir := []string{""}
err := putter.LoadConfiguration(in, inputDir)
assert.NilError(t, err)
}

func TestPutterLoadConfigurationFailure(t *testing.T) {
type testCase struct {
name string
Expand All @@ -180,7 +163,7 @@ func TestPutterLoadConfigurationFailure(t *testing.T) {

test := func(t *testing.T, tc testCase) {
in := testhelp.ToJSON(t, tc.putInput)
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.LoadConfiguration(in, tc.args)

Expand All @@ -207,20 +190,6 @@ func TestPutterLoadConfigurationFailure(t *testing.T) {
args: []string{},
wantErr: "put: concourse resource protocol violation: missing input directory",
},
{
name: "invalid GH endpoint url in source",
putInput: cogito.PutRequest{
Source: cogito.Source{
Owner: "owner",
Repo: "repo",
AccessToken: "token",
GithubApiEndpoint: "invalid-api-endpoint",
},
Params: cogito.PutParams{State: cogito.StateSuccess},
},
args: []string{},
wantErr: "put: source: github_api_endpoint 'invalid-api-endpoint' is an invalid api endpoint",
},
}

for _, tc := range testCases {
Expand All @@ -235,7 +204,7 @@ func TestPutterLoadConfigurationInvalidParamsFailure(t *testing.T) {
"params": {"pizza": "margherita"}
}`)
wantErr := `put: parsing request: json: unknown field "pizza"`
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.LoadConfiguration(in, nil)

Expand All @@ -249,7 +218,7 @@ func TestPutterLoadConfigurationMissingGchatwebHook(t *testing.T) {
"params": {}
}`)
wantErr := `put: source: missing keys: gchat_webhook`
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.LoadConfiguration(in, nil)

Expand All @@ -263,7 +232,7 @@ func TestPutterLoadConfigurationUnknownSink(t *testing.T) {
"params": {}
}`)
wantErr := `put: source: invalid sink(s): [pizza]`
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.LoadConfiguration(in, nil)

Expand All @@ -277,7 +246,7 @@ func TestPutterLoadConfigurationUnknownSinkPutParams(t *testing.T) {
"params": {"sinks": ["pizza"]}
}`)
wantErr := `put: arguments: unsupported sink(s): [pizza]`
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.LoadConfiguration(in, nil)

Expand All @@ -293,7 +262,7 @@ func TestPutterProcessInputDirSuccess(t *testing.T) {
}

test := func(t *testing.T, tc testCase) {
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())
tmpDir := testhelp.MakeGitRepoFromTestdata(t, tc.inputDir,
"https://github.com/dummy-owner/dummy-repo", "dummySHA", "banana")
putter.InputDir = filepath.Join(tmpDir, filepath.Base(tc.inputDir))
Expand Down Expand Up @@ -348,7 +317,7 @@ func TestPutterProcessInputDirFailure(t *testing.T) {
test := func(t *testing.T, tc testCase) {
tmpDir := testhelp.MakeGitRepoFromTestdata(t, tc.inputDir,
"https://github.com/dummy-owner/dummy-repo", "dummySHA", "banana mango")
putter := cogito.NewPutter("dummy-api", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())
putter.Request = cogito.PutRequest{
Source: cogito.Source{Owner: "dummy-owner", Repo: "dummy-repo"},
Params: tc.params,
Expand Down Expand Up @@ -425,7 +394,7 @@ func TestPutterProcessInputDirNonExisting(t *testing.T) {
}

func TestPutterSinks(t *testing.T) {
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

sinks := putter.Sinks()
assert.Assert(t, len(sinks) == 2)
Expand All @@ -436,27 +405,8 @@ func TestPutterSinks(t *testing.T) {
assert.Assert(t, ok2)
}

func TestGitHubCommitStatusSinkApiEndpointOverrideFromSource(t *testing.T) {
// default case
defaultApiEndpoint := github.API
defaultPutter := cogito.NewPutter(defaultApiEndpoint, hclog.NewNullLogger())
sinks := defaultPutter.Sinks()
ghSink := sinks[1].(cogito.GitHubCommitStatusSink)
assert.Assert(t, ghSink.GhAPI == defaultApiEndpoint)

// override
customEndpointPutter := cogito.NewPutter(defaultApiEndpoint, hclog.NewNullLogger())
customEndpoint := "https://ghe.company.com"
customEndpointPutter.Request = cogito.PutRequest{
Source: cogito.Source{GithubApiEndpoint: customEndpoint},
}
customPutterSinks := customEndpointPutter.Sinks()
customPutterGhSink := customPutterSinks[1].(cogito.GitHubCommitStatusSink)
assert.Assert(t, customPutterGhSink.GhAPI == customEndpoint)
}

func TestPutterCustomSinks(t *testing.T) {
putter := cogito.NewPutter("dummy-api", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())
putter.Request = cogito.PutRequest{
Params: cogito.PutParams{Sinks: []string{"gchat"}},
}
Expand All @@ -467,15 +417,15 @@ func TestPutterCustomSinks(t *testing.T) {
}

func TestPutterOutputSuccess(t *testing.T) {
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.Output(io.Discard)

assert.NilError(t, err)
}

func TestPutterOutputFailure(t *testing.T) {
putter := cogito.NewPutter("dummy-API", hclog.NewNullLogger())
putter := cogito.NewPutter(hclog.NewNullLogger())

err := putter.Output(&testhelp.FailingWriter{})

Expand Down
Loading

0 comments on commit 4aef045

Please sign in to comment.