Merge pull request #78 from jakalkhalili/master

Add seedjob agent
This commit is contained in:
Tomasz Sęk 2019-08-27 13:48:58 +02:00 committed by GitHub
commit 183ff51eda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 348 additions and 227 deletions

View File

@ -5,13 +5,17 @@ import (
"fmt"
"net/http"
"os/exec"
"regexp"
"strings"
"github.com/bndr/gojenkins"
"github.com/pkg/errors"
)
var errorNotFound = errors.New("404")
var (
errorNotFound = errors.New("404")
regex = regexp.MustCompile("(<application-desc main-class=\"hudson.remoting.jnlp.Main\"><argument>)(?P<secret>[a-z0-9]*)")
)
// Jenkins defines Jenkins API
type Jenkins interface {
@ -52,6 +56,7 @@ type Jenkins interface {
CreateView(name string, viewType string) (*gojenkins.View, error)
Poll() (int, error)
ExecuteScript(groovyScript string) (logs string, err error)
GetNodeSecret(name string) (string, error)
}
type jenkins struct {
@ -136,3 +141,27 @@ func isNotFoundError(err error) bool {
}
return false
}
func (jenkins *jenkins) GetNodeSecret(name string) (string, error) {
var content string
_, err := jenkins.Requester.GetXML(fmt.Sprintf("/computer/%s/slave-agent.jnlp", name), &content, nil)
if err != nil {
return "", err
}
match := regex.FindStringSubmatch(content)
if match == nil {
return "", errors.New("Node secret cannot be parsed")
}
result := make(map[string]string)
for i, name := range regex.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
return result["secret"], nil
}

View File

@ -16,6 +16,19 @@ type MockJenkins struct {
recorder *MockJenkinsMockRecorder
}
func (m *MockJenkins) GetNodeSecret(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNodeSecret", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
func (mr *MockJenkinsMockRecorder) GetNodeSecret(name string) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeSecret", reflect.TypeOf((*MockJenkins)(nil).GetNodeSecret), name)
}
// MockJenkinsMockRecorder is the mock recorder for MockJenkins
type MockJenkinsMockRecorder struct {
mock *MockJenkins

View File

@ -2,7 +2,6 @@ package resources
import (
"fmt"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"

View File

@ -11,27 +11,21 @@ import (
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs"
"github.com/jenkinsci/kubernetes-operator/pkg/log"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/groovy"
"github.com/go-logr/logr"
stackerr "github.com/pkg/errors"
apps "k8s.io/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
k8s "sigs.k8s.io/controller-runtime/pkg/client"
)
const (
// ConfigureSeedJobsName this is the fixed seed job name
ConfigureSeedJobsName = constants.OperatorName + "-configure-seed-job"
idParameterName = "ID"
credentialIDParameterName = "CREDENTIAL_ID"
repositoryURLParameterName = "REPOSITORY_URL"
repositoryBranchParameterName = "REPOSITORY_BRANCH"
targetsParameterName = "TARGETS"
displayNameParameterName = "SEED_JOB_DISPLAY_NAME"
// UsernameSecretKey is username data key in Kubernetes secret used to create Jenkins username/password credential
UsernameSecretKey = "username"
// PasswordSecretKey is password data key in Kubernetes secret used to create Jenkins username/password credential
@ -42,6 +36,9 @@ const (
// JenkinsCredentialTypeLabelName is label for kubernetes-credentials-provider-plugin which determine Jenkins
// credential type
JenkinsCredentialTypeLabelName = "jenkins.io/credentials-type"
// AgentName is the name of seed job agent
AgentName = "jnlp"
)
// SeedJobs defines API for configuring and ensuring Jenkins Seed Jobs and Deploy Keys
@ -67,20 +64,35 @@ func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err
return false, s.restartJenkinsMasterPod(*jenkins)
}
if err = s.createJob(); err != nil {
s.logger.V(log.VWarn).Info("Couldn't create jenkins seed job")
return false, err
if len(jenkins.Spec.SeedJobs) > 0 {
err := s.createAgent(s.jenkinsClient, s.k8sClient, jenkins, jenkins.Namespace, AgentName)
if err != nil {
return false, err
}
} else if len(jenkins.Spec.SeedJobs) == 0 {
err := s.k8sClient.Delete(context.TODO(), &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: jenkins.Namespace,
Name: AgentName,
},
})
if err != nil && !apierrors.IsNotFound(err) {
return false, err
}
}
if err = s.ensureLabelsForSecrets(*jenkins); err != nil {
return false, err
}
done, err = s.buildJobs(jenkins)
requeue, err := s.createJobs(jenkins)
if err != nil {
s.logger.V(log.VWarn).Info("Couldn't build jenkins seed job")
return false, err
}
if requeue {
return false, nil
}
seedJobIDs := s.getAllSeedJobIDs(*jenkins)
if done && !reflect.DeepEqual(seedJobIDs, jenkins.Status.CreatedSeedJobs) {
@ -88,19 +100,35 @@ func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err
return false, stackerr.WithStack(s.k8sClient.Update(context.TODO(), jenkins))
}
return done, nil
return true, nil
}
// createJob is responsible for creating jenkins job which configures jenkins seed jobs and deploy keys
func (s *SeedJobs) createJob() error {
_, created, err := s.jenkinsClient.CreateOrUpdateJob(seedJobConfigXML, ConfigureSeedJobsName)
if err != nil {
return err
func (s *SeedJobs) createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err error) {
groovyClient := groovy.New(s.jenkinsClient, s.k8sClient, s.logger, jenkins, "user-groovy", jenkins.Spec.GroovyScripts.Customization)
for _, seedJob := range jenkins.Spec.SeedJobs {
credentialValue, err := s.credentialValue(jenkins.Namespace, seedJob)
if err != nil {
return true, err
}
groovyScript := seedJobCreatingGroovyScript(seedJob)
hash := sha256.New()
hash.Write([]byte(groovyScript))
hash.Write([]byte(credentialValue))
requeue, err := groovyClient.EnsureSingle(seedJob.ID, fmt.Sprintf("%s.groovy", seedJob.ID), base64.URLEncoding.EncodeToString(hash.Sum(nil)), groovyScript)
if err != nil {
return true, err
}
if requeue {
return true, nil
}
}
if created {
s.logger.Info(fmt.Sprintf("'%s' job has been created", ConfigureSeedJobsName))
}
return nil
return false, nil
}
// ensureLabelsForSecrets adds labels to Kubernetes secrets where are Jenkins credentials used for seed jobs,
@ -132,45 +160,6 @@ func (s *SeedJobs) ensureLabelsForSecrets(jenkins v1alpha2.Jenkins) error {
return nil
}
// buildJobs is responsible for running jenkins builds which configures jenkins seed jobs and deploy keys
func (s *SeedJobs) buildJobs(jenkins *v1alpha2.Jenkins) (done bool, err error) {
allDone := true
for _, seedJob := range jenkins.Spec.SeedJobs {
credentialValue, err := s.credentialValue(jenkins.Namespace, seedJob)
if err != nil {
return false, err
}
parameters := map[string]string{
idParameterName: seedJob.ID,
credentialIDParameterName: seedJob.CredentialID,
repositoryURLParameterName: seedJob.RepositoryURL,
repositoryBranchParameterName: seedJob.RepositoryBranch,
targetsParameterName: seedJob.Targets,
displayNameParameterName: fmt.Sprintf("Seed Job from %s", seedJob.ID),
}
hash := sha256.New()
hash.Write([]byte(parameters[idParameterName]))
hash.Write([]byte(parameters[credentialIDParameterName]))
hash.Write([]byte(credentialValue))
hash.Write([]byte(parameters[repositoryURLParameterName]))
hash.Write([]byte(parameters[repositoryBranchParameterName]))
hash.Write([]byte(parameters[targetsParameterName]))
hash.Write([]byte(parameters[displayNameParameterName]))
encodedHash := base64.URLEncoding.EncodeToString(hash.Sum(nil))
jobsClient := jobs.New(s.jenkinsClient, s.k8sClient, s.logger)
done, err := jobsClient.EnsureBuildJob(ConfigureSeedJobsName, encodedHash, parameters, jenkins, true)
if err != nil {
return false, err
}
if !done {
allDone = false
}
}
return allDone, nil
}
func (s *SeedJobs) credentialValue(namespace string, seedJob v1alpha2.SeedJob) (string, error) {
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
secret := &corev1.Secret{}
@ -233,56 +222,117 @@ func (s *SeedJobs) isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool {
return false
}
// seedJobConfigXML this is the XML representation of seed job
var seedJobConfigXML = `
<flow-definition plugin="workflow-job@2.30">
<actions/>
<description>Configure Seed Jobs</description>
<keepDependencies>false</keepDependencies>
<properties>
<hudson.model.ParametersDefinitionProperty>
<parameterDefinitions>
<hudson.model.StringParameterDefinition>
<name>` + idParameterName + `</name>
<description></description>
<defaultValue></defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>` + credentialIDParameterName + `</name>
<description></description>
<defaultValue></defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>` + repositoryURLParameterName + `</name>
<description></description>
<defaultValue></defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>` + repositoryBranchParameterName + `</name>
<description></description>
<defaultValue>master</defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>` + displayNameParameterName + `</name>
<description></description>
<defaultValue></defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>` + targetsParameterName + `</name>
<description></description>
<defaultValue>cicd/jobs/*.jenkins</defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
</parameterDefinitions>
</hudson.model.ParametersDefinitionProperty>
</properties>
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61">
<script>
// createAgent deploys Jenkins agent to Kubernetes cluster
func (s SeedJobs) createAgent(jenkinsClient jenkinsclient.Jenkins, k8sClient client.Client, jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string) error {
_, err := jenkinsClient.GetNode(agentName)
// Create node if not exists
if err != nil && err.Error() == "No node found" {
_, err = jenkinsClient.CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName)
if err != nil {
return err
}
} else if err != nil {
return err
}
secret, err := jenkinsClient.GetNodeSecret(agentName)
if err != nil {
return err
}
deployment := agentDeployment(jenkinsManifest, namespace, agentName, secret)
err = k8sClient.Create(context.TODO(), deployment)
if apierrors.IsAlreadyExists(err) {
err := k8sClient.Update(context.TODO(), deployment)
if err != nil {
return err
}
} else if err != nil {
return err
}
return nil
}
func agentDeployment(jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string, secret string) *apps.Deployment {
return &apps.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: agentName,
Namespace: namespace,
},
Spec: apps.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "jnlp",
Image: "jenkins/jnlp-slave:alpine",
Env: []corev1.EnvVar{
{
Name: "JENKINS_TUNNEL",
Value: fmt.Sprintf("%s.%s:%d",
resources.GetJenkinsSlavesServiceName(jenkinsManifest),
jenkinsManifest.ObjectMeta.Namespace,
jenkinsManifest.Spec.SlaveService.Port),
},
{
Name: "JENKINS_SECRET",
Value: secret,
},
{
Name: "JENKINS_AGENT_NAME",
Value: agentName,
},
{
Name: "JENKINS_URL",
Value: fmt.Sprintf("http://%s.%s:%d",
resources.GetJenkinsHTTPServiceName(jenkinsManifest),
jenkinsManifest.ObjectMeta.Namespace,
jenkinsManifest.Spec.Service.Port,
),
},
{
Name: "JENKINS_AGENT_WORKDIR",
Value: "/home/jenkins/agent",
},
},
},
},
},
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": fmt.Sprintf("%s-selector", agentName),
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": fmt.Sprintf("%s-selector", agentName),
},
},
},
}
}
func seedJobCreatingGroovyScript(s v1alpha2.SeedJob) string {
return `
import hudson.model.FreeStyleProject;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.BranchSpec;
import hudson.triggers.SCMTrigger;
import hudson.util.Secret;
import javaposse.jobdsl.plugin.*;
import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import jenkins.model.JenkinsLocationConfiguration;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition;
import hudson.model.FreeStyleProject
import hudson.model.labels.LabelAtom
import hudson.plugins.git.BranchSpec
@ -298,44 +348,40 @@ import static com.google.common.collect.Lists.newArrayList
Jenkins jenkins = Jenkins.instance
def jobDslSeedName = &quot;${params.` + idParameterName + `}-` + constants.SeedJobSuffix + `&quot;
def jobDslSeedName = "` + s.ID + `-` + constants.SeedJobSuffix + `";
def jobRef = jenkins.getItem(jobDslSeedName)
def repoList = GitSCM.createRepoList(&quot;${params.` + repositoryURLParameterName + `}&quot;, &quot;${params.` + credentialIDParameterName + `}&quot;)
def gitExtensions = [new CloneOption(true, true, &quot;&quot;, 10)]
def repoList = GitSCM.createRepoList("` + s.RepositoryURL + `", "` + s.CredentialID + `")
def gitExtensions = [new CloneOption(true, true, ";", 10)]
def scm = new GitSCM(
repoList,
newArrayList(new BranchSpec(&quot;${params.` + repositoryBranchParameterName + `}&quot;)),
newArrayList(new BranchSpec("` + s.RepositoryBranch + `")),
false,
Collections.&lt;SubmoduleConfig&gt; emptyList(),
Collections.<SubmoduleConfig>emptyList(),
null,
null,
gitExtensions
)
def executeDslScripts = new ExecuteDslScripts()
executeDslScripts.setTargets(&quot;${params.` + targetsParameterName + `}&quot;)
executeDslScripts.setTargets("` + s.Targets + `")
executeDslScripts.setSandbox(false)
executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE)
executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE)
executeDslScripts.setLookupStrategy(LookupStrategy.SEED_JOB)
executeDslScripts.setAdditionalClasspath(&quot;src&quot;)
if (jobRef == null) {
jobRef = jenkins.createProject(FreeStyleProject, jobDslSeedName)
}
jobRef.getBuildersList().clear()
jobRef.getBuildersList().add(executeDslScripts)
jobRef.setDisplayName(&quot;${params.` + displayNameParameterName + `}&quot;)
jobRef.setDisplayName("` + fmt.Sprintf("Seed Job from %s", s.ID) + `")
jobRef.setScm(scm)
// TODO don't use master executors
jobRef.setAssignedLabel(new LabelAtom(&quot;master&quot;))
jobRef.setAssignedLabel(new LabelAtom("` + AgentName + `"))
jenkins.getQueue().schedule(jobRef)
</script>
<sandbox>false</sandbox>
</definition>
<triggers/>
<disabled>false</disabled>
</flow-definition>
`
}

View File

@ -2,16 +2,17 @@ package seedjobs
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
"github.com/bndr/gojenkins"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -21,97 +22,6 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
func TestEnsureSeedJobs(t *testing.T) {
// given
logger := logf.ZapLogger(false)
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
jenkinsClient := client.NewMockJenkins(ctrl)
fakeClient := fake.NewFakeClient()
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
assert.NoError(t, err)
jenkins := jenkinsCustomResource()
err = fakeClient.Create(ctx, jenkins)
assert.NoError(t, err)
buildNumber := int64(1)
for reconcileAttempt := 1; reconcileAttempt <= 2; reconcileAttempt++ {
logger.Info(fmt.Sprintf("Reconcile attempt #%d", reconcileAttempt))
seedJobs := New(jenkinsClient, fakeClient, logger)
// first run - should create job and schedule build
if reconcileAttempt == 1 {
jenkinsClient.
EXPECT().
CreateOrUpdateJob(seedJobConfigXML, ConfigureSeedJobsName).
Return(nil, true, nil)
jenkinsClient.
EXPECT().
GetJob(ConfigureSeedJobsName).
Return(&gojenkins.Job{
Raw: &gojenkins.JobResponse{
NextBuildNumber: buildNumber,
},
}, nil)
jenkinsClient.
EXPECT().
BuildJob(ConfigureSeedJobsName, gomock.Any()).
Return(int64(0), nil)
}
// second run - should update and finish job
if reconcileAttempt == 2 {
jenkinsClient.
EXPECT().
CreateOrUpdateJob(seedJobConfigXML, ConfigureSeedJobsName).
Return(nil, false, nil)
jenkinsClient.
EXPECT().
GetBuild(ConfigureSeedJobsName, gomock.Any()).
Return(&gojenkins.Build{
Raw: &gojenkins.BuildResponse{
Result: string(v1alpha2.BuildSuccessStatus),
},
}, nil)
}
done, err := seedJobs.EnsureSeedJobs(jenkins)
assert.NoError(t, err)
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
assert.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.Builds), "There is one running job")
build := jenkins.Status.Builds[0]
assert.Equal(t, buildNumber, build.Number)
assert.Equal(t, ConfigureSeedJobsName, build.JobName)
assert.NotNil(t, build.CreateTime)
assert.NotEmpty(t, build.Hash)
assert.NotNil(t, build.LastUpdateTime)
assert.Equal(t, 0, build.Retires)
// first run - should create job and schedule build
if reconcileAttempt == 1 {
assert.False(t, done)
assert.Equal(t, string(v1alpha2.BuildRunningStatus), string(build.Status))
}
// second run - should update and finish job
if reconcileAttempt == 2 {
assert.False(t, done)
assert.Equal(t, string(v1alpha2.BuildSuccessStatus), string(build.Status))
}
}
}
func jenkinsCustomResource() *v1alpha2.Jenkins {
return &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
@ -152,6 +62,130 @@ func jenkinsCustomResource() *v1alpha2.Jenkins {
}
}
func TestEnsureSeedJobs(t *testing.T) {
t.Run("happy", func(t *testing.T) {
// given
logger := logf.ZapLogger(false)
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
fakeClient := fake.NewFakeClient()
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
assert.NoError(t, err)
jenkins := jenkinsCustomResource()
err = fakeClient.Create(ctx, jenkins)
assert.NoError(t, err)
agentName := "jnlp"
agentSecret := "test-secret"
testNode := &gojenkins.Node{
Raw: &gojenkins.NodeResponse{
DisplayName: agentName,
},
}
jenkinsClient.EXPECT().GetNode(agentName).Return(nil, nil).AnyTimes()
jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).Return(testNode, nil).AnyTimes()
jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(agentSecret, nil).AnyTimes()
jenkinsClient.EXPECT().ExecuteScript(seedJobCreatingGroovyScript(jenkins.Spec.SeedJobs[0])).AnyTimes()
seedJobClient := New(jenkinsClient, fakeClient, logger)
// when
_, err = seedJobClient.EnsureSeedJobs(jenkins)
// then
assert.NoError(t, err)
var agentDeployment appsv1.Deployment
err = fakeClient.Get(ctx, types.NamespacedName{Namespace: jenkins.Namespace, Name: agentName}, &agentDeployment)
assert.NoError(t, err)
})
t.Run("delete agent deployment when no seed jobs", func(t *testing.T) {
// given
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
agentName := "test-agent"
agentSecret := "test-secret"
jenkins := jenkinsCustomResource()
jenkins.Spec.SeedJobs = []v1alpha2.SeedJob{}
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
fakeClient := fake.NewFakeClient()
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
assert.NoError(t, err)
jenkinsClient.EXPECT().GetNode(agentName).AnyTimes()
jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).AnyTimes()
jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(agentSecret, nil).AnyTimes()
seedJobsClient := New(jenkinsClient, fakeClient, nil)
err = fakeClient.Create(ctx, &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: agentName,
Namespace: jenkins.Namespace,
},
})
assert.NoError(t, err)
// when
_, err = seedJobsClient.EnsureSeedJobs(jenkins)
// then
assert.NoError(t, err)
var deployment appsv1.Deployment
err = fakeClient.Get(ctx, types.NamespacedName{Name: agentName, Namespace: jenkins.Namespace}, &deployment)
assert.False(t, errors.IsNotFound(err), "Agent deployment hasn't been deleted")
})
}
func TestCreateAgent(t *testing.T) {
t.Run("don't fail when deployment is already created", func(t *testing.T) {
// given
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
agentName := "test-agent"
agentSecret := "test-secret"
jenkins := jenkinsCustomResource()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
fakeClient := fake.NewFakeClient()
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
assert.NoError(t, err)
jenkinsClient.EXPECT().GetNode(agentName).AnyTimes()
jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).AnyTimes()
jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(agentSecret, nil).AnyTimes()
seedJobsClient := New(jenkinsClient, fakeClient, nil)
err = fakeClient.Create(ctx, &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: agentName,
Namespace: jenkins.Namespace,
},
})
assert.NoError(t, err)
// when
err = seedJobsClient.createAgent(jenkinsClient, fakeClient, jenkinsCustomResource(), jenkins.Namespace, agentName)
// then
assert.NoError(t, err)
})
}
func TestSeedJobs_isRecreatePodNeeded(t *testing.T) {
seedJobsClient := New(nil, nil, nil)
t.Run("empty", func(t *testing.T) {

View File

@ -4,7 +4,7 @@ const (
// OperatorName is a operator name
OperatorName = "jenkins-operator"
// DefaultAmountOfExecutors is the default amount of Jenkins executors
DefaultAmountOfExecutors = 3
DefaultAmountOfExecutors = 0
// SeedJobSuffix is a suffix added for all seed jobs
SeedJobSuffix = "job-dsl-seed"
// DefaultJenkinsMasterImage is the default Jenkins master docker image