From db3ee2b2ddd6094e5ae37d9a7d7a70aa225de596 Mon Sep 17 00:00:00 2001 From: Duane May Date: Tue, 19 Jul 2022 18:57:06 +0000 Subject: [PATCH 01/24] only count reviews from authors who can push to the repository Signed-off-by: Ben Fuller Co-authored-by: Ben Fuller --- github.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/github.go b/github.go index ab10cbdc..1635e1b8 100644 --- a/github.go +++ b/github.go @@ -106,8 +106,10 @@ func (m *GithubClient) ListPullRequests(prStates []githubv4.PullRequestState) ([ Node struct { PullRequestObject Reviews struct { - TotalCount int - } `graphql:"reviews(states: $prReviewStates)"` + Nodes []struct { + AuthorCanPushToRepository bool + } + } `graphql:"reviews(last: $prReviewsLast,states: $prReviewStates)"` Commits struct { Edges []struct { Node struct { @@ -136,6 +138,7 @@ func (m *GithubClient) ListPullRequests(prStates []githubv4.PullRequestState) ([ "repositoryOwner": githubv4.String(m.Owner), "repositoryName": githubv4.String(m.Repository), "prFirst": githubv4.Int(100), + "prReviewsLast": githubv4.Int(100), "prStates": prStates, "prCursor": (*githubv4.String)(nil), "commitsLast": githubv4.Int(1), @@ -150,6 +153,12 @@ func (m *GithubClient) ListPullRequests(prStates []githubv4.PullRequestState) ([ } for _, p := range query.Repository.PullRequests.Edges { labels := make([]LabelObject, len(p.Node.Labels.Edges)) + numApprovals := 0 + for _, review := range p.Node.Reviews.Nodes { + if review.AuthorCanPushToRepository { + numApprovals++ + } + } for _, l := range p.Node.Labels.Edges { labels = append(labels, l.Node.LabelObject) } @@ -158,7 +167,7 @@ func (m *GithubClient) ListPullRequests(prStates []githubv4.PullRequestState) ([ response = append(response, &PullRequest{ PullRequestObject: p.Node.PullRequestObject, Tip: c.Node.Commit, - ApprovedReviewCount: p.Node.Reviews.TotalCount, + ApprovedReviewCount: numApprovals, Labels: labels, }) } From d2e30254c915024cbff56735feddaf67802c212b Mon Sep 17 00:00:00 2001 From: Benjamin Fuller Date: Tue, 30 May 2023 08:13:57 -0700 Subject: [PATCH 02/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d46077ff..3a92aee9 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ resource_types: - name: pull-request type: docker-image source: - repository: teliaoss/github-pr-resource + repository: loggregatorbot/github-pr-resource resources: - name: pull-request From fa124d7d6741010628ae9351226fbe8c3b6c0497 Mon Sep 17 00:00:00 2001 From: Benjamin Fuller Date: Tue, 30 May 2023 08:15:24 -0700 Subject: [PATCH 03/24] Update pipeline.yml --- e2e/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/pipeline.yml b/e2e/pipeline.yml index f4b9574c..10925141 100644 --- a/e2e/pipeline.yml +++ b/e2e/pipeline.yml @@ -2,7 +2,7 @@ resource_types: - name: pull-request type: docker-image source: - repository: teliaoss/github-pr-resource + repository: loggregatorbot/github-pr-resource tag: dev resources: From 14ed79c3448132cd7fdadeffe3791a62f2666fec Mon Sep 17 00:00:00 2001 From: Benjamin Fuller Date: Tue, 30 May 2023 08:16:01 -0700 Subject: [PATCH 04/24] Update Taskfile.yml --- Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index f01ebc28..6197a975 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -2,7 +2,7 @@ version: '2' vars: BUILD_DIR: build - DOCKER_REPO: teliaoss/github-pr-resource + DOCKER_REPO: loggregatorbot/github-pr-resource tasks: default: From b4fc2c4968f3fa9538434c507e5e525cb80d09cc Mon Sep 17 00:00:00 2001 From: Benjamin Fuller Date: Tue, 30 May 2023 08:16:56 -0700 Subject: [PATCH 05/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a92aee9..74725829 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/telia-oss/github-pr-resource.svg?branch=master)](https://travis-ci.org/telia-oss/github-pr-resource) [![Go Report Card](https://goreportcard.com/badge/github.com/telia-oss/github-pr-resource)](https://goreportcard.com/report/github.com/telia-oss/github-pr-resource) -[![Docker Automated build](https://img.shields.io/docker/automated/teliaoss/github-pr-resource.svg)](https://hub.docker.com/r/teliaoss/github-pr-resource/) +[![Docker Automated build](https://img.shields.io/docker/automated/teliaoss/github-pr-resource.svg)](https://hub.docker.com/r/loggregatorbot/github-pr-resource/) [graphql-api]: https://developer.github.com/v4 [original-resource]: https://github.com/jtarchie/github-pullrequest-resource From 444529b42d3e456b7e1bd912f96416bca0a20d82 Mon Sep 17 00:00:00 2001 From: Benjamin Fuller Date: Wed, 31 May 2023 09:45:32 -0700 Subject: [PATCH 06/24] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74725829..0911de44 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ ## Github PR resource -[![Build Status](https://travis-ci.org/telia-oss/github-pr-resource.svg?branch=master)](https://travis-ci.org/telia-oss/github-pr-resource) -[![Go Report Card](https://goreportcard.com/badge/github.com/telia-oss/github-pr-resource)](https://goreportcard.com/report/github.com/telia-oss/github-pr-resource) -[![Docker Automated build](https://img.shields.io/docker/automated/teliaoss/github-pr-resource.svg)](https://hub.docker.com/r/loggregatorbot/github-pr-resource/) +[![Go Report Card](https://goreportcard.com/badge/github.com/cloudfoundry-community/github-pr-resource)](https://goreportcard.com/report/github.com/cloudfoundry-community/github-pr-resource) +[![Docker Automated build](https://img.shields.io/docker/automated/loggregatorbot/github-pr-resource.svg)](https://hub.docker.com/r/loggregatorbot/github-pr-resource/) [graphql-api]: https://developer.github.com/v4 [original-resource]: https://github.com/jtarchie/github-pullrequest-resource From 0d3da30e34b32dfbed0a996ef32c316e969ff5d8 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Thu, 21 May 2020 13:54:27 -0400 Subject: [PATCH 07/24] Close #26: Return all open PRs instead of filtering by date This resource can inadvertently miss Pull Requests due to out-of-order commits across PRs. If PR#2 is opened after PR#1, but the head commit of PR#2 is older than the head commit of PR#1, the resource will not include PR#2 in the list of new versions provided to Concourse. Rather than attempt to find a different way of tracking which PRs are "new" given an input version, we can remove the date-based filtering and return all open PRs. Concourse can deduplicate versions based on metadata, which means that we will only trigger new jobs for versions that Concourse hasn't seen before. This makes it easier for teams to use this resource to track PRs in Concourse, since they no longer have to ensure that a PR has a later head commit than all currently-opened PRs in order to notify Concourse that their PR exists. --- check.go | 5 ----- check_test.go | 53 +++++++++++++++++++++++++++++++++++-------------- e2e/e2e_test.go | 19 ++++++------------ 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/check.go b/check.go index 64df9e24..4cd09beb 100644 --- a/check.go +++ b/check.go @@ -44,11 +44,6 @@ Loop: continue } - // Filter out commits that are too old. - if !p.UpdatedDate().Time.After(request.Version.CommittedDate) { - continue - } - // Filter out pull request if it does not contain at least one of the desired labels if len(request.Source.Labels) > 0 { labelFound := false diff --git a/check_test.go b/check_test.go index 8c422914..956a8728 100644 --- a/check_test.go +++ b/check_test.go @@ -50,7 +50,7 @@ func TestCheck(t *testing.T) { }, { - description: "check returns the previous version when its still latest", + description: "check returns all open PRs if there is a previous", source: resource.Source{ Repository: "itsdalmo/test-repository", AccessToken: "oauthtoken", @@ -59,20 +59,13 @@ func TestCheck(t *testing.T) { pullRequests: testPullRequests, files: [][]string{}, expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[1]), - }, - }, - - { - description: "check returns all new versions since the last", - source: resource.Source{ - Repository: "itsdalmo/test-repository", - AccessToken: "oauthtoken", - }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[11]), + resource.NewVersion(testPullRequests[8]), + resource.NewVersion(testPullRequests[7]), + resource.NewVersion(testPullRequests[6]), + resource.NewVersion(testPullRequests[5]), + resource.NewVersion(testPullRequests[4]), + resource.NewVersion(testPullRequests[3]), resource.NewVersion(testPullRequests[2]), resource.NewVersion(testPullRequests[1]), }, @@ -93,6 +86,7 @@ func TestCheck(t *testing.T) { {"terraform/modules/variables.tf", "travis.yml"}, }, expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[3]), resource.NewVersion(testPullRequests[2]), }, }, @@ -112,6 +106,7 @@ func TestCheck(t *testing.T) { {"terraform/modules/variables.tf", "travis.yml"}, }, expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[3]), resource.NewVersion(testPullRequests[2]), }, }, @@ -126,6 +121,15 @@ func TestCheck(t *testing.T) { version: resource.NewVersion(testPullRequests[1]), pullRequests: testPullRequests, expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[11]), + resource.NewVersion(testPullRequests[8]), + resource.NewVersion(testPullRequests[7]), + resource.NewVersion(testPullRequests[6]), + resource.NewVersion(testPullRequests[5]), + resource.NewVersion(testPullRequests[4]), + resource.NewVersion(testPullRequests[3]), + resource.NewVersion(testPullRequests[2]), + resource.NewVersion(testPullRequests[1]), resource.NewVersion(testPullRequests[0]), }, }, @@ -140,6 +144,13 @@ func TestCheck(t *testing.T) { version: resource.NewVersion(testPullRequests[3]), pullRequests: testPullRequests, expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[11]), + resource.NewVersion(testPullRequests[8]), + resource.NewVersion(testPullRequests[7]), + resource.NewVersion(testPullRequests[6]), + resource.NewVersion(testPullRequests[5]), + resource.NewVersion(testPullRequests[4]), + resource.NewVersion(testPullRequests[3]), resource.NewVersion(testPullRequests[1]), }, }, @@ -154,6 +165,13 @@ func TestCheck(t *testing.T) { version: resource.NewVersion(testPullRequests[3]), pullRequests: testPullRequests, expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[11]), + resource.NewVersion(testPullRequests[8]), + resource.NewVersion(testPullRequests[7]), + resource.NewVersion(testPullRequests[6]), + resource.NewVersion(testPullRequests[5]), + resource.NewVersion(testPullRequests[4]), + resource.NewVersion(testPullRequests[3]), resource.NewVersion(testPullRequests[2]), resource.NewVersion(testPullRequests[1]), }, @@ -169,6 +187,11 @@ func TestCheck(t *testing.T) { version: resource.NewVersion(testPullRequests[5]), pullRequests: testPullRequests, expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[11]), + resource.NewVersion(testPullRequests[8]), + resource.NewVersion(testPullRequests[7]), + resource.NewVersion(testPullRequests[6]), + resource.NewVersion(testPullRequests[5]), resource.NewVersion(testPullRequests[3]), resource.NewVersion(testPullRequests[2]), resource.NewVersion(testPullRequests[1]), diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 523838c2..d8ca2c68 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -23,6 +23,9 @@ import ( ) var ( + firstCommitID = "23dc9f552bf989d1a4aeb65ce23351dee0ec9019" + firstPullRequestID = "3" + firstDateTime = time.Date(2018, time.May, 11, 7, 28, 56, 0, time.UTC) targetCommitID = "a5114f6ab89f4b736655642a11e8d15ce363d882" targetPullRequestID = "4" targetDateTime = time.Date(2018, time.May, 11, 8, 43, 48, 0, time.UTC) @@ -54,25 +57,15 @@ func TestCheckE2E(t *testing.T) { }, { - description: "check returns the previous version when its still latest", + description: "check returns all open PRs if there is a previous version", source: resource.Source{ Repository: "itsdalmo/test-repository", AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), }, version: resource.Version{PR: latestPullRequestID, Commit: latestCommitID, CommittedDate: latestDateTime}, expected: resource.CheckResponse{ - resource.Version{PR: latestPullRequestID, Commit: latestCommitID, CommittedDate: latestDateTime}, - }, - }, - - { - description: "check returns all new versions since the last", - source: resource.Source{ - Repository: "itsdalmo/test-repository", - AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), - }, - version: resource.Version{PR: targetPullRequestID, Commit: targetCommitID, CommittedDate: targetDateTime}, - expected: resource.CheckResponse{ + resource.Version{PR: firstPullRequestID, Commit: firstCommitID, CommittedDate: firstDateTime}, + resource.Version{PR: targetPullRequestID, Commit: targetCommitID, CommittedDate: targetDateTime}, resource.Version{PR: latestPullRequestID, Commit: latestCommitID, CommittedDate: latestDateTime}, }, }, From 9324a44c157e1511b9265c40212f0dafc04f0eec Mon Sep 17 00:00:00 2001 From: Giuseppe Capizzi Date: Fri, 31 Mar 2023 15:19:00 +0000 Subject: [PATCH 08/24] Remove approved_review_count from version struct This triggers CI when the approved review count changes, which is not the correct behaviour Co-authored-by: Kieron Browne --- in_test.go | 68 ++++++++++++++++++++++++------------------------------ models.go | 18 +++++++-------- 2 files changed, 38 insertions(+), 48 deletions(-) diff --git a/in_test.go b/in_test.go index 17e7abfc..63816d7a 100644 --- a/in_test.go +++ b/in_test.go @@ -16,7 +16,6 @@ import ( ) func TestGet(t *testing.T) { - tests := []struct { description string source resource.Source @@ -35,15 +34,14 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, - ApprovedReviewCount: "0", - State: githubv4.PullRequestStateOpen, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{}, pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { @@ -54,15 +52,14 @@ func TestGet(t *testing.T) { GitCryptKey: "gitcryptkey", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, - ApprovedReviewCount: "0", - State: githubv4.PullRequestStateOpen, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{}, pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { @@ -72,17 +69,16 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, - ApprovedReviewCount: "0", - State: githubv4.PullRequestStateOpen, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ IntegrationTool: "rebase", }, pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { @@ -92,17 +88,16 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, - ApprovedReviewCount: "0", - State: githubv4.PullRequestStateOpen, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ IntegrationTool: "checkout", }, pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { @@ -112,17 +107,16 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, - ApprovedReviewCount: "0", - State: githubv4.PullRequestStateOpen, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ GitDepth: 2, }, pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { @@ -132,11 +126,10 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, - ApprovedReviewCount: "0", - State: githubv4.PullRequestStateOpen, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ ListChangedFiles: true, @@ -150,7 +143,7 @@ func TestGet(t *testing.T) { Path: "Other.md", }, }, - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, filesString: "README.md\nOther.md\n", }, @@ -278,7 +271,6 @@ func TestGet(t *testing.T) { } func TestGetSkipDownload(t *testing.T) { - tests := []struct { description string source resource.Source diff --git a/models.go b/models.go index 9e4e7b1c..7a6ac2bd 100644 --- a/models.go +++ b/models.go @@ -71,21 +71,19 @@ type MetadataField struct { // Version communicated with Concourse. type Version struct { - PR string `json:"pr"` - Commit string `json:"commit"` - CommittedDate time.Time `json:"committed,omitempty"` - ApprovedReviewCount string `json:"approved_review_count"` - State githubv4.PullRequestState `json:"state"` + PR string `json:"pr"` + Commit string `json:"commit"` + CommittedDate time.Time `json:"committed,omitempty"` + State githubv4.PullRequestState `json:"state"` } // NewVersion constructs a new Version. func NewVersion(p *PullRequest) Version { return Version{ - PR: strconv.Itoa(p.Number), - Commit: p.Tip.OID, - CommittedDate: p.UpdatedDate().Time, - ApprovedReviewCount: strconv.Itoa(p.ApprovedReviewCount), - State: p.State, + PR: strconv.Itoa(p.Number), + Commit: p.Tip.OID, + CommittedDate: p.UpdatedDate().Time, + State: p.State, } } From 13fab4ff39531d06c6ec40528079f174f58ccd0a Mon Sep 17 00:00:00 2001 From: Georgi Sabev Date: Wed, 31 May 2023 13:10:34 +0000 Subject: [PATCH 09/24] Trust users from given teams or trusted list This allows finer control of the RequiredReviewApprovals property. It is ignored if the user is in one of the trusted teams, or in the trusted user list. This way we can run CI on trusted users without needing an approval, but untrusted users will require a PR approval before their changes are run in CI. In order to reduce the number of calls to github we are caching trusted users for the duration of a single check. Note: github should be configured to remove approvals if new commits are received on a PR so that a user doesn't circumvent security by pushing a malicious commit after an approved legitimate commit on the same PR. Co-authored-by: Kieron Browne Co-authored-by: Georgi Sabev Co-authored-by: Danail Branekov --- check.go | 43 +++++- check_test.go | 342 ++++++++++++++++++++++++++----------------- fakes/fake_github.go | 78 ++++++++++ github.go | 37 ++++- github_test.go | 27 ++++ go.sum | 10 -- in_test.go | 16 +- models.go | 7 + out_test.go | 26 ++-- 9 files changed, 413 insertions(+), 173 deletions(-) create mode 100644 github_test.go diff --git a/check.go b/check.go index 4cd09beb..f838b3c6 100644 --- a/check.go +++ b/check.go @@ -10,6 +10,8 @@ import ( "github.com/shurcooL/githubv4" ) +var trustedTeamMembers map[string]bool = map[string]bool{} + // Check (business logic) func Check(request CheckRequest, manager Github) (CheckResponse, error) { var response CheckResponse @@ -73,8 +75,14 @@ Loop: continue } - // Filter pull request if it does not have the required number of approved review(s). - if p.ApprovedReviewCount < request.Source.RequiredReviewApprovals { + prAuthorTrusted, err := userTrusted(p.Author.Login, request.Source.TrustedUsers, request.Source.TrustedTeams, manager) + if err != nil { + return nil, fmt.Errorf("failed to list users in team %v: %s", request.Source.TrustedTeams, err) + } + + prApproved := p.ApprovedReviewCount >= request.Source.RequiredReviewApprovals + + if !prAuthorTrusted && !prApproved { continue } @@ -116,6 +124,7 @@ Loop: continue Loop } } + response = append(response, NewVersion(p)) } @@ -130,9 +139,39 @@ Loop: if len(response) != 0 && request.Version.PR == "" { response = CheckResponse{response[len(response)-1]} } + return response, nil } +func userTrusted(user string, trustedUsers, trustedTeams []string, manager Github) (bool, error) { + for _, u := range trustedUsers { + if user == u { + return true, nil + } + } + + if trustedTeamMembers[user] { + return true, nil + } + + for _, team := range trustedTeams { + teamMembers, err := manager.ListTeamMembers(team) + if err != nil { + return false, fmt.Errorf("failed to list team members of team %q: %s", trustedTeams, err) + } + + for _, u := range teamMembers { + trustedTeamMembers[u] = true + } + + if trustedTeamMembers[user] { + return true, nil + } + } + + return false, nil +} + // ContainsSkipCI returns true if a string contains [ci skip] or [skip ci]. func ContainsSkipCI(s string) bool { re := regexp.MustCompile("(?i)\\[(ci skip|skip ci)\\]") diff --git a/check_test.go b/check_test.go index 956a8728..1353f1c6 100644 --- a/check_test.go +++ b/check_test.go @@ -10,30 +10,39 @@ import ( ) var ( - testPullRequests = []*resource.PullRequest{ - createTestPR(1, "master", true, false, 0, nil, false, githubv4.PullRequestStateOpen), - createTestPR(2, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - createTestPR(3, "master", false, false, 0, nil, true, githubv4.PullRequestStateOpen), - createTestPR(4, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - createTestPR(5, "master", false, true, 0, nil, false, githubv4.PullRequestStateOpen), - createTestPR(6, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - createTestPR(7, "develop", false, false, 0, []string{"enhancement"}, false, githubv4.PullRequestStateOpen), - createTestPR(8, "master", false, false, 1, []string{"wontfix"}, false, githubv4.PullRequestStateOpen), - createTestPR(9, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), - createTestPR(10, "master", false, false, 0, nil, false, githubv4.PullRequestStateClosed), - createTestPR(11, "master", false, false, 0, nil, false, githubv4.PullRequestStateMerged), - createTestPR(12, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + standardTestPRs = []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", true, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(2, "master", "anonymous", false, false, 0, nil, true, githubv4.PullRequestStateOpen), + createTestPR(3, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(4, "master", "anonymous", false, true, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(5, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(6, "develop", "anonymous", false, false, 0, []string{"enhancement"}, false, githubv4.PullRequestStateOpen), + createTestPR(7, "master", "anonymous", false, false, 1, []string{"wontfix"}, false, githubv4.PullRequestStateOpen), + createTestPR(8, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(9, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateClosed), + createTestPR(10, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateMerged), + createTestPR(11, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + } + multiTeamTestPRs = []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "foo-member", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(2, "master", "bar-member", false, false, 0, nil, false, githubv4.PullRequestStateOpen), } ) +func ptrTo(i int) *int { + return &i +} + func TestCheck(t *testing.T) { tests := []struct { - description string - source resource.Source - version resource.Version - files [][]string - pullRequests []*resource.PullRequest - expected resource.CheckResponse + description string + source resource.Source + version resource.Version + files [][]string + pullRequests []*resource.PullRequest + expectedIndexes []int }{ { description: "check returns the latest version if there is no previous", @@ -41,12 +50,9 @@ func TestCheck(t *testing.T) { Repository: "itsdalmo/test-repository", AccessToken: "oauthtoken", }, - version: resource.Version{}, - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[1]), - }, + pullRequests: standardTestPRs, + files: [][]string{}, + expectedIndexes: []int{1}, }, { @@ -55,20 +61,10 @@ func TestCheck(t *testing.T) { Repository: "itsdalmo/test-repository", AccessToken: "oauthtoken", }, - version: resource.NewVersion(testPullRequests[1]), - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[11]), - resource.NewVersion(testPullRequests[8]), - resource.NewVersion(testPullRequests[7]), - resource.NewVersion(testPullRequests[6]), - resource.NewVersion(testPullRequests[5]), - resource.NewVersion(testPullRequests[4]), - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[2]), - resource.NewVersion(testPullRequests[1]), - }, + version: resource.NewVersion(standardTestPRs[1]), + pullRequests: standardTestPRs, + files: [][]string{}, + expectedIndexes: []int{11, 8, 7, 6, 5, 4, 3, 2, 1}, }, { @@ -78,17 +74,14 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", Paths: []string{"terraform/*/*.tf", "terraform/*/*/*.tf"}, }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, + version: resource.NewVersion(standardTestPRs[3]), + pullRequests: standardTestPRs, files: [][]string{ {"README.md", "travis.yml"}, {"terraform/modules/ecs/main.tf", "README.md"}, {"terraform/modules/variables.tf", "travis.yml"}, }, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[2]), - }, + expectedIndexes: []int{3, 2}, }, { @@ -98,17 +91,14 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", IgnorePaths: []string{"*.md", "*.yml"}, }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, + version: resource.NewVersion(standardTestPRs[3]), + pullRequests: standardTestPRs, files: [][]string{ {"README.md", "travis.yml"}, {"terraform/modules/ecs/main.tf", "README.md"}, {"terraform/modules/variables.tf", "travis.yml"}, }, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[2]), - }, + expectedIndexes: []int{3, 2}, }, { @@ -118,20 +108,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", DisableCISkip: true, }, - version: resource.NewVersion(testPullRequests[1]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[11]), - resource.NewVersion(testPullRequests[8]), - resource.NewVersion(testPullRequests[7]), - resource.NewVersion(testPullRequests[6]), - resource.NewVersion(testPullRequests[5]), - resource.NewVersion(testPullRequests[4]), - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[2]), - resource.NewVersion(testPullRequests[1]), - resource.NewVersion(testPullRequests[0]), - }, + version: resource.NewVersion(standardTestPRs[1]), + pullRequests: standardTestPRs, + expectedIndexes: []int{11, 8, 7, 6, 5, 4, 3, 2, 1, 0}, }, { @@ -141,18 +120,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", IgnoreDrafts: true, }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[11]), - resource.NewVersion(testPullRequests[8]), - resource.NewVersion(testPullRequests[7]), - resource.NewVersion(testPullRequests[6]), - resource.NewVersion(testPullRequests[5]), - resource.NewVersion(testPullRequests[4]), - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[1]), - }, + version: resource.NewVersion(standardTestPRs[3]), + pullRequests: standardTestPRs, + expectedIndexes: []int{11, 8, 7, 6, 5, 4, 3, 1}, }, { @@ -162,19 +132,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", IgnoreDrafts: false, }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[11]), - resource.NewVersion(testPullRequests[8]), - resource.NewVersion(testPullRequests[7]), - resource.NewVersion(testPullRequests[6]), - resource.NewVersion(testPullRequests[5]), - resource.NewVersion(testPullRequests[4]), - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[2]), - resource.NewVersion(testPullRequests[1]), - }, + version: resource.NewVersion(standardTestPRs[3]), + pullRequests: standardTestPRs, + expectedIndexes: []int{11, 8, 7, 6, 5, 4, 3, 2, 1}, }, { @@ -184,18 +144,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", DisableForks: true, }, - version: resource.NewVersion(testPullRequests[5]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[11]), - resource.NewVersion(testPullRequests[8]), - resource.NewVersion(testPullRequests[7]), - resource.NewVersion(testPullRequests[6]), - resource.NewVersion(testPullRequests[5]), - resource.NewVersion(testPullRequests[3]), - resource.NewVersion(testPullRequests[2]), - resource.NewVersion(testPullRequests[1]), - }, + version: resource.NewVersion(standardTestPRs[5]), + pullRequests: standardTestPRs, + expectedIndexes: []int{11, 8, 7, 6, 5, 3, 2, 1}, }, { @@ -205,26 +156,110 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", BaseBranch: "develop", }, - version: resource.Version{}, - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[6]), + pullRequests: standardTestPRs, + files: [][]string{}, + expectedIndexes: []int{6}, + }, + + { + description: "check always allows prs from team members", + pullRequests: []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "foo-member", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + }, + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + RequiredReviewApprovals: 1, + TrustedTeams: []string{"foo"}, + }, + expectedIndexes: []int{1}, + }, + + { + description: "check always allows prs from team members from more than one team", + pullRequests: multiTeamTestPRs, + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + RequiredReviewApprovals: 1, + TrustedTeams: []string{"foo", "bar"}, + }, + version: resource.NewVersion(multiTeamTestPRs[1]), + expectedIndexes: []int{2, 1}, + }, + + { + description: "check does not allow prs from non-members", + pullRequests: []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + }, + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + RequiredReviewApprovals: 1, + TrustedTeams: []string{"foo"}, + }, + expectedIndexes: nil, + }, + + { + description: "check allows approved prs from non-members", + pullRequests: []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 1, nil, false, githubv4.PullRequestStateOpen), + }, + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + RequiredReviewApprovals: 1, + TrustedTeams: []string{"foo"}, }, + expectedIndexes: []int{0}, }, { - description: "check correctly ignores PRs with no approved reviews when specified", + description: "check always allows approved prs from team members", + pullRequests: []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "foo-member", false, false, 1, nil, false, githubv4.PullRequestStateOpen), + }, source: resource.Source{ Repository: "itsdalmo/test-repository", AccessToken: "oauthtoken", RequiredReviewApprovals: 1, + TrustedTeams: []string{"foo"}, + }, + expectedIndexes: []int{1}, + }, + + { + description: "check always allows prs from trusted users", + pullRequests: []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "dependabot[bot]", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, - version: resource.NewVersion(testPullRequests[8]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[7]), + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + RequiredReviewApprovals: 1, + TrustedUsers: []string{"dependabot[bot]"}, }, + expectedIndexes: []int{1}, + }, + + { + description: "check always allows approved prs from dependabot", + pullRequests: []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "dependabot[bot]", false, false, 1, nil, false, githubv4.PullRequestStateOpen), + }, + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + RequiredReviewApprovals: 1, + TrustedUsers: []string{"dependabot[bot]"}, + }, + expectedIndexes: []int{1}, }, { @@ -234,12 +269,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", Labels: []string{"enhancement"}, }, - version: resource.Version{}, - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[6]), - }, + pullRequests: standardTestPRs, + files: [][]string{}, + expectedIndexes: []int{6}, }, { @@ -249,12 +281,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", States: []githubv4.PullRequestState{githubv4.PullRequestStateClosed}, }, - version: resource.Version{}, - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[9]), - }, + pullRequests: standardTestPRs, + files: [][]string{}, + expectedIndexes: []int{9}, }, { @@ -264,10 +293,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", States: []githubv4.PullRequestState{githubv4.PullRequestStateOpen}, }, - version: resource.Version{}, - pullRequests: testPullRequests[9:11], - files: [][]string{}, - expected: resource.CheckResponse(nil), + pullRequests: standardTestPRs[9:11], + files: [][]string{}, + expectedIndexes: nil, }, { @@ -277,13 +305,10 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", States: []githubv4.PullRequestState{githubv4.PullRequestStateClosed, githubv4.PullRequestStateMerged}, }, - version: resource.NewVersion(testPullRequests[11]), - pullRequests: testPullRequests, - files: [][]string{}, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[9]), - resource.NewVersion(testPullRequests[10]), - }, + version: resource.NewVersion(standardTestPRs[11]), + pullRequests: standardTestPRs, + files: [][]string{}, + expectedIndexes: []int{9, 10}, }, } @@ -297,7 +322,7 @@ func TestCheck(t *testing.T) { } for i := range tc.pullRequests { for j := range filterStates { - if filterStates[j] == tc.pullRequests[i].PullRequestObject.State { + if filterStates[j] == tc.pullRequests[i].State { pullRequests = append(pullRequests, tc.pullRequests[i]) break } @@ -309,11 +334,26 @@ func TestCheck(t *testing.T) { github.ListModifiedFilesReturnsOnCall(i, file, nil) } + github.ListTeamMembersStub = func(teamName string) ([]string, error) { + switch teamName { + case "foo": + return []string{"foo-member"}, nil + case "bar": + return []string{"bar-member"}, nil + } + return []string{}, nil + } + input := resource.CheckRequest{Source: tc.source, Version: tc.version} output, err := resource.Check(input, github) + var expectedOutput resource.CheckResponse + for _, idx := range tc.expectedIndexes { + expectedOutput = append(expectedOutput, resource.NewVersion(tc.pullRequests[idx])) + } + if assert.NoError(t, err) { - assert.Equal(t, tc.expected, output) + assert.Equal(t, expectedOutput, output) } assert.Equal(t, 1, github.ListPullRequestsCallCount()) }) @@ -573,3 +613,31 @@ func TestIsInsidePath(t *testing.T) { }) } } + +func TestCheckTeamMemberCache(t *testing.T) { + t.Run("foo", func(t *testing.T) { + github := new(fakes.FakeGithub) + + github.ListTeamMembersReturns([]string{"member"}, nil) + + prs := []*resource.PullRequest{ + createTestPR(0, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(1, "master", "member", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(2, "master", "member", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(3, "master", "member", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + } + github.ListPullRequestsReturns(prs, nil) + + input := resource.CheckRequest{ + Source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + TrustedTeams: []string{"team"}, + }, + Version: resource.NewVersion(prs[0]), + } + _, err := resource.Check(input, github) + assert.Equal(t, nil, err) + assert.Equal(t, 1, github.ListTeamMembersCallCount()) + }) +} diff --git a/fakes/fake_github.go b/fakes/fake_github.go index 1847478f..558c2e4f 100644 --- a/fakes/fake_github.go +++ b/fakes/fake_github.go @@ -74,6 +74,19 @@ type FakeGithub struct { result1 []*resource.PullRequest result2 error } + ListTeamMembersStub func(string) ([]string, error) + listTeamMembersMutex sync.RWMutex + listTeamMembersArgsForCall []struct { + arg1 string + } + listTeamMembersReturns struct { + result1 []string + result2 error + } + listTeamMembersReturnsOnCall map[int]struct { + result1 []string + result2 error + } PostCommentStub func(string, string) error postCommentMutex sync.RWMutex postCommentArgsForCall []struct { @@ -425,6 +438,69 @@ func (fake *FakeGithub) ListPullRequestsReturnsOnCall(i int, result1 []*resource }{result1, result2} } +func (fake *FakeGithub) ListTeamMembers(arg1 string) ([]string, error) { + fake.listTeamMembersMutex.Lock() + ret, specificReturn := fake.listTeamMembersReturnsOnCall[len(fake.listTeamMembersArgsForCall)] + fake.listTeamMembersArgsForCall = append(fake.listTeamMembersArgsForCall, struct { + arg1 string + }{arg1}) + fake.recordInvocation("ListTeamMembers", []interface{}{arg1}) + fake.listTeamMembersMutex.Unlock() + if fake.ListTeamMembersStub != nil { + return fake.ListTeamMembersStub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.listTeamMembersReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeGithub) ListTeamMembersCallCount() int { + fake.listTeamMembersMutex.RLock() + defer fake.listTeamMembersMutex.RUnlock() + return len(fake.listTeamMembersArgsForCall) +} + +func (fake *FakeGithub) ListTeamMembersCalls(stub func(string) ([]string, error)) { + fake.listTeamMembersMutex.Lock() + defer fake.listTeamMembersMutex.Unlock() + fake.ListTeamMembersStub = stub +} + +func (fake *FakeGithub) ListTeamMembersArgsForCall(i int) string { + fake.listTeamMembersMutex.RLock() + defer fake.listTeamMembersMutex.RUnlock() + argsForCall := fake.listTeamMembersArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeGithub) ListTeamMembersReturns(result1 []string, result2 error) { + fake.listTeamMembersMutex.Lock() + defer fake.listTeamMembersMutex.Unlock() + fake.ListTeamMembersStub = nil + fake.listTeamMembersReturns = struct { + result1 []string + result2 error + }{result1, result2} +} + +func (fake *FakeGithub) ListTeamMembersReturnsOnCall(i int, result1 []string, result2 error) { + fake.listTeamMembersMutex.Lock() + defer fake.listTeamMembersMutex.Unlock() + fake.ListTeamMembersStub = nil + if fake.listTeamMembersReturnsOnCall == nil { + fake.listTeamMembersReturnsOnCall = make(map[int]struct { + result1 []string + result2 error + }) + } + fake.listTeamMembersReturnsOnCall[i] = struct { + result1 []string + result2 error + }{result1, result2} +} + func (fake *FakeGithub) PostComment(arg1 string, arg2 string) error { fake.postCommentMutex.Lock() ret, specificReturn := fake.postCommentReturnsOnCall[len(fake.postCommentArgsForCall)] @@ -564,6 +640,8 @@ func (fake *FakeGithub) Invocations() map[string][][]interface{} { defer fake.listModifiedFilesMutex.RUnlock() fake.listPullRequestsMutex.RLock() defer fake.listPullRequestsMutex.RUnlock() + fake.listTeamMembersMutex.RLock() + defer fake.listTeamMembersMutex.RUnlock() fake.postCommentMutex.RLock() defer fake.postCommentMutex.RUnlock() fake.updateCommitStatusMutex.RLock() diff --git a/github.go b/github.go index 1635e1b8..eabc603f 100644 --- a/github.go +++ b/github.go @@ -18,10 +18,12 @@ import ( ) // Github for testing purposes. +// //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o fakes/fake_github.go . Github type Github interface { ListPullRequests([]githubv4.PullRequestState) ([]*PullRequest, error) ListModifiedFiles(int) ([]string, error) + ListTeamMembers(string) ([]string, error) PostComment(string, string) error GetPullRequest(string, string) (*PullRequest, error) GetChangedFiles(string, string) ([]ChangedFileObject, error) @@ -48,9 +50,10 @@ func NewGithubClient(s *Source) (*GithubClient, error) { // source: https://github.com/google/go-github/pull/598#issuecomment-333039238 var ctx context.Context if s.SkipSSLVerification { - insecureClient := &http.Client{Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, + insecureClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, } ctx = context.WithValue(context.TODO(), oauth2.HTTPClient, insecureClient) } else { @@ -180,6 +183,34 @@ func (m *GithubClient) ListPullRequests(prStates []githubv4.PullRequestState) ([ return response, nil } +func (m *GithubClient) ListTeamMembers(team string) ([]string, error) { + var query struct { + Organization struct { + Team struct { + Members struct { + Nodes []struct { + Login string + } + } + } `graphql:"team(slug:$teamName)"` + } `graphql:"organization(login:$orgName)"` + } + + vars := map[string]interface{}{ + "teamName": githubv4.String(team), + "orgName": githubv4.String(m.Owner), + } + + teamMembers := []string{} + if err := m.V4.Query(context.Background(), &query, vars); err != nil { + return nil, fmt.Errorf("query failed: %w", err) + } + for _, user := range query.Organization.Team.Members.Nodes { + teamMembers = append(teamMembers, user.Login) + } + return teamMembers, nil +} + // ListModifiedFiles in a pull request (not supported by V4 API). func (m *GithubClient) ListModifiedFiles(prNumber int) ([]string, error) { var files []string diff --git a/github_test.go b/github_test.go new file mode 100644 index 00000000..58a419d2 --- /dev/null +++ b/github_test.go @@ -0,0 +1,27 @@ +//go:build e2e +// +build e2e + +package resource_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + resource "github.com/telia-oss/github-pr-resource" +) + +func TestListTeamMembers(t *testing.T) { + source := resource.Source{ + Repository: "cloudfoundry/community", + AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), + } + + client, err := resource.NewGithubClient(&source) + require.NoError(t, err) + + val, err := client.ListTeamMembers("wg-admin") + require.NoError(t, err) + + require.Contains(t, val, "thelinuxfoundation") +} diff --git a/go.sum b/go.sum index 82ec24e7..55243049 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -31,7 +30,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3 h1:z1lXirM9f9WTcdmzSZahKh/t+LCqPiiwK2/DB1kLlI4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3/go.mod h1:1ftk08SazyElaaNvmqAfZWGwJzshjCfBXDLoQtPAMNk= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -39,7 +37,6 @@ github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= @@ -51,12 +48,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -74,17 +69,14 @@ golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -93,7 +85,6 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200301222351-066e0c02454c h1:FD7jysxM+EJqg5UYYy3XYDsAiUickFsn4UiaanJkf8c= golang.org/x/tools v0.0.0-20200301222351-066e0c02454c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200423205358-59e73619c742 h1:9OGWpORUXvk8AsaBJlpzzDx7Srv/rSK6rvjcsJq4rJo= golang.org/x/tools v0.0.0-20200423205358-59e73619c742/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -111,7 +102,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/in_test.go b/in_test.go index 63816d7a..ce0b0b34 100644 --- a/in_test.go +++ b/in_test.go @@ -40,7 +40,7 @@ func TestGet(t *testing.T) { State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{}, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, @@ -58,7 +58,7 @@ func TestGet(t *testing.T) { State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{}, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, @@ -77,7 +77,7 @@ func TestGet(t *testing.T) { parameters: resource.GetParameters{ IntegrationTool: "rebase", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, @@ -96,7 +96,7 @@ func TestGet(t *testing.T) { parameters: resource.GetParameters{ IntegrationTool: "checkout", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, @@ -115,7 +115,7 @@ func TestGet(t *testing.T) { parameters: resource.GetParameters{ GitDepth: 2, }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","state":"OPEN"}`, metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, @@ -134,7 +134,7 @@ func TestGet(t *testing.T) { parameters: resource.GetParameters{ ListChangedFiles: true, }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), files: []resource.ChangedFileObject{ { Path: "README.md", @@ -313,6 +313,7 @@ func TestGetSkipDownload(t *testing.T) { func createTestPR( count int, baseName string, + author string, skipCI bool, isCrossRepo bool, approvedReviews int, @@ -353,6 +354,9 @@ func createTestPR( State: state, ClosedAt: githubv4.DateTime{Time: time.Now()}, MergedAt: githubv4.DateTime{Time: time.Now()}, + Author: resource.Actor{ + Login: author, + }, }, Tip: resource.CommitObject{ ID: fmt.Sprintf("commit%s", n), diff --git a/models.go b/models.go index 7a6ac2bd..e9ed82c8 100644 --- a/models.go +++ b/models.go @@ -27,6 +27,8 @@ type Source struct { RequiredReviewApprovals int `json:"required_review_approvals"` Labels []string `json:"labels"` States []githubv4.PullRequestState `json:"states"` + TrustedTeams []string `json:"trusted_teams"` + TrustedUsers []string `json:"trusted_users"` } // Validate the source configuration. @@ -112,6 +114,11 @@ type PullRequestObject struct { State githubv4.PullRequestState ClosedAt githubv4.DateTime MergedAt githubv4.DateTime + Author Actor +} + +type Actor struct { + Login string } // UpdatedDate returns the last time a PR was updated, either by commit diff --git a/out_test.go b/out_test.go index 4d430c7b..cd69a60d 100644 --- a/out_test.go +++ b/out_test.go @@ -14,7 +14,6 @@ import ( ) func TestPut(t *testing.T) { - tests := []struct { description string source resource.Source @@ -34,7 +33,7 @@ func TestPut(t *testing.T) { CommittedDate: time.Time{}, }, parameters: resource.PutParameters{}, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -51,7 +50,7 @@ func TestPut(t *testing.T) { parameters: resource.PutParameters{ Status: "success", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -69,7 +68,7 @@ func TestPut(t *testing.T) { Status: "failure", Context: "build", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -88,7 +87,7 @@ func TestPut(t *testing.T) { BaseContext: "concourse-ci-custom", Context: "build", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -106,7 +105,7 @@ func TestPut(t *testing.T) { Status: "failure", TargetURL: "https://targeturl.com/concourse", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -124,7 +123,7 @@ func TestPut(t *testing.T) { Status: "failure", Description: "Concourse CI build", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -141,7 +140,7 @@ func TestPut(t *testing.T) { parameters: resource.PutParameters{ Comment: "comment", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -158,7 +157,7 @@ func TestPut(t *testing.T) { parameters: resource.PutParameters{ DeletePreviousComments: true, }, - pullRequest: createTestPR(1, "master", false, false, 0, []string{}, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, []string{}, false, githubv4.PullRequestStateOpen), }, } @@ -219,7 +218,6 @@ func TestPut(t *testing.T) { } func TestVariableSubstitution(t *testing.T) { - var ( variableName = "BUILD_JOB_NAME" variableValue = "my-job" @@ -235,7 +233,6 @@ func TestVariableSubstitution(t *testing.T) { expectedTargetURL string pullRequest *resource.PullRequest }{ - { description: "we can substitute environment variables for Comment", source: resource.Source{ @@ -251,7 +248,7 @@ func TestVariableSubstitution(t *testing.T) { Comment: fmt.Sprintf("$%s", variableName), }, expectedComment: variableValue, - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -270,7 +267,7 @@ func TestVariableSubstitution(t *testing.T) { TargetURL: fmt.Sprintf("%s$%s", variableURL, variableName), }, expectedTargetURL: fmt.Sprintf("%s%s", variableURL, variableValue), - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -288,7 +285,7 @@ func TestVariableSubstitution(t *testing.T) { Comment: "$THIS_IS_NOT_SUBSTITUTED", }, expectedComment: "$THIS_IS_NOT_SUBSTITUTED", - pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + pullRequest: createTestPR(1, "master", "anonymous", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, } @@ -329,7 +326,6 @@ func TestVariableSubstitution(t *testing.T) { assert.Equal(t, tc.expectedComment, comment) } } - }) } } From 7fc67e95b680139e45715625def377cb79031df2 Mon Sep 17 00:00:00 2001 From: Ben Fuller Date: Fri, 21 Jul 2023 16:35:23 +0000 Subject: [PATCH 10/24] update ci, starting config --- .github/workflows/go.yml | 37 +++++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 21 --------------------- 2 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/go.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 00000000..67fda7b2 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,37 @@ +name: Go + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: "go.mod" + - run: go test --race ./... + + vet: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: "go.mod" + - run: go vet ./... + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: "go.mod" + - uses: golangci/golangci-lint-action@v3.6.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 9e56bb94..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,21 +0,0 @@ -on: [push, pull_request] -name: Test -jobs: - test: - strategy: - matrix: - go-version: [1.14.x, 1.15.x] - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v2 - - name: Install Task - run: curl -sL https://taskfile.dev/install.sh | sh - - name: Run CI Task - run: ./bin/task ci - From 3db4560e1f6a37b351c5104424592e9ecfe3ea2a Mon Sep 17 00:00:00 2001 From: Ben Fuller Date: Fri, 21 Jul 2023 16:51:56 +0000 Subject: [PATCH 11/24] Fix linting --- check.go | 2 +- models.go | 2 +- out_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/check.go b/check.go index 64df9e24..73aa980d 100644 --- a/check.go +++ b/check.go @@ -140,7 +140,7 @@ Loop: // ContainsSkipCI returns true if a string contains [ci skip] or [skip ci]. func ContainsSkipCI(s string) bool { - re := regexp.MustCompile("(?i)\\[(ci skip|skip ci)\\]") + re := regexp.MustCompile(`(?i)\[(ci skip|skip ci)\]`) return re.MatchString(s) } diff --git a/models.go b/models.go index 9e4e7b1c..bca99e68 100644 --- a/models.go +++ b/models.go @@ -49,7 +49,7 @@ func (s *Source) Validate() error { case githubv4.PullRequestStateClosed: case githubv4.PullRequestStateMerged: default: - return errors.New(fmt.Sprintf("states value \"%s\" must be one of: OPEN, MERGED, CLOSED", state)) + return fmt.Errorf("states value \"%s\" must be one of: OPEN, MERGED, CLOSED", state) } } return nil diff --git a/out_test.go b/out_test.go index 4d430c7b..b9f370b0 100644 --- a/out_test.go +++ b/out_test.go @@ -314,7 +314,7 @@ func TestVariableSubstitution(t *testing.T) { os.Setenv(variableName, variableValue) putInput := resource.PutRequest{Source: tc.source, Params: tc.parameters} - _, err = resource.Put(putInput, github, dir) + _, _ = resource.Put(putInput, github, dir) if tc.parameters.TargetURL != "" { if assert.Equal(t, 1, github.UpdateCommitStatusCallCount()) { From 95c8f96cc9744ea247f114e1713212b58c9095f5 Mon Sep 17 00:00:00 2001 From: Ben Fuller Date: Fri, 21 Jul 2023 17:04:59 +0000 Subject: [PATCH 12/24] add test docker push --- .github/workflows/docker.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..ef6f1ce6 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,30 @@ +name: ci + +on: + push: + branches: + - 'main' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: loggregatorbot/github-pr-resource:github-ci-test + From 1d94bf6e3f57c83adde9947d29b806135b43ccba Mon Sep 17 00:00:00 2001 From: Georgi Sabev Date: Wed, 2 Aug 2023 14:57:54 +0000 Subject: [PATCH 13/24] Update README on `trusted_teams` and `trusted_users` Add details on `trusted_teams` and `trusted_users` source configuration parameters. Co-authored-by: Danail Branekov --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0911de44..41f5623a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Make sure to check out [#migrating](#migrating) to learn more. | Parameter | Required | Example | Description | |-----------------------------|----------|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `repository` | Yes | `itsdalmo/test-repository` | The repository to target. | -| `access_token` | Yes | | A Github Access Token with repository access (required for setting status on commits). N.B. If you want github-pr-resource to work with a private repository. Set `repo:full` permissions on the access token you create on GitHub. If it is a public repository, `repo:status` is enough. | +| `access_token` | Yes | | A Github Access Token with repository access (required for setting status on commits). N.B. If you want github-pr-resource to work with a private repository. Set `repo:full` permissions on the access token you create on GitHub. If it is a public repository, `repo:status` is enough. When using `trusted_teams`, the `read:org` scope has to be enabled. | | `v3_endpoint` | No | `https://api.github.com` | Endpoint to use for the V3 Github API (Restful). | | `v4_endpoint` | No | `https://api.github.com/graphql` | Endpoint to use for the V4 Github API (Graphql). | | `paths` | No | `["terraform/*/*.tf"]` | Only produce new versions if the PR includes changes to files that match one or more glob patterns or prefixes. | @@ -31,6 +31,8 @@ Make sure to check out [#migrating](#migrating) to learn more. | `disable_forks` | No | `true` | Disable triggering of the resource if the pull request's fork repository is different to the configured repository. | | `ignore_drafts` | No | `false` | Disable triggering of the resource if the pull request is in Draft status. | | `required_review_approvals` | No | `2` | Disable triggering of the resource if the pull request does not have at least `X` approved review(s). | +| `trusted_teams` | No | `["wg-cf-on-k8s-bots"]` | PRs from members of the trusted teams always trigger the resource regardless of the PR approval status. | +| `trusted_users` | No | `["dependabot"]` | PRs from trusted users always trigger the resource regardless of the PR approval status. | | `git_crypt_key` | No | `AEdJVENSWVBUS0VZAAAAA...` | Base64 encoded git-crypt key. Setting this will unlock / decrypt the repository with git-crypt. To get the key simply execute `git-crypt export-key -- - | base64` in an encrypted repository. | | `base_branch` | No | `master` | Name of a branch. The pipeline will only trigger on pull requests against the specified branch. | | `labels` | No | `["bug", "enhancement"]` | The labels on the PR. The pipeline will only trigger on pull requests having at least one of the specified labels. | From 7a6dbb156e16a42b05afac310617f2f293a2aa24 Mon Sep 17 00:00:00 2001 From: Benjamin Fuller Date: Wed, 16 Aug 2023 10:38:47 -0700 Subject: [PATCH 14/24] push docker to latest --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ef6f1ce6..c77978b5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -26,5 +26,5 @@ jobs: uses: docker/build-push-action@v4 with: push: true - tags: loggregatorbot/github-pr-resource:github-ci-test + tags: loggregatorbot/github-pr-resource:latest From 37f49756bd939ad6996f4eb501c1c5b1170d64fd Mon Sep 17 00:00:00 2001 From: Danail Branekov Date: Thu, 17 Aug 2023 13:47:13 +0000 Subject: [PATCH 15/24] Remove unused function Co-authored-by: Georgi Sabev --- check_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/check_test.go b/check_test.go index 1353f1c6..c09d719f 100644 --- a/check_test.go +++ b/check_test.go @@ -31,10 +31,6 @@ var ( } ) -func ptrTo(i int) *int { - return &i -} - func TestCheck(t *testing.T) { tests := []struct { description string From 26cc2d846d5431d8f72987c5656499c939a83f46 Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Mon, 5 Feb 2024 23:20:54 +0100 Subject: [PATCH 16/24] Generate pipeline from 'docker/base' CF community template --- ci/pipeline.yml | 409 +++++++++++++++++++++++++++++ ci/repipe | 151 +++++++++++ ci/scripts/release | 59 +++++ ci/scripts/update-dockerhub-readme | 31 +++ ci/settings.yml | 41 +++ 5 files changed, 691 insertions(+) create mode 100644 ci/pipeline.yml create mode 100755 ci/repipe create mode 100755 ci/scripts/release create mode 100755 ci/scripts/update-dockerhub-readme create mode 100644 ci/settings.yml diff --git a/ci/pipeline.yml b/ci/pipeline.yml new file mode 100644 index 00000000..eddfcebc --- /dev/null +++ b/ci/pipeline.yml @@ -0,0 +1,409 @@ +--- +# +# ci/pipeline.yml +# +# Pipeline structure file for a Docker Image pipeline +# +# DO NOT MAKE CHANGES TO THIS FILE. Instead, modify +# ci/settings.yml and override what needs overridden. +# This uses spruce, so you have some options there. +# +# author: James Hunt +# Dennis Bell +# created: 2016-03-04 + +meta: + name: (( param "Please name your pipeline" )) + release: (( grab meta.name )) + target: (( param "Please identify the name of the target Concourse CI" )) + url: (( param "Please specify the full URL of the target Concourse CI" )) + pipeline: (( grab meta.name )) + + git: + email: (( param "Please provide the git email for automated commits" )) + name: (( param "Please provide the git name for automated commits" )) + + image: + name: starkandwayne/concourse + tag: latest + registry: + username: (( param "Please set your Docker registry username for your pipeline image" )) + password: (( param "Please set your Docker registry password for your pipeline image" )) + + aws: + bucket: (( concat meta.name "-pipeline" )) + region_name: us-east-1 + access_key: (( param "Please set your AWS Access Key ID" )) + secret_key: (( param "Please set your AWS Secret Key ID" )) + + github: + uri: (( concat "git@github.com:" meta.github.owner "/" meta.github.repo )) + owner: (( param "Please specify the name of the user / organization that owns the Github repository" )) + repo: (( param "Please specify the name of the Github repository" )) + branch: master + pr_base_branch: (( grab meta.github.branch )) + private_key: (( param "Please generate an SSH Deployment Key for this repo and specify it here" )) + access_token: (( param "Please generate a Personal Access Token and specify it here" )) + + dockerhub: + username: (( param "Please specify the username for your Dockerhub account" )) + password: (( param "Please specify the password for your Dockerhub account" )) + repository: (( param "Please specify the name of the image (repo/name) that you are building" )) + + slack: + webhook: (( param "Please specify your Slack Incoming Webhook Integration URL" )) + channel: (( param "Please specify the channel (#name) or user (@user) to send messages to" )) + username: concourse + icon: https://cl.ly/2F421Y300u07/concourse-logo-blue-transparent.png + success_moji: ":airplane_departure:" + fail_moji: ":airplane_arriving:" + upset_moji: ":sadpanda:" + pipeline_url: (( concat meta.url "/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}" )) + fail_link: (( concat "<" meta.slack.pipeline_url "/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}| Concourse Failure! " meta.slack.upset_moji ">" )) + fail_text: '(( concat meta.slack.fail_link " " meta.pipeline ": `${BUILD_JOB_NAME}` job failed" ))' + +groups: + - name: (( grab meta.name )) + jobs: + - build + - build-pr + - rc + - promote + - name: versioning + jobs: + - major + - minor + +jobs: + - name: build + public: true + serial: true + plan: + - in_parallel: + - { get: git, trigger: true } + - { get: docker-image-build-task } + + - task: build-docker-image + image: docker-image-build-task + privileged: true + config: + platform: linux + inputs: [ { name: git, path: "." } ] + outputs: [ name: image ] + caches: [ path: cache ] + run: { path: build } + output_mapping: + image: built-image + + - put: edge + inputs: [ built-image ] + params: { image: built-image/image.tar } + + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: (( grab meta.slack.fail_text )) + + - name: build-pr + public: true + serial: true + plan: + - in_parallel: + - { get: git-pull-requests, trigger: true, version: every } + - { get: docker-image-build-task } + - { get: image } + + - put: git-pull-requests + params: + path: git-pull-requests + status: PENDING + + - task: build-docker-image + image: docker-image-build-task + privileged: true + config: + platform: linux + inputs: [ { name: git-pull-requests, path: "." } ] + outputs: [ name: image ] + caches: [ path: cache ] + run: { path: build } + output_mapping: + image: built-image + on_success: + put: git-pull-requests + params: + path: git-pull-requests + status: SUCCESS + on_failure: + put: git-pull-requests + params: + path: git-pull-requests + status: FAILURE + + - task: pr-success-message + image: image + config: + platform: linux + inputs: [ name: git-pull-requests ] + outputs: [ name: message ] + run: + path: bash + args: + - -c + - | + set -ueo pipefail + pr_author=$( < git-pull-requests/.git/resource/author) + pr_author_email=$(< git-pull-requests/.git/resource/author_email) + pr_title=$( < git-pull-requests/.git/resource/title) + pr_url=$( < git-pull-requests/.git/resource/url) + echo "Pull request “_<${pr_url}|${pr_title}>_”" \ + "by *${pr_author:-${pr_author_email}}*" \ + "has passed Docker build :tada:" \ + "Merge when ready ${SUCCESS_MOJI}:"$'\n'"${pr_url}" \ + > message/body + params: + SUCCESS_MOJI: (( grab meta.slack.success_moji )) + + on_success: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text_file: message/body + + - name: rc + public: true + serial: true + plan: + - in_parallel: + - { get: edge, trigger: true, passed: [ build ] } + - { get: git, trigger: true, passed: [ build ] } + - { get: version, params: { pre: rc } } + - { get: image } + - task: release-notes + image: image + config: + platform: linux + inputs: [ name: git ] + run: + path: sh + args: + - -ce + - | + cd git + if [ -f ci/release_notes.md ]; then + echo "###### RELEASE NOTES ###############" + echo + cat ci/release_notes.md + echo + echo "########################################" + echo + else + echo "NO RELEASE NOTES HAVE BEEN WRITTEN" + echo "You *might* want to do that before" + echo "hitting (+) on that shipit job..." + echo + fi + - put: version + params: { file: version/number } + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: (( grab meta.slack.fail_text )) + + - name: minor + public: true + serial: true + plan: + - { get: version, trigger: false, params: { bump: minor } } + - { put: version, params: { file: version/number } } + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: (( grab meta.slack.fail_text )) + + - name: major + public: true + serial: true + plan: + - { get: version, trigger: false, params: { bump: major } } + - { put: version, params: { file: version/number } } + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: (( grab meta.slack.fail_text )) + + - name: promote + public: true + serial: true + plan: + - in_parallel: + - { get: version, passed: [ rc ], params: { bump: final } } + - { get: edge, passed: [ rc ], params: { format: oci } } + - { get: git, passed: [ rc ] } + - { get: image } + + - task: release + image: image + config: + platform: linux + inputs: + - name: git + - name: version + outputs: + - name: gh + - name: pushme + - name: dockerhub + run: + path: ./git/ci/scripts/release + args: [] + params: + REPO_ROOT: git + RELEASE_ROOT: gh + RELEASE_NAME: (( grab meta.release )) + REPO_OUT: pushme/git + VERSION_FROM: version/number + GIT_EMAIL: (( grab meta.git.email )) + GIT_NAME: (( grab meta.git.name )) + + - load_var: version + file: version/number + - put: latest + inputs: [ edge ] + params: + image: edge/image.tar + version: ((.:version)) + bump_aliases: true + - task: update-dockerhub-readme + image: image + config: + platform: linux + inputs: + - name: git + - name: dockerhub + run: + path: ./git/ci/scripts/update-dockerhub-readme + params: + DOCKERHUB_USERNAME: (( grab meta.dockerhub.username )) + DOCKERHUB_PASSWORD: (( grab meta.dockerhub.password )) + DOCKERHUB_REPOSITORY: (( grab meta.dockerhub.repository )) + + - put: version + params: + bump: patch + - put: git + params: + rebase: true + repository: pushme/git + - put: github + params: + name: gh/name + tag: gh/tag + body: gh/notes.md + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: (( grab meta.slack.fail_text )) + +resource_types: + - name: slack-notification + type: registry-image + source: + repository: cfcommunity/slack-notification-resource + + - name: pull-request + type: registry-image + source: + repository: teliaoss/github-pr-resource + +resources: + - name: image + type: registry-image + icon: docker + source: + repository: (( grab meta.image.name )) + tag: (( grab meta.image.tag )) + username: (( grab meta.image.registry.username )) + password: (( grab meta.image.registry.password )) + + - name: git + type: git + icon: github + source: + uri: (( grab meta.github.uri )) + branch: (( grab meta.github.branch )) + private_key: (( grab meta.github.private_key )) + + - name: git-pull-requests + type: pull-request + icon: github + source: + access_token: (( grab meta.github.access_token )) + repository: (( concat meta.github.owner "/" meta.github.repo )) + base_branch: (( grab meta.github.pr_base_branch )) + + - name: docker-image-build-task + type: registry-image + icon: docker + source: + repository: concourse/oci-build-task + + - name: edge + type: registry-image + icon: docker + source: + username: (( grab meta.dockerhub.username )) + password: (( grab meta.dockerhub.password )) + repository: (( grab meta.dockerhub.repository )) + tag: edge + + - name: latest + type: registry-image + icon: docker + source: + username: (( grab meta.dockerhub.username )) + password: (( grab meta.dockerhub.password )) + repository: (( grab meta.dockerhub.repository )) + tag: latest + + - name: version + type: semver + icon: aws + source: + driver: s3 + bucket: (( grab meta.aws.bucket )) + region_name: (( grab meta.aws.region_name )) + key: version + access_key_id: (( grab meta.aws.access_key )) + secret_access_key: (( grab meta.aws.secret_key )) + initial_version: (( grab meta.initial_version || "0.0.1" )) + + - name: notify + type: slack-notification + icon: slack + source: + url: (( grab meta.slack.webhook )) + + - name: github + type: github-release + icon: github + source: + user: (( grab meta.github.owner )) + repository: (( grab meta.github.repo )) + access_token: (( grab meta.github.access_token )) diff --git a/ci/repipe b/ci/repipe new file mode 100755 index 00000000..1be1a0b2 --- /dev/null +++ b/ci/repipe @@ -0,0 +1,151 @@ +#!/bin/bash +# +# ci/repipe +# +# Script for merging together pipeline configuration files +# (via Spruce!) and configuring Concourse. +# +# author: James Hunt +# Dennis Bell +# created: 2016-03-04 + +need_command() { + local cmd=${1:?need_command() - no command name given} + + if [[ ! -x "$(command -v $cmd)" ]]; then + echo >&2 "${cmd} is not installed." + if [[ "${cmd}" == "spruce" ]]; then + echo >&2 "Please download it from https://github.com/geofffranks/spruce/releases" + fi + exit 2 + fi +} + +NO_FLY= +SAVE_MANIFEST= +VALIDATE_PIPELINE= +NON_INTERACTIVE= + +cleanup() { + rm -f save-manifest.yml + if [[ -n ${SAVE_MANIFEST} && -e .deploy.yml ]]; then + mv .deploy.yml save-manifest.yml + fi + rm -f .deploy.yml +} + +usage() { + echo Command line arguments: + echo "no-fly Do not execute any fly commands" + echo "save-manifest Save manifest to file save-manifest" + echo "validate Validate pipeline instead of set pipeline" + echo "validate-strict Validate pipeline with strict mode" + echo "non-interactive Run set-pipeline in non-interactive mode" + echo "open Open pipeline dashboard to browser (if possible)" +} + +open_pipeline() { + url=$(show_pipeline_url) + cleanup + if [[ -x /usr/bin/open ]]; then + exec /usr/bin/open "$url" + else + echo "Sorry, but I was not able to automagically open" + echo "your Concourse Pipeline in the browser." + echo + echo "Here's a link you can click on, though:" + echo + echo " $url" + echo + exit 0; + fi +} + +show_pipeline_url() { + spruce merge --skip-eval pipeline.yml ${settings_file} > .deploy.yml + concourse_url=$(spruce json .deploy.yml | jq -r ".meta.url") + team=$(spruce json .deploy.yml | jq -r ".meta.team // \"main\"") + pipeline=$(spruce merge --skip-eval \ + --cherry-pick meta.pipeline \ + --cherry-pick meta.name \ + .deploy.yml | spruce merge - | spruce json | jq -r ".meta.pipeline") + + echo "$concourse_url/teams/$team/pipelines/$pipeline" + exit 0 +} + +for arg do + case "${arg}" in + no-fly|no_fly) NO_FLY="yes" ;; + save-manifest|save_manifest) SAVE_MANIFEST="yes" ;; + validate) VALIDATE_PIPELINE="normal" ;; + validate-strict|validate_strict) VALIDATE_PIPELINE="strict" ;; + non-interactive|non_interactive) NON_INTERACTIVE="--non-interactive" ;; + url) SHOW_PIPELINE_URL="yes" ;; + open) OPEN_PIPELINE="yes" ;; + help|-h|--help) usage; exit 0 ;; + *) echo Invalid argument + usage + exit 1 + esac +done + +cd $(dirname $BASH_SOURCE[0]) +echo >&2 "Working in $(pwd)" +need_command spruce + +# Allow for target-specific settings +settings_file="$(ls -1 settings.yml ${CONCOURSE_TARGET:+"settings-${CONCOURSE_TARGET}.yml"} 2>/dev/null | head -n1)" +if [[ -z "$settings_file" ]] +then + echo >&2 "Missing local settings in ci/settings.yml${CONCOURSE_TARGET:+" or ci/settings-${CONCOURSE_TARGET}.yml"}!" + exit 1 +fi + +echo >&2 "Using settings found in ${settings_file}" + +set -e +trap "cleanup" QUIT TERM EXIT INT + +[[ -n ${SHOW_PIPELINE_URL} ]] && { show_pipeline_url; exit 0; } +[[ -n ${OPEN_PIPELINE} ]] && { open_pipeline; exit 0; } + +spruce merge pipeline.yml ${settings_file} > .deploy.yml +PIPELINE=$(spruce json .deploy.yml | jq -r '.meta.pipeline // ""') +if [[ -z ${PIPELINE} ]]; then + echo >&2 "Missing pipeline name in ci/settings.yml!" + exit 1 +fi + +TARGET_FROM_SETTINGS=$(spruce json .deploy.yml | jq -r '.meta.target // ""') +if [[ -z ${CONCOURSE_TARGET} ]]; then + TARGET=${TARGET_FROM_SETTINGS} +elif [[ "$CONCOURSE_TARGET" != "$TARGET_FROM_SETTINGS" ]] +then + echo >&2 "Target in {$settings_file} differs from target in \$CONCOURSE_TARGET" + echo >&2 " \$CONCOURSE_TARGET: $CONCOURSE_TARGET" + echo >&2 " Target in file: $TARGET_FROM_SETTINGS" + exit 1 +else + TARGET=${CONCOURSE_TARGET} +fi + +if [[ -z ${TARGET} ]]; then + echo >&2 "Missing Concourse Target in ci/settings.yml!" + exit 1 +fi + +fly_cmd="${FLY_CMD:-fly}" + +[[ -n ${NO_FLY} ]] && { echo no fly execution requested ; exit 0; } + +case "${VALIDATE_PIPELINE}" in + normal) fly_opts="validate-pipeline" ;; + strict) fly_opts="validate-pipeline --strict" ;; + *) fly_opts="set-pipeline ${NON_INTERACTIVE} --pipeline ${PIPELINE}" ;; +esac + +set +x +$fly_cmd --target ${TARGET} ${fly_opts} --config .deploy.yml +[[ -n ${VALIDATE_PIPELINE} ]] && exit 0 +$fly_cmd --target ${TARGET} unpause-pipeline --pipeline ${PIPELINE} diff --git a/ci/scripts/release b/ci/scripts/release new file mode 100755 index 00000000..2865e03a --- /dev/null +++ b/ci/scripts/release @@ -0,0 +1,59 @@ +#!/bin/bash +# +# ci/scripts/release +# +# Script for generating Github release / tag assets +# and managing release notes for a Docker Image pipeline +# +# author: James Hunt +# Dennis Bell +# created: 2016-03-04 + +set -eu + +: ${GIT_EMAIL:?required} +: ${GIT_NAME:?required} + +if [[ ! -f ${REPO_ROOT}/ci/release_notes.md ]]; then + echo >&2 "ci/release_notes.md not found. Did you forget to write them?" + exit 1 +fi + +if [[ -z ${VERSION_FROM} ]]; then + echo >&2 "VERSION_FROM environment variable not set, or empty. Did you misconfigure Concourse?" + exit 2 +fi +if [[ ! -f ${VERSION_FROM} ]]; then + echo >&2 "Version file (${VERSION_FROM}) not found. Did you misconfigure Concourse?" + exit 2 +fi +VERSION=$(cat ${VERSION_FROM}) +if [[ -z ${VERSION} ]]; then + echo >&2 "Version file (${VERSION_FROM}) was empty. Did you misconfigure Concourse?" + exit 2 +fi + +echo "v${VERSION}" > ${RELEASE_ROOT}/tag +echo "${RELEASE_NAME} v${VERSION}" > ${RELEASE_ROOT}/name +mv ${REPO_ROOT}/ci/release_notes.md ${RELEASE_ROOT}/notes.md + +# GIT! +if [[ -z $(git config --global user.email) ]]; then + git config --global user.email "${GIT_EMAIL}" +fi +if [[ -z $(git config --global user.name) ]]; then + git config --global user.name "${GIT_NAME}" +fi + +(cd ${REPO_ROOT} + git merge --no-edit master + git add -A + git status + git commit -m "release v${VERSION} [skip ci]") + +# so that future steps in the pipeline can push our changes +cp -a ${REPO_ROOT} ${REPO_OUT} + +sed -e '/START_OF_DOCKERHUB_STRIP/,/END_OF_DOCKERHUB_STRIP/d' \ + git/README.md \ + > dockerhub/README.md diff --git a/ci/scripts/update-dockerhub-readme b/ci/scripts/update-dockerhub-readme new file mode 100755 index 00000000..be4530c8 --- /dev/null +++ b/ci/scripts/update-dockerhub-readme @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: ${DOCKERHUB_USERNAME:?"required"} +: ${DOCKERHUB_PASSWORD:?"required"} +: ${DOCKERHUB_REPOSITORY:?"required"} + + +payload=$(jq --null-input \ + --arg "username" "${DOCKERHUB_USERNAME}" \ + --arg "password" "${DOCKERHUB_PASSWORD}" \ + '{ "username": $username, "password": $password }') + +curl --silent --fail --show-error --location \ + --request "POST" \ + --url "https://hub.docker.com/v2/users/login" \ + --header "Content-Type: application/json" \ + --data-raw "${payload}" \ + > ./token.json + +payload=$(jq --null-input \ + --arg "full_description" "$(cat dockerhub/README.md)" \ + '{ "full_description": $full_description }') + +curl --silent --fail --show-error --location --include \ + --request "PATCH" \ + --url "https://hub.docker.com/v2/repositories/${DOCKERHUB_REPOSITORY}" \ + --header "Content-Type: application/json" \ + --header "Authorization: JWT $(jq --raw-output '.token' ./token.json)" \ + --data-raw "${payload}" diff --git a/ci/settings.yml b/ci/settings.yml new file mode 100644 index 00000000..f7176ac8 --- /dev/null +++ b/ci/settings.yml @@ -0,0 +1,41 @@ +--- +meta: + name: github-pr-resource + target: gk + url: https://ci.gstack.io + + initial_version: 1.0.0 + + git: + email: ((git-commit-email)) + name: ((git-commit-name)) + + image: + registry: + username: ((docker-registry-username)) + password: ((docker-registry-password)) + + dockerhub: + username: ((dockerhub-username)) + email: ((dockerhub-email)) + password: ((dockerhub-password)) + repository: (( concat meta.github.owner "/" meta.name )) + + aws: + bucket: (( grab meta.pipeline )) + region_name: us-east-1 + access_key: ((aws-access-key)) + secret_key: ((aws-secret-key)) + + github: + owner: + repo: github-pr-resource + branch: main + private_key: ((github-private-key)) + access_token: ((github-access-token)) + + slack: + webhook: ((slack-webhook)) + username: ((slack-username)) + icon: ((slack-icon-url)) + channel: "#github-pr-resource" From 08a1a404a001e3140b84ff01d95371c63f583ffe Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Mon, 5 Feb 2024 23:21:06 +0100 Subject: [PATCH 17/24] Setup image-builder pipeline with tailored bits specific to Concourse resources --- .../workflows/trigger-git-resource-check.yml | 14 + .../workflows/trigger-pr-resource-check.yml | 14 + Dockerfile | 4 +- ci/settings.yml | 302 +++++++++++++++++- 4 files changed, 321 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/trigger-git-resource-check.yml create mode 100644 .github/workflows/trigger-pr-resource-check.yml diff --git a/.github/workflows/trigger-git-resource-check.yml b/.github/workflows/trigger-git-resource-check.yml new file mode 100644 index 00000000..1aed3a3e --- /dev/null +++ b/.github/workflows/trigger-git-resource-check.yml @@ -0,0 +1,14 @@ +name: Trigger Concourse git resource +on: [ push, workflow_dispatch ] +jobs: + trigger-resource-check: + runs-on: ubuntu-latest + steps: + - name: Trigger resource check + uses: gstackio/trigger-concourse-resource-check-action@v1 + with: + concourse-url: https://ci.gstack.io + concourse-team: gk-plat-devs + concourse-pipeline: github-pr-resource + concourse-resource: git + concourse-webhook-token: ${{ secrets.GK_CONCOURSE_WEBHOOK_TOKEN }} diff --git a/.github/workflows/trigger-pr-resource-check.yml b/.github/workflows/trigger-pr-resource-check.yml new file mode 100644 index 00000000..331e5fd8 --- /dev/null +++ b/.github/workflows/trigger-pr-resource-check.yml @@ -0,0 +1,14 @@ +name: Trigger Concourse pull-request resource +on: [ push, pull_request, workflow_dispatch ] +jobs: + trigger-resource-check: + runs-on: ubuntu-latest + steps: + - name: Trigger resource check + uses: gstackio/trigger-concourse-resource-check-action@v1 + with: + concourse-url: https://ci.gstack.io + concourse-team: gk-plat-devs + concourse-pipeline: github-pr-resource + concourse-resource: git-pull-requests + concourse-webhook-token: ${{ secrets.GK_CONCOURSE_WEBHOOK_TOKEN }} diff --git a/Dockerfile b/Dockerfile index fc00995a..b2332b4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM golang:1.14 as builder +FROM golang:alpine AS builder ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource RUN curl -sL https://taskfile.dev/install.sh | sh RUN ./bin/task build -FROM alpine:3.11 as resource +FROM alpine AS resource COPY --from=builder /go/src/github.com/telia-oss/github-pr-resource/build /opt/resource RUN apk add --update --no-cache \ git \ diff --git a/ci/settings.yml b/ci/settings.yml index f7176ac8..568270f9 100644 --- a/ci/settings.yml +++ b/ci/settings.yml @@ -1,34 +1,44 @@ --- meta: name: github-pr-resource - target: gk + release: GitHub Pull-Request Concourse resource + target: gk-plat-devs url: https://ci.gstack.io - initial_version: 1.0.0 + initial_version: "0.24.0" git: email: ((git-commit-email)) name: ((git-commit-name)) image: + name: (( concat meta.private-registry.host "/gstack/gk-ops" )) registry: - username: ((docker-registry-username)) - password: ((docker-registry-password)) + username: (( grab meta.private-registry.username )) + password: (( grab meta.private-registry.password )) dockerhub: - username: ((dockerhub-username)) - email: ((dockerhub-email)) - password: ((dockerhub-password)) - repository: (( concat meta.github.owner "/" meta.name )) + username: ((dockerhub-username)) + password: ((dockerhub-password)) + org: cfcommunity + repository: (( concat meta.dockerhub.org "/" meta.name )) + short_desc: + Concourse CI resource for working with Pull-Requests on GitHub + + private-registry: + host: harbor.ci.gstack.io + username: ((private-registry-username)) + password: ((private-registry-password)) + repository: (( concat meta.private-registry.host "/" meta.dockerhub.org "/" meta.name )) aws: bucket: (( grab meta.pipeline )) - region_name: us-east-1 + region_name: eu-west-3 access_key: ((aws-access-key)) secret_key: ((aws-secret-key)) github: - owner: + owner: cloudfoundry-community repo: github-pr-resource branch: main private_key: ((github-private-key)) @@ -38,4 +48,274 @@ meta: webhook: ((slack-webhook)) username: ((slack-username)) icon: ((slack-icon-url)) - channel: "#github-pr-resource" + channel: "#oss-pipelines-notifications" + +groups: + - name: bump + jobs: + - bump-deps + +jobs: + - name: build + plan: + - (( inline )) + - in_parallel: + - (( append )) + - { get: golang-alpine, trigger: true, params: { format: oci } } + - { get: alpine-latest, trigger: true, params: { format: oci } } + + - # task: build-docker-image + config: + inputs: + - (( append )) + - name: golang-alpine + - name: alpine-latest + params: + IMAGE_ARG_golang: golang-alpine/image.tar + IMAGE_ARG_alpine: alpine-latest/image.tar + + - name: build-pr + plan: + - (( inline )) + - in_parallel: + - (( append )) + - { get: golang-alpine, trigger: true, params: { format: oci } } + - { get: alpine-latest, trigger: true, params: { format: oci } } + + - {} # put: git-pull-requests + + - # task: build-docker-image + config: + inputs: + - (( replace )) + - name: git-pull-requests + - name: golang-alpine + - name: alpine-latest + params: + CONTEXT: git-pull-requests + IMAGE_ARG_golang: golang-alpine/image.tar + IMAGE_ARG_alpine: alpine-latest/image.tar + + - name: promote + plan: + - (( insert after 1 )) # insert after “task: release” + - task: append-usage + image: image + config: + platform: linux + inputs: [ name: gh, name: version ] + outputs: [ name: gh ] + run: + path: bash + args: + - -c + - | + set -ueo pipefail + cat >> gh/notes.md < ../golang-info/previous-version + ( + set -x + go_version=$( + go version \ + | awk '{sub(/go/,"",$3); sub(/\.[[:digit:]]+$/,"",$3); print $3}' + ) + sed -i -Ee "s/^go [[:digit:].]+\$/go ${go_version}/" go.mod + go get -t -u ./... + go mod tidy + + echo "${go_version}" > ../golang-info/version + ) + + - task: build-docker-image + image: docker-image-build-task + privileged: true + config: + platform: linux + inputs: + - { name: git, path: "." } + - { name: golang-alpine } + - { name: alpine-latest } + outputs: [ name: built-image ] + caches: [ path: cache ] + run: { path: build } + params: + IMAGE_ARG_golang: golang-alpine/image.tar + IMAGE_ARG_alpine: alpine-latest/image.tar + + - task: generate-messages + image: image + config: + platform: linux + inputs: [ name: golang-info ] + outputs: [ name: messages ] + run: + path: bash + args: + - -c + - | + set -ueo pipefail + go_prev_ver=$(< golang-info/previous-version) + go_curr_ver=$(< golang-info/version) + + version_details="${go_curr_ver}" + if [[ ${go_curr_ver} != ${go_prev_ver} ]]; then + version_details="from ${go_prev_ver} to ${go_curr_ver} and its" + fi + echo "Update Golang ${version_details} dependencies" \ + > messages/commit-message + + echo "${PIPELINE_NAME}: successfully bumped Golang ${version_details} dependencies," \ + "with passing tests! :tada:" \ + "<${PIPELINE_URL}|Ship it when ready!> ${SUCCESS_MOJI}" \ + > messages/notif-body + params: + SUCCESS_MOJI: (( grab meta.slack.success_moji )) + PIPELINE_URL: (( grab meta.slack.pipeline_url )) + PIPELINE_NAME: (( grab meta.pipeline )) + + - task: git-commit + image: image + file: gk-automation/tasks/git/commit.yml + input_mapping: + repo: repo-bumped + commit-info: messages + params: + GIT_COMMIT_NAME: (( grab meta.git.name )) + GIT_COMMIT_EMAIL: (( grab meta.git.email )) + GIT_DIFF_OPTS: --color-words + + - put: git + params: + repository: repo-committed + rebase: true + + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: (( grab meta.slack.fail_text )) + on_success: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text_file: messages/notif-body + +resources: + + - name: git + check_every: 24h + webhook_token: ((gk-concourse-webhook-token)) + + - name: git-pull-requests + check_every: 24h + webhook_token: ((gk-concourse-webhook-token)) + + - name: edge + source: + username: (( grab meta.private-registry.username )) + password: (( grab meta.private-registry.password )) + repository: (( grab meta.private-registry.repository )) + + - name: latest + source: + username: (( grab meta.private-registry.username )) + password: (( grab meta.private-registry.password )) + repository: (( grab meta.private-registry.repository )) + + - name: version + icon: github + source: + bucket: (( prune )) + region_name: (( prune )) + key: (( prune )) + access_key_id: (( prune )) + secret_access_key: (( prune )) + driver: git + uri: git@github.com:gstackio/gk-pipelines-compass.git + branch: master + file: (( concat "versions/" meta.pipeline )) + private_key: ((github-private-key)) + git_user: "((git-commit-name)) <((git-commit-email))>" + + - name: golang-alpine + type: registry-image + icon: docker + check_every: 24h + source: + repository: golang + tag: alpine + + - name: alpine-latest + type: registry-image + icon: docker + check_every: 24h + source: + repository: alpine + semver_constraint: "< 1970" # Avoid YYYYMMDD tags like '20231219' + + - name: gk-automation + type: git + icon: github + check_every: 24h + source: + uri: git@github.com:gstackio/gk-automation.git + branch: master + private_key: (( grab meta.github.private_key )) + + - name: weekly + type: time + icon: alarm + source: + location: Europe/Paris + start: "3:00" + stop: "4:30" + days: [ Saturday ] From 24f7fc85b53a689e9a4efaf10985d7de968c4b65 Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Mon, 5 Feb 2024 23:27:29 +0100 Subject: [PATCH 18/24] Stick to the standard 'golang' image (not alpine) --- Dockerfile | 2 +- ci/settings.yml | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index b2332b4a..117c98d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:alpine AS builder +FROM golang AS builder ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource RUN curl -sL https://taskfile.dev/install.sh | sh diff --git a/ci/settings.yml b/ci/settings.yml index 568270f9..6151a6c8 100644 --- a/ci/settings.yml +++ b/ci/settings.yml @@ -61,17 +61,17 @@ jobs: - (( inline )) - in_parallel: - (( append )) - - { get: golang-alpine, trigger: true, params: { format: oci } } + - { get: golang-latest, trigger: true, params: { format: oci } } - { get: alpine-latest, trigger: true, params: { format: oci } } - # task: build-docker-image config: inputs: - (( append )) - - name: golang-alpine + - name: golang-latest - name: alpine-latest params: - IMAGE_ARG_golang: golang-alpine/image.tar + IMAGE_ARG_golang: golang-latest/image.tar IMAGE_ARG_alpine: alpine-latest/image.tar - name: build-pr @@ -79,7 +79,7 @@ jobs: - (( inline )) - in_parallel: - (( append )) - - { get: golang-alpine, trigger: true, params: { format: oci } } + - { get: golang-latest, trigger: true, params: { format: oci } } - { get: alpine-latest, trigger: true, params: { format: oci } } - {} # put: git-pull-requests @@ -89,11 +89,11 @@ jobs: inputs: - (( replace )) - name: git-pull-requests - - name: golang-alpine + - name: golang-latest - name: alpine-latest params: CONTEXT: git-pull-requests - IMAGE_ARG_golang: golang-alpine/image.tar + IMAGE_ARG_golang: golang-latest/image.tar IMAGE_ARG_alpine: alpine-latest/image.tar - name: promote @@ -135,13 +135,13 @@ jobs: - { get: weekly, trigger: true } - { get: git } - { get: gk-automation } - - { get: golang-alpine, params: { format: oci } } + - { get: golang-latest, params: { format: oci } } - { get: alpine-latest, params: { format: oci } } - { get: image } - { get: docker-image-build-task } - task: bump-golang-deps - image: golang-alpine + image: golang-latest config: platform: linux inputs: [ { name: git, path: repo } ] @@ -178,13 +178,13 @@ jobs: platform: linux inputs: - { name: git, path: "." } - - { name: golang-alpine } + - { name: golang-latest } - { name: alpine-latest } outputs: [ name: built-image ] caches: [ path: cache ] run: { path: build } params: - IMAGE_ARG_golang: golang-alpine/image.tar + IMAGE_ARG_golang: golang-latest/image.tar IMAGE_ARG_alpine: alpine-latest/image.tar - task: generate-messages @@ -286,13 +286,13 @@ resources: private_key: ((github-private-key)) git_user: "((git-commit-name)) <((git-commit-email))>" - - name: golang-alpine + - name: golang-latest type: registry-image icon: docker check_every: 24h source: - repository: golang - tag: alpine + repository: golang + semver_constraint: "< 1970" # Avoid YYYYMMDD tags like '20231219' - name: alpine-latest type: registry-image From fa6bbb1c1a8536c5ac5ac3a171d2be73f436822b Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Mon, 5 Feb 2024 23:34:52 +0100 Subject: [PATCH 19/24] Stick versions used in builds to those captured by the 'bump-deps' job --- ci/settings.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ci/settings.yml b/ci/settings.yml index 6151a6c8..ac6e1b0b 100644 --- a/ci/settings.yml +++ b/ci/settings.yml @@ -61,8 +61,8 @@ jobs: - (( inline )) - in_parallel: - (( append )) - - { get: golang-latest, trigger: true, params: { format: oci } } - - { get: alpine-latest, trigger: true, params: { format: oci } } + - { get: golang-latest, passed: [ bump-deps ], trigger: true, params: { format: oci } } + - { get: alpine-latest, passed: [ bump-deps ], trigger: true, params: { format: oci } } - # task: build-docker-image config: @@ -79,8 +79,8 @@ jobs: - (( inline )) - in_parallel: - (( append )) - - { get: golang-latest, trigger: true, params: { format: oci } } - - { get: alpine-latest, trigger: true, params: { format: oci } } + - { get: golang-latest, passed: [ bump-deps ], trigger: true, params: { format: oci } } + - { get: alpine-latest, passed: [ bump-deps ], trigger: true, params: { format: oci } } - {} # put: git-pull-requests From 28d11f395a0f7378d9bd9ceedd40ab7238e0cf4b Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Mon, 5 Feb 2024 23:58:40 +0100 Subject: [PATCH 20/24] Pin the task runner to v3.33.1, as v3.34.0 has dropped support for schema v2 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 117c98d7..3d24c948 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang AS builder ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource -RUN curl -sL https://taskfile.dev/install.sh | sh +RUN curl -sL https://taskfile.dev/install.sh | TAG=v3.33.1 sh RUN ./bin/task build FROM alpine AS resource From d81e4f85b8b3a83a1f7a818419671ebac7002b19 Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Tue, 6 Feb 2024 00:03:21 +0100 Subject: [PATCH 21/24] Pinning uses $1 instead of an env var --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3d24c948..c8adab6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang AS builder ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource -RUN curl -sL https://taskfile.dev/install.sh | TAG=v3.33.1 sh +RUN curl -sL https://taskfile.dev/install.sh | sh v3.33.1 RUN ./bin/task build FROM alpine AS resource From 38b00e0e9c29c15f11cc2585535d7589873810e0 Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Tue, 6 Feb 2024 00:19:30 +0100 Subject: [PATCH 22/24] Bourne shell resquires a script filename --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c8adab6f..be4706b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang AS builder ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource -RUN curl -sL https://taskfile.dev/install.sh | sh v3.33.1 +RUN curl -sL https://taskfile.dev/install.sh | sh /dev/stdin v3.33.1 RUN ./bin/task build FROM alpine AS resource From ab5ee9b5a97e28167d0e5bbd969cf363406b7b00 Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Tue, 6 Feb 2024 00:30:12 +0100 Subject: [PATCH 23/24] Git-crypt is now a standard apk package --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index be4706b7..3d99a234 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,10 +10,9 @@ RUN apk add --update --no-cache \ git \ git-lfs \ openssh \ + git-crypt \ && chmod +x /opt/resource/* COPY scripts/askpass.sh /usr/local/bin/askpass.sh -ADD scripts/install_git_crypt.sh install_git_crypt.sh -RUN ./install_git_crypt.sh && rm ./install_git_crypt.sh FROM resource LABEL MAINTAINER=telia-oss From a01bb286bff3c9cc09cc30d95e4d8e6d24a16e3f Mon Sep 17 00:00:00 2001 From: Benjamin Gandon Date: Tue, 6 Feb 2024 00:41:16 +0100 Subject: [PATCH 24/24] Document the provided metadata, and related migration to perform --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41f5623a..ec69ef5c 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ generate notifications over the webhook. So if you have a repository with little | `skip_download` | No | `true` | Use with `get_params` in a `put` step to do nothing on the implicit get. | | `integration_tool` | No | `rebase` | The integration tool to use, `merge`, `rebase` or `checkout`. Defaults to `merge`. | | `git_depth` | No | `1` | Shallow clone the repository using the `--depth` Git option | -| `submodules` | No | `true` | Recursively clone git submodules. Defaults to false. | +| `submodules` | No | `true` | Recursively clone git submodules. Defaults to false. | | `list_changed_files` | No | `true` | Generate a list of changed files and save alongside metadata | -| `fetch_tags` | No | `true` | Fetch tags from remote repository | +| `fetch_tags` | No | `true` | Fetch tags from remote repository | Clones the base (e.g. `master` branch) at the latest commit, and merges the pull request at the specified commit into master. This ensures that we are both testing and setting status on the exact commit that was requested in @@ -90,6 +90,19 @@ The information in `metadata.json` is also available as individual files in the is available as `.git/resource/base_sha`. For a complete list of available (individual) metadata files, please check the code [here](https://github.com/telia-oss/github-pr-resource/blob/master/in.go#L66). +- `author`: the user login of the pull request author +- `author_email`: the e-mail address of the pull request author +- `base_name`: the base branch of the pull request +- `base_sha`: the commit of the base branch of the pull request +- `head_name`: the branch associated with the pull request +- `head_sha`: the latest commit hash of the branch associated with the pull request +- `message`: the message of the last commit of the pull request, as designated by `head_sha` +- `pr`: the pull request ID number +- `state`: the state of the pull request, e.g. `OPEN` +- `title`: the title of the pull request +- `url`: the URL for the pull request + + When specifying `skip_download` the pull request volume mounted to subsequent tasks will be empty, which is a problem when you set e.g. the pending status before running the actual tests. The workaround for this is to use an alias for the `put` (see https://github.com/telia-oss/github-pr-resource/issues/32 for more details). @@ -244,6 +257,32 @@ If you are coming from [jtarchie/github-pullrequest-resource][original-resource] - `merge.*` - `label` +#### Metadata stored in the `.git` directory + +The original resource stores [a bunch of metadata][metadata] related to the +pull request as `git config`, or plain files in the `.git` directory. This +resource provide most metadata with possibly different names, and the files +are to be found in the `.git/reource` directory. + +If you were using the metadata stored in Git config, you need to update your +code. For example `git config --get pullrequest.url` in some Bash code can be +replaced by `echo $(< .git/resource/url)`. + +Here is the list of changes: + +- `.git/id` -> `.git/resource/pr` +- `.git/url` -> `.git/resource/url` +- `.git/base_branch` -> `.git/resource/base_name` +- `.git/base_sha` -> `.git/resource/base_sha` +- `.git/branch` -> `.git/resource/head_name` +- `.git/head_sha` -> `.git/resource/head_sha` +- `.git/userlogin` -> `.git/resource/author` +- `.git/body` -> _no equivalent_ + +[metadata]: https://github.com/jtarchie/github-pullrequest-resource#in-clone-the-repository-at-the-given-pull-request-ref + +#### Possibly incompatible resource history + Note that if you are migrating from the original resource on a Concourse version prior to `v5.0.0`, you might see an error `failed to unmarshal request: json: unknown field "ref"`. The solution is to rename the resource so that the history is wiped. See [#64](https://github.com/telia-oss/github-pr-resource/issues/64) for details.