1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

add step to create a GitHub issue (#2481)

* add step to create a GirHub issue

* add groovy library step

* Update githubcreateissue.yaml

Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com>
This commit is contained in:
Oliver Nocon
2020-12-21 17:13:16 +01:00
committed by GitHub
parent cf7ca8f791
commit 7de42230e0
11 changed files with 438 additions and 0 deletions

43
cmd/githubCreateIssue.go Normal file
View File

@@ -0,0 +1,43 @@
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 githubCreateIssueService interface {
Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error)
}
func githubCreateIssue(config githubCreateIssueOptions, 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 = runGithubCreateIssue(ctx, &config, telemetryData, client.Issues)
if err != nil {
log.Entry().WithError(err).Fatal("Failed to comment on issue")
}
}
func runGithubCreateIssue(ctx context.Context, config *githubCreateIssueOptions, _ *telemetry.CustomData, ghCreateIssueService githubCreateIssueService) error {
issue := github.IssueRequest{
Body: &config.Body,
Title: &config.Title,
}
newIssue, resp, err := ghCreateIssueService.Create(ctx, config.Owner, config.Repository, &issue)
if err != nil {
log.Entry().Errorf("GitHub response code %v", resp.Status)
return errors.Wrap(err, "Error occurred when creating issue")
}
log.Entry().Debugf("New issue created: %v", newIssue)
return nil
}

View File

@@ -0,0 +1,185 @@
// 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 githubCreateIssueOptions struct {
APIURL string `json:"apiUrl,omitempty"`
Body string `json:"body,omitempty"`
Owner string `json:"owner,omitempty"`
Repository string `json:"repository,omitempty"`
Title string `json:"title,omitempty"`
Token string `json:"token,omitempty"`
}
// GithubCreateIssueCommand Create a new GitHub issue.
func GithubCreateIssueCommand() *cobra.Command {
const STEP_NAME = "githubCreateIssue"
metadata := githubCreateIssueMetadata()
var stepConfig githubCreateIssueOptions
var startTime time.Time
var createGithubCreateIssueCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Create a new GitHub issue.",
Long: `This step allows you to create a new GitHub issue.
You will be able to use this step for example for regular jobs to report into your repository in case of new security findings.`,
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() {
config.RemoveVaultSecretFiles()
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetryData.ErrorCategory = log.GetErrorCategory().String()
telemetry.Send(&telemetryData)
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
githubCreateIssue(stepConfig, &telemetryData)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addGithubCreateIssueFlags(createGithubCreateIssueCmd, &stepConfig)
return createGithubCreateIssueCmd
}
func addGithubCreateIssueFlags(cmd *cobra.Command, stepConfig *githubCreateIssueOptions) {
cmd.Flags().StringVar(&stepConfig.APIURL, "apiUrl", `https://api.github.com`, "Set the GitHub API url.")
cmd.Flags().StringVar(&stepConfig.Body, "body", os.Getenv("PIPER_body"), "Defines the content of the issue, e.g. using markdown syntax.")
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.Title, "title", os.Getenv("PIPER_title"), "Defines the title for the Issue.")
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("body")
cmd.MarkFlagRequired("owner")
cmd.MarkFlagRequired("repository")
cmd.MarkFlagRequired("title")
cmd.MarkFlagRequired("token")
}
// retrieve step metadata
func githubCreateIssueMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "githubCreateIssue",
Aliases: []config.Alias{},
Description: "Create a new GitHub issue.",
},
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: "body",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "owner",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "github/owner",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubOrg"}},
},
{
Name: "repository",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "github/repository",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubRepo"}},
},
{
Name: "title",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "token",
ResourceRef: []config.ResourceReference{
{
Name: "githubTokenCredentialsId",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/github", "$(vaultBasePath)/$(vaultPipelineName)/github", "$(vaultBasePath)/GROUP-SECRETS/github"},
Type: "vaultSecret",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{{Name: "githubToken"}, {Name: "access_token"}},
},
},
},
},
}
return theMetaData
}

View File

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

View File

@@ -0,0 +1,77 @@
package cmd
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/google/go-github/v32/github"
"github.com/stretchr/testify/assert"
)
type ghCreateIssueMock struct {
issue *github.IssueRequest
issueID int64
issueError error
owner string
repo string
number int
}
func (g *ghCreateIssueMock) Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error) {
g.issue = issue
g.owner = owner
g.repo = repo
issueResponse := github.Issue{ID: &g.issueID, Title: issue.Title, Body: issue.Body}
ghRes := github.Response{Response: &http.Response{Status: "200"}}
if g.issueError != nil {
ghRes.Status = "401"
}
return &issueResponse, &ghRes, g.issueError
}
func TestRunGithubCreateIssue(t *testing.T) {
ctx := context.Background()
t.Parallel()
t.Run("Success", func(t *testing.T) {
// init
ghCreateIssueService := ghCreateIssueMock{
issueID: 1,
}
config := githubCreateIssueOptions{
Owner: "TEST",
Repository: "test",
Body: "This is my test body",
Title: "This is my title",
}
// test
err := runGithubCreateIssue(ctx, &config, nil, &ghCreateIssueService)
// assert
assert.NoError(t, err)
assert.Equal(t, config.Owner, ghCreateIssueService.owner)
assert.Equal(t, config.Repository, ghCreateIssueService.repo)
assert.Equal(t, config.Body, ghCreateIssueService.issue.GetBody())
assert.Equal(t, config.Title, ghCreateIssueService.issue.GetTitle())
})
t.Run("Error", func(t *testing.T) {
// init
ghCreateIssueService := ghCreateIssueMock{
issueError: fmt.Errorf("error creating issue"),
}
config := githubCreateIssueOptions{}
// test
err := runGithubCreateIssue(ctx, &config, nil, &ghCreateIssueService)
// assert
assert.EqualError(t, err, "Error occurred when creating issue: error creating issue")
})
}

View File

@@ -36,6 +36,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"gctsRollback": gctsRollbackMetadata(),
"githubCheckBranchProtection": githubCheckBranchProtectionMetadata(),
"githubCommentIssue": githubCommentIssueMetadata(),
"githubCreateIssue": githubCreateIssueMetadata(),
"githubCreatePullRequest": githubCreatePullRequestMetadata(),
"githubPublishRelease": githubPublishReleaseMetadata(),
"githubSetCommitStatus": githubSetCommitStatusMetadata(),

View File

@@ -75,6 +75,7 @@ func Execute() {
rootCmd.AddCommand(XsDeployCommand())
rootCmd.AddCommand(GithubCheckBranchProtectionCommand())
rootCmd.AddCommand(GithubCommentIssueCommand())
rootCmd.AddCommand(GithubCreateIssueCommand())
rootCmd.AddCommand(GithubCreatePullRequestCommand())
rootCmd.AddCommand(GithubPublishReleaseCommand())
rootCmd.AddCommand(GithubSetCommitStatusCommand())

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

@@ -92,6 +92,7 @@ nav:
- gctsRollback: steps/gctsRollback.md
- githubCheckBranchProtection: steps/githubCheckBranchProtection.md
- githubCommentIssue: steps/githubCommentIssue.md
- githubCreateIssue: steps/githubCreateIssue.md
- githubCreatePullRequest: steps/githubCreatePullRequest.md
- githubPublishRelease: steps/githubPublishRelease.md
- githubSetCommitStatus: steps/githubSetCommitStatus.md

View File

@@ -0,0 +1,89 @@
metadata:
name: githubCreateIssue
description: Create a new GitHub issue.
longDescription: |
This step allows you to create a new GitHub issue.
You will be able to use this step for example for regular jobs to report into your repository in case of new security findings.
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: body
description: Defines the content of the issue, e.g. using markdown syntax.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: owner
aliases:
- name: githubOrg
description: Name of the GitHub organization.
resourceRef:
- name: commonPipelineEnvironment
param: github/owner
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: repository
aliases:
- name: githubRepo
description: Name of the GitHub repository.
resourceRef:
- name: commonPipelineEnvironment
param: github/repository
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: title
description: Defines the title for the Issue.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
- name: token
aliases:
- name: githubToken
- name: access_token
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
- type: vaultSecret
paths:
- $(vaultPath)/github
- $(vaultBasePath)/$(vaultPipelineName)/github
- $(vaultBasePath)/GROUP-SECRETS/github

View File

@@ -139,6 +139,7 @@ public class CommonStepsTest extends BasePiperTest{
'buildSetResult',
'runClosures',
'checkmarxExecuteScan', //implementing new golang pattern without fields
'githubCreateIssue', //implementing new golang pattern without fields
'githubPublishRelease', //implementing new golang pattern without fields
'githubCheckBranchProtection', //implementing new golang pattern without fields
'githubCommentIssue', //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/githubcreateissue.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'token', id: 'githubTokenCredentialsId', env: ['PIPER_token']]
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}