Add seedjob agent
This commit is contained in:
parent
76e72702f2
commit
ebd44b83fc
|
|
@ -3,15 +3,18 @@ package client
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/bndr/gojenkins"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 +55,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 +140,23 @@ 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)
|
||||
result := make(map[string]string)
|
||||
|
||||
for i, name := range regex.SubexpNames() {
|
||||
if i != 0 && name != "" {
|
||||
result[name] = match[i]
|
||||
}
|
||||
}
|
||||
|
||||
return result["secret"], nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -168,7 +168,6 @@ import jenkins.model.GlobalConfiguration
|
|||
GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).useScriptSecurity=false
|
||||
GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save()
|
||||
`
|
||||
|
||||
// GetBaseConfigurationConfigMapName returns name of Kubernetes config map used to base configuration
|
||||
func GetBaseConfigurationConfigMapName(jenkins *v1alpha2.Jenkins) string {
|
||||
return fmt.Sprintf("%s-base-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
|
||||
|
|
|
|||
|
|
@ -5,20 +5,24 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||
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"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
stackerr "github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
k8s "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -42,6 +46,10 @@ 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
|
||||
AgentName = "seed-job-agent"
|
||||
AgentNamespace = "default"
|
||||
)
|
||||
|
||||
// SeedJobs defines API for configuring and ensuring Jenkins Seed Jobs and Deploy Keys
|
||||
|
|
@ -88,6 +96,24 @@ func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err
|
|||
return false, stackerr.WithStack(s.k8sClient.Update(context.TODO(), jenkins))
|
||||
}
|
||||
|
||||
if len(seedJobIDs) > 0 {
|
||||
err := CreateAgent(s.jenkinsClient, s.k8sClient, jenkins, jenkins.Namespace, AgentName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if len(seedJobIDs) == 0 {
|
||||
err := s.k8sClient.Delete(context.TODO(), &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: AgentNamespace,
|
||||
Name: fmt.Sprintf("%s-deployment", AgentName),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return done, err
|
||||
}
|
||||
}
|
||||
|
||||
return done, nil
|
||||
}
|
||||
|
||||
|
|
@ -233,6 +259,122 @@ func (s *SeedJobs) isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func CreateAgent(jenkinsClient jenkinsclient.Jenkins, k8sClient client.Client, jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string) error {
|
||||
var exists bool
|
||||
|
||||
nodes, err := jenkinsClient.GetAllNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(nodes) != 0 {
|
||||
for _, node := range nodes {
|
||||
if node.GetName() == agentName {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create node if not exists
|
||||
if !exists {
|
||||
_, err = jenkinsClient.CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
deployments := &apps.DeploymentList{}
|
||||
exists = false
|
||||
secret, err := jenkinsClient.GetNodeSecret(agentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deployment := agentDeployment(jenkinsManifest, namespace, agentName, secret)
|
||||
err = k8sClient.List(context.TODO(), &client.ListOptions{}, deployments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(deployments.Items) > 0 {
|
||||
for _, deployment := range deployments.Items {
|
||||
if deployment.ObjectMeta.Name == fmt.Sprintf("%s-deployment", agentName) {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create deployment if not exists
|
||||
if !exists {
|
||||
err = k8sClient.Create(context.TODO(), deployment)
|
||||
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: fmt.Sprintf("%s-deployment", agentName),
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: apps.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: fmt.Sprintf("%s-container", agentName),
|
||||
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),
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: apps.DeploymentStatus{},
|
||||
}
|
||||
}
|
||||
|
||||
// seedJobConfigXML this is the XML representation of seed job
|
||||
var seedJobConfigXML = `
|
||||
<flow-definition plugin="workflow-job@2.30">
|
||||
|
|
@ -319,7 +461,6 @@ executeDslScripts.setSandbox(false)
|
|||
executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE)
|
||||
executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE)
|
||||
executeDslScripts.setLookupStrategy(LookupStrategy.SEED_JOB)
|
||||
executeDslScripts.setAdditionalClasspath("src")
|
||||
|
||||
if (jobRef == null) {
|
||||
jobRef = jenkins.createProject(FreeStyleProject, jobDslSeedName)
|
||||
|
|
@ -329,7 +470,7 @@ jobRef.getBuildersList().add(executeDslScripts)
|
|||
jobRef.setDisplayName("${params.` + displayNameParameterName + `}")
|
||||
jobRef.setScm(scm)
|
||||
// TODO don't use master executors
|
||||
jobRef.setAssignedLabel(new LabelAtom("master"))
|
||||
jobRef.setAssignedLabel(new LabelAtom("`+AgentName+`"))
|
||||
|
||||
jenkins.getQueue().schedule(jobRef)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import (
|
|||
"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"
|
||||
|
|
@ -28,7 +29,7 @@ func TestEnsureSeedJobs(t *testing.T) {
|
|||
ctx := context.TODO()
|
||||
defer ctrl.Finish()
|
||||
|
||||
jenkinsClient := client.NewMockJenkins(ctrl)
|
||||
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -219,3 +220,80 @@ func TestSeedJobs_isRecreatePodNeeded(t *testing.T) {
|
|||
assert.True(t, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateAgent(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()
|
||||
|
||||
namespace := "test-namespace"
|
||||
agentName := "test-agent"
|
||||
secret := "test-secret"
|
||||
jenkinsCustomRes := jenkinsCustomResource()
|
||||
testNode := &gojenkins.Node{
|
||||
Raw: &gojenkins.NodeResponse{
|
||||
DisplayName: agentName,
|
||||
},
|
||||
}
|
||||
|
||||
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jenkinsClient.EXPECT().GetNode(agentName).Return(testNode, nil)
|
||||
jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(secret, nil)
|
||||
jenkinsClient.EXPECT().GetAllNodes().Return([]*gojenkins.Node{}, nil)
|
||||
jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).Return(testNode, nil)
|
||||
|
||||
// when
|
||||
err = CreateAgent(jenkinsClient, fakeClient, jenkinsCustomRes, namespace, agentName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
//then
|
||||
err = fakeClient.Get(ctx, types.NamespacedName{Name: fmt.Sprintf("%s-deployment", agentName), Namespace: namespace}, &appsv1.Deployment{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
node, err := jenkinsClient.GetNode(agentName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, node.Raw.DisplayName, testNode.Raw.DisplayName)
|
||||
})
|
||||
|
||||
t.Run("not fail when deployment is available", func(t *testing.T) {
|
||||
// given
|
||||
ctrl := gomock.NewController(t)
|
||||
ctx := context.TODO()
|
||||
defer ctrl.Finish()
|
||||
|
||||
namespace := "test-namespace"
|
||||
agentName := "test-agent"
|
||||
secret := "test-secret"
|
||||
|
||||
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(secret, nil)
|
||||
jenkinsClient.EXPECT().GetAllNodes().Return([]*gojenkins.Node{}, nil)
|
||||
jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName)
|
||||
|
||||
// when
|
||||
err = fakeClient.Create(ctx, &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-deployment", agentName),
|
||||
Namespace:namespace,
|
||||
},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
// then
|
||||
err = CreateAgent(jenkinsClient, fakeClient, jenkinsCustomResource(), namespace, agentName)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue