mirror of
https://github.com/jenkinsci/kubernetes-operator.git
synced 2026-10-19 09:10:58 +02:00
Add reconcile loop of Jenkins base configuration
This commit is contained in:
@@ -5,12 +5,12 @@ import (
|
||||
)
|
||||
|
||||
// AddToManagerFuncs is a list of functions to add all Controllers to the Manager
|
||||
var AddToManagerFuncs []func(manager.Manager) error
|
||||
var AddToManagerFuncs []func(manager manager.Manager, local, minikube bool) error
|
||||
|
||||
// AddToManager adds all Controllers to the Manager
|
||||
func AddToManager(m manager.Manager) error {
|
||||
func AddToManager(m manager.Manager, local, minikube bool) error {
|
||||
for _, f := range AddToManagerFuncs {
|
||||
if err := f(m); err != nil {
|
||||
if err := f(m, local, minikube); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/bndr/gojenkins"
|
||||
)
|
||||
|
||||
// Jenkins defines Jenkins API
|
||||
type Jenkins interface {
|
||||
GenerateToken(userName, tokenName string) (*UserToken, error)
|
||||
//Info() (*gojenkins.executorResponse, error)
|
||||
CreateNode(name string, numExecutors int, description string, remoteFS string, options ...interface{}) (*gojenkins.Node, error)
|
||||
CreateJob(config string, options ...interface{}) (*gojenkins.Job, error)
|
||||
RenameJob(job string, name string) *gojenkins.Job
|
||||
CopyJob(copyFrom string, newName string) (*gojenkins.Job, error)
|
||||
DeleteJob(name string) (bool, error)
|
||||
BuildJob(name string, options ...interface{}) (bool, error)
|
||||
GetNode(name string) (*gojenkins.Node, error)
|
||||
GetBuild(jobName string, number int64) (*gojenkins.Build, error)
|
||||
GetJob(id string) (*gojenkins.Job, error)
|
||||
GetAllNodes() ([]*gojenkins.Node, error)
|
||||
//GetAllBuildIds(job string) ([]jobBuild, error)
|
||||
//GetAllJobNames() ([]job, error)
|
||||
GetAllJobs() ([]*gojenkins.Job, error)
|
||||
GetQueue() (*gojenkins.Queue, error)
|
||||
GetQueueUrl() string
|
||||
//GetArtifactData(id string) (*fingerPrintResponse, error)
|
||||
GetPlugins(depth int) (*gojenkins.Plugins, error)
|
||||
HasPlugin(name string) (*gojenkins.Plugin, error)
|
||||
ValidateFingerPrint(id string) (bool, error)
|
||||
GetView(name string) (*gojenkins.View, error)
|
||||
GetAllViews() ([]*gojenkins.View, error)
|
||||
CreateView(name string, viewType string) (*gojenkins.View, error)
|
||||
}
|
||||
|
||||
type jenkins struct {
|
||||
gojenkins.Jenkins
|
||||
}
|
||||
|
||||
// BuildJenkinsAPIUrl returns Jenkins API URL
|
||||
func BuildJenkinsAPIUrl(namespace, serviceName string, portNumber int, local, minikube bool) (string, error) {
|
||||
// Get Jenkins URL from minikube command
|
||||
if local && minikube {
|
||||
cmd := exec.Command("minikube", "service", "--url", "-n", namespace, serviceName)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
// First is for http, the second one is for Jenkins slaves communication
|
||||
// see pkg/controller/jenkins/configuration/base/resources/service.go
|
||||
url := lines[0]
|
||||
return url, nil
|
||||
}
|
||||
|
||||
if local {
|
||||
// When run locally make port-forward to jenkins pod ('kubectl -n default port-forward jenkins-operator-example 8080')
|
||||
return fmt.Sprintf("http://localhost:%d", portNumber), nil
|
||||
}
|
||||
|
||||
// Connect through Kubernetes service, operator has to be run inside cluster
|
||||
return fmt.Sprintf("http://%s:%d", serviceName, portNumber), nil
|
||||
}
|
||||
|
||||
// New creates Jenkins API client
|
||||
func New(url, user, passwordOrToken string) (Jenkins, error) {
|
||||
if strings.HasSuffix(url, "/") {
|
||||
url = url[:len(url)-1]
|
||||
}
|
||||
|
||||
jenkinsClient := &jenkins{}
|
||||
jenkinsClient.Server = url
|
||||
jenkinsClient.Requester = &gojenkins.Requester{
|
||||
Base: url,
|
||||
SslVerify: true,
|
||||
Client: http.DefaultClient,
|
||||
BasicAuth: &gojenkins.BasicAuth{Username: user, Password: passwordOrToken},
|
||||
Headers: http.Header{},
|
||||
}
|
||||
if _, err := jenkinsClient.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := jenkinsClient.Poll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return nil, fmt.Errorf("Invalid status code returned: %d", status)
|
||||
}
|
||||
|
||||
return jenkinsClient, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type userTokenResponseData struct {
|
||||
Name string `json:"tokenName"`
|
||||
UUID string `json:"tokenUuid"`
|
||||
Value string `json:"tokenValue"`
|
||||
}
|
||||
|
||||
type userTokenResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data userTokenResponseData `json:"data"`
|
||||
}
|
||||
|
||||
// UserToken defines user token for Jenkins API communication
|
||||
type UserToken struct {
|
||||
raw *userTokenResponse
|
||||
base string
|
||||
}
|
||||
|
||||
// GetToken returns user token
|
||||
func (token *UserToken) GetToken() string {
|
||||
return token.raw.Data.Value
|
||||
}
|
||||
|
||||
func (jenkins *jenkins) GenerateToken(userName, tokenName string) (*UserToken, error) {
|
||||
token := &UserToken{raw: new(userTokenResponse),
|
||||
base: fmt.Sprintf("/user/%s/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken", userName)}
|
||||
endpoint := token.base
|
||||
data := map[string]string{"newTokenName": tokenName}
|
||||
r, err := jenkins.Requester.Post(endpoint, nil, token.raw, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.StatusCode == http.StatusOK {
|
||||
if token.raw.Status == "ok" {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return nil, errors.New(token.raw.Status)
|
||||
}
|
||||
|
||||
return nil, errors.New(strconv.Itoa(r.StatusCode))
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
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/log"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
// ReconcileJenkinsBaseConfiguration defines values required for Jenkins base configuration
|
||||
type ReconcileJenkinsBaseConfiguration struct {
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
logger logr.Logger
|
||||
jenkins *virtuslabv1alpha1.Jenkins
|
||||
local, minikube bool
|
||||
}
|
||||
|
||||
// New create structure which takes care of base configuration
|
||||
func New(client client.Client, scheme *runtime.Scheme, logger logr.Logger,
|
||||
jenkins *virtuslabv1alpha1.Jenkins, local, minikube bool) *ReconcileJenkinsBaseConfiguration {
|
||||
return &ReconcileJenkinsBaseConfiguration{
|
||||
client: client,
|
||||
scheme: scheme,
|
||||
logger: logger,
|
||||
jenkins: jenkins,
|
||||
local: local,
|
||||
minikube: minikube,
|
||||
}
|
||||
}
|
||||
|
||||
// Reconcile takes care of base configuration
|
||||
func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (*reconcile.Result, error) {
|
||||
if !r.validate(r.jenkins) {
|
||||
r.logger.V(log.VWarn).Info("Please correct Jenkins CR")
|
||||
return &reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
metaObject := resources.NewResourceObjectMeta(r.jenkins)
|
||||
|
||||
if err := r.createOperatorCredentialsSecret(metaObject); err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Operator credentials secret is present")
|
||||
|
||||
if err := r.createScriptsConfigMap(metaObject); err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Scripts config map is present")
|
||||
|
||||
if err := r.createBaseConfigurationConfigMap(metaObject); err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Base configuration config map is present")
|
||||
|
||||
if err := r.createService(metaObject); err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Service is present")
|
||||
|
||||
result, err := r.createJenkinsMasterPod(metaObject)
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Jenkins master pod is present")
|
||||
|
||||
result, err = r.waitForJenkins(metaObject)
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Jenkins master pod is ready")
|
||||
|
||||
_, err = r.getJenkinsClient(metaObject)
|
||||
if err != nil {
|
||||
return &reconcile.Result{}, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Jenkins API client set")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createOperatorCredentialsSecret(meta metav1.ObjectMeta) error {
|
||||
found := &corev1.Secret{}
|
||||
err := r.client.Get(context.TODO(), types.NamespacedName{Name: resources.GetOperatorCredentialsSecretName(r.jenkins), Namespace: r.jenkins.ObjectMeta.Namespace}, found)
|
||||
|
||||
if err != nil && apierrors.IsNotFound(err) {
|
||||
return r.createResource(resources.NewOperatorCredentialsSecret(meta, r.jenkins))
|
||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Data[resources.OperatorCredentialsSecretUserNameKey] != nil &&
|
||||
found.Data[resources.OperatorCredentialsSecretPasswordKey] != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.updateResource(resources.NewOperatorCredentialsSecret(meta, r.jenkins))
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createScriptsConfigMap(meta metav1.ObjectMeta) error {
|
||||
scripts, err := resources.NewScriptsConfigMap(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 {
|
||||
return err
|
||||
}
|
||||
return r.createOrUpdateResource(scripts)
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createService(meta metav1.ObjectMeta) error {
|
||||
err := r.createResource(resources.NewService(&meta, r.minikube))
|
||||
if err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) getJenkinsMasterPod(meta metav1.ObjectMeta) (*corev1.Pod, error) {
|
||||
jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins)
|
||||
currentJenkinsMasterPod := &corev1.Pod{}
|
||||
err := r.client.Get(context.TODO(), types.NamespacedName{Name: jenkinsMasterPod.Name, Namespace: jenkinsMasterPod.Namespace}, currentJenkinsMasterPod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return currentJenkinsMasterPod, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createJenkinsMasterPod(meta metav1.ObjectMeta) (*reconcile.Result, error) {
|
||||
// Check if this Pod already exists
|
||||
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins)
|
||||
r.logger.Info(fmt.Sprintf("Creating a new Jenkins Master Pod %s/%s", jenkinsMasterPod.Namespace, jenkinsMasterPod.Name))
|
||||
err = r.createResource(jenkinsMasterPod)
|
||||
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
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
} else if err != nil && !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Recreate pod
|
||||
recreatePod := false
|
||||
if currentJenkinsMasterPod != nil &&
|
||||
(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))
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
recreatePod = true
|
||||
}
|
||||
|
||||
if currentJenkinsMasterPod != nil && recreatePod && currentJenkinsMasterPod.ObjectMeta.DeletionTimestamp == nil {
|
||||
r.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name))
|
||||
if err := r.client.Delete(context.TODO(), currentJenkinsMasterPod); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &reconcile.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMeta) (*reconcile.Result, error) {
|
||||
jenkinsMasterPodStatus, err := r.getJenkinsMasterPod(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if jenkinsMasterPodStatus.ObjectMeta.DeletionTimestamp != nil {
|
||||
r.logger.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")
|
||||
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")
|
||||
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) getJenkinsClient(meta metav1.ObjectMeta) (jenkinsclient.Jenkins, error) {
|
||||
jenkinsURL, err := jenkinsclient.BuildJenkinsAPIUrl(
|
||||
r.jenkins.ObjectMeta.Namespace, meta.Name, resources.HTTPPortInt, r.local, r.minikube)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info(fmt.Sprintf("Jenkins API URL %s", jenkinsURL))
|
||||
|
||||
credentialsSecret := &corev1.Secret{}
|
||||
err = r.client.Get(context.TODO(), types.NamespacedName{Name: resources.GetOperatorCredentialsSecretName(r.jenkins), Namespace: r.jenkins.ObjectMeta.Namespace}, credentialsSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tokenCreationTime *time.Time
|
||||
tokenCreationTimeBytes := credentialsSecret.Data[resources.OperatorCredentialsSecretTokenCreationKey]
|
||||
if tokenCreationTimeBytes != nil {
|
||||
tokenCreationTime = &time.Time{}
|
||||
err = tokenCreationTime.UnmarshalText(tokenCreationTimeBytes)
|
||||
if err != nil {
|
||||
tokenCreationTime = nil
|
||||
}
|
||||
|
||||
}
|
||||
if credentialsSecret.Data[resources.OperatorCredentialsSecretTokenKey] == nil ||
|
||||
tokenCreationTimeBytes == nil || tokenCreationTime == nil ||
|
||||
currentJenkinsMasterPod.ObjectMeta.CreationTimestamp.Time.UTC().After(tokenCreationTime.UTC()) {
|
||||
r.logger.Info("Generating Jenkins API token for operator")
|
||||
userName := string(credentialsSecret.Data[resources.OperatorCredentialsSecretUserNameKey])
|
||||
jenkinsClient, err := jenkinsclient.New(
|
||||
jenkinsURL,
|
||||
userName,
|
||||
string(credentialsSecret.Data[resources.OperatorCredentialsSecretPasswordKey]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := jenkinsClient.GenerateToken(userName, "token")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentialsSecret.Data[resources.OperatorCredentialsSecretTokenKey] = []byte(token.GetToken())
|
||||
now, _ := time.Now().UTC().MarshalText()
|
||||
credentialsSecret.Data[resources.OperatorCredentialsSecretTokenCreationKey] = now
|
||||
err = r.updateResource(credentialsSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return jenkinsclient.New(
|
||||
jenkinsURL,
|
||||
string(credentialsSecret.Data[resources.OperatorCredentialsSecretUserNameKey]),
|
||||
string(credentialsSecret.Data[resources.OperatorCredentialsSecretTokenKey]))
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createResource(obj metav1.Object) error {
|
||||
runtimeObj, ok := obj.(runtime.Object)
|
||||
if !ok {
|
||||
return fmt.Errorf("is not a %T a runtime.Object", obj)
|
||||
}
|
||||
|
||||
// Set Jenkins instance as the owner and controller
|
||||
if err := controllerutil.SetControllerReference(r.jenkins, obj, r.scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.client.Create(context.TODO(), runtimeObj)
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) updateResource(obj metav1.Object) error {
|
||||
runtimeObj, ok := obj.(runtime.Object)
|
||||
if !ok {
|
||||
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)
|
||||
_ = controllerutil.SetControllerReference(r.jenkins, obj, r.scheme)
|
||||
|
||||
return r.client.Update(context.TODO(), runtimeObj)
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createOrUpdateResource(obj metav1.Object) error {
|
||||
runtimeObj, ok := obj.(runtime.Object)
|
||||
if !ok {
|
||||
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)
|
||||
_ = controllerutil.SetControllerReference(r.jenkins, obj, r.scheme)
|
||||
|
||||
err := r.client.Create(context.TODO(), runtimeObj)
|
||||
if err != nil && errors.IsAlreadyExists(err) {
|
||||
return r.updateResource(obj)
|
||||
} else if err != nil && !errors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const createOperatorUserFileName = "createOperatorUser.groovy"
|
||||
|
||||
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)
|
||||
jenkins.save()
|
||||
`))
|
||||
|
||||
func buildCreateJenkinsOperatorUserGroovyScript() (*string, error) {
|
||||
data := struct {
|
||||
OperatorCredentialsPath string
|
||||
OperatorUserNameFile string
|
||||
OperatorPasswordFile string
|
||||
}{
|
||||
OperatorCredentialsPath: jenkinsOperatorCredentialsVolumePath,
|
||||
OperatorUserNameFile: OperatorCredentialsSecretUserNameKey,
|
||||
OperatorPasswordFile: OperatorCredentialsSecretPasswordKey,
|
||||
}
|
||||
|
||||
output, err := renderTemplate(createOperatorUserGroovyFmtTemplate, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// NewResourceObjectMeta builds ObjectMeta for all Kubernetes resources created by operator
|
||||
func NewResourceObjectMeta(jenkins *virtuslabv1alpha1.Jenkins) metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: GetResourceName(jenkins),
|
||||
Namespace: jenkins.ObjectMeta.Namespace,
|
||||
Labels: BuildResourceLabels(jenkins),
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
const (
|
||||
jenkinsHomeVolumeName = "home"
|
||||
jenkinsHomePath = "/var/jenkins/home"
|
||||
|
||||
jenkinsScriptsVolumeName = "scripts"
|
||||
jenkinsScriptsVolumePath = "/var/jenkins/scripts"
|
||||
initScriptName = "init.sh"
|
||||
|
||||
jenkinsOperatorCredentialsVolumeName = "operator-credentials"
|
||||
jenkinsOperatorCredentialsVolumePath = "/var/jenkins/operator-credentials"
|
||||
|
||||
jenkinsBaseConfigurationVolumeName = "base-configuration"
|
||||
jenkinsBaseConfigurationVolumePath = "/var/jenkins/base-configuration"
|
||||
|
||||
httpPortName = "http"
|
||||
slavePortName = "slavelistener"
|
||||
// HTTPPortInt defines Jenkins master HTTP port
|
||||
HTTPPortInt = 8080
|
||||
slavePortInt = 50000
|
||||
httpPortInt32 = int32(8080)
|
||||
slavePortInt32 = int32(50000)
|
||||
|
||||
jenkinsUserUID = int64(1000) // build in Docker image jenkins user UID
|
||||
)
|
||||
|
||||
func buildPodTypeMeta() metav1.TypeMeta {
|
||||
return metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
}
|
||||
}
|
||||
|
||||
// NewJenkinsMasterPod builds Jenkins Master Kubernetes Pod resource
|
||||
func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) *corev1.Pod {
|
||||
initialDelaySeconds := int32(30)
|
||||
timeoutSeconds := int32(5)
|
||||
failureThreshold := int32(12)
|
||||
runAsUser := jenkinsUserUID
|
||||
|
||||
objectMeta.Annotations = jenkins.Spec.Master.Annotations
|
||||
|
||||
return &corev1.Pod{
|
||||
TypeMeta: buildPodTypeMeta(),
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
SecurityContext: &corev1.PodSecurityContext{
|
||||
RunAsUser: &runAsUser,
|
||||
RunAsGroup: &runAsUser,
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "jenkins-master",
|
||||
Image: jenkins.Spec.Master.Image,
|
||||
Command: []string{
|
||||
"bash",
|
||||
fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName),
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/login",
|
||||
Port: intstr.FromString(httpPortName),
|
||||
Scheme: corev1.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: initialDelaySeconds,
|
||||
TimeoutSeconds: timeoutSeconds,
|
||||
FailureThreshold: failureThreshold,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/login",
|
||||
Port: intstr.FromString(httpPortName),
|
||||
Scheme: corev1.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: initialDelaySeconds,
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: slavePortName,
|
||||
ContainerPort: slavePortInt32,
|
||||
},
|
||||
{
|
||||
Name: httpPortName,
|
||||
ContainerPort: httpPortInt32,
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "JENKINS_HOME",
|
||||
Value: jenkinsHomePath,
|
||||
},
|
||||
{
|
||||
Name: "JAVA_OPTS",
|
||||
Value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true",
|
||||
},
|
||||
},
|
||||
Resources: jenkins.Spec.Master.Resources,
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: jenkinsHomeVolumeName,
|
||||
MountPath: jenkinsHomePath,
|
||||
ReadOnly: false,
|
||||
},
|
||||
{
|
||||
Name: jenkinsScriptsVolumeName,
|
||||
MountPath: jenkinsScriptsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsBaseConfigurationVolumeName,
|
||||
MountPath: jenkinsBaseConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsOperatorCredentialsVolumeName,
|
||||
MountPath: jenkinsOperatorCredentialsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: jenkinsHomeVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: jenkinsScriptsVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: getScriptsConfigMapName(jenkins),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: jenkinsBaseConfigurationVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: GetBaseConfigurationConfigMapName(jenkins),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: jenkinsOperatorCredentialsVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: GetOperatorCredentialsSecretName(jenkins),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var randomCharset = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
func randomString(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = randomCharset[rand.Intn(len(randomCharset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var initBashTemplate = template.Must(template.New(initScriptName).Parse(`#!/usr/bin/env bash
|
||||
set -e
|
||||
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
|
||||
|
||||
/sbin/tini -s -- /usr/local/bin/jenkins.sh
|
||||
`))
|
||||
|
||||
func buildConfigMapTypeMeta() metav1.TypeMeta {
|
||||
return metav1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
}
|
||||
}
|
||||
|
||||
func buildInitBashScript() (*string, error) {
|
||||
data := struct {
|
||||
JenkinsHomePath string
|
||||
BaseConfigurationPath string
|
||||
}{
|
||||
JenkinsHomePath: jenkinsHomePath,
|
||||
BaseConfigurationPath: jenkinsBaseConfigurationVolumePath,
|
||||
}
|
||||
|
||||
output, err := renderTemplate(initBashTemplate, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &output, nil
|
||||
}
|
||||
|
||||
func getScriptsConfigMapName(jenkins *virtuslabv1alpha1.Jenkins) string {
|
||||
return fmt.Sprintf("jenkins-operator-scripts-%s", jenkins.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
// NewScriptsConfigMap builds Kubernetes config map used to store scripts
|
||||
func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) (*corev1.ConfigMap, error) {
|
||||
meta.Name = getScriptsConfigMapName(jenkins)
|
||||
|
||||
initBashScript, err := buildInitBashScript()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: buildConfigMapTypeMeta(),
|
||||
ObjectMeta: meta,
|
||||
Data: map[string]string{
|
||||
initScriptName: *initBashScript,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// OperatorUserName defines username for Jenkins API calls
|
||||
OperatorUserName = "jenkins-operator"
|
||||
// OperatorCredentialsSecretUserNameKey defines key of username in operator credentials secret
|
||||
OperatorCredentialsSecretUserNameKey = "user"
|
||||
// OperatorCredentialsSecretPasswordKey defines key of password in operator credentials secret
|
||||
OperatorCredentialsSecretPasswordKey = "password"
|
||||
// OperatorCredentialsSecretTokenKey defines key of token in operator credentials secret
|
||||
OperatorCredentialsSecretTokenKey = "token"
|
||||
// OperatorCredentialsSecretTokenCreationKey defines key of token creation time in operator credentials secret
|
||||
OperatorCredentialsSecretTokenCreationKey = "tokenCreationTime"
|
||||
)
|
||||
|
||||
func buildSecretTypeMeta() metav1.TypeMeta {
|
||||
return metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// NewOperatorCredentialsSecret builds the Kubernetes secret used to store jenkins operator credentials
|
||||
// to allow calls to Jenkins API
|
||||
func NewOperatorCredentialsSecret(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) *corev1.Secret {
|
||||
meta.Name = GetOperatorCredentialsSecretName(jenkins)
|
||||
return &corev1.Secret{
|
||||
TypeMeta: buildSecretTypeMeta(),
|
||||
ObjectMeta: meta,
|
||||
Data: map[string][]byte{
|
||||
OperatorCredentialsSecretUserNameKey: []byte(OperatorUserName),
|
||||
OperatorCredentialsSecretPasswordKey: []byte(randomString(20)),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func buildServiceTypeMeta() metav1.TypeMeta {
|
||||
return metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "v1",
|
||||
}
|
||||
}
|
||||
|
||||
// NewService builds the Kubernetes service resource
|
||||
func NewService(meta *metav1.ObjectMeta, minikube bool) *corev1.Service {
|
||||
service := &corev1.Service{
|
||||
TypeMeta: buildServiceTypeMeta(),
|
||||
ObjectMeta: *meta,
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: meta.Labels,
|
||||
// The first port have to be Jenkins http port because when run with minikube
|
||||
// command 'minikube service' returns endpoints in the same sequence
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: httpPortName,
|
||||
Port: httpPortInt32,
|
||||
TargetPort: intstr.FromInt(HTTPPortInt),
|
||||
},
|
||||
{
|
||||
Name: slavePortName,
|
||||
Port: slavePortInt32,
|
||||
TargetPort: intstr.FromInt(slavePortInt),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if minikube {
|
||||
// When running locally with minikube cluster Jenkins Service have to be exposed via node port
|
||||
// to allow communication operator -> Jenkins API
|
||||
service.Spec.Type = corev1.ServiceTypeNodePort
|
||||
} else {
|
||||
service.Spec.Type = corev1.ServiceTypeClusterIP
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func renderTemplate(template *template.Template, data interface{}) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
if err := template.Execute(&buffer, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
|
||||
docker "github.com/docker/distribution/reference"
|
||||
)
|
||||
|
||||
var (
|
||||
dockerImageRegexp = regexp.MustCompile(`^` + docker.TagRegexp.String() + `$`)
|
||||
)
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) validate(jenkins *virtuslabv1alpha1.Jenkins) bool {
|
||||
if jenkins.Spec.Master.Image == "" {
|
||||
r.logger.V(0).Info("Image not set")
|
||||
return false
|
||||
}
|
||||
|
||||
if !dockerImageRegexp.MatchString(jenkins.Spec.Master.Image) && !docker.ReferenceRegexp.MatchString(jenkins.Spec.Master.Image) {
|
||||
r.logger.V(0).Info("Invalid image")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -2,37 +2,38 @@ package jenkins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base"
|
||||
"github.com/VirtusLab/jenkins-operator/pkg/log"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
)
|
||||
|
||||
/**
|
||||
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
|
||||
* business logic. Delete these comments after modifying this file.*
|
||||
*/
|
||||
|
||||
// Add creates a new Jenkins Controller and adds it to the Manager. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func Add(mgr manager.Manager) error {
|
||||
return add(mgr, newReconciler(mgr))
|
||||
func Add(mgr manager.Manager, local, minikube bool) error {
|
||||
return add(mgr, newReconciler(mgr, local, minikube))
|
||||
}
|
||||
|
||||
// newReconciler returns a new reconcile.Reconciler
|
||||
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
|
||||
return &ReconcileJenkins{client: mgr.GetClient(), scheme: mgr.GetScheme()}
|
||||
func newReconciler(mgr manager.Manager, local, minikube bool) reconcile.Reconciler {
|
||||
return &ReconcileJenkins{
|
||||
client: mgr.GetClient(),
|
||||
scheme: mgr.GetScheme(),
|
||||
local: local,
|
||||
minikube: minikube,
|
||||
}
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
@@ -68,23 +69,20 @@ var _ reconcile.Reconciler = &ReconcileJenkins{}
|
||||
type ReconcileJenkins struct {
|
||||
// This client, initialized using mgr.Client() above, is a split client
|
||||
// that reads objects from the cache and writes to the apiserver
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
local, minikube bool
|
||||
}
|
||||
|
||||
// Reconcile reads that state of the cluster for a Jenkins object and makes changes based on the state read
|
||||
// and what is in the Jenkins.Spec
|
||||
// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates
|
||||
// a Pod as an example
|
||||
// Note:
|
||||
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
|
||||
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
|
||||
func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
||||
log.Printf("Reconciling Jenkins %s/%s\n", request.Namespace, request.Name)
|
||||
logger := r.buildLogger(request.Name)
|
||||
logger.Info("Reconciling Jenkins")
|
||||
|
||||
// Fetch the Jenkins instance
|
||||
instance := &virtuslabv1alpha1.Jenkins{}
|
||||
err := r.client.Get(context.TODO(), request.NamespacedName, instance)
|
||||
jenkins := &virtuslabv1alpha1.Jenkins{}
|
||||
err := r.client.Get(context.TODO(), request.NamespacedName, jenkins)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Request object not found, could have been deleted after reconcile request.
|
||||
@@ -96,54 +94,26 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// Define a new Pod object
|
||||
pod := newPodForCR(instance)
|
||||
|
||||
// Set Jenkins instance as the owner and controller
|
||||
if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil {
|
||||
baseConfiguration := base.New(r.client, r.scheme, logger, jenkins, r.local, r.minikube)
|
||||
result, err := baseConfiguration.Reconcile()
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// Check if this Pod already exists
|
||||
found := &corev1.Pod{}
|
||||
err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, found)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
log.Printf("Creating a new Pod %s/%s\n", pod.Namespace, pod.Name)
|
||||
err = r.client.Create(context.TODO(), pod)
|
||||
if result != nil {
|
||||
return *result, nil
|
||||
}
|
||||
if err == nil && result == nil && jenkins.Status.BaseConfigurationCompletedTime == nil {
|
||||
now := metav1.Now()
|
||||
jenkins.Status.BaseConfigurationCompletedTime = &now
|
||||
err = r.client.Update(context.TODO(), jenkins)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// Pod created successfully - don't requeue
|
||||
return reconcile.Result{}, nil
|
||||
} else if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// Pod already exists - don't requeue
|
||||
log.Printf("Skip reconcile: Pod %s/%s already exists", found.Namespace, found.Name)
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
// newPodForCR returns a busybox pod with the same name/namespace as the cr
|
||||
func newPodForCR(cr *virtuslabv1alpha1.Jenkins) *corev1.Pod {
|
||||
labels := map[string]string{
|
||||
"app": cr.Name,
|
||||
}
|
||||
return &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cr.Name + "-pod",
|
||||
Namespace: cr.Namespace,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "busybox",
|
||||
Image: "busybox",
|
||||
Command: []string{"sleep", "3600"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
func (r *ReconcileJenkins) buildLogger(jenkinsName string) logr.Logger {
|
||||
return log.Log.WithValues("cr", jenkinsName)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user