Merge pull request #51 from jakalkhalili/v0.2.0

#45 Add support for imagePullSecrets parameter
This commit is contained in:
Tomasz Sęk 2019-07-15 10:04:36 +02:00 committed by GitHub
commit ba3da3502a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 353 additions and 7 deletions

View File

@ -5,12 +5,13 @@ This document describes a getting started guide for **jenkins-operator** and an
1. [First Steps](#first-steps) 1. [First Steps](#first-steps)
2. [Deploy Jenkins](#deploy-jenkins) 2. [Deploy Jenkins](#deploy-jenkins)
3. [Configure Seed Jobs and Pipelines](#configure-seed-jobs-and-pipelines) 3. [Configure Seed Jobs and Pipelines](#configure-seed-jobs-and-pipelines)
4. [Install Plugins](#install-plugins) 4. [Pulling Docker images from private repositories](#pulling-docker-images-from-private-repositories)
5. [Configure Backup & Restore](#configure-backup-and-restore) 5. [Install Plugins](#install-plugins)
6. [AKS](#aks) 6. [Configure Backup & Restore](#configure-backup-and-restore)
7. [Jenkins login credentials](#jenkins-login-credentials) 7. [AKS](#aks)
8. [Override default Jenkins container command](#override-default-Jenkins-container-command) 8. [Jenkins login credentials](#jenkins-login-credentials)
9. [Debugging](#debugging) 9. [Override default Jenkins container command](#override-default-Jenkins-container-command)
10. [Debugging](#debugging)
## First Steps ## First Steps
@ -293,6 +294,79 @@ data:
password: password_or_token password: password_or_token
``` ```
## Pulling Docker images from private repositories
To pull Docker Image from private repository you can use `imagePullSecrets`.
Please follow the instructions on [creating a secret with a docker config](https://kubernetes.io/docs/concepts/containers/images/?origin_team=T42NTAGHM#creating-a-secret-with-a-docker-config).
### Docker Hub Configuration
To use Docker Hub additional steps are required.
Edit the previously created secret:
```bash
kubectl -n <namespace> edit secret <name>
```
The `.dockerconfigjson` key's value needs to be replaced with a modified version.
After modifications it needs to be encoded as Base64 value before setting the `.dockerconfigjson` key:q.
Example config file to modify and use:
```
{
"auths":{
"https://index.docker.io/v1/":{
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"auth.docker.io":{
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"registry.docker.io":{
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"docker.io":{
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"https://registry-1.docker.io/v2/": {
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"registry-1.docker.io/v2/": {
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"registry-1.docker.io": {
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
},
"https://registry-1.docker.io": {
"username":"user",
"password":"password",
"email":"yourdockeremail@gmail.com",
"auth":"base64 of string user:password"
}
}
}
```
## Jenkins Customisation ## Jenkins Customisation
Jenkins can be customized using groovy scripts or configuration as code plugin. All custom configuration is stored in Jenkins can be customized using groovy scripts or configuration as code plugin. All custom configuration is stored in

View File

@ -308,7 +308,7 @@ To use default CRD file:
kubectl -n <namespace> apply -f https://github.com/jenkinsci/kubernetes-operator/blob/master/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml kubectl -n <namespace> apply -f https://github.com/jenkinsci/kubernetes-operator/blob/master/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml
``` ```
## Update RBAC to new verison ## Update RBAC to new version
New operator version requires updated RBAC permissions: New operator version requires updated RBAC permissions:

View File

@ -210,6 +210,13 @@ type JenkinsMaster struct {
// memory: 600Mi // memory: 600Mi
Containers []Container `json:"containers,omitempty"` Containers []Container `json:"containers,omitempty"`
// ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec.
// If specified, these secrets will be passed to individual puller implementations for them to use. For example,
// in the case of docker, only DockerConfig type secrets are honored.
// More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod
// +optional
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
// List of volumes that can be mounted by containers belonging to the pod. // List of volumes that can be mounted by containers belonging to the pod.
// More info: https://kubernetes.io/docs/concepts/storage/volumes // More info: https://kubernetes.io/docs/concepts/storage/volumes
// +optional // +optional

View File

@ -322,6 +322,11 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
if in.ImagePullSecrets != nil {
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
*out = make([]v1.LocalObjectReference, len(*in))
copy(*out, *in)
}
if in.Volumes != nil { if in.Volumes != nil {
in, out := &in.Volumes, &out.Volumes in, out := &in.Volumes, &out.Volumes
*out = make([]v1.Volume, len(*in)) *out = make([]v1.Volume, len(*in))

View File

@ -527,6 +527,12 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa
return true return true
} }
if !reflect.DeepEqual(r.jenkins.Spec.Master.ImagePullSecrets, currentJenkinsMasterPod.Spec.ImagePullSecrets) {
r.logger.Info(fmt.Sprintf("Jenkins Pod ImagePullSecrets has changed, actual '%+v' required '%+v', recreating pod",
currentJenkinsMasterPod.Spec.ImagePullSecrets, r.jenkins.Spec.Master.ImagePullSecrets))
return true
}
if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) { if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) {
r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v', recreating pod", r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v', recreating pod",
currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector)) currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector))

View File

@ -124,6 +124,29 @@ func TestGetJenkinsOpts(t *testing.T) {
assert.Contains(t, opts, "httpPort") assert.Contains(t, opts, "httpPort")
assert.Equal(t, opts["httpPort"], "8080") assert.Equal(t, opts["httpPort"], "8080")
}) })
t.Run("JENKINS_OPTS have --httpPort=--8080 argument", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
Containers: []v1alpha2.Container{
{
Env: []corev1.EnvVar{
{Name: "JENKINS_OPTS", Value: "--httpPort=--8080"},
},
},
},
},
},
}
opts := GetJenkinsOpts(jenkins)
assert.Equal(t, 1, len(opts))
assert.NotContains(t, opts, "prefix")
assert.Contains(t, opts, "httpPort")
assert.Equal(t, opts["httpPort"], "--8080")
})
} }
func TestCompareContainerVolumeMounts(t *testing.T) { func TestCompareContainerVolumeMounts(t *testing.T) {

View File

@ -288,6 +288,7 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins
Containers: newContainers(jenkins), Containers: newContainers(jenkins),
Volumes: append(GetJenkinsMasterPodBaseVolumes(jenkins), jenkins.Spec.Master.Volumes...), Volumes: append(GetJenkinsMasterPodBaseVolumes(jenkins), jenkins.Spec.Master.Volumes...),
SecurityContext: jenkins.Spec.Master.SecurityContext, SecurityContext: jenkins.Spec.Master.SecurityContext,
ImagePullSecrets: jenkins.Spec.Master.ImagePullSecrets,
}, },
} }
} }

View File

@ -60,6 +60,47 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins)
return true, nil return true, nil
} }
func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() (bool, error) {
var err error
for _, sr := range r.jenkins.Spec.Master.ImagePullSecrets {
valid, err := r.validateImagePullSecret(sr.Name)
if err != nil || !valid {
return false, nil
}
}
return true, err
}
func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecret(name string) (bool, error) {
secret := &corev1.Secret{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
if err != nil && apierrors.IsNotFound(err) {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret %s not found defined in spec.master.imagePullSecrets", name))
return false, nil
} else if err != nil && !apierrors.IsNotFound(err) {
return false, stackerr.WithStack(err)
}
if secret.Data["docker-server"] == nil {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-server' key.", name))
return false, nil
}
if secret.Data["docker-username"] == nil {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-username' key.", name))
return false, nil
}
if secret.Data["docker-password"] == nil {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-password' key.", name))
return false, nil
}
if secret.Data["docker-email"] == nil {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-email' key.", name))
return false, nil
}
return true, nil
}
func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() (bool, error) { func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() (bool, error) {
valid := true valid := true
for _, volume := range r.jenkins.Spec.Master.Volumes { for _, volume := range r.jenkins.Spec.Master.Volumes {

View File

@ -132,6 +132,195 @@ func TestValidatePlugins(t *testing.T) {
}) })
} }
func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T) {
t.Run("happy", func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ref",
},
Data: map[string][]byte{
"docker-server": []byte("test_server"),
"docker-username": []byte("test_user"),
"docker-password": []byte("test_password"),
"docker-email": []byte("test_email"),
},
}
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
ImagePullSecrets: []corev1.LocalObjectReference{
{Name: secret.ObjectMeta.Name},
},
},
},
}
fakeClient := fake.NewFakeClient()
err := fakeClient.Create(context.TODO(), secret)
assert.NoError(t, err)
baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false),
&jenkins, false, false, nil, nil)
got, err := baseReconcileLoop.validateImagePullSecrets()
assert.Equal(t, got, true)
assert.NoError(t, err)
})
t.Run("no secret", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
ImagePullSecrets: []corev1.LocalObjectReference{
{Name: "test-ref"},
},
},
},
}
fakeClient := fake.NewFakeClient()
baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false),
&jenkins, false, false, nil, nil)
got, _ := baseReconcileLoop.validateImagePullSecrets()
assert.Equal(t, got, false)
})
t.Run("no docker email", func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ref",
},
Data: map[string][]byte{
"docker-server": []byte("test_server"),
"docker-username": []byte("test_user"),
"docker-password": []byte("test_password"),
},
}
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
ImagePullSecrets: []corev1.LocalObjectReference{
{Name: secret.ObjectMeta.Name},
},
},
},
}
fakeClient := fake.NewFakeClient()
err := fakeClient.Create(context.TODO(), secret)
assert.NoError(t, err)
baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false),
&jenkins, false, false, nil, nil)
got, _ := baseReconcileLoop.validateImagePullSecrets()
assert.Equal(t, got, false)
})
t.Run("no docker password", func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ref",
},
Data: map[string][]byte{
"docker-server": []byte("test_server"),
"docker-username": []byte("test_user"),
"docker-email": []byte("test_email"),
},
}
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
ImagePullSecrets: []corev1.LocalObjectReference{
{Name: secret.ObjectMeta.Name},
},
},
},
}
fakeClient := fake.NewFakeClient()
err := fakeClient.Create(context.TODO(), secret)
assert.NoError(t, err)
baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false),
&jenkins, false, false, nil, nil)
got, _ := baseReconcileLoop.validateImagePullSecrets()
assert.Equal(t, got, false)
})
t.Run("no docker username", func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ref",
},
Data: map[string][]byte{
"docker-server": []byte("test_server"),
"docker-password": []byte("test_password"),
"docker-email": []byte("test_email"),
},
}
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
ImagePullSecrets: []corev1.LocalObjectReference{
{Name: secret.ObjectMeta.Name},
},
},
},
}
fakeClient := fake.NewFakeClient()
err := fakeClient.Create(context.TODO(), secret)
assert.NoError(t, err)
baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false),
&jenkins, false, false, nil, nil)
got, _ := baseReconcileLoop.validateImagePullSecrets()
assert.Equal(t, got, false)
})
t.Run("no docker server", func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ref",
},
Data: map[string][]byte{
"docker-username": []byte("test_user"),
"docker-password": []byte("test_password"),
"docker-email": []byte("test_email"),
},
}
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
ImagePullSecrets: []corev1.LocalObjectReference{
{Name: secret.ObjectMeta.Name},
},
},
},
}
fakeClient := fake.NewFakeClient()
err := fakeClient.Create(context.TODO(), secret)
assert.NoError(t, err)
baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false),
&jenkins, false, false, nil, nil)
got, _ := baseReconcileLoop.validateImagePullSecrets()
assert.Equal(t, got, false)
})
}
func TestValidateJenkinsMasterPodEnvs(t *testing.T) { func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
t.Run("happy", func(t *testing.T) { t.Run("happy", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{ jenkins := v1alpha2.Jenkins{