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

fix(jenkins): fix job invocation (#2868)

* update mock

* update signarture

* add test case

* use latest gojenkins

* add integration test

* update mock

* add todo

* add job wrapper

* add job mock

* add test cases

* refactor

* cleanup

* update integration test case
This commit is contained in:
Christopher Fenner
2021-06-02 16:45:22 +02:00
committed by GitHub
parent d8a8a73184
commit 824cd7d768
8 changed files with 254 additions and 32 deletions

2
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/bmatcuk/doublestar v1.3.2
github.com/bndr/gojenkins v1.1.0
github.com/bndr/gojenkins v1.1.1-0.20210520222939-90ed82bfdff6
github.com/elliotchance/orderedmap v1.3.0
github.com/evanphx/json-patch v4.9.0+incompatible
github.com/getsentry/sentry-go v0.7.0

4
go.sum
View File

@@ -244,8 +244,8 @@ github.com/bmatcuk/doublestar v1.3.2 h1:mzUncgFmpzNUhIITFqGdZ8nUU0O7JTJzRO8VdkeL
github.com/bmatcuk/doublestar v1.3.2/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI=
github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM=
github.com/bndr/gojenkins v1.1.1-0.20210520222939-90ed82bfdff6 h1:yHK3nXjSRklq0SWhK6KW6kJtTebTUvVxN3gPmUtoVJ4=
github.com/bndr/gojenkins v1.1.1-0.20210520222939-90ed82bfdff6/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=

View File

@@ -0,0 +1,49 @@
// can be execute with go test -tags=integration ./integration/...
package main
import (
"context"
"net/http"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SAP/jenkins-library/pkg/jenkins"
)
func TestTriggerJob(t *testing.T) {
t.Skip("no Jenkins instance for testing available yet")
//TODO: check if testcontainers can be used
// init
ctx := context.Background()
// ctx = context.WithValue(ctx, "debug", true)
// os.Setenv("PIPER_INTEGRATION_JENKINS_USER_NAME", "")
// os.Setenv("PIPER_INTEGRATION_JENKINS_TOKEN", "")
// os.Setenv("PIPER_INTEGRATION_JENKINS_HOST", "")
// os.Setenv("PIPER_INTEGRATION_JENKINS_JOB_NAME", "")
host := os.Getenv("PIPER_INTEGRATION_JENKINS_HOST")
user := os.Getenv("PIPER_INTEGRATION_JENKINS_USER_NAME")
token := os.Getenv("PIPER_INTEGRATION_JENKINS_TOKEN")
jobName := os.Getenv("PIPER_INTEGRATION_JENKINS_JOB_NAME")
require.NotEmpty(t, host, "Jenkins host url is missing")
require.NotEmpty(t, user, "Jenkins user name is missing")
require.NotEmpty(t, token, "Jenkins token is missing")
require.NotEmpty(t, jobName, "Jenkins job name is missing")
jenx, err := jenkins.Instance(ctx, http.DefaultClient, host, user, token)
require.NotNil(t, jenx, "could not connect to Jenkins instance")
require.NoError(t, err)
// test
job, getJobErr := jenkins.GetJob(ctx, jenx, jobName)
build, triggerJobErr := jenkins.TriggerJob(ctx, jenx, job, nil)
// asserts
assert.NoError(t, getJobErr)
assert.NoError(t, triggerJobErr)
assert.NotNil(t, build)
assert.True(t, build.IsRunning(ctx))
}

View File

@@ -7,13 +7,15 @@ import (
"strings"
"github.com/bndr/gojenkins"
"github.com/pkg/errors"
)
// Jenkins is an interface to abstract gojenkins.Jenkins.
// mock generated with: mockery --name Jenkins --dir pkg/jenkins --output pkg/jenkins/mocks
type Jenkins interface {
GetJobObj(ctx context.Context, name string) *gojenkins.Job
BuildJob(ctx context.Context, name string, params map[string]string) (int64, error)
GetBuildFromQueueID(ctx context.Context, queueid int64) (*gojenkins.Build, error)
GetBuildFromQueueID(ctx context.Context, job *gojenkins.Job, queueid int64) (*gojenkins.Build, error)
}
// Instance connects to a Jenkins instance and returns a handler.
@@ -23,12 +25,22 @@ func Instance(ctx context.Context, client *http.Client, jenkinsURL, user, token
Init(ctx)
}
// TriggerJob starts a build for a given job name.
func TriggerJob(ctx context.Context, jenkins Jenkins, jobName string, parameters map[string]string) (*gojenkins.Build, error) {
func GetJob(ctx context.Context, jenkins Jenkins, jobName string) (Job, error) {
// get job id
jobID := strings.ReplaceAll(jobName, "/", "/job/")
// get job
return &JobImpl{Job: jenkins.GetJobObj(ctx, jobID)}, nil
}
// TriggerJob starts a build for a given job name.
func TriggerJob(ctx context.Context, jenkins Jenkins, job Job, parameters map[string]string) (*gojenkins.Build, error) {
// update job
_, pollJobErr := job.Poll(ctx)
if pollJobErr != nil {
return nil, errors.Wrapf(pollJobErr, "failed to load job")
}
// start job
queueID, startBuildErr := jenkins.BuildJob(ctx, jobID, parameters)
queueID, startBuildErr := job.InvokeSimple(ctx, parameters)
if startBuildErr != nil {
return nil, startBuildErr
}
@@ -40,5 +52,5 @@ func TriggerJob(ctx context.Context, jenkins Jenkins, jobName string, parameters
}
// get build
return jenkins.GetBuildFromQueueID(ctx, queueID)
return jenkins.GetBuildFromQueueID(ctx, job.GetJob(), queueID)
}

View File

@@ -3,7 +3,6 @@ package jenkins
import (
"context"
"fmt"
"strings"
"testing"
"github.com/SAP/jenkins-library/pkg/jenkins/mocks"
@@ -19,20 +18,39 @@ func TestInterfaceCompatibility(t *testing.T) {
func TestTriggerJob(t *testing.T) {
ctx := context.Background()
jobName := "ContinuousDelivery/piper-library"
jobID := strings.ReplaceAll(jobName, "/", "/job/")
jobParameters := map[string]string{}
t.Run("error - job not updated", func(t *testing.T) {
// init
jenkins := &mocks.Jenkins{}
jenkins.Test(t)
job := &mocks.Job{}
job.Test(t)
job.
On("Poll", ctx).Return(404, fmt.Errorf(mock.Anything))
// test
build, err := TriggerJob(ctx, jenkins, job, jobParameters)
// asserts
job.AssertExpectations(t)
jenkins.AssertExpectations(t)
assert.EqualError(t, err, fmt.Sprintf("failed to load job: %s", mock.Anything))
assert.Nil(t, build)
})
t.Run("error - task not started", func(t *testing.T) {
// init
queueID := int64(0)
jenkins := &mocks.Jenkins{}
jenkins.
On("BuildJob", ctx, jobID, map[string]string{}).
jenkins.Test(t)
job := &mocks.Job{}
job.Test(t)
job.
On("Poll", ctx).Return(200, nil).
On("InvokeSimple", ctx, map[string]string{}).
Return(queueID, fmt.Errorf(mock.Anything))
// test
build, err := TriggerJob(ctx, jenkins, jobName, jobParameters)
build, err := TriggerJob(ctx, jenkins, job, jobParameters)
// asserts
job.AssertExpectations(t)
jenkins.AssertExpectations(t)
assert.EqualError(t, err, mock.Anything)
assert.Nil(t, build)
@@ -41,12 +59,17 @@ func TestTriggerJob(t *testing.T) {
// init
queueID := int64(0)
jenkins := &mocks.Jenkins{}
jenkins.
On("BuildJob", ctx, jobID, map[string]string{}).
jenkins.Test(t)
job := &mocks.Job{}
job.Test(t)
job.
On("Poll", ctx).Return(200, nil).
On("InvokeSimple", ctx, jobParameters).
Return(queueID, nil)
// test
build, err := TriggerJob(ctx, jenkins, jobName, jobParameters)
build, err := TriggerJob(ctx, jenkins, job, jobParameters)
// asserts
job.AssertExpectations(t)
jenkins.AssertExpectations(t)
assert.EqualError(t, err, "unable to queue build")
assert.Nil(t, build)
@@ -56,14 +79,21 @@ func TestTriggerJob(t *testing.T) {
queueID := int64(43)
jenkins := &mocks.Jenkins{}
jenkins.Test(t)
jenkins.
On("BuildJob", ctx, jobID, map[string]string{}).
job := &mocks.Job{}
job.Test(t)
job.
On("Poll", ctx).Return(200, nil).
On("InvokeSimple", ctx, jobParameters).
Return(queueID, nil).
On("GetBuildFromQueueID", ctx, queueID).
On("GetJob").
Return(&gojenkins.Job{})
jenkins.
On("GetBuildFromQueueID", ctx, mock.Anything, queueID).
Return(nil, fmt.Errorf(mock.Anything))
// test
build, err := TriggerJob(ctx, jenkins, jobName, jobParameters)
build, err := TriggerJob(ctx, jenkins, job, jobParameters)
// asserts
job.AssertExpectations(t)
jenkins.AssertExpectations(t)
assert.EqualError(t, err, mock.Anything)
assert.Nil(t, build)
@@ -73,13 +103,19 @@ func TestTriggerJob(t *testing.T) {
queueID := int64(43)
jenkins := &mocks.Jenkins{}
jenkins.Test(t)
jenkins.
On("BuildJob", ctx, jobID, map[string]string{}).
job := &mocks.Job{}
job.Test(t)
job.
On("Poll", ctx).Return(200, nil).
On("InvokeSimple", ctx, jobParameters).
Return(queueID, nil).
On("GetBuildFromQueueID", ctx, queueID).
On("GetJob").
Return(&gojenkins.Job{})
jenkins.
On("GetBuildFromQueueID", ctx, mock.Anything, queueID).
Return(&gojenkins.Build{}, nil)
// test
build, err := TriggerJob(ctx, jenkins, jobName, jobParameters)
build, err := TriggerJob(ctx, jenkins, job, jobParameters)
// asserts
jenkins.AssertExpectations(t)
assert.NoError(t, err)

35
pkg/jenkins/job.go Normal file
View File

@@ -0,0 +1,35 @@
package jenkins
import (
"context"
"github.com/bndr/gojenkins"
)
// Task is an interface to abstract gojenkins.Task.
// mock generated with: mockery --name Job --dir pkg/jenkins --output pkg/jenkins/mocks
type Job interface {
Poll(context.Context) (int, error)
InvokeSimple(ctx context.Context, params map[string]string) (int64, error)
GetJob() *gojenkins.Job
}
// JobImpl is a wrapper struct for gojenkins.Task that respects the Task interface.
type JobImpl struct {
Job *gojenkins.Job
}
// Poll refers to the gojenkins.Job.Poll function.
func (t *JobImpl) Poll(ctx context.Context) (int, error) {
return t.Job.Poll(ctx)
}
// InvokeSimple refers to the gojenkins.Job.InvokeSimple function.
func (t *JobImpl) InvokeSimple(ctx context.Context, params map[string]string) (int64, error) {
return t.Job.InvokeSimple(ctx, params)
}
// GetJob returns wrapped gojenkins.Job.
func (t *JobImpl) GetJob() *gojenkins.Job {
return t.Job
}

View File

@@ -36,13 +36,13 @@ func (_m *Jenkins) BuildJob(ctx context.Context, name string, params map[string]
return r0, r1
}
// GetBuildFromQueueID provides a mock function with given fields: ctx, queueid
func (_m *Jenkins) GetBuildFromQueueID(ctx context.Context, queueid int64) (*gojenkins.Build, error) {
ret := _m.Called(ctx, queueid)
// GetBuildFromQueueID provides a mock function with given fields: ctx, job, queueid
func (_m *Jenkins) GetBuildFromQueueID(ctx context.Context, job *gojenkins.Job, queueid int64) (*gojenkins.Build, error) {
ret := _m.Called(ctx, job, queueid)
var r0 *gojenkins.Build
if rf, ok := ret.Get(0).(func(context.Context, int64) *gojenkins.Build); ok {
r0 = rf(ctx, queueid)
if rf, ok := ret.Get(0).(func(context.Context, *gojenkins.Job, int64) *gojenkins.Build); ok {
r0 = rf(ctx, job, queueid)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gojenkins.Build)
@@ -50,11 +50,27 @@ func (_m *Jenkins) GetBuildFromQueueID(ctx context.Context, queueid int64) (*goj
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, queueid)
if rf, ok := ret.Get(1).(func(context.Context, *gojenkins.Job, int64) error); ok {
r1 = rf(ctx, job, queueid)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetJobObj provides a mock function with given fields: ctx, name
func (_m *Jenkins) GetJobObj(ctx context.Context, name string) *gojenkins.Job {
ret := _m.Called(ctx, name)
var r0 *gojenkins.Job
if rf, ok := ret.Get(0).(func(context.Context, string) *gojenkins.Job); ok {
r0 = rf(ctx, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gojenkins.Job)
}
}
return r0
}

74
pkg/jenkins/mocks/Job.go Normal file
View File

@@ -0,0 +1,74 @@
// Code generated by mockery v2.7.5. DO NOT EDIT.
package mocks
import (
context "context"
gojenkins "github.com/bndr/gojenkins"
mock "github.com/stretchr/testify/mock"
)
// Job is an autogenerated mock type for the Job type
type Job struct {
mock.Mock
}
// GetJob provides a mock function with given fields:
func (_m *Job) GetJob() *gojenkins.Job {
ret := _m.Called()
var r0 *gojenkins.Job
if rf, ok := ret.Get(0).(func() *gojenkins.Job); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gojenkins.Job)
}
}
return r0
}
// InvokeSimple provides a mock function with given fields: ctx, params
func (_m *Job) InvokeSimple(ctx context.Context, params map[string]string) (int64, error) {
ret := _m.Called(ctx, params)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, map[string]string) int64); ok {
r0 = rf(ctx, params)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, map[string]string) error); ok {
r1 = rf(ctx, params)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Poll provides a mock function with given fields: _a0
func (_m *Job) Poll(_a0 context.Context) (int, error) {
ret := _m.Called(_a0)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context) int); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}