1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

Add step to set GitHub status (#2012)

* add step for GitHub branch protection check

* add command to piper command

* remove unnecessary parameter

* Update resources/metadata/githubbranchprotection.yaml

* Add step to set GitHub status

* add groovy part

* update generation & go mod tidy

* update groovy tests

* update github api to fix bug

* add comment

* go mod tidy

* update naming

* update docs

* update docs

* Update resources/metadata/githubstatus.yaml

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>

* Update resources/metadata/githubstatus.yaml

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>

* Update resources/metadata/githubstatus.yaml

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>

* update generated files

* update GitHub step order

* Update interface usage

* do not export interfaces

* go mod tidy

Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com>
Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
This commit is contained in:
Oliver Nocon 2020-09-14 18:08:24 +02:00 committed by GitHub
parent 6a0d694d0c
commit 5856e35d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 467 additions and 11 deletions

View File

@ -14,7 +14,7 @@ import (
piperGithub "github.com/SAP/jenkins-library/pkg/github"
)
type githubRepositoriesService interface {
type gitHubBranchProtectionRepositoriesService interface {
GetBranchProtection(ctx context.Context, owner, repo, branch string) (*github.Protection, *github.Response, error)
}
@ -30,7 +30,7 @@ func githubCheckBranchProtection(config githubCheckBranchProtectionOptions, tele
}
}
func runGithubCheckBranchProtection(ctx context.Context, config *githubCheckBranchProtectionOptions, telemetryData *telemetry.CustomData, ghRepositoriesService githubRepositoriesService) error {
func runGithubCheckBranchProtection(ctx context.Context, config *githubCheckBranchProtectionOptions, telemetryData *telemetry.CustomData, ghRepositoriesService gitHubBranchProtectionRepositoriesService) 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")

View File

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
)
type ghRepoService struct {
type ghCheckBranchRepoService struct {
protection github.Protection
serviceError error
owner string
@ -19,7 +19,7 @@ type ghRepoService struct {
branch string
}
func (g *ghRepoService) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*github.Protection, *github.Response, error) {
func (g *ghCheckBranchRepoService) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*github.Protection, *github.Response, error) {
g.owner = owner
g.repo = repo
g.branch = branch
@ -33,7 +33,7 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
t.Run("no checks active", func(t *testing.T) {
config := githubCheckBranchProtectionOptions{Branch: "testBranch", Owner: "testOrg", Repository: "testRepo"}
ghRepo := ghRepoService{}
ghRepo := ghCheckBranchRepoService{}
err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo)
assert.NoError(t, err)
assert.Equal(t, config.Branch, ghRepo.branch)
@ -43,7 +43,7 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
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")}
ghRepo := ghCheckBranchRepoService{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")
})
@ -57,7 +57,7 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
RequireEnforceAdmins: true,
RequiredApprovingReviewCount: 1,
}
ghRepo := ghRepoService{protection: github.Protection{
ghRepo := ghCheckBranchRepoService{protection: github.Protection{
RequiredStatusChecks: &github.RequiredStatusChecks{Contexts: []string{"check0", "check1", "check2", "check3"}},
EnforceAdmins: &github.AdminEnforcement{Enabled: true},
RequiredPullRequestReviews: &github.PullRequestReviewsEnforcement{RequiredApprovingReviewCount: 1},
@ -73,7 +73,7 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
config := githubCheckBranchProtectionOptions{
RequiredChecks: []string{"check1", "check2"},
}
ghRepo := ghRepoService{protection: github.Protection{
ghRepo := ghCheckBranchRepoService{protection: github.Protection{
RequiredStatusChecks: &github.RequiredStatusChecks{Contexts: []string{"check0", "check1"}},
}}
err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo)
@ -84,7 +84,7 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
config := githubCheckBranchProtectionOptions{
RequireEnforceAdmins: true,
}
ghRepo := ghRepoService{protection: github.Protection{
ghRepo := ghCheckBranchRepoService{protection: github.Protection{
EnforceAdmins: &github.AdminEnforcement{Enabled: false},
}}
err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo)
@ -95,7 +95,7 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
config := githubCheckBranchProtectionOptions{
RequiredApprovingReviewCount: 2,
}
ghRepo := ghRepoService{protection: github.Protection{
ghRepo := ghCheckBranchRepoService{protection: github.Protection{
RequiredPullRequestReviews: &github.PullRequestReviewsEnforcement{RequiredApprovingReviewCount: 1},
}}
err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo)

View File

@ -0,0 +1,38 @@
package cmd
import (
"context"
"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 gitHubCommitStatusRepositoriesService interface {
CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error)
}
func githubSetCommitStatus(config githubSetCommitStatusOptions, 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 = runGithubSetCommitStatus(ctx, &config, telemetryData, client.Repositories)
if err != nil {
log.Entry().WithError(err).Fatal("GitHub status update failed")
}
}
func runGithubSetCommitStatus(ctx context.Context, config *githubSetCommitStatusOptions, telemetryData *telemetry.CustomData, ghRepositoriesService gitHubCommitStatusRepositoriesService) error {
status := github.RepoStatus{State: &config.Status, TargetURL: &config.TargetURL}
_, _, err := ghRepositoriesService.CreateStatus(ctx, config.Owner, config.Repository, config.CommitID, &status)
if err != nil {
return errors.Wrapf(err, "failed to set status '%v' on commitId '%v'", config.Status, config.CommitID)
}
return nil
}

View File

@ -0,0 +1,199 @@
// 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 githubSetCommitStatusOptions struct {
APIURL string `json:"apiUrl,omitempty"`
CommitID string `json:"commitId,omitempty"`
Context string `json:"context,omitempty"`
Description string `json:"description,omitempty"`
Owner string `json:"owner,omitempty"`
Repository string `json:"repository,omitempty"`
Status string `json:"status,omitempty"`
TargetURL string `json:"targetUrl,omitempty"`
Token string `json:"token,omitempty"`
}
// GithubSetCommitStatusCommand Set a status of a certain commit.
func GithubSetCommitStatusCommand() *cobra.Command {
const STEP_NAME = "githubSetCommitStatus"
metadata := githubSetCommitStatusMetadata()
var stepConfig githubSetCommitStatusOptions
var startTime time.Time
var createGithubSetCommitStatusCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Set a status of a certain commit.",
Long: `This step allows you to set a status for a certain commit.
Details can be found here: https://developer.github.com/v3/repos/statuses/.
Typically, following information is set:
* state (pending, failure, success)
* context
* target URL (link to details)
It can for example be used to create additional check indicators for a pull request which can be evaluated and also be enforced by GitHub configuration.`,
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)
githubSetCommitStatus(stepConfig, &telemetryData)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addGithubSetCommitStatusFlags(createGithubSetCommitStatusCmd, &stepConfig)
return createGithubSetCommitStatusCmd
}
func addGithubSetCommitStatusFlags(cmd *cobra.Command, stepConfig *githubSetCommitStatusOptions) {
cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", `https://api.github.com`, "Set the GitHub API URL.")
cmd.Flags().StringVar(&stepConfig.CommitID, "commitId", os.Getenv("PIPER_commitId"), "The commitId for which the status should be set.")
cmd.Flags().StringVar(&stepConfig.Context, "context", os.Getenv("PIPER_context"), "Label for the status which will for example show up in a pull request.")
cmd.Flags().StringVar(&stepConfig.Description, "description", os.Getenv("PIPER_description"), "Short description of the status.")
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.Status, "status", os.Getenv("PIPER_status"), "Status which should be set on the commitId.")
cmd.Flags().StringVar(&stepConfig.TargetURL, "targetUrl", os.Getenv("PIPER_targetUrl"), "Target URL to associate the status with.")
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("commitId")
cmd.MarkFlagRequired("context")
cmd.MarkFlagRequired("owner")
cmd.MarkFlagRequired("repository")
cmd.MarkFlagRequired("status")
cmd.MarkFlagRequired("token")
}
// retrieve step metadata
func githubSetCommitStatusMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "githubSetCommitStatus",
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: "commitId",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "context",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "description",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
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: "status",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "targetUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
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
}

View File

@ -0,0 +1,16 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGithubSetCommitStatusCommand(t *testing.T) {
testCmd := GithubSetCommitStatusCommand()
// only high level testing performed - details are tested in step generation procedure
assert.Equal(t, "githubSetCommitStatus", testCmd.Use, "command name incorrect")
}

View File

@ -0,0 +1,53 @@
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 ghSetCommitRepoService struct {
serviceError error
owner string
ref string
repo string
status *github.RepoStatus
}
func (g *ghSetCommitRepoService) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) {
g.owner = owner
g.repo = repo
g.ref = ref
g.status = status
return nil, nil, g.serviceError
}
func TestRunGithubSetCommitStatus(t *testing.T) {
ctx := context.Background()
telemetryData := telemetry.CustomData{}
t.Run("success case", func(t *testing.T) {
config := githubSetCommitStatusOptions{CommitID: "testSha", Owner: "testOrg", Repository: "testRepo", Status: "success", TargetURL: "https://test.url"}
ghRepo := ghSetCommitRepoService{}
err := runGithubSetCommitStatus(ctx, &config, &telemetryData, &ghRepo)
expectedStatus := github.RepoStatus{State: &config.Status, TargetURL: &config.TargetURL}
assert.NoError(t, err)
assert.Equal(t, config.CommitID, ghRepo.ref)
assert.Equal(t, config.Owner, ghRepo.owner)
assert.Equal(t, config.Repository, ghRepo.repo)
assert.Equal(t, &expectedStatus, ghRepo.status)
})
t.Run("error calling GitHub", func(t *testing.T) {
config := githubSetCommitStatusOptions{CommitID: "testSha", Owner: "testOrg", Repository: "testRepo", Status: "pending"}
ghRepo := ghSetCommitRepoService{serviceError: fmt.Errorf("gh test error")}
err := runGithubSetCommitStatus(ctx, &config, &telemetryData, &ghRepo)
assert.EqualError(t, err, "failed to set status 'pending' on commitId 'testSha': gh test error")
})
}

View File

@ -73,6 +73,7 @@ func Execute() {
rootCmd.AddCommand(GithubCheckBranchProtectionCommand())
rootCmd.AddCommand(GithubCreatePullRequestCommand())
rootCmd.AddCommand(GithubPublishReleaseCommand())
rootCmd.AddCommand(GithubSetCommitStatusCommand())
rootCmd.AddCommand(CloudFoundryDeleteServiceCommand())
rootCmd.AddCommand(AbapEnvironmentPullGitRepoCommand())
rootCmd.AddCommand(AbapEnvironmentCloneGitRepoCommand())

View File

@ -0,0 +1,13 @@
# ${docGenStepName}
## Prerequisites
You need to create a personal access token within GitHub and add this to the Jenkins credentials store.
Please see [GitHub documentation for details about creating the personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/).
## ${docGenParameters}
## ${docGenConfiguration}
## ${docGenDescription}

View File

@ -0,0 +1,13 @@
# ${docGenStepName}
## Prerequisites
You need to create a personal access token within GitHub and add this to the Jenkins credentials store.
Please see [GitHub documentation for details about creating the personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/).
## ${docGenParameters}
## ${docGenConfiguration}
## ${docGenDescription}

View File

@ -79,7 +79,9 @@ nav:
- gctsDeploy: steps/gctsDeploy.md
- gctsExecuteABAPUnitTests: steps/gctsExecuteABAPUnitTests.md
- gctsRollback: steps/gctsRollback.md
- githubCheckBranchProtection: steps/githubCheckBranchProtection.md
- githubPublishRelease: steps/githubPublishRelease.md
- githubSetCommitStatus: steps/githubSetCommitStatus.md
- hadolintExecute: steps/hadolintExecute.md
- handlePipelineStepErrors: steps/handlePipelineStepErrors.md
- healthExecuteCheck: steps/healthExecuteCheck.md

1
go.sum
View File

@ -523,7 +523,6 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.1.2 h1:YjFNKqxzWUVZND8d4ItF9wuYlE75WQfECE7yKX/Nu3o=
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.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=

View File

@ -0,0 +1,110 @@
metadata:
name: githubSetCommitStatus
description: Set a status of a certain commit.
longDescription: |
This step allows you to set a status for a certain commit.
Details can be found here: https://developer.github.com/v3/repos/statuses/.
Typically, following information is set:
* state (pending, failure, success)
* context
* target URL (link to details)
It can for example be used to create additional check indicators for a pull request which can be evaluated and also be enforced by GitHub configuration.
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: commitId
description: The commitId for which the status should be set.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: context
description: Label for the status which will for example show up in a pull request.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: description
description: Short description of the status.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
- 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: status
description: Status which should be set on the commitId.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
possibleValues:
- failure
- pending
- success
mandatory: true
- name: targetUrl
description: Target URL to associate the status with.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
- 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

View File

@ -131,6 +131,7 @@ public class CommonStepsTest extends BasePiperTest{
'checkmarxExecuteScan', //implementing new golang pattern without fields
'githubPublishRelease', //implementing new golang pattern without fields
'githubCheckBranchProtection', //implementing new golang pattern without fields
'githubSetCommitStatus', //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

View File

@ -0,0 +1,11 @@
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/githubstatus.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'token', id: 'githubTokenCredentialsId', env: ['PIPER_token']]
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}