diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..c77978b5 --- /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:latest + 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 - 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..3d99a234 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,18 @@ -FROM golang:1.14 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 +RUN curl -sL https://taskfile.dev/install.sh | sh /dev/stdin v3.33.1 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 \ 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 diff --git a/README.md b/README.md index d46077ff..ec69ef5c 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/teliaoss/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 @@ -22,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. | @@ -32,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. | @@ -72,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 @@ -89,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). @@ -136,7 +150,7 @@ resource_types: - name: pull-request type: docker-image source: - repository: teliaoss/github-pr-resource + repository: loggregatorbot/github-pr-resource resources: - name: pull-request @@ -243,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. 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: diff --git a/check.go b/check.go index 64df9e24..0c214fb8 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 @@ -44,11 +46,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 @@ -78,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 } @@ -121,6 +124,7 @@ Loop: continue Loop } } + response = append(response, NewVersion(p)) } @@ -135,12 +139,42 @@ 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)\\]") + re := regexp.MustCompile(`(?i)\[(ci skip|skip ci)\]`) return re.MatchString(s) } diff --git a/check_test.go b/check_test.go index 8c422914..c09d719f 100644 --- a/check_test.go +++ b/check_test.go @@ -10,30 +10,35 @@ 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 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,41 +46,21 @@ 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}, }, { - 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", }, - version: resource.NewVersion(testPullRequests[1]), - 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[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}, }, { @@ -85,16 +70,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[2]), - }, + expectedIndexes: []int{3, 2}, }, { @@ -104,16 +87,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[2]), - }, + expectedIndexes: []int{3, 2}, }, { @@ -123,11 +104,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", DisableCISkip: true, }, - version: resource.NewVersion(testPullRequests[1]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[0]), - }, + version: resource.NewVersion(standardTestPRs[1]), + pullRequests: standardTestPRs, + expectedIndexes: []int{11, 8, 7, 6, 5, 4, 3, 2, 1, 0}, }, { @@ -137,11 +116,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", IgnoreDrafts: true, }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - resource.NewVersion(testPullRequests[1]), - }, + version: resource.NewVersion(standardTestPRs[3]), + pullRequests: standardTestPRs, + expectedIndexes: []int{11, 8, 7, 6, 5, 4, 3, 1}, }, { @@ -151,12 +128,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", IgnoreDrafts: false, }, - version: resource.NewVersion(testPullRequests[3]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - 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}, }, { @@ -166,13 +140,9 @@ func TestCheck(t *testing.T) { AccessToken: "oauthtoken", DisableForks: true, }, - version: resource.NewVersion(testPullRequests[5]), - pullRequests: testPullRequests, - expected: resource.CheckResponse{ - 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}, }, { @@ -182,26 +152,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), + }, + 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), }, - 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}, }, { @@ -211,12 +265,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}, }, { @@ -226,12 +277,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}, }, { @@ -241,10 +289,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, }, { @@ -254,13 +301,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}, }, } @@ -274,7 +318,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 } @@ -286,11 +330,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()) }) @@ -550,3 +609,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/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..ac6e1b0b --- /dev/null +++ b/ci/settings.yml @@ -0,0 +1,321 @@ +--- +meta: + name: github-pr-resource + release: GitHub Pull-Request Concourse resource + target: gk-plat-devs + url: https://ci.gstack.io + + 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: (( grab meta.private-registry.username )) + password: (( grab meta.private-registry.password )) + + dockerhub: + 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: eu-west-3 + access_key: ((aws-access-key)) + secret_key: ((aws-secret-key)) + + github: + owner: cloudfoundry-community + 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: "#oss-pipelines-notifications" + +groups: + - name: bump + jobs: + - bump-deps + +jobs: + - name: build + plan: + - (( inline )) + - in_parallel: + - (( append )) + - { 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: + inputs: + - (( append )) + - name: golang-latest + - name: alpine-latest + params: + IMAGE_ARG_golang: golang-latest/image.tar + IMAGE_ARG_alpine: alpine-latest/image.tar + + - name: build-pr + plan: + - (( inline )) + - in_parallel: + - (( append )) + - { 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 + + - # task: build-docker-image + config: + inputs: + - (( replace )) + - name: git-pull-requests + - name: golang-latest + - name: alpine-latest + params: + CONTEXT: git-pull-requests + IMAGE_ARG_golang: golang-latest/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-latest } + - { name: alpine-latest } + outputs: [ name: built-image ] + caches: [ path: cache ] + run: { path: build } + params: + IMAGE_ARG_golang: golang-latest/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-latest + type: registry-image + icon: docker + check_every: 24h + source: + repository: golang + semver_constraint: "< 1970" # Avoid YYYYMMDD tags like '20231219' + + - 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 ] 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}, }, }, 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: 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 ab10cbdc..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 { @@ -106,8 +109,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 +141,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 +156,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 +170,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, }) } @@ -171,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 17e7abfc..ce0b0b34 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"}`, + 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"}]`, }, { @@ -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"}`, + 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"}]`, }, { @@ -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"}`, + 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"}]`, }, { @@ -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"}`, + 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"}]`, }, { @@ -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"}`, + 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"}]`, }, { @@ -132,16 +126,15 @@ 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, }, - 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", @@ -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 @@ -321,6 +313,7 @@ func TestGetSkipDownload(t *testing.T) { func createTestPR( count int, baseName string, + author string, skipCI bool, isCrossRepo bool, approvedReviews int, @@ -361,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 9e4e7b1c..8372fcfe 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. @@ -49,7 +51,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 @@ -71,21 +73,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, } } @@ -114,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..15d201e3 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), }, } @@ -314,7 +311,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()) { @@ -329,7 +326,6 @@ func TestVariableSubstitution(t *testing.T) { assert.Equal(t, tc.expectedComment, comment) } } - }) } }