From f1cfca2e7660ecfe3d53f55a619e6256ad48c416 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Fri, 11 Sep 2020 15:28:43 +0200 Subject: [PATCH] Add step for GitHub branch protection check (#2010) * add step for GitHub branch protection check * add command to piper command * remove unnecessary parameter * Update resources/metadata/githubbranchprotection.yaml * add groovy part * update generation & go mod tidy * update groovy tests * fix bug with go-github version Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com> --- cmd/fortifyExecuteScan.go | 2 +- cmd/fortifyExecuteScan_test.go | 7 +- cmd/githubCheckBranchProtection.go | 64 +++++++ cmd/githubCheckBranchProtection_generated.go | 180 ++++++++++++++++++ ...hubCheckBranchProtection_generated_test.go | 16 ++ cmd/githubCheckBranchProtection_test.go | 105 ++++++++++ cmd/githubCreatePullRequest.go | 2 +- cmd/githubCreatePullRequest_generated.go | 4 +- cmd/githubCreatePullRequest_test.go | 6 +- cmd/githubPublishRelease.go | 2 +- cmd/githubPublishRelease_generated.go | 4 +- cmd/githubPublishRelease_test.go | 12 +- cmd/piper.go | 3 +- go.mod | 2 +- go.sum | 2 + pkg/github/github.go | 2 +- .../metadata/githubbranchprotection.yaml | 90 +++++++++ resources/metadata/githubcreatepr.yaml | 4 +- resources/metadata/githubrelease.yaml | 4 +- test/groovy/CommonStepsTest.groovy | 1 + vars/githubCheckBranchProtection.groovy | 11 ++ 21 files changed, 497 insertions(+), 26 deletions(-) create mode 100644 cmd/githubCheckBranchProtection.go create mode 100644 cmd/githubCheckBranchProtection_generated.go create mode 100644 cmd/githubCheckBranchProtection_generated_test.go create mode 100644 cmd/githubCheckBranchProtection_test.go create mode 100644 resources/metadata/githubbranchprotection.yaml create mode 100644 vars/githubCheckBranchProtection.groovy diff --git a/cmd/fortifyExecuteScan.go b/cmd/fortifyExecuteScan.go index 3a4386c11..98a2e8a65 100644 --- a/cmd/fortifyExecuteScan.go +++ b/cmd/fortifyExecuteScan.go @@ -17,7 +17,7 @@ import ( "github.com/bmatcuk/doublestar" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v32/github" "github.com/google/uuid" "github.com/piper-validation/fortify-client-go/models" diff --git a/cmd/fortifyExecuteScan_test.go b/cmd/fortifyExecuteScan_test.go index 48ce39d48..f6a8c25a0 100644 --- a/cmd/fortifyExecuteScan_test.go +++ b/cmd/fortifyExecuteScan_test.go @@ -5,8 +5,6 @@ import ( "context" "errors" "fmt" - "github.com/SAP/jenkins-library/pkg/fortify" - "github.com/SAP/jenkins-library/pkg/log" "io" "io/ioutil" "os" @@ -15,7 +13,10 @@ import ( "testing" "time" - "github.com/google/go-github/v28/github" + "github.com/SAP/jenkins-library/pkg/fortify" + "github.com/SAP/jenkins-library/pkg/log" + + "github.com/google/go-github/v32/github" "github.com/stretchr/testify/assert" "github.com/piper-validation/fortify-client-go/models" diff --git a/cmd/githubCheckBranchProtection.go b/cmd/githubCheckBranchProtection.go new file mode 100644 index 000000000..61ad263f6 --- /dev/null +++ b/cmd/githubCheckBranchProtection.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "context" + "fmt" + + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/google/go-github/v32/github" + + "github.com/pkg/errors" + + piperGithub "github.com/SAP/jenkins-library/pkg/github" +) + +type githubRepositoriesService interface { + GetBranchProtection(ctx context.Context, owner, repo, branch string) (*github.Protection, *github.Response, error) +} + +func githubCheckBranchProtection(config githubCheckBranchProtectionOptions, telemetryData *telemetry.CustomData) { + ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "") + if err != nil { + log.Entry().WithError(err).Fatal("Failed to get GitHub client") + } + + err = runGithubCheckBranchProtection(ctx, &config, telemetryData, client.Repositories) + if err != nil { + log.Entry().WithError(err).Fatal("GitHub branch protection check failed") + } +} + +func runGithubCheckBranchProtection(ctx context.Context, config *githubCheckBranchProtectionOptions, telemetryData *telemetry.CustomData, ghRepositoriesService githubRepositoriesService) error { + ghProtection, _, err := ghRepositoriesService.GetBranchProtection(ctx, config.Owner, config.Repository, config.Branch) + if err != nil { + return errors.Wrap(err, "failed to read branch protection information") + } + + // validate required status checks + for _, check := range config.RequiredChecks { + var found bool + for _, context := range ghProtection.GetRequiredStatusChecks().Contexts { + if check == context { + found = true + } + } + if !found { + return fmt.Errorf("required status check '%v' not found in branch protection configuration", check) + } + } + + // validate that admins are enforced in checks + if config.RequireEnforceAdmins { + if !ghProtection.GetEnforceAdmins().Enabled { + return fmt.Errorf("admins are not enforced in branch protection configuration") + } + } + + // validate number of mandatory reviewers + if config.RequiredApprovingReviewCount > 0 && ghProtection.GetRequiredPullRequestReviews().RequiredApprovingReviewCount < config.RequiredApprovingReviewCount { + return fmt.Errorf("not enough mandatory reviewers in branch protection configuration, expected at least %v, got %v", config.RequiredApprovingReviewCount, ghProtection.GetRequiredPullRequestReviews().RequiredApprovingReviewCount) + } + + return nil +} diff --git a/cmd/githubCheckBranchProtection_generated.go b/cmd/githubCheckBranchProtection_generated.go new file mode 100644 index 000000000..8c1b2f18b --- /dev/null +++ b/cmd/githubCheckBranchProtection_generated.go @@ -0,0 +1,180 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/spf13/cobra" +) + +type githubCheckBranchProtectionOptions struct { + APIURL string `json:"apiUrl,omitempty"` + Branch string `json:"branch,omitempty"` + Owner string `json:"owner,omitempty"` + Repository string `json:"repository,omitempty"` + RequiredChecks []string `json:"requiredChecks,omitempty"` + RequireEnforceAdmins bool `json:"requireEnforceAdmins,omitempty"` + RequiredApprovingReviewCount int `json:"requiredApprovingReviewCount,omitempty"` + Token string `json:"token,omitempty"` +} + +// GithubCheckBranchProtectionCommand Check branch protection of a GitHub branch +func GithubCheckBranchProtectionCommand() *cobra.Command { + const STEP_NAME = "githubCheckBranchProtection" + + metadata := githubCheckBranchProtectionMetadata() + var stepConfig githubCheckBranchProtectionOptions + var startTime time.Time + + var createGithubCheckBranchProtectionCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Check branch protection of a GitHub branch", + Long: `This step allows you to check if certain branch protection rules are fulfilled. + +It can for example be used to verify if certain status checks are mandatory. This can be helpful to decide if a certain check needs to be performed again after merging a pull request.`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + log.RegisterSecret(stepConfig.Token) + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + return nil + }, + Run: func(_ *cobra.Command, _ []string) { + telemetryData := telemetry.CustomData{} + telemetryData.ErrorCode = "1" + handler := func() { + telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + telemetry.Send(&telemetryData) + } + log.DeferExitHandler(handler) + defer handler() + telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + githubCheckBranchProtection(stepConfig, &telemetryData) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addGithubCheckBranchProtectionFlags(createGithubCheckBranchProtectionCmd, &stepConfig) + return createGithubCheckBranchProtectionCmd +} + +func addGithubCheckBranchProtectionFlags(cmd *cobra.Command, stepConfig *githubCheckBranchProtectionOptions) { + cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", `https://api.github.com`, "Set the GitHub API url.") + cmd.Flags().StringVar(&stepConfig.Branch, "branch", os.Getenv("PIPER_branch"), "The name of the branch for which the protection settings should be checked.") + cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Name of the GitHub organization.") + cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Name of the GitHub repository.") + cmd.Flags().StringSliceVar(&stepConfig.RequiredChecks, "requiredChecks", []string{}, "List of checks which have to be set to required in the GitHub repository configuration.") + cmd.Flags().BoolVar(&stepConfig.RequireEnforceAdmins, "requireEnforceAdmins", false, "Check if 'Include Administrators' option is set in the GitHub repository configuration.") + cmd.Flags().IntVar(&stepConfig.RequiredApprovingReviewCount, "requiredApprovingReviewCount", 0, "Check if 'Require pull request reviews before merging' option is set with at least the defined number of reviewers in the GitHub repository configuration.") + cmd.Flags().StringVar(&stepConfig.Token, "token", os.Getenv("PIPER_token"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line.") + + cmd.MarkFlagRequired("apiUrl") + cmd.MarkFlagRequired("branch") + cmd.MarkFlagRequired("owner") + cmd.MarkFlagRequired("repository") + cmd.MarkFlagRequired("token") +} + +// retrieve step metadata +func githubCheckBranchProtectionMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "githubCheckBranchProtection", + Aliases: []config.Alias{}, + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Parameters: []config.StepParameters{ + { + Name: "apiUrl", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "githubApiUrl"}}, + }, + { + Name: "branch", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "owner", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "githubOrg"}}, + }, + { + Name: "repository", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "githubRepo"}}, + }, + { + Name: "requiredChecks", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "[]string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "requireEnforceAdmins", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "requiredApprovingReviewCount", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "token", + ResourceRef: []config.ResourceReference{{Name: "githubTokenCredentialsId", Param: ""}}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{{Name: "githubToken"}}, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/githubCheckBranchProtection_generated_test.go b/cmd/githubCheckBranchProtection_generated_test.go new file mode 100644 index 000000000..87cb09bfe --- /dev/null +++ b/cmd/githubCheckBranchProtection_generated_test.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGithubCheckBranchProtectionCommand(t *testing.T) { + + testCmd := GithubCheckBranchProtectionCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "githubCheckBranchProtection", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/githubCheckBranchProtection_test.go b/cmd/githubCheckBranchProtection_test.go new file mode 100644 index 000000000..9bd93c57d --- /dev/null +++ b/cmd/githubCheckBranchProtection_test.go @@ -0,0 +1,105 @@ +package cmd + +import ( + "context" + "fmt" + "testing" + + "github.com/SAP/jenkins-library/pkg/telemetry" + + "github.com/google/go-github/v32/github" + "github.com/stretchr/testify/assert" +) + +type ghRepoService struct { + protection github.Protection + serviceError error + owner string + repo string + branch string +} + +func (g *ghRepoService) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*github.Protection, *github.Response, error) { + g.owner = owner + g.repo = repo + g.branch = branch + + return &g.protection, nil, g.serviceError +} + +func TestRunGithubCheckBranchProtection(t *testing.T) { + ctx := context.Background() + telemetryData := telemetry.CustomData{} + + t.Run("no checks active", func(t *testing.T) { + config := githubCheckBranchProtectionOptions{Branch: "testBranch", Owner: "testOrg", Repository: "testRepo"} + ghRepo := ghRepoService{} + err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) + assert.NoError(t, err) + assert.Equal(t, config.Branch, ghRepo.branch) + assert.Equal(t, config.Owner, ghRepo.owner) + assert.Equal(t, config.Repository, ghRepo.repo) + }) + + t.Run("error calling GitHub", func(t *testing.T) { + config := githubCheckBranchProtectionOptions{Branch: "testBranch", Owner: "testOrg", Repository: "testRepo"} + ghRepo := ghRepoService{serviceError: fmt.Errorf("gh test error")} + err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) + assert.EqualError(t, err, "failed to read branch protection information: gh test error") + }) + + t.Run("all checks ok", func(t *testing.T) { + config := githubCheckBranchProtectionOptions{ + Branch: "testBranch", + Owner: "testOrg", + Repository: "testRepo", + RequiredChecks: []string{"check1", "check2"}, + RequireEnforceAdmins: true, + RequiredApprovingReviewCount: 1, + } + ghRepo := ghRepoService{protection: github.Protection{ + RequiredStatusChecks: &github.RequiredStatusChecks{Contexts: []string{"check0", "check1", "check2", "check3"}}, + EnforceAdmins: &github.AdminEnforcement{Enabled: true}, + RequiredPullRequestReviews: &github.PullRequestReviewsEnforcement{RequiredApprovingReviewCount: 1}, + }} + err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) + assert.NoError(t, err) + assert.Equal(t, config.Branch, ghRepo.branch) + assert.Equal(t, config.Owner, ghRepo.owner) + assert.Equal(t, config.Repository, ghRepo.repo) + }) + + t.Run("status check missing", func(t *testing.T) { + config := githubCheckBranchProtectionOptions{ + RequiredChecks: []string{"check1", "check2"}, + } + ghRepo := ghRepoService{protection: github.Protection{ + RequiredStatusChecks: &github.RequiredStatusChecks{Contexts: []string{"check0", "check1"}}, + }} + err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) + assert.Contains(t, fmt.Sprint(err), "required status check 'check2' not found") + }) + + t.Run("admin enforcement inactive", func(t *testing.T) { + config := githubCheckBranchProtectionOptions{ + RequireEnforceAdmins: true, + } + ghRepo := ghRepoService{protection: github.Protection{ + EnforceAdmins: &github.AdminEnforcement{Enabled: false}, + }} + err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) + assert.Contains(t, fmt.Sprint(err), "admins are not enforced") + }) + + t.Run("not enough reviewers", func(t *testing.T) { + config := githubCheckBranchProtectionOptions{ + RequiredApprovingReviewCount: 2, + } + ghRepo := ghRepoService{protection: github.Protection{ + RequiredPullRequestReviews: &github.PullRequestReviewsEnforcement{RequiredApprovingReviewCount: 1}, + }} + err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo) + assert.Contains(t, fmt.Sprint(err), "not enough mandatory reviewers") + }) + +} diff --git a/cmd/githubCreatePullRequest.go b/cmd/githubCreatePullRequest.go index 601b86f93..fcab34774 100644 --- a/cmd/githubCreatePullRequest.go +++ b/cmd/githubCreatePullRequest.go @@ -5,7 +5,7 @@ import ( "github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/telemetry" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v32/github" "github.com/pkg/errors" piperGithub "github.com/SAP/jenkins-library/pkg/github" diff --git a/cmd/githubCreatePullRequest_generated.go b/cmd/githubCreatePullRequest_generated.go index f76baed21..9e278f391 100644 --- a/cmd/githubCreatePullRequest_generated.go +++ b/cmd/githubCreatePullRequest_generated.go @@ -90,8 +90,8 @@ func addGithubCreatePullRequestFlags(cmd *cobra.Command, stepConfig *githubCreat cmd.Flags().StringVar(&stepConfig.Body, "body", os.Getenv("PIPER_body"), "The description text of the pull request in markdown format.") cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", `https://api.github.com`, "Set the GitHub API url.") cmd.Flags().StringVar(&stepConfig.Head, "head", os.Getenv("PIPER_head"), "The name of the branch where your changes are implemented.") - cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.") - cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.") + cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Name of the GitHub organization.") + cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Name of the GitHub repository.") cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", `https://github.com`, "GitHub server url for end-user access.") cmd.Flags().StringVar(&stepConfig.Title, "title", os.Getenv("PIPER_title"), "Title of the pull request.") cmd.Flags().StringVar(&stepConfig.Token, "token", os.Getenv("PIPER_token"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line") diff --git a/cmd/githubCreatePullRequest_test.go b/cmd/githubCreatePullRequest_test.go index a82b9268c..f084e8867 100644 --- a/cmd/githubCreatePullRequest_test.go +++ b/cmd/githubCreatePullRequest_test.go @@ -6,7 +6,7 @@ import ( "net/http" "testing" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v32/github" "github.com/stretchr/testify/assert" ) @@ -46,9 +46,9 @@ func (g *ghIssueMock) Edit(ctx context.Context, owner string, repo string, numbe g.owner = owner g.repo = repo g.number = number - labels := []github.Label{} + labels := []*github.Label{} for _, l := range *issue.Labels { - labels = append(labels, github.Label{Name: &l}) + labels = append(labels, &github.Label{Name: &l}) } assignees := []*github.User{} diff --git a/cmd/githubPublishRelease.go b/cmd/githubPublishRelease.go index 225b8d7ec..80da498f9 100644 --- a/cmd/githubPublishRelease.go +++ b/cmd/githubPublishRelease.go @@ -10,7 +10,7 @@ import ( "github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/telemetry" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v32/github" "github.com/pkg/errors" piperGithub "github.com/SAP/jenkins-library/pkg/github" diff --git a/cmd/githubPublishRelease_generated.go b/cmd/githubPublishRelease_generated.go index 21dd34147..ff3902a8f 100644 --- a/cmd/githubPublishRelease_generated.go +++ b/cmd/githubPublishRelease_generated.go @@ -103,10 +103,10 @@ func addGithubPublishReleaseFlags(cmd *cobra.Command, stepConfig *githubPublishR cmd.Flags().StringVar(&stepConfig.Commitish, "commitish", `master`, "Target git commitish for the release") cmd.Flags().StringSliceVar(&stepConfig.ExcludeLabels, "excludeLabels", []string{}, "Allows to exclude issues with dedicated list of labels.") cmd.Flags().StringSliceVar(&stepConfig.Labels, "labels", []string{}, "Labels to include in issue search.") - cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.") + cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Name of the GitHub organization.") cmd.Flags().BoolVar(&stepConfig.PreRelease, "preRelease", false, "If set to `true` the release will be marked as Pre-release.") cmd.Flags().StringVar(&stepConfig.ReleaseBodyHeader, "releaseBodyHeader", os.Getenv("PIPER_releaseBodyHeader"), "Content which will appear for the release.") - cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Set the GitHub repository.") + cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Name of the GitHub repository.") cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", `https://github.com`, "GitHub server url for end-user access.") cmd.Flags().StringVar(&stepConfig.Token, "token", os.Getenv("PIPER_token"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line") cmd.Flags().StringVar(&stepConfig.UploadURL, "uploadUrl", `https://uploads.github.com`, "Set the GitHub API url.") diff --git a/cmd/githubPublishRelease_test.go b/cmd/githubPublishRelease_test.go index 8fb23e816..825b6d1e4 100644 --- a/cmd/githubPublishRelease_test.go +++ b/cmd/githubPublishRelease_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v32/github" "github.com/stretchr/testify/assert" ) @@ -393,11 +393,11 @@ func TestIsExcluded(t *testing.T) { }{ {issue: nil, excludeLabels: nil, expected: false}, {issue: &github.Issue{}, excludeLabels: nil, expected: false}, - {issue: &github.Issue{Labels: []github.Label{{Name: &l1}}}, excludeLabels: nil, expected: false}, - {issue: &github.Issue{Labels: []github.Label{{Name: &l1}}}, excludeLabels: []string{"label0"}, expected: false}, - {issue: &github.Issue{Labels: []github.Label{{Name: &l1}}}, excludeLabels: []string{"label1"}, expected: true}, - {issue: &github.Issue{Labels: []github.Label{{Name: &l1}, {Name: &l2}}}, excludeLabels: []string{}, expected: false}, - {issue: &github.Issue{Labels: []github.Label{{Name: &l1}, {Name: &l2}}}, excludeLabels: []string{"label1"}, expected: true}, + {issue: &github.Issue{Labels: []*github.Label{{Name: &l1}}}, excludeLabels: nil, expected: false}, + {issue: &github.Issue{Labels: []*github.Label{{Name: &l1}}}, excludeLabels: []string{"label0"}, expected: false}, + {issue: &github.Issue{Labels: []*github.Label{{Name: &l1}}}, excludeLabels: []string{"label1"}, expected: true}, + {issue: &github.Issue{Labels: []*github.Label{{Name: &l1}, {Name: &l2}}}, excludeLabels: []string{}, expected: false}, + {issue: &github.Issue{Labels: []*github.Label{{Name: &l1}, {Name: &l2}}}, excludeLabels: []string{"label1"}, expected: true}, } for k, v := range tt { diff --git a/cmd/piper.go b/cmd/piper.go index 0e38d1476..dec9ed479 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -70,8 +70,9 @@ func Execute() { rootCmd.AddCommand(SonarExecuteScanCommand()) rootCmd.AddCommand(KubernetesDeployCommand()) rootCmd.AddCommand(XsDeployCommand()) - rootCmd.AddCommand(GithubPublishReleaseCommand()) + rootCmd.AddCommand(GithubCheckBranchProtectionCommand()) rootCmd.AddCommand(GithubCreatePullRequestCommand()) + rootCmd.AddCommand(GithubPublishReleaseCommand()) rootCmd.AddCommand(CloudFoundryDeleteServiceCommand()) rootCmd.AddCommand(AbapEnvironmentPullGitRepoCommand()) rootCmd.AddCommand(AbapEnvironmentCloneGitRepoCommand()) diff --git a/go.mod b/go.mod index c1eef3f58..b0dbf65f9 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/go-openapi/strfmt v0.19.5 github.com/google/go-cmp v0.5.2 github.com/google/go-containerregistry v0.1.2 - github.com/google/go-github/v28 v28.1.1 + github.com/google/go-github/v32 v32.1.1-0.20200822031254-5f5296864910 github.com/google/uuid v1.1.2 github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/hashicorp/go-retryablehttp v0.6.7 // indirect diff --git a/go.sum b/go.sum index 41c9e9918..8df18d587 100644 --- a/go.sum +++ b/go.sum @@ -525,6 +525,8 @@ github.com/google/go-containerregistry v0.1.2 h1:YjFNKqxzWUVZND8d4ItF9wuYlE75WQf github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-github/v32 v32.1.1-0.20200822031254-5f5296864910 h1:LHut2hbOGGxCSGE4OGRuiah5kPcUBM0dHdmI3FEj2jc= +github.com/google/go-github/v32 v32.1.1-0.20200822031254-5f5296864910/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= diff --git a/pkg/github/github.go b/pkg/github/github.go index 5897a999c..82570d550 100644 --- a/pkg/github/github.go +++ b/pkg/github/github.go @@ -3,7 +3,7 @@ package github import ( "context" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v32/github" "golang.org/x/oauth2" ) diff --git a/resources/metadata/githubbranchprotection.yaml b/resources/metadata/githubbranchprotection.yaml new file mode 100644 index 000000000..470c6b40d --- /dev/null +++ b/resources/metadata/githubbranchprotection.yaml @@ -0,0 +1,90 @@ +metadata: + name: githubCheckBranchProtection + description: Check branch protection of a GitHub branch + longDescription: | + This step allows you to check if certain branch protection rules are fulfilled. + + It can for example be used to verify if certain status checks are mandatory. This can be helpful to decide if a certain check needs to be performed again after merging a pull request. +spec: + inputs: + secrets: + - name: githubTokenCredentialsId + description: Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub. + type: jenkins + params: + - name: apiUrl + aliases: + - name: githubApiUrl + description: Set the GitHub API url. + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + type: string + default: https://api.github.com + mandatory: true + - name: branch + description: The name of the branch for which the protection settings should be checked. + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + mandatory: true + - name: owner + aliases: + - name: githubOrg + description: Name of the GitHub organization. + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + mandatory: true + - name: repository + aliases: + - name: githubRepo + description: Name of the GitHub repository. + scope: + - PARAMETERS + - STAGES + - STEPS + type: string + mandatory: true + - name: requiredChecks + description: List of checks which have to be set to required in the GitHub repository configuration. + scope: + - PARAMETERS + - STAGES + - STEPS + type: "[]string" + - name: requireEnforceAdmins + description: Check if 'Include Administrators' option is set in the GitHub repository configuration. + scope: + - PARAMETERS + - STAGES + - STEPS + type: bool + - name: requiredApprovingReviewCount + description: Check if 'Require pull request reviews before merging' option is set with at least the defined number of reviewers in the GitHub repository configuration. + scope: + - PARAMETERS + - STAGES + - STEPS + type: int + - name: token + aliases: + - name: githubToken + description: GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line. + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + type: string + mandatory: true + secret: true + resourceRef: + - name: githubTokenCredentialsId + type: secret diff --git a/resources/metadata/githubcreatepr.yaml b/resources/metadata/githubcreatepr.yaml index cf300617b..b44d828fb 100644 --- a/resources/metadata/githubcreatepr.yaml +++ b/resources/metadata/githubcreatepr.yaml @@ -58,7 +58,7 @@ spec: - name: owner aliases: - name: githubOrg - description: Set the GitHub organization. + description: Name of the GitHub organization. scope: - PARAMETERS - STAGES @@ -68,7 +68,7 @@ spec: - name: repository aliases: - name: githubRepo - description: Set the GitHub repository. + description: Name of the GitHub repository. scope: - PARAMETERS - STAGES diff --git a/resources/metadata/githubrelease.yaml b/resources/metadata/githubrelease.yaml index 80e52371f..a51c98d9f 100644 --- a/resources/metadata/githubrelease.yaml +++ b/resources/metadata/githubrelease.yaml @@ -79,7 +79,7 @@ spec: - name: owner aliases: - name: githubOrg - description: "Set the GitHub organization." + description: Name of the GitHub organization. resourceRef: - name: commonPipelineEnvironment param: github/owner @@ -107,7 +107,7 @@ spec: - name: repository aliases: - name: githubRepo - description: "Set the GitHub repository." + description: Name of the GitHub repository. resourceRef: - name: commonPipelineEnvironment param: github/repository diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 19d83a96a..f84aebb61 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -130,6 +130,7 @@ public class CommonStepsTest extends BasePiperTest{ 'runClosures', 'checkmarxExecuteScan', //implementing new golang pattern without fields 'githubPublishRelease', //implementing new golang pattern without fields + 'githubCheckBranchProtection', //implementing new golang pattern without fields 'kubernetesDeploy', //implementing new golang pattern without fields 'piperExecuteBin', //implementing new golang pattern without fields 'protecodeExecuteScan', //implementing new golang pattern without fields diff --git a/vars/githubCheckBranchProtection.groovy b/vars/githubCheckBranchProtection.groovy new file mode 100644 index 000000000..2b4a1c911 --- /dev/null +++ b/vars/githubCheckBranchProtection.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/githubbranchprotection.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'token', id: 'githubTokenCredentialsId', env: ['PIPER_token']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}