#28 Mount secrets for groovy and CasC customization

This commit is contained in:
Tomasz Sęk 2019-06-30 23:11:45 +02:00
parent 66236d5459
commit d0b02de429
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
5 changed files with 112 additions and 195 deletions

View File

@ -13,7 +13,6 @@ import (
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/backuprestore"
"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/groovy"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
"github.com/jenkinsci/kubernetes-operator/pkg/log"
@ -149,15 +148,15 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureResourcesRequiredForJenkinsPod
}
r.logger.V(log.VDebug).Info("Base configuration config map is present")
if err := r.createUserConfigurationConfigMap(metaObject); err != nil {
if err := r.addLabelForWatchesResources(r.jenkins.Spec.GroovyScripts.Customization); err != nil {
return err
}
r.logger.V(log.VDebug).Info("User configuration config map is present")
r.logger.V(log.VDebug).Info("GroovyScripts Secret and ConfigMap added watched labels")
if err := r.createUserConfigurationSecret(metaObject); err != nil {
if err := r.addLabelForWatchesResources(r.jenkins.Spec.ConfigurationAsCode.Customization); err != nil {
return err
}
r.logger.V(log.VDebug).Info("User configuration secret is present")
r.logger.V(log.VDebug).Info("ConfigurationAsCode Secret and ConfigMap added watched labels")
if err := r.createRBAC(metaObject); err != nil {
return err
@ -289,33 +288,49 @@ func (r *ReconcileJenkinsBaseConfiguration) createBaseConfigurationConfigMap(met
return stackerr.WithStack(r.createOrUpdateResource(configMap))
}
func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationConfigMap(meta metav1.ObjectMeta) error {
currentConfigMap := &corev1.ConfigMap{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: resources.GetUserConfigurationConfigMapNameFromJenkins(r.jenkins), Namespace: r.jenkins.Namespace}, currentConfigMap)
if err != nil && errors.IsNotFound(err) {
return stackerr.WithStack(r.k8sClient.Create(context.TODO(), resources.NewUserConfigurationConfigMap(r.jenkins)))
} else if err != nil {
return stackerr.WithStack(err)
}
if !resources.VerifyIfLabelsAreSet(currentConfigMap, resources.BuildLabelsForWatchedResources(*r.jenkins)) {
currentConfigMap.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(*r.jenkins)
return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentConfigMap))
func (r *ReconcileJenkinsBaseConfiguration) addLabelForWatchesResources(customization v1alpha2.Customization) error {
labelsForWatchedResources := resources.BuildLabelsForWatchedResources(*r.jenkins)
if len(customization.Secret.Name) > 0 {
secret := &corev1.Secret{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: customization.Secret.Name, Namespace: r.jenkins.Namespace}, secret)
if err != nil {
return stackerr.WithStack(err)
}
if !resources.VerifyIfLabelsAreSet(secret, labelsForWatchedResources) {
if len(secret.ObjectMeta.Labels) == 0 {
secret.ObjectMeta.Labels = map[string]string{}
}
for key, value := range labelsForWatchedResources {
secret.ObjectMeta.Labels[key] = value
}
if err = r.k8sClient.Update(context.TODO(), secret); err != nil {
return stackerr.WithStack(r.k8sClient.Update(context.TODO(), secret))
}
}
}
return nil
}
for _, configMapRef := range customization.Configurations {
configMap := &corev1.ConfigMap{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: configMapRef.Name, Namespace: r.jenkins.Namespace}, configMap)
if err != nil {
return stackerr.WithStack(err)
}
func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationSecret(meta metav1.ObjectMeta) error {
currentSecret := &corev1.Secret{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: resources.GetUserConfigurationSecretNameFromJenkins(r.jenkins), Namespace: r.jenkins.Namespace}, currentSecret)
if err != nil && errors.IsNotFound(err) {
return stackerr.WithStack(r.k8sClient.Create(context.TODO(), resources.NewUserConfigurationSecret(r.jenkins)))
} else if err != nil {
return stackerr.WithStack(err)
}
if !resources.VerifyIfLabelsAreSet(currentSecret, resources.BuildLabelsForWatchedResources(*r.jenkins)) {
currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(*r.jenkins)
return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentSecret))
if !resources.VerifyIfLabelsAreSet(configMap, labelsForWatchedResources) {
if len(configMap.ObjectMeta.Labels) == 0 {
configMap.ObjectMeta.Labels = map[string]string{}
}
for key, value := range labelsForWatchedResources {
configMap.ObjectMeta.Labels[key] = value
}
if err = r.k8sClient.Update(context.TODO(), configMap); err != nil {
return stackerr.WithStack(r.k8sClient.Update(context.TODO(), configMap))
}
}
}
return nil

View File

@ -30,19 +30,12 @@ const (
jenkinsInitConfigurationVolumeName = "init-configuration"
jenkinsInitConfigurationVolumePath = jenkinsPath + "/init-configuration"
jenkinsBaseConfigurationVolumeName = "base-configuration"
// JenkinsBaseConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins
// this scripts are provided by jenkins-operator
JenkinsBaseConfigurationVolumePath = jenkinsPath + "/base-configuration"
jenkinsUserConfigurationVolumeName = "user-configuration"
// JenkinsUserConfigurationVolumePath is a path where are groovy scripts and CasC configs used to configure Jenkins
// this script is provided by user
JenkinsUserConfigurationVolumePath = jenkinsPath + "/user-configuration"
userConfigurationSecretVolumeName = "user-configuration-secrets"
// UserConfigurationSecretVolumePath is a path where are secrets used for groovy scripts and CasC configs
UserConfigurationSecretVolumePath = jenkinsPath + "/user-configuration-secrets"
// GroovyScriptsSecretVolumePath is a path where are groovy scripts used to configure Jenkins
// This script is provided by user
GroovyScriptsSecretVolumePath = jenkinsPath + "/groovy-scripts-secrets"
// ConfigurationAsCodeSecretVolumePath is a path where are CasC configs used to configure Jenkins
// This script is provided by user
ConfigurationAsCodeSecretVolumePath = jenkinsPath + "/configuration-as-code-secrets"
httpPortName = "http"
slavePortName = "slavelistener"
@ -68,8 +61,8 @@ func GetJenkinsMasterContainerBaseCommand() []string {
}
// GetJenkinsMasterContainerBaseEnvs returns Jenkins master pod envs required by operator
func GetJenkinsMasterContainerBaseEnvs() []corev1.EnvVar {
return []corev1.EnvVar{
func GetJenkinsMasterContainerBaseEnvs(jenkins *v1alpha2.Jenkins) []corev1.EnvVar {
envVars := []corev1.EnvVar{
{
Name: "JENKINS_HOME",
Value: jenkinsHomePath,
@ -78,11 +71,16 @@ func GetJenkinsMasterContainerBaseEnvs() []corev1.EnvVar {
Name: "JAVA_OPTS",
Value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true",
},
{
Name: "SECRETS", // https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/demos/kubernetes-secrets/README.md
Value: UserConfigurationSecretVolumePath,
},
}
if len(jenkins.Spec.ConfigurationAsCode.Secret.Name) > 0 {
envVars = append(envVars, corev1.EnvVar{
Name: "SECRETS", // https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/demos/kubernetes-secrets/README.md
Value: ConfigurationAsCodeSecretVolumePath,
})
}
return envVars
}
// GetJenkinsMasterPodBaseVolumes returns Jenkins master pod volumes required by operator
@ -90,7 +88,7 @@ func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume {
configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode
secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode
var scriptsVolumeDefaultMode int32 = 0777
return []corev1.Volume{
volumes := []corev1.Volume{
{
Name: JenkinsHomeVolumeName,
VolumeSource: corev1.VolumeSource{
@ -119,28 +117,6 @@ func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume {
},
},
},
{
Name: jenkinsBaseConfigurationVolumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
DefaultMode: &configMapVolumeSourceDefaultMode,
LocalObjectReference: corev1.LocalObjectReference{
Name: GetBaseConfigurationConfigMapName(jenkins),
},
},
},
},
{
Name: jenkinsUserConfigurationVolumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
DefaultMode: &configMapVolumeSourceDefaultMode,
LocalObjectReference: corev1.LocalObjectReference{
Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins),
},
},
},
},
{
Name: jenkinsOperatorCredentialsVolumeName,
VolumeSource: corev1.VolumeSource{
@ -150,21 +126,45 @@ func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume {
},
},
},
{
Name: userConfigurationSecretVolumeName,
}
if len(jenkins.Spec.GroovyScripts.Secret.Name) > 0 {
volumes = append(volumes, corev1.Volume{
Name: getGroovyScriptsSecretVolumeName(jenkins),
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
DefaultMode: &secretVolumeSourceDefaultMode,
SecretName: GetUserConfigurationSecretNameFromJenkins(jenkins),
SecretName: jenkins.Spec.GroovyScripts.Secret.Name,
},
},
},
})
}
if len(jenkins.Spec.ConfigurationAsCode.Secret.Name) > 0 {
volumes = append(volumes, corev1.Volume{
Name: getConfigurationAsCodeSecretVolumeName(jenkins),
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
DefaultMode: &secretVolumeSourceDefaultMode,
SecretName: jenkins.Spec.ConfigurationAsCode.Secret.Name,
},
},
})
}
return volumes
}
func getGroovyScriptsSecretVolumeName(jenkins *v1alpha2.Jenkins) string {
return "gs-" + jenkins.Spec.GroovyScripts.Secret.Name
}
func getConfigurationAsCodeSecretVolumeName(jenkins *v1alpha2.Jenkins) string {
return "casc-" + jenkins.Spec.GroovyScripts.Secret.Name
}
// GetJenkinsMasterContainerBaseVolumeMounts returns Jenkins master pod volume mounts required by operator
func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount {
return []corev1.VolumeMount{
func GetJenkinsMasterContainerBaseVolumeMounts(jenkins *v1alpha2.Jenkins) []corev1.VolumeMount {
volumeMounts := []corev1.VolumeMount{
{
Name: JenkinsHomeVolumeName,
MountPath: jenkinsHomePath,
@ -180,33 +180,35 @@ func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount {
MountPath: jenkinsInitConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsBaseConfigurationVolumeName,
MountPath: JenkinsBaseConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsUserConfigurationVolumeName,
MountPath: JenkinsUserConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsOperatorCredentialsVolumeName,
MountPath: jenkinsOperatorCredentialsVolumePath,
ReadOnly: true,
},
{
Name: userConfigurationSecretVolumeName,
MountPath: UserConfigurationSecretVolumePath,
ReadOnly: true,
},
}
if len(jenkins.Spec.GroovyScripts.Secret.Name) > 0 {
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: getGroovyScriptsSecretVolumeName(jenkins),
MountPath: GroovyScriptsSecretVolumePath,
ReadOnly: true,
})
}
if len(jenkins.Spec.ConfigurationAsCode.Secret.Name) > 0 {
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: getConfigurationAsCodeSecretVolumeName(jenkins),
MountPath: ConfigurationAsCodeSecretVolumePath,
ReadOnly: true,
})
}
return volumeMounts
}
// NewJenkinsMasterContainer returns Jenkins master Kubernetes container
func NewJenkinsMasterContainer(jenkins *v1alpha2.Jenkins) corev1.Container {
jenkinsContainer := jenkins.Spec.Master.Containers[0]
envs := GetJenkinsMasterContainerBaseEnvs()
envs := GetJenkinsMasterContainerBaseEnvs(jenkins)
envs = append(envs, jenkinsContainer.Env...)
return corev1.Container{
@ -230,7 +232,7 @@ func NewJenkinsMasterContainer(jenkins *v1alpha2.Jenkins) corev1.Container {
},
Env: envs,
Resources: jenkinsContainer.Resources,
VolumeMounts: append(GetJenkinsMasterContainerBaseVolumeMounts(), jenkinsContainer.VolumeMounts...),
VolumeMounts: append(GetJenkinsMasterContainerBaseVolumeMounts(jenkins), jenkinsContainer.VolumeMounts...),
}
}
@ -272,7 +274,6 @@ func GetJenkinsMasterPodName(jenkins v1alpha2.Jenkins) string {
// NewJenkinsMasterPod builds Jenkins Master Kubernetes Pod resource
func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins) *corev1.Pod {
serviceAccountName := objectMeta.Name
objectMeta.Annotations = jenkins.Spec.Master.Annotations
objectMeta.Name = GetJenkinsMasterPodName(*jenkins)
@ -283,10 +284,10 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins
Spec: corev1.PodSpec{
ServiceAccountName: serviceAccountName,
RestartPolicy: corev1.RestartPolicyNever,
SecurityContext: jenkins.Spec.Master.SecurityContext,
NodeSelector: jenkins.Spec.Master.NodeSelector,
Containers: newContainers(jenkins),
Volumes: append(GetJenkinsMasterPodBaseVolumes(jenkins), jenkins.Spec.Master.Volumes...),
SecurityContext: jenkins.Spec.Master.SecurityContext,
},
}
}

View File

@ -7,16 +7,8 @@ import (
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func buildServiceTypeMeta() metav1.TypeMeta {
return metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
}
}
// UpdateService returns new service with override fields from config
func UpdateService(actual corev1.Service, config v1alpha2.Service) corev1.Service {
actual.ObjectMeta.Annotations = config.Annotations

View File

@ -1,58 +0,0 @@
package resources
import (
"fmt"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const configureTheme = `
import jenkins.*
import jenkins.model.*
import hudson.*
import hudson.model.*
import org.jenkinsci.plugins.simpletheme.ThemeElement
import org.jenkinsci.plugins.simpletheme.CssTextThemeElement
import org.jenkinsci.plugins.simpletheme.CssUrlThemeElement
Jenkins jenkins = Jenkins.getInstance()
def decorator = Jenkins.instance.getDescriptorByType(org.codefirst.SimpleThemeDecorator.class)
List<ThemeElement> configElements = new ArrayList<>();
configElements.add(new CssTextThemeElement("DEFAULT"));
configElements.add(new CssUrlThemeElement("https://cdn.rawgit.com/afonsof/jenkins-material-theme/gh-pages/dist/material-light-green.css"));
decorator.setElements(configElements);
decorator.save();
jenkins.save()
`
// GetUserConfigurationConfigMapNameFromJenkins returns name of Kubernetes config map used to user configuration
func GetUserConfigurationConfigMapNameFromJenkins(jenkins *v1alpha2.Jenkins) string {
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
}
// GetUserConfigurationConfigMapName returns name of Kubernetes config map used to user configuration
func GetUserConfigurationConfigMapName(jenkinsCRName string) string {
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkinsCRName)
}
// NewUserConfigurationConfigMap builds Kubernetes config map used to user configuration
func NewUserConfigurationConfigMap(jenkins *v1alpha2.Jenkins) *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: buildConfigMapTypeMeta(),
ObjectMeta: metav1.ObjectMeta{
Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins),
Namespace: jenkins.ObjectMeta.Namespace,
Labels: BuildLabelsForWatchedResources(*jenkins),
},
Data: map[string]string{
"1-configure-theme.groovy": configureTheme,
},
}
}

View File

@ -1,33 +0,0 @@
package resources
import (
"fmt"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GetUserConfigurationSecretNameFromJenkins returns name of Kubernetes secret used to store jenkins operator credentials
func GetUserConfigurationSecretNameFromJenkins(jenkins *v1alpha2.Jenkins) string {
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkins.Name)
}
// GetUserConfigurationSecretName returns name of Kubernetes secret used to store jenkins operator credentials
func GetUserConfigurationSecretName(jenkinsCRName string) string {
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkinsCRName)
}
// NewUserConfigurationSecret builds the Kubernetes secret resource which is used to store user sensitive data for Jenkins configuration
func NewUserConfigurationSecret(jenkins *v1alpha2.Jenkins) *corev1.Secret {
return &corev1.Secret{
TypeMeta: buildServiceTypeMeta(),
ObjectMeta: metav1.ObjectMeta{
Name: GetUserConfigurationSecretNameFromJenkins(jenkins),
Namespace: jenkins.ObjectMeta.Namespace,
Labels: BuildLabelsForWatchedResources(*jenkins),
},
}
}