mirror of
https://github.com/jenkinsci/kubernetes-operator.git
synced 2026-10-17 21:35:38 +02:00
Add base configuration groovy scripts job
This commit is contained in:
@@ -9,6 +9,9 @@ import (
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
jenkinsclient "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/client"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base/resources"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/user/theme"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/groovy"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/plugin"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/log"
|
||||
|
||||
@@ -64,6 +67,11 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (*reconcile.Result, jenk
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Scripts config map is present")
|
||||
|
||||
if err := r.createInitConfigurationConfigMap(metaObject); err != nil {
|
||||
return &reconcile.Result{}, nil, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Init configuration config map is present")
|
||||
|
||||
if err := r.createBaseConfigurationConfigMap(metaObject); err != nil {
|
||||
return &reconcile.Result{}, nil, err
|
||||
}
|
||||
@@ -113,7 +121,8 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (*reconcile.Result, jenk
|
||||
}
|
||||
}
|
||||
|
||||
return nil, jenkinsClient, nil
|
||||
result, err = r.baseConfiguration(jenkinsClient)
|
||||
return result, jenkinsClient, err
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) {
|
||||
@@ -183,6 +192,14 @@ func (r *ReconcileJenkinsBaseConfiguration) createScriptsConfigMap(meta metav1.O
|
||||
return r.createOrUpdateResource(scripts)
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createInitConfigurationConfigMap(meta metav1.ObjectMeta) error {
|
||||
scripts, err := resources.NewInitConfigurationConfigMap(meta, r.jenkins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.createOrUpdateResource(scripts)
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createBaseConfigurationConfigMap(meta metav1.ObjectMeta) error {
|
||||
scripts, err := resources.NewBaseConfigurationConfigMap(meta, r.jenkins)
|
||||
if err != nil {
|
||||
@@ -220,12 +237,10 @@ func (r *ReconcileJenkinsBaseConfiguration) createJenkinsMasterPod(meta metav1.O
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.jenkins.Status.BaseConfigurationCompletedTime != nil {
|
||||
r.jenkins.Status.BaseConfigurationCompletedTime = nil
|
||||
err = r.updateResource(r.jenkins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.jenkins.Status = virtuslabv1alpha1.JenkinsStatus{}
|
||||
err = r.updateResource(r.jenkins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
} else if err != nil && !errors.IsNotFound(err) {
|
||||
@@ -238,25 +253,25 @@ func (r *ReconcileJenkinsBaseConfiguration) createJenkinsMasterPod(meta metav1.O
|
||||
(currentJenkinsMasterPod.Status.Phase == corev1.PodFailed ||
|
||||
currentJenkinsMasterPod.Status.Phase == corev1.PodSucceeded ||
|
||||
currentJenkinsMasterPod.Status.Phase == corev1.PodUnknown) {
|
||||
r.logger.Info(fmt.Sprintf("Invalid Jenkins pod phase %v, recreating pod", currentJenkinsMasterPod.Status.Phase))
|
||||
r.logger.Info(fmt.Sprintf("Invalid Jenkins pod phase '%+v', recreating pod", currentJenkinsMasterPod.Status.Phase))
|
||||
recreatePod = true
|
||||
}
|
||||
|
||||
if currentJenkinsMasterPod != nil &&
|
||||
r.jenkins.Spec.Master.Image != currentJenkinsMasterPod.Spec.Containers[0].Image {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins image has changed to %v, recreating pod", r.jenkins.Spec.Master.Image))
|
||||
r.logger.Info(fmt.Sprintf("Jenkins image has changed to '%+v', recreating pod", r.jenkins.Spec.Master.Image))
|
||||
recreatePod = true
|
||||
}
|
||||
|
||||
if currentJenkinsMasterPod != nil && len(r.jenkins.Spec.Master.Annotations) > 0 &&
|
||||
!reflect.DeepEqual(r.jenkins.Spec.Master.Annotations, currentJenkinsMasterPod.ObjectMeta.Annotations) {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins pod annotations have changed to %v, recreating pod", r.jenkins.Spec.Master.Annotations))
|
||||
r.logger.Info(fmt.Sprintf("Jenkins pod annotations have changed to '%+v', recreating pod", r.jenkins.Spec.Master.Annotations))
|
||||
recreatePod = true
|
||||
}
|
||||
|
||||
if currentJenkinsMasterPod != nil &&
|
||||
!reflect.DeepEqual(r.jenkins.Spec.Master.Resources, currentJenkinsMasterPod.Spec.Containers[0].Resources) {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins pod resources have changed to %v, recreating pod", r.jenkins.Spec.Master.Resources))
|
||||
r.logger.Info(fmt.Sprintf("Jenkins pod resources have changed to '%+v', recreating pod", r.jenkins.Spec.Master.Resources))
|
||||
recreatePod = true
|
||||
}
|
||||
|
||||
@@ -278,18 +293,18 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMet
|
||||
}
|
||||
|
||||
if jenkinsMasterPodStatus.ObjectMeta.DeletionTimestamp != nil {
|
||||
r.logger.Info("Jenkins master pod is terminating")
|
||||
r.logger.V(log.VDebug).Info("Jenkins master pod is terminating")
|
||||
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
|
||||
}
|
||||
|
||||
if jenkinsMasterPodStatus.Status.Phase != corev1.PodRunning {
|
||||
r.logger.Info("Jenkins master pod not ready")
|
||||
r.logger.V(log.VDebug).Info("Jenkins master pod not ready")
|
||||
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
|
||||
}
|
||||
|
||||
for _, containerStatus := range jenkinsMasterPodStatus.Status.ContainerStatuses {
|
||||
if !containerStatus.Ready {
|
||||
r.logger.Info("Jenkins master pod not ready, readiness probe failed")
|
||||
r.logger.V(log.VDebug).Info("Jenkins master pod not ready, readiness probe failed")
|
||||
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
|
||||
}
|
||||
}
|
||||
@@ -357,3 +372,25 @@ func (r *ReconcileJenkinsBaseConfiguration) getJenkinsClient(meta metav1.ObjectM
|
||||
string(credentialsSecret.Data[resources.OperatorCredentialsSecretUserNameKey]),
|
||||
string(credentialsSecret.Data[resources.OperatorCredentialsSecretTokenKey]))
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) baseConfiguration(jenkinsClient jenkinsclient.Jenkins) (*reconcile.Result, error) {
|
||||
groovyClient := groovy.New(jenkinsClient, r.client, r.logger, fmt.Sprintf("%s-base-configuration", constants.OperatorName), resources.JenkinsBaseConfigurationVolumePath)
|
||||
|
||||
err := groovyClient.ConfigureGroovyJob()
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// set custom jenkins theme
|
||||
done, err := groovyClient.EnsureGroovyJob(theme.SetThemeGroovyScript, r.jenkins)
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// build not finished yet - requeue reconciliation loop with timeout
|
||||
if !done {
|
||||
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (r *ReconcileJenkinsBaseConfiguration) updateResource(obj metav1.Object) er
|
||||
return fmt.Errorf("is not a %T a runtime.Object", obj)
|
||||
}
|
||||
|
||||
// Set Jenkins instance as the owner and controller, don't check error(can be already set)
|
||||
// set Jenkins instance as the owner and controller, don't check error(can be already set)
|
||||
_ = controllerutil.SetControllerReference(r.jenkins, obj, r.scheme)
|
||||
|
||||
return r.client.Update(context.TODO(), runtimeObj)
|
||||
@@ -42,7 +42,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createOrUpdateResource(obj metav1.Ob
|
||||
return fmt.Errorf("is not a %T a runtime.Object", obj)
|
||||
}
|
||||
|
||||
// Set Jenkins instance as the owner and controller, don't check error(can be already set)
|
||||
// set Jenkins instance as the owner and controller, don't check error(can be already set)
|
||||
_ = controllerutil.SetControllerReference(r.jenkins, obj, r.scheme)
|
||||
|
||||
err := r.client.Create(context.TODO(), runtimeObj)
|
||||
|
||||
+140
-40
@@ -2,72 +2,172 @@ package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/render"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const createOperatorUserFileName = "createOperatorUser.groovy"
|
||||
const basicSettingsFmt = `import jenkins.model.Jenkins
|
||||
import jenkins.model.JenkinsLocationConfiguration
|
||||
import hudson.model.Node.Mode
|
||||
|
||||
var createOperatorUserGroovyFmtTemplate = template.Must(template.New(createOperatorUserFileName).Parse(`
|
||||
import hudson.security.*
|
||||
|
||||
def jenkins = jenkins.model.Jenkins.getInstance()
|
||||
|
||||
def hudsonRealm = new HudsonPrivateSecurityRealm(false)
|
||||
hudsonRealm.createAccount(
|
||||
new File('{{ .OperatorCredentialsPath }}/{{ .OperatorUserNameFile }}').text,
|
||||
new File('{{ .OperatorCredentialsPath }}/{{ .OperatorPasswordFile }}').text)
|
||||
jenkins.setSecurityRealm(hudsonRealm)
|
||||
|
||||
def strategy = new FullControlOnceLoggedInAuthorizationStrategy()
|
||||
strategy.setAllowAnonymousRead(false)
|
||||
jenkins.setAuthorizationStrategy(strategy)
|
||||
def jenkins = Jenkins.instance
|
||||
//Number of jobs that run simultaneously on master, currently only backup and SeedJob.
|
||||
jenkins.setNumExecutors(%d)
|
||||
//Jobs must specify that they want to run on master
|
||||
jenkins.setMode(Mode.EXCLUSIVE)
|
||||
jenkins.save()
|
||||
`))
|
||||
|
||||
func buildCreateJenkinsOperatorUserGroovyScript() (*string, error) {
|
||||
data := struct {
|
||||
OperatorCredentialsPath string
|
||||
OperatorUserNameFile string
|
||||
OperatorPasswordFile string
|
||||
}{
|
||||
OperatorCredentialsPath: jenkinsOperatorCredentialsVolumePath,
|
||||
OperatorUserNameFile: OperatorCredentialsSecretUserNameKey,
|
||||
OperatorPasswordFile: OperatorCredentialsSecretPasswordKey,
|
||||
}
|
||||
`
|
||||
|
||||
output, err := render.Render(createOperatorUserGroovyFmtTemplate, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
const enableCSRF = `import hudson.security.csrf.DefaultCrumbIssuer
|
||||
import jenkins.model.Jenkins
|
||||
|
||||
return &output, nil
|
||||
def jenkins = Jenkins.instance
|
||||
|
||||
if (jenkins.getCrumbIssuer() == null) {
|
||||
jenkins.setCrumbIssuer(new DefaultCrumbIssuer(true))
|
||||
jenkins.save()
|
||||
println('CSRF Protection enabled.')
|
||||
} else {
|
||||
println('CSRF Protection already configured.')
|
||||
}
|
||||
`
|
||||
|
||||
const disableUsageStats = `import jenkins.model.Jenkins
|
||||
|
||||
def jenkins = Jenkins.instance
|
||||
|
||||
if (jenkins.isUsageStatisticsCollected()) {
|
||||
jenkins.setNoUsageStatistics(true)
|
||||
jenkins.save()
|
||||
println('Jenkins usage stats submitting disabled.')
|
||||
} else {
|
||||
println('Nothing changed. Usage stats are not submitted to the Jenkins project.')
|
||||
}
|
||||
`
|
||||
|
||||
const enableMasterAccessControl = `import jenkins.security.s2m.AdminWhitelistRule
|
||||
import jenkins.model.Jenkins
|
||||
|
||||
// see https://wiki.jenkins-ci.org/display/JENKINS/Slave+To+Master+Access+Control
|
||||
def jenkins = Jenkins.instance
|
||||
jenkins.getInjector()
|
||||
.getInstance(AdminWhitelistRule.class)
|
||||
.setMasterKillSwitch(false) // for real though, false equals enabled..........
|
||||
jenkins.save()
|
||||
`
|
||||
|
||||
const disableInsecureFeatures = `
|
||||
import jenkins.*
|
||||
import jenkins.model.*
|
||||
import hudson.model.*
|
||||
import jenkins.security.s2m.*
|
||||
|
||||
def jenkins = Jenkins.instance
|
||||
|
||||
println("Disabling insecure Jenkins features...")
|
||||
|
||||
println("Disabling insecure protocols...")
|
||||
println("Old protocols: [" + jenkins.getAgentProtocols().join(", ") + "]")
|
||||
HashSet<String> newProtocols = new HashSet<>(jenkins.getAgentProtocols())
|
||||
newProtocols.removeAll(Arrays.asList("JNLP3-connect", "JNLP2-connect", "JNLP-connect", "CLI-connect"))
|
||||
println("New protocols: [" + newProtocols.join(", ") + "]")
|
||||
jenkins.setAgentProtocols(newProtocols)
|
||||
|
||||
println("Disabling CLI access of /cli URL...")
|
||||
def remove = { list ->
|
||||
list.each { item ->
|
||||
if (item.getClass().name.contains("CLIAction")) {
|
||||
println("Removing extension ${item.getClass().name}")
|
||||
list.remove(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
remove(jenkins.getExtensionList(RootAction.class))
|
||||
remove(jenkins.actions)
|
||||
|
||||
println("Disable CLI completely...")
|
||||
CLI.get().setEnabled(false)
|
||||
println("CLI disabled")
|
||||
|
||||
jenkins.save()
|
||||
`
|
||||
|
||||
const configureKubernetesPluginFmt = `import com.cloudbees.plugins.credentials.CredentialsScope
|
||||
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
|
||||
import com.cloudbees.plugins.credentials.domains.Domain
|
||||
import jenkins.model.Jenkins
|
||||
import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud
|
||||
import org.csanchez.jenkins.plugins.kubernetes.ServiceAccountCredential
|
||||
|
||||
def kubernetesCredentialsId = 'kubernetes-namespace-token'
|
||||
def jenkins = Jenkins.getInstance()
|
||||
|
||||
ServiceAccountCredential serviceAccountCredential = new ServiceAccountCredential(
|
||||
CredentialsScope.GLOBAL,
|
||||
kubernetesCredentialsId,
|
||||
"Kubernetes Namespace Token"
|
||||
)
|
||||
SystemCredentialsProvider.getInstance().getStore().addCredentials(Domain.global(), serviceAccountCredential)
|
||||
|
||||
KubernetesCloud kubernetes = new KubernetesCloud("kubernetes")
|
||||
kubernetes.setServerUrl("https://kubernetes.default")
|
||||
kubernetes.setNamespace("%s")
|
||||
kubernetes.setCredentialsId(kubernetesCredentialsId)
|
||||
kubernetes.setJenkinsUrl("http://%s:%d")
|
||||
kubernetes.setRetentionTimeout(15)
|
||||
jenkins.clouds.add(kubernetes)
|
||||
|
||||
jenkins.save()
|
||||
`
|
||||
|
||||
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()
|
||||
`
|
||||
|
||||
// GetBaseConfigurationConfigMapName returns name of Kubernetes config map used to base configuration
|
||||
func GetBaseConfigurationConfigMapName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("jenkins-operator-base-configuration-%s", jenkins.ObjectMeta.Name)
|
||||
return fmt.Sprintf("%s-base-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
// NewBaseConfigurationConfigMap builds Kubernetes config map used to base configuration
|
||||
func NewBaseConfigurationConfigMap(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) (*corev1.ConfigMap, error) {
|
||||
meta.Name = GetBaseConfigurationConfigMapName(jenkins)
|
||||
|
||||
createJenkinsOperatorUserGroovy, err := buildCreateJenkinsOperatorUserGroovyScript()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: buildConfigMapTypeMeta(),
|
||||
ObjectMeta: meta,
|
||||
Data: map[string]string{
|
||||
createOperatorUserFileName: *createJenkinsOperatorUserGroovy,
|
||||
"1-basic-settings.groovy": fmt.Sprintf(basicSettingsFmt, constants.DefaultAmountOfExecutors),
|
||||
"2-enable-csrf.groovy": enableCSRF,
|
||||
"3-disable-usage-stats.groovy": disableUsageStats,
|
||||
"4-enable-master-access-control.groovy": enableMasterAccessControl,
|
||||
"5-disable-insecure-features.groovy": disableInsecureFeatures,
|
||||
"6-configure-kubernetes-plugin.groovy": fmt.Sprintf(configureKubernetesPluginFmt,
|
||||
jenkins.ObjectMeta.Namespace, GetResourceName(jenkins), HTTPPortInt),
|
||||
"7-configure-theme.groovy": configureTheme,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
"text/template"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
@@ -49,14 +50,14 @@ func buildCreateJenkinsOperatorUserGroovyScript() (*string, error) {
|
||||
return &output, nil
|
||||
}
|
||||
|
||||
// GetBaseConfigurationConfigMapName returns name of Kubernetes config map used to base configuration
|
||||
func GetBaseConfigurationConfigMapName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("jenkins-operator-base-configuration-%s", jenkins.ObjectMeta.Name)
|
||||
// GetInitConfigurationConfigMapName returns name of Kubernetes config map used to init configuration
|
||||
func GetInitConfigurationConfigMapName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("%s-init-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
// NewBaseConfigurationConfigMap builds Kubernetes config map used to base configuration
|
||||
func NewBaseConfigurationConfigMap(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) (*corev1.ConfigMap, error) {
|
||||
meta.Name = GetBaseConfigurationConfigMapName(jenkins)
|
||||
// NewInitConfigurationConfigMap builds Kubernetes config map used to init configuration
|
||||
func NewInitConfigurationConfigMap(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) (*corev1.ConfigMap, error) {
|
||||
meta.Name = GetInitConfigurationConfigMapName(jenkins)
|
||||
|
||||
createJenkinsOperatorUserGroovy, err := buildCreateJenkinsOperatorUserGroovyScript()
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
@@ -20,12 +21,12 @@ func NewResourceObjectMeta(jenkins *virtuslabv1alpha1.Jenkins) metav1.ObjectMeta
|
||||
// BuildResourceLabels returns labels for all Kubernetes resources created by operator
|
||||
func BuildResourceLabels(jenkins *virtuslabv1alpha1.Jenkins) map[string]string {
|
||||
return map[string]string{
|
||||
"app": "jenkins-master",
|
||||
"jenkins-cr": jenkins.Name,
|
||||
constants.LabelAppKey: constants.LabelAppValue,
|
||||
constants.LabelJenkinsCRKey: jenkins.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// GetResourceName returns name of Kubernetes resource base on Jenkins CR
|
||||
func GetResourceName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("jenkins-operator-%s", jenkins.ObjectMeta.Name)
|
||||
return fmt.Sprintf("%s-%s", constants.LabelAppValue, jenkins.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,12 @@ const (
|
||||
jenkinsOperatorCredentialsVolumeName = "operator-credentials"
|
||||
jenkinsOperatorCredentialsVolumePath = "/var/jenkins/operator-credentials"
|
||||
|
||||
jenkinsInitConfigurationVolumeName = "init-configuration"
|
||||
jenkinsInitConfigurationVolumePath = "/var/jenkins/init-configuration"
|
||||
|
||||
jenkinsBaseConfigurationVolumeName = "base-configuration"
|
||||
jenkinsBaseConfigurationVolumePath = "/var/jenkins/base-configuration"
|
||||
// JenkinsBaseConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins
|
||||
JenkinsBaseConfigurationVolumePath = "/var/jenkins/base-configuration"
|
||||
|
||||
httpPortName = "http"
|
||||
slavePortName = "slavelistener"
|
||||
@@ -122,9 +126,14 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *virtuslabv1alpha
|
||||
MountPath: jenkinsScriptsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsInitConfigurationVolumeName,
|
||||
MountPath: jenkinsInitConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsBaseConfigurationVolumeName,
|
||||
MountPath: jenkinsBaseConfigurationVolumePath,
|
||||
MountPath: JenkinsBaseConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
@@ -152,6 +161,16 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *virtuslabv1alpha
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: jenkinsInitConfigurationVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: GetInitConfigurationConfigMapName(jenkins),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: jenkinsBaseConfigurationVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/render"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -245,7 +246,7 @@ set -x
|
||||
|
||||
# https://wiki.jenkins.io/display/JENKINS/Post-initialization+script
|
||||
mkdir -p {{ .JenkinsHomePath }}/init.groovy.d
|
||||
cp -n {{ .BaseConfigurationPath }}/*.groovy {{ .JenkinsHomePath }}/init.groovy.d
|
||||
cp -n {{ .InitConfigurationPath }}/*.groovy {{ .JenkinsHomePath }}/init.groovy.d
|
||||
|
||||
mkdir -p {{ .JenkinsHomePath }}/scripts
|
||||
cp {{ .JenkinsScriptsVolumePath }}/*.sh {{ .JenkinsHomePath }}/scripts
|
||||
@@ -274,13 +275,13 @@ func buildConfigMapTypeMeta() metav1.TypeMeta {
|
||||
func buildInitBashScript(pluginsToInstall map[string][]string) (*string, error) {
|
||||
data := struct {
|
||||
JenkinsHomePath string
|
||||
BaseConfigurationPath string
|
||||
InitConfigurationPath string
|
||||
InstallPluginsCommand string
|
||||
JenkinsScriptsVolumePath string
|
||||
Plugins map[string][]string
|
||||
}{
|
||||
JenkinsHomePath: jenkinsHomePath,
|
||||
BaseConfigurationPath: jenkinsBaseConfigurationVolumePath,
|
||||
InitConfigurationPath: jenkinsInitConfigurationVolumePath,
|
||||
Plugins: pluginsToInstall,
|
||||
InstallPluginsCommand: installPluginsCommand,
|
||||
JenkinsScriptsVolumePath: jenkinsScriptsVolumePath,
|
||||
@@ -295,7 +296,7 @@ func buildInitBashScript(pluginsToInstall map[string][]string) (*string, error)
|
||||
}
|
||||
|
||||
func getScriptsConfigMapName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("jenkins-operator-scripts-%s", jenkins.ObjectMeta.Name)
|
||||
return fmt.Sprintf("%s-scripts-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
// NewScriptsConfigMap builds Kubernetes config map used to store scripts
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
@@ -32,7 +33,7 @@ func buildSecretTypeMeta() metav1.TypeMeta {
|
||||
// GetOperatorCredentialsSecretName returns name of Kubernetes secret used to store jenkins operator credentials
|
||||
// to allow calls to Jenkins API
|
||||
func GetOperatorCredentialsSecretName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("jenkins-operator-credentials-%s", jenkins.Name)
|
||||
return fmt.Sprintf("%s-credentials-%s", constants.OperatorName, jenkins.Name)
|
||||
}
|
||||
|
||||
// NewOperatorCredentialsSecret builds the Kubernetes secret used to store jenkins operator credentials
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
jenkinsclient "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/client"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/user/seedjobs"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/user/theme"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/groovy"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/jobs"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
@@ -42,8 +40,7 @@ func (r *ReconcileUserConfiguration) Reconcile() (*reconcile.Result, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// reconcile custom groovy scripts
|
||||
return r.reconcileCustomGroovy()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileUserConfiguration) reconcileSeedJobs() (*reconcile.Result, error) {
|
||||
@@ -67,25 +64,3 @@ func (r *ReconcileUserConfiguration) reconcileSeedJobs() (*reconcile.Result, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileUserConfiguration) reconcileCustomGroovy() (*reconcile.Result, error) {
|
||||
groovyClient := groovy.New(r.jenkinsClient, r.k8sClient, r.logger)
|
||||
|
||||
err := groovyClient.ConfigureGroovyJob()
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// set custom jenkins theme
|
||||
done, err := groovyClient.EnsureGroovyJob(theme.SetThemeGroovyScript, r.jenkins)
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// build not finished yet - requeue reconciliation loop with timeout
|
||||
if !done {
|
||||
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
// OperatorName is a operator name
|
||||
OperatorName = "jenkins-operator"
|
||||
// DefaultAmountOfExecutors is the default amount of Jenkins executors
|
||||
DefaultAmountOfExecutors = 3
|
||||
)
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
LabelAppKey = "app"
|
||||
LabelAppValue = OperatorName
|
||||
|
||||
LabelWatchKey = "watch"
|
||||
LabelWatchValue = "true"
|
||||
|
||||
LabelJenkinsCRKey = "jenkins-cr"
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package groovy
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
jenkinsclient "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/client"
|
||||
@@ -12,32 +13,29 @@ import (
|
||||
k8s "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExecuteGroovyJobName this is the fixed execute groovy job name
|
||||
ExecuteGroovyJobName = "Execute Groovy Scripts"
|
||||
|
||||
groovyScriptParameterName = "GROOVY_SCRIPT"
|
||||
)
|
||||
|
||||
// Groovy defines API for groovy scripts execution via jenkins job
|
||||
type Groovy struct {
|
||||
jenkinsClient jenkinsclient.Jenkins
|
||||
k8sClient k8s.Client
|
||||
logger logr.Logger
|
||||
jobName string
|
||||
scriptsPath string
|
||||
}
|
||||
|
||||
// New creates new instance of Groovy
|
||||
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger) *Groovy {
|
||||
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jobName, scriptsPath string) *Groovy {
|
||||
return &Groovy{
|
||||
jenkinsClient: jenkinsClient,
|
||||
k8sClient: k8sClient,
|
||||
logger: logger,
|
||||
jobName: jobName,
|
||||
scriptsPath: scriptsPath,
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureGroovyJob configures jenkins job for executing groovy scripts
|
||||
func (g *Groovy) ConfigureGroovyJob() error {
|
||||
_, err := g.jenkinsClient.CreateOrUpdateJob(groovyJobConfigXML, ExecuteGroovyJobName)
|
||||
_, err := g.jenkinsClient.CreateOrUpdateJob(fmt.Sprintf(configurationJobXMLFmt, g.scriptsPath, g.scriptsPath), g.jobName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -53,43 +51,32 @@ func (g *Groovy) EnsureGroovyJob(groovyScript string, jenkins *virtuslabv1alpha1
|
||||
hash.Write([]byte(groovyScript))
|
||||
encodedHash := base64.URLEncoding.EncodeToString(hash.Sum(nil))
|
||||
|
||||
parameters := map[string]string{
|
||||
groovyScriptParameterName: groovyScript,
|
||||
}
|
||||
|
||||
done, err := jobsClient.EnsureBuildJob(ExecuteGroovyJobName, encodedHash, parameters, jenkins, true)
|
||||
done, err := jobsClient.EnsureBuildJob(g.jobName, encodedHash, map[string]string{}, jenkins, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return done, nil
|
||||
}
|
||||
|
||||
// FIXME(antoniaklja) use mask-password plugin for params.GROOVY_SCRIPT
|
||||
// TODO add groovy script name
|
||||
var groovyJobConfigXML = `
|
||||
<flow-definition plugin="workflow-job@2.30">
|
||||
const configurationJobXMLFmt = `<?xml version='1.1' encoding='UTF-8'?>
|
||||
<flow-definition plugin="workflow-job@2.25">
|
||||
<actions/>
|
||||
<description></description>
|
||||
<keepDependencies>false</keepDependencies>
|
||||
<properties>
|
||||
<hudson.model.ParametersDefinitionProperty>
|
||||
<parameterDefinitions>
|
||||
<hudson.model.TextParameterDefinition>
|
||||
<name>` + groovyScriptParameterName + `</name>
|
||||
<description></description>
|
||||
<defaultValue></defaultValue>
|
||||
<trim>false</trim>
|
||||
</hudson.model.TextParameterDefinition>
|
||||
</parameterDefinitions>
|
||||
</hudson.model.ParametersDefinitionProperty>
|
||||
</properties>
|
||||
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61">
|
||||
<script>import hudson.util.RemotingDiagnostics
|
||||
import jenkins.model.Jenkins.MasterComputer
|
||||
<properties/>
|
||||
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.31">
|
||||
<script>import groovy.io.FileType
|
||||
|
||||
println RemotingDiagnostics.executeGroovy("""
|
||||
${params.GROOVY_SCRIPT}
|
||||
""", MasterComputer.localChannel)</script>
|
||||
node('master') {
|
||||
def scriptsText = sh(script: 'ls %s', returnStdout: true).trim()
|
||||
def scripts = []
|
||||
scripts.addAll(scriptsText.tokenize('\n'))
|
||||
for(script in scripts) {
|
||||
stage(script) {
|
||||
load "%s/${script}"
|
||||
}
|
||||
}
|
||||
}</script>
|
||||
<sandbox>false</sandbox>
|
||||
</definition>
|
||||
<triggers/>
|
||||
|
||||
@@ -1 +1,54 @@
|
||||
package jenkins
|
||||
|
||||
import (
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
// enqueueRequestForJenkins enqueues a Request for secrets and configmaps created by jenkins-operator.
|
||||
type enqueueRequestForJenkins struct{}
|
||||
|
||||
func (e *enqueueRequestForJenkins) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
|
||||
if req := e.getOwnerReconcileRequests(evt.Meta); req != nil {
|
||||
q.Add(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *enqueueRequestForJenkins) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
|
||||
if req := e.getOwnerReconcileRequests(evt.MetaOld); req != nil {
|
||||
q.Add(*req)
|
||||
}
|
||||
if req := e.getOwnerReconcileRequests(evt.MetaNew); req != nil {
|
||||
q.Add(*req)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *enqueueRequestForJenkins) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
|
||||
if req := e.getOwnerReconcileRequests(evt.Meta); req != nil {
|
||||
q.Add(*req)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *enqueueRequestForJenkins) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) {
|
||||
if req := e.getOwnerReconcileRequests(evt.Meta); req != nil {
|
||||
q.Add(*req)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *enqueueRequestForJenkins) getOwnerReconcileRequests(object metav1.Object) *reconcile.Request {
|
||||
if object.GetLabels()[constants.LabelAppKey] == constants.LabelAppValue &&
|
||||
object.GetLabels()[constants.LabelWatchKey] == constants.LabelWatchValue &&
|
||||
len(object.GetLabels()[constants.LabelJenkinsCRKey]) > 0 {
|
||||
return &reconcile.Request{NamespacedName: types.NamespacedName{
|
||||
Namespace: object.GetNamespace(),
|
||||
Name: object.GetLabels()[constants.LabelJenkinsCRKey],
|
||||
}}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO Modify this to be the types you create that are owned by the primary resource
|
||||
// Watch for changes to secondary resource Pods and requeue the owner Jenkins
|
||||
err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{
|
||||
IsController: true,
|
||||
@@ -62,6 +61,17 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
return err
|
||||
}
|
||||
|
||||
jenkinsHandler := &enqueueRequestForJenkins{}
|
||||
err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, jenkinsHandler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, jenkinsHandler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user