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 ( // JenkinsMasterContainerName is the Jenkins master container name in pod JenkinsMasterContainerName = "jenkins-master" // JenkinsHomeVolumeName is the Jenkins home volume name JenkinsHomeVolumeName = "jenkins-home" jenkinsPath = "/var/jenkins" jenkinsHomePath = jenkinsPath + "/home" jenkinsScriptsVolumeName = "scripts" // JenkinsScriptsVolumePath is a path where are scripts used to configure Jenkins JenkinsScriptsVolumePath = jenkinsPath + "/scripts" // InitScriptName is the init script name which configures init.groovy.d, scripts and install plugins InitScriptName = "init.sh" jenkinsOperatorCredentialsVolumeName = "operator-credentials" jenkinsOperatorCredentialsVolumePath = jenkinsPath + "/operator-credentials" jenkinsInitConfigurationVolumeName = "init-configuration" jenkinsInitConfigurationVolumePath = jenkinsPath + "/init-configuration" // 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" // HTTPPortInt defines Jenkins master HTTP port HTTPPortInt = 8080 ) func buildPodTypeMeta() metav1.TypeMeta { return metav1.TypeMeta{ Kind: "Pod", APIVersion: "v1", } } // GetJenkinsMasterContainerBaseCommand returns default Jenkins master container command func GetJenkinsMasterContainerBaseCommand() []string { return []string{ "bash", "-c", fmt.Sprintf("%s/%s && /sbin/tini -s -- /usr/local/bin/jenkins.sh", JenkinsScriptsVolumePath, InitScriptName), } } // GetJenkinsMasterContainerBaseEnvs returns Jenkins master pod envs required by operator func GetJenkinsMasterContainerBaseEnvs(jenkins *v1alpha2.Jenkins) []corev1.EnvVar { envVars := []corev1.EnvVar{ { Name: "JENKINS_HOME", Value: jenkinsHomePath, }, } 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 func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode var scriptsVolumeDefaultMode int32 = 0777 volumes := []corev1.Volume{ { Name: JenkinsHomeVolumeName, VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }, { Name: jenkinsScriptsVolumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ DefaultMode: &scriptsVolumeDefaultMode, LocalObjectReference: corev1.LocalObjectReference{ Name: getScriptsConfigMapName(jenkins), }, }, }, }, { Name: jenkinsInitConfigurationVolumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ DefaultMode: &configMapVolumeSourceDefaultMode, LocalObjectReference: corev1.LocalObjectReference{ Name: GetInitConfigurationConfigMapName(jenkins), }, }, }, }, { Name: jenkinsOperatorCredentialsVolumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ DefaultMode: &secretVolumeSourceDefaultMode, SecretName: GetOperatorCredentialsSecretName(jenkins), }, }, }, } 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: 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 checkSecretVolumesPresence(jenkins *v1alpha2.Jenkins) (groovyExists bool, cascExists bool) { for _, volume := range GetJenkinsMasterPodBaseVolumes(jenkins) { if volume.Name == ("gs-" + jenkins.Spec.GroovyScripts.Secret.Name) { groovyExists = true } else if volume.Name == ("casc-" + jenkins.Spec.ConfigurationAsCode.Secret.Name) { cascExists = true } } return groovyExists, cascExists } func getGroovyScriptsSecretVolumeName(jenkins *v1alpha2.Jenkins) string { return "gs-" + jenkins.Spec.GroovyScripts.Secret.Name } func getConfigurationAsCodeSecretVolumeName(jenkins *v1alpha2.Jenkins) string { return "casc-" + jenkins.Spec.ConfigurationAsCode.Secret.Name } // GetJenkinsMasterContainerBaseVolumeMounts returns Jenkins master pod volume mounts required by operator func GetJenkinsMasterContainerBaseVolumeMounts(jenkins *v1alpha2.Jenkins) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ { Name: JenkinsHomeVolumeName, MountPath: jenkinsHomePath, ReadOnly: false, }, { Name: jenkinsScriptsVolumeName, MountPath: JenkinsScriptsVolumePath, ReadOnly: true, }, { Name: jenkinsInitConfigurationVolumeName, MountPath: jenkinsInitConfigurationVolumePath, ReadOnly: true, }, { Name: jenkinsOperatorCredentialsVolumeName, MountPath: jenkinsOperatorCredentialsVolumePath, 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(jenkins) envs = append(envs, jenkinsContainer.Env...) return corev1.Container{ Name: JenkinsMasterContainerName, Image: jenkinsContainer.Image, ImagePullPolicy: jenkinsContainer.ImagePullPolicy, Command: jenkinsContainer.Command, LivenessProbe: jenkinsContainer.LivenessProbe, ReadinessProbe: jenkinsContainer.ReadinessProbe, Ports: []corev1.ContainerPort{ { Name: httpPortName, ContainerPort: constants.DefaultHTTPPortInt32, Protocol: corev1.ProtocolTCP, }, { Name: slavePortName, ContainerPort: constants.DefaultSlavePortInt32, Protocol: corev1.ProtocolTCP, }, }, SecurityContext: jenkinsContainer.SecurityContext, Env: envs, Resources: jenkinsContainer.Resources, VolumeMounts: append(GetJenkinsMasterContainerBaseVolumeMounts(jenkins), jenkinsContainer.VolumeMounts...), } } // ConvertJenkinsContainerToKubernetesContainer converts Jenkins container to Kubernetes container func ConvertJenkinsContainerToKubernetesContainer(container v1alpha2.Container) corev1.Container { return corev1.Container{ Name: container.Name, Image: container.Image, Command: container.Command, Args: container.Args, WorkingDir: container.WorkingDir, Ports: container.Ports, EnvFrom: container.EnvFrom, Env: container.Env, Resources: container.Resources, VolumeMounts: container.VolumeMounts, LivenessProbe: container.LivenessProbe, ReadinessProbe: container.ReadinessProbe, Lifecycle: container.Lifecycle, ImagePullPolicy: container.ImagePullPolicy, SecurityContext: container.SecurityContext, } } func newContainers(jenkins *v1alpha2.Jenkins) (containers []corev1.Container) { containers = append(containers, NewJenkinsMasterContainer(jenkins)) for _, container := range jenkins.Spec.Master.Containers[1:] { containers = append(containers, ConvertJenkinsContainerToKubernetesContainer(container)) } return } // GetJenkinsMasterPodName returns Jenkins pod name for given CR func GetJenkinsMasterPodName(jenkins v1alpha2.Jenkins) string { return fmt.Sprintf("jenkins-%s", jenkins.Name) } // 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) return &corev1.Pod{ TypeMeta: buildPodTypeMeta(), ObjectMeta: objectMeta, Spec: corev1.PodSpec{ ServiceAccountName: serviceAccountName, RestartPolicy: corev1.RestartPolicyNever, NodeSelector: jenkins.Spec.Master.NodeSelector, Containers: newContainers(jenkins), Volumes: append(GetJenkinsMasterPodBaseVolumes(jenkins), jenkins.Spec.Master.Volumes...), SecurityContext: jenkins.Spec.Master.SecurityContext, ImagePullSecrets: jenkins.Spec.Master.ImagePullSecrets, }, } }