Add base configuration groovy scripts job

This commit is contained in:
Tomasz Sęk
2019-01-10 14:44:30 +01:00
parent c26090dce8
commit ca27bf4df8
15 changed files with 545 additions and 162 deletions
@@ -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)
@@ -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"
)
+23 -36
View File
@@ -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(&quot;&quot;&quot;
${params.GROOVY_SCRIPT}
&quot;&quot;&quot;, MasterComputer.localChannel)</script>
node(&apos;master&apos;) {
def scriptsText = sh(script: &apos;ls %s&apos;, returnStdout: true).trim()
def scripts = []
scripts.addAll(scriptsText.tokenize(&apos;\n&apos;))
for(script in scripts) {
stage(script) {
load &quot;%s/${script}&quot;
}
}
}</script>
<sandbox>false</sandbox>
</definition>
<triggers/>
+53
View File
@@ -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
}
+11 -1
View File
@@ -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
}