From 7ce906597b9171b9700a1e6e3fbcef16230d7d7a Mon Sep 17 00:00:00 2001 From: aliculPix4D Date: Fri, 27 Oct 2023 14:03:15 +0200 Subject: [PATCH] cogito: configure 'source.github_hostname' to override the GitHub API in tests --- cmd/cogito/main_test.go | 9 +++++++-- cogito/ghcommitsink.go | 13 +++++++++++++ cogito/ghcommitsink_test.go | 14 ++++++++++++-- cogito/protocol.go | 7 ++++++- cogito/protocol_test.go | 3 +++ cogito/put_test.go | 4 ++-- cogito/putter.go | 9 +++++---- cogito/putter_private_test.go | 17 +++++++++++------ github/commitstatus.go | 4 +++- 9 files changed, 62 insertions(+), 18 deletions(-) diff --git a/cmd/cogito/main_test.go b/cmd/cogito/main_test.go index da6f2a85..e9597e76 100644 --- a/cmd/cogito/main_test.go +++ b/cmd/cogito/main_test.go @@ -63,6 +63,10 @@ func TestRunPutSuccess(t *testing.T) { var ghReq github.AddRequest var ghUrl *url.URL gitHubSpy := testhelp.SpyHttpServer(&ghReq, nil, &ghUrl, http.StatusCreated) + gitHubSpyURL, err := url.Parse(gitHubSpy.URL) + if err != nil { + t.Fatalf("error parsing SpyHttpServer URL: %s", err) + } var chatMsg googlechat.BasicMessage chatReply := googlechat.MessageReply{} var gchatUrl *url.URL @@ -72,6 +76,7 @@ func TestRunPutSuccess(t *testing.T) { Owner: "the-owner", Repo: "the-repo", AccessToken: "the-secret", + GhHostname: gitHubSpyURL.Host, GChatWebHook: googleChatSpy.URL, LogLevel: "debug", }, @@ -80,9 +85,9 @@ func TestRunPutSuccess(t *testing.T) { var out bytes.Buffer var logOut bytes.Buffer inputDir := testhelp.MakeGitRepoFromTestdata(t, "../../cogito/testdata/one-repo/a-repo", - testhelp.HttpsRemote("github.com", "the-owner", "the-repo"), "dummySHA", wantGitRef) + testhelp.HttpsRemote(gitHubSpyURL.Host, "the-owner", "the-repo"), "dummySHA", wantGitRef) - err := mainErr(in, &out, &logOut, []string{"out", inputDir}) + err = mainErr(in, &out, &logOut, []string{"out", inputDir}) assert.NilError(t, err, "\nout: %s\nlogOut: %s", out.String(), logOut.String()) // diff --git a/cogito/ghcommitsink.go b/cogito/ghcommitsink.go index 37c7e9d2..8c0815dc 100644 --- a/cogito/ghcommitsink.go +++ b/cogito/ghcommitsink.go @@ -1,7 +1,9 @@ package cogito import ( + "fmt" "log/slog" + "strings" "time" "github.com/hashicorp/go-hclog" @@ -41,6 +43,7 @@ func (sink GitHubCommitStatusSink) Send() error { context := ghMakeContext(sink.Request) target := &github.Target{ + Server: apiEndpoint(sink.Request.Source.GhHostname), Retry: retry.Retry{ FirstDelay: retryFirstDelay, BackoffLimit: retryBackoffLimit, @@ -88,3 +91,13 @@ func ghMakeContext(request PutRequest) string { } return context } + +func apiEndpoint(hostname string) string { + if strings.Contains(hostname, "127.0.0.1") { + return fmt.Sprintf("http://%s", hostname) + } + if strings.Contains(hostname, "github.com") { + return fmt.Sprintf("https://%s", hostname) + } + return github.API +} diff --git a/cogito/ghcommitsink_test.go b/cogito/ghcommitsink_test.go index 5188f686..d74c0fc0 100644 --- a/cogito/ghcommitsink_test.go +++ b/cogito/ghcommitsink_test.go @@ -23,16 +23,21 @@ func TestSinkGitHubCommitStatusSendSuccess(t *testing.T) { var ghReq github.AddRequest var URL *url.URL ts := testhelp.SpyHttpServer(&ghReq, nil, &URL, http.StatusCreated) + gitHubSpyURL, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("error parsing SpyHttpServer URL: %s", err) + } sink := cogito.GitHubCommitStatusSink{ Log: hclog.NewNullLogger(), GitRef: wantGitRef, Request: cogito.PutRequest{ + Source: cogito.Source{GhHostname: gitHubSpyURL.Host}, Params: cogito.PutParams{State: wantState}, Env: cogito.Environment{BuildJobName: jobName}, }, } - err := sink.Send() + err = sink.Send() assert.NilError(t, err) ts.Close() // Avoid races before the following asserts. @@ -46,16 +51,21 @@ func TestSinkGitHubCommitStatusSendFailure(t *testing.T) { http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusTeapot) })) + gitHubSpyURL, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("error parsing SpyHttpServer URL: %s", err) + } defer ts.Close() sink := cogito.GitHubCommitStatusSink{ Log: hclog.NewNullLogger(), GitRef: "deadbeefdeadbeef", Request: cogito.PutRequest{ + Source: cogito.Source{GhHostname: gitHubSpyURL.Host}, Params: cogito.PutParams{State: cogito.StatePending}, }, } - err := sink.Send() + err = sink.Send() assert.ErrorContains(t, err, `failed to add state "pending" for commit deadbee: 418 I'm a teapot`) diff --git a/cogito/protocol.go b/cogito/protocol.go index ac0b2d95..a88d5ef7 100644 --- a/cogito/protocol.go +++ b/cogito/protocol.go @@ -10,6 +10,7 @@ import ( "os" "strings" + "github.com/Pix4D/cogito/github" "github.com/Pix4D/cogito/sets" ) @@ -164,6 +165,7 @@ type Source struct { // // Optional // + GhHostname string `json:"github_hostname"` GChatWebHook string `json:"gchat_webhook"` // SENSITIVE LogLevel string `json:"log_level"` LogUrl string `json:"log_url"` // DEPRECATED @@ -179,6 +181,7 @@ 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_hostname: %s\n", src.GhHostname) 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) @@ -268,7 +271,9 @@ func (src *Source) Validate() error { if len(src.ChatNotifyOnStates) == 0 { src.ChatNotifyOnStates = defaultNotifyStates } - + if src.GhHostname == "" { + src.GhHostname = github.GhDefaultHostname + } return nil } diff --git a/cogito/protocol_test.go b/cogito/protocol_test.go index 508910ac..43928493 100644 --- a/cogito/protocol_test.go +++ b/cogito/protocol_test.go @@ -175,6 +175,7 @@ func TestSourcePrintLogRedaction(t *testing.T) { source := cogito.Source{ Owner: "the-owner", Repo: "the-repo", + GhHostname: "github.com", AccessToken: "sensitive-the-access-token", GChatWebHook: "sensitive-gchat-webhook", LogLevel: "debug", @@ -186,6 +187,7 @@ func TestSourcePrintLogRedaction(t *testing.T) { t.Run("fmt.Print redacts fields", func(t *testing.T) { want := `owner: the-owner repo: the-repo +github_hostname: github.com access_token: ***REDACTED*** gchat_webhook: ***REDACTED*** log_level: debug @@ -205,6 +207,7 @@ sinks: []` } want := `owner: the-owner repo: +github_hostname: access_token: gchat_webhook: log_level: diff --git a/cogito/put_test.go b/cogito/put_test.go index 3bcac49e..294494ab 100644 --- a/cogito/put_test.go +++ b/cogito/put_test.go @@ -267,7 +267,7 @@ func TestPutterProcessInputDirSuccess(t *testing.T) { "https://github.com/dummy-owner/dummy-repo", "dummySHA", "banana") putter.InputDir = filepath.Join(tmpDir, filepath.Base(tc.inputDir)) putter.Request = cogito.PutRequest{ - Source: cogito.Source{Owner: "dummy-owner", Repo: "dummy-repo", Sinks: tc.sink}, + Source: cogito.Source{GhHostname: "github.com", Owner: "dummy-owner", Repo: "dummy-repo", Sinks: tc.sink}, Params: tc.params, } @@ -319,7 +319,7 @@ func TestPutterProcessInputDirFailure(t *testing.T) { "https://github.com/dummy-owner/dummy-repo", "dummySHA", "banana mango") putter := cogito.NewPutter(hclog.NewNullLogger()) putter.Request = cogito.PutRequest{ - Source: cogito.Source{Owner: "dummy-owner", Repo: "dummy-repo"}, + Source: cogito.Source{GhHostname: "github.com", Owner: "dummy-owner", Repo: "dummy-repo"}, Params: tc.params, } putter.InputDir = filepath.Join(tmpDir, filepath.Base(tc.inputDir)) diff --git a/cogito/putter.go b/cogito/putter.go index 406dccc4..496f883a 100644 --- a/cogito/putter.go +++ b/cogito/putter.go @@ -131,7 +131,7 @@ func (putter *ProdPutter) ProcessInputDir() error { case 1: repoDir := filepath.Join(putter.InputDir, inputDirs.OrderedList()[0]) putter.log.Debug("", "inputDirs", inputDirs, "repoDir", repoDir, "msgDir", msgDir) - if err := checkGitRepoDir(repoDir, source.Owner, source.Repo); err != nil { + if err := checkGitRepoDir(repoDir, source.GhHostname, source.Owner, source.Repo); err != nil { return err } putter.gitRef, err = getGitCommit(repoDir) @@ -237,7 +237,7 @@ func collectInputDirs(dir string) ([]string, error) { // - The repo configuration contains a "remote origin" section. // - The remote origin url can be parsed following the GitHub conventions. // - The result of the parse matches OWNER and REPO. -func checkGitRepoDir(dir, owner, repo string) error { +func checkGitRepoDir(dir, hostname, owner, repo string) error { cfg, err := mini.LoadConfiguration(filepath.Join(dir, ".git/config")) if err != nil { return fmt.Errorf("parsing .git/config: %w", err) @@ -259,7 +259,7 @@ func checkGitRepoDir(dir, owner, repo string) error { if err != nil { return fmt.Errorf(".git/config: remote: %w", err) } - left := []string{"github.com", owner, repo} + left := []string{hostname, owner, repo} right := []string{gu.URL.Host, gu.Owner, gu.Repo} for i, l := range left { r := right[i] @@ -272,10 +272,11 @@ Git repository configuration (received as 'inputs:' in this PUT step): repo: %s Cogito SOURCE configuration: + hostname: %s owner: %s repo: %s`, gitUrl, gu.Owner, gu.Repo, - owner, repo) + hostname, owner, repo) } } return nil diff --git a/cogito/putter_private_test.go b/cogito/putter_private_test.go index 0abc3738..9570fce1 100644 --- a/cogito/putter_private_test.go +++ b/cogito/putter_private_test.go @@ -2,6 +2,7 @@ package cogito import ( "errors" + "fmt" "net/url" "os" "path/filepath" @@ -65,6 +66,7 @@ func TestCheckGitRepoDirSuccess(t *testing.T) { repoURL string } + const wantHostname = "github.com" const wantOwner = "smiling" const wantRepo = "butterfly" @@ -73,7 +75,7 @@ func TestCheckGitRepoDirSuccess(t *testing.T) { "dummySHA", "dummyHead") err := checkGitRepoDir(filepath.Join(inputDir, filepath.Base(tc.dir)), - wantOwner, wantRepo) + wantHostname, wantOwner, wantRepo) assert.NilError(t, err) } @@ -82,22 +84,22 @@ func TestCheckGitRepoDirSuccess(t *testing.T) { { name: "repo with good SSH remote", dir: "testdata/one-repo/a-repo", - repoURL: testhelp.SshRemote("github.com", wantOwner, wantRepo), + repoURL: testhelp.SshRemote(wantHostname, wantOwner, wantRepo), }, { name: "repo with good HTTPS remote", dir: "testdata/one-repo/a-repo", - repoURL: testhelp.HttpsRemote("github.com", wantOwner, wantRepo), + repoURL: testhelp.HttpsRemote(wantHostname, wantOwner, wantRepo), }, { name: "repo with good HTTP remote", dir: "testdata/one-repo/a-repo", - repoURL: testhelp.HttpRemote("github.com", wantOwner, wantRepo), + repoURL: testhelp.HttpRemote(wantHostname, wantOwner, wantRepo), }, { name: "PR resource but with basic auth in URL (see PR #46)", dir: "testdata/one-repo/a-repo", - repoURL: "https://x-oauth-basic:ghp_XXX@github.com/smiling/butterfly.git", + repoURL: fmt.Sprintf("https://x-oauth-basic:ghp_XXX@%s/%s/%s.git", wantHostname, wantOwner, wantRepo), }, } @@ -114,6 +116,7 @@ func TestCheckGitRepoDirFailure(t *testing.T) { wantErrWild string // wildcard matching } + const wantHostname = "github.com" const wantOwner = "smiling" const wantRepo = "butterfly" @@ -122,7 +125,7 @@ func TestCheckGitRepoDirFailure(t *testing.T) { "dummySHA", "dummyHead") err := checkGitRepoDir(filepath.Join(inDir, filepath.Base(tc.dir)), - wantOwner, wantRepo) + wantHostname, wantOwner, wantRepo) assert.ErrorContains(t, err, tc.wantErrWild) } @@ -152,6 +155,7 @@ Git repository configuration (received as 'inputs:' in this PUT step): repo: repo-a Cogito SOURCE configuration: + hostname: github.com owner: smiling repo: butterfly`, }, @@ -167,6 +171,7 @@ Git repository configuration (received as 'inputs:' in this PUT step): repo: repo-a Cogito SOURCE configuration: + hostname: github.com owner: smiling repo: butterfly`, }, diff --git a/github/commitstatus.go b/github/commitstatus.go index 31f862dc..b1e688af 100644 --- a/github/commitstatus.go +++ b/github/commitstatus.go @@ -31,12 +31,14 @@ func (e *StatusError) Error() string { return fmt.Sprintf("%s\n%s", e.What, e.Details) } +// Default GitHub hostname +const GhDefaultHostname = "github.com" + // API is the GitHub API endpoint. const API = "https://api.github.com" type Target struct { // Server is the GitHub API server. - // Currently, hardcoded to https://api.github.com Server string // Retry controls the retry logic. Retry retry.Retry