Skip to content

Commit

Permalink
inject abstration not implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jkhelil committed May 14, 2020
1 parent 3747cce commit 419bd76
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 102 deletions.
2 changes: 1 addition & 1 deletion pkg/controller/jenkins/configuration/base/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureBaseConfiguration(jenkinsClien
Configurations: []v1alpha2.ConfigMapRef{{Name: resources.GetBaseConfigurationConfigMapName(r.Configuration.Jenkins)}},
},
}
groovyClient := groovy.New(jenkinsClient, r.Client, r.logger, r.Configuration.Jenkins, "base-groovy", customization.Customization)
groovyClient := groovy.New(jenkinsClient, r.Client, r.Configuration.Jenkins, "base-groovy", customization.Customization)
requeue, err := groovyClient.Ensure(func(name string) bool {
return strings.HasSuffix(name, ".groovy")
}, func(groovyScript string) string {
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/jenkins/configuration/base/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ func TestValidateConfigMapVolume(t *testing.T) {

baseReconcileLoop := New(configuration.Configuration{
Jenkins: &v1alpha2.Jenkins{ObjectMeta: metav1.ObjectMeta{Name: "example"}},
Client: fakeClient,
Client: fakeClient,
}, client.JenkinsAPIConnectionSettings{})

got, err := baseReconcileLoop.validateConfigMapVolume(volume)
Expand Down Expand Up @@ -652,7 +652,7 @@ func TestValidateSecretVolume(t *testing.T) {
fakeClient := fake.NewFakeClient()
baseReconcileLoop := New(configuration.Configuration{
Jenkins: &v1alpha2.Jenkins{ObjectMeta: metav1.ObjectMeta{Name: "example"}},
Client: fakeClient,
Client: fakeClient,
}, client.JenkinsAPIConnectionSettings{})

got, err := baseReconcileLoop.validateSecretVolume(volume)
Expand Down
17 changes: 10 additions & 7 deletions pkg/controller/jenkins/configuration/user/casc/casc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,29 @@ import (
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/groovy"

"github.com/go-logr/logr"
k8s "sigs.k8s.io/controller-runtime/pkg/client"
)

const groovyUtf8MaxStringLength = 65535

// ConfigurationAsCode defines API which configures Jenkins with help Configuration as a code plugin
type ConfigurationAsCode struct {
// ConfigurationAsCode defines client for configurationAsCode
type ConfigurationAsCode interface {
Ensure(jenkins *v1alpha2.Jenkins) (requeue bool, err error)
}

type configurationAsCode struct {
groovyClient *groovy.Groovy
}

// New creates new instance of ConfigurationAsCode
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jenkins *v1alpha2.Jenkins) *ConfigurationAsCode {
return &ConfigurationAsCode{
groovyClient: groovy.New(jenkinsClient, k8sClient, logger, jenkins, "user-casc", jenkins.Spec.ConfigurationAsCode.Customization),
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, jenkins *v1alpha2.Jenkins) ConfigurationAsCode {
return &configurationAsCode{
groovyClient: groovy.New(jenkinsClient, k8sClient, jenkins, "user-casc", jenkins.Spec.ConfigurationAsCode.Customization),
}
}

// Ensure configures Jenkins with help Configuration as a code plugin
func (c *ConfigurationAsCode) Ensure(jenkins *v1alpha2.Jenkins) (requeue bool, err error) {
func (c *configurationAsCode) Ensure(jenkins *v1alpha2.Jenkins) (requeue bool, err error) {
requeue, err = c.groovyClient.WaitForSecretSynchronization(resources.ConfigurationAsCodeSecretVolumePath)
if err != nil || requeue {
return requeue, err
Expand Down
45 changes: 29 additions & 16 deletions pkg/controller/jenkins/configuration/user/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package user
import (
"strings"

"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"

"github.com/go-logr/logr"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration"
Expand All @@ -15,35 +17,46 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// ReconcileUserConfiguration defines values required for Jenkins user configuration
type ReconcileUserConfiguration struct {
// ReconcileUserConfiguration defines api client for reconcileUserConfiguration
type ReconcileUserConfiguration interface {
ReconcileCasc() (reconcile.Result, error)
ReconcileOthers() (reconcile.Result, error)
Validate(jenkins *v1alpha2.Jenkins) ([]string, error)
}

type reconcileUserConfiguration struct {
configuration.Configuration
jenkinsClient jenkinsclient.Jenkins
logger logr.Logger
}

// New create structure which takes care of user configuration
func New(configuration configuration.Configuration, jenkinsClient jenkinsclient.Jenkins) *ReconcileUserConfiguration {
return &ReconcileUserConfiguration{
// New create structure which takes care of user configuration.
func New(configuration configuration.Configuration, jenkinsClient jenkinsclient.Jenkins) ReconcileUserConfiguration {
return &reconcileUserConfiguration{
Configuration: configuration,
jenkinsClient: jenkinsClient,
logger: log.Log.WithValues("cr", configuration.Jenkins.Name),
}
}

// Reconcile it's a main reconciliation loop for user supplied configuration
func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) {
backupAndRestore := backuprestore.New(r.Configuration, r.logger)

result, err := r.ensureUserConfiguration(r.jenkinsClient)
// ReconcileCasc is a reconcile loop for casc.
func (r *reconcileUserConfiguration) ReconcileCasc() (reconcile.Result, error) {
result, err := r.ensureCasc(r.jenkinsClient)
if err != nil {
return reconcile.Result{}, err
}
if result.Requeue {
return result, nil
}

result, err = r.ensureSeedJobs()
return reconcile.Result{}, nil
}

// Reconcile it's a main reconciliation loop for user supplied configuration
func (r *reconcileUserConfiguration) ReconcileOthers() (reconcile.Result, error) {
backupAndRestore := backuprestore.New(r.Configuration, r.logger)

result, err := r.ensureSeedJobs()
if err != nil {
return reconcile.Result{}, err
}
Expand All @@ -65,8 +78,8 @@ func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) {
return reconcile.Result{}, nil
}

func (r *ReconcileUserConfiguration) ensureSeedJobs() (reconcile.Result, error) {
seedJobs := seedjobs.New(r.jenkinsClient, r.Configuration, r.logger)
func (r *reconcileUserConfiguration) ensureSeedJobs() (reconcile.Result, error) {
seedJobs := seedjobs.New(r.jenkinsClient, r.Configuration)
done, err := seedJobs.EnsureSeedJobs(r.Configuration.Jenkins)
if err != nil {
return reconcile.Result{}, err
Expand All @@ -77,8 +90,8 @@ func (r *ReconcileUserConfiguration) ensureSeedJobs() (reconcile.Result, error)
return reconcile.Result{}, nil
}

func (r *ReconcileUserConfiguration) ensureUserConfiguration(jenkinsClient jenkinsclient.Jenkins) (reconcile.Result, error) {
configurationAsCodeClient := casc.New(jenkinsClient, r.Client, r.logger, r.Configuration.Jenkins)
func (r *reconcileUserConfiguration) ensureCasc(jenkinsClient jenkinsclient.Jenkins) (reconcile.Result, error) {
configurationAsCodeClient := casc.New(jenkinsClient, r.Client, r.Configuration.Jenkins)
requeue, err := configurationAsCodeClient.Ensure(r.Configuration.Jenkins)
if err != nil {
return reconcile.Result{}, err
Expand All @@ -87,7 +100,7 @@ func (r *ReconcileUserConfiguration) ensureUserConfiguration(jenkinsClient jenki
return reconcile.Result{Requeue: true}, nil
}

groovyClient := groovy.New(jenkinsClient, r.Client, r.logger, r.Configuration.Jenkins, "user-groovy", r.Configuration.Jenkins.Spec.GroovyScripts.Customization)
groovyClient := groovy.New(jenkinsClient, r.Client, r.Configuration.Jenkins, "user-groovy", r.Configuration.Jenkins.Spec.GroovyScripts.Customization)
requeue, err = groovyClient.WaitForSecretSynchronization(resources.GroovyScriptsSecretVolumePath)
if err != nil {
return reconcile.Result{}, err
Expand Down
48 changes: 32 additions & 16 deletions pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"reflect"
"text/template"

"github.com/go-logr/logr"
"github.com/jenkinsci/kubernetes-operator/internal/render"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
Expand All @@ -16,8 +17,7 @@ import (
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/groovy"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/notifications/reason"

"github.com/go-logr/logr"
"github.com/jenkinsci/kubernetes-operator/pkg/log"
stackerr "github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -142,24 +142,40 @@ jobRef.setAssignedLabel(new LabelAtom("{{ .AgentName }}"))
jenkins.getQueue().schedule(jobRef)
`))

// SeedJobs defines API for configuring and ensuring Jenkins Seed Jobs and Deploy Keys
type SeedJobs struct {
// SeedJobs defines client interface to SeedJobs
type SeedJobs interface {
EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err error)
waitForSeedJobAgent(agentName string) (requeue bool, err error)
createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err error)
ensureLabelsForSecrets(jenkins v1alpha2.Jenkins) error
credentialValue(namespace string, seedJob v1alpha2.SeedJob) (string, error)
getAllSeedJobIDs(jenkins v1alpha2.Jenkins) []string
isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool
createAgent(jenkinsClient jenkinsclient.Jenkins, k8sClient client.Client, jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string) error
ValidateSeedJobs(jenkins v1alpha2.Jenkins) ([]string, error)
validateSchedule(job v1alpha2.SeedJob, str string, key string) []string
validateGitHubPushTrigger(jenkins v1alpha2.Jenkins) []string
validateBitbucketPushTrigger(jenkins v1alpha2.Jenkins) []string
validateIfIDIsUnique(seedJobs []v1alpha2.SeedJob) []string
}

type seedJobs struct {
configuration.Configuration
jenkinsClient jenkinsclient.Jenkins
logger logr.Logger
}

// New creates SeedJobs object
func New(jenkinsClient jenkinsclient.Jenkins, config configuration.Configuration, logger logr.Logger) *SeedJobs {
return &SeedJobs{
func New(jenkinsClient jenkinsclient.Jenkins, config configuration.Configuration) SeedJobs {
return &seedJobs{
Configuration: config,
jenkinsClient: jenkinsClient,
logger: logger,
logger: log.Log.WithValues("cr", config.Jenkins.Name),
}
}

// EnsureSeedJobs configures seed job and runs it for every entry from Jenkins.Spec.SeedJobs
func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err error) {
func (s *seedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err error) {
if s.isRecreatePodNeeded(*jenkins) {
message := "Some seed job has been deleted, recreating pod"
s.logger.Info(message)
Expand Down Expand Up @@ -218,7 +234,7 @@ func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err
return true, nil
}

func (s SeedJobs) waitForSeedJobAgent(agentName string) (requeue bool, err error) {
func (s *seedJobs) waitForSeedJobAgent(agentName string) (requeue bool, err error) {
agent := appsv1.Deployment{}
err = s.Client.Get(context.TODO(), types.NamespacedName{Name: agentDeploymentName(*s.Jenkins, agentName), Namespace: s.Jenkins.Namespace}, &agent)
if apierrors.IsNotFound(err) {
Expand All @@ -237,8 +253,8 @@ func (s SeedJobs) waitForSeedJobAgent(agentName string) (requeue bool, err error
}

// createJob is responsible for creating jenkins job which configures jenkins seed jobs and deploy keys
func (s *SeedJobs) createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err error) {
groovyClient := groovy.New(s.jenkinsClient, s.Client, s.logger, jenkins, "seed-jobs", jenkins.Spec.GroovyScripts.Customization)
func (s *seedJobs) createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err error) {
groovyClient := groovy.New(s.jenkinsClient, s.Client, jenkins, "seed-jobs", jenkins.Spec.GroovyScripts.Customization)
for _, seedJob := range jenkins.Spec.SeedJobs {
credentialValue, err := s.credentialValue(jenkins.Namespace, seedJob)
if err != nil {
Expand Down Expand Up @@ -269,7 +285,7 @@ func (s *SeedJobs) createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err erro
// ensureLabelsForSecrets adds labels to Kubernetes secrets where are Jenkins credentials used for seed jobs,
// thanks to them kubernetes-credentials-provider-plugin will create Jenkins credentials in Jenkins and
// Operator will able to watch any changes made to them
func (s *SeedJobs) ensureLabelsForSecrets(jenkins v1alpha2.Jenkins) error {
func (s *seedJobs) ensureLabelsForSecrets(jenkins v1alpha2.Jenkins) error {
for _, seedJob := range jenkins.Spec.SeedJobs {
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
requiredLabels := resources.BuildLabelsForWatchedResources(jenkins)
Expand All @@ -294,7 +310,7 @@ func (s *SeedJobs) ensureLabelsForSecrets(jenkins v1alpha2.Jenkins) error {
return nil
}

func (s *SeedJobs) credentialValue(namespace string, seedJob v1alpha2.SeedJob) (string, error) {
func (s *seedJobs) credentialValue(namespace string, seedJob v1alpha2.SeedJob) (string, error) {
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
secret := &corev1.Secret{}
namespaceName := types.NamespacedName{Namespace: namespace, Name: seedJob.CredentialID}
Expand All @@ -311,15 +327,15 @@ func (s *SeedJobs) credentialValue(namespace string, seedJob v1alpha2.SeedJob) (
return "", nil
}

func (s *SeedJobs) getAllSeedJobIDs(jenkins v1alpha2.Jenkins) []string {
func (s *seedJobs) getAllSeedJobIDs(jenkins v1alpha2.Jenkins) []string {
var ids []string
for _, seedJob := range jenkins.Spec.SeedJobs {
ids = append(ids, seedJob.ID)
}
return ids
}

func (s *SeedJobs) isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool {
func (s *seedJobs) isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool {
for _, createdSeedJob := range jenkins.Status.CreatedSeedJobs {
found := false
for _, seedJob := range jenkins.Spec.SeedJobs {
Expand All @@ -336,7 +352,7 @@ func (s *SeedJobs) isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool {
}

// createAgent deploys Jenkins agent to Kubernetes cluster
func (s SeedJobs) createAgent(jenkinsClient jenkinsclient.Jenkins, k8sClient client.Client, jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string) error {
func (s *seedJobs) createAgent(jenkinsClient jenkinsclient.Jenkins, k8sClient client.Client, jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string) error {
_, err := jenkinsClient.GetNode(agentName)

// Create node if not exists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
logf "sigs.k8s.io/controller-runtime/pkg/log/zap"
)

const agentSecret = "test-secret"
Expand Down Expand Up @@ -70,7 +69,6 @@ func jenkinsCustomResource() *v1alpha2.Jenkins {
func TestEnsureSeedJobs(t *testing.T) {
t.Run("happy", func(t *testing.T) {
// given
logger := logf.Logger(false)
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
Expand Down Expand Up @@ -105,7 +103,7 @@ func TestEnsureSeedJobs(t *testing.T) {
jenkinsClient.EXPECT().GetNodeSecret(AgentName).Return(agentSecret, nil).AnyTimes()
jenkinsClient.EXPECT().ExecuteScript(seedJobCreatingScript).AnyTimes()

seedJobClient := New(jenkinsClient, config, logger)
seedJobClient := New(jenkinsClient, config)

// when
_, err = seedJobClient.EnsureSeedJobs(jenkins)
Expand All @@ -120,7 +118,6 @@ func TestEnsureSeedJobs(t *testing.T) {

t.Run("delete agent deployment when no seed jobs", func(t *testing.T) {
// given
logger := logf.Logger(false)
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
Expand All @@ -144,7 +141,7 @@ func TestEnsureSeedJobs(t *testing.T) {
jenkinsClient.EXPECT().CreateNode(AgentName, 1, "The jenkins-operator generated agent", "/home/jenkins", AgentName).AnyTimes()
jenkinsClient.EXPECT().GetNodeSecret(AgentName).Return(agentSecret, nil).AnyTimes()

seedJobsClient := New(jenkinsClient, config, logger)
seedJobsClient := New(jenkinsClient, config)

err = fakeClient.Create(ctx, &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -170,7 +167,6 @@ func TestEnsureSeedJobs(t *testing.T) {
func TestCreateAgent(t *testing.T) {
t.Run("don't fail when deployment is already created", func(t *testing.T) {
// given
logger := logf.Logger(false)
ctrl := gomock.NewController(t)
ctx := context.TODO()
defer ctrl.Finish()
Expand All @@ -190,9 +186,10 @@ func TestCreateAgent(t *testing.T) {
Client: fakeClient,
ClientSet: kubernetes.Clientset{},
Notifications: nil,
Jenkins: &v1alpha2.Jenkins{},
}

seedJobsClient := New(jenkinsClient, config, logger)
seedJobsClient := New(jenkinsClient, config)

err = fakeClient.Create(ctx, &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -215,8 +212,9 @@ func TestSeedJobs_isRecreatePodNeeded(t *testing.T) {
Client: nil,
ClientSet: kubernetes.Clientset{},
Notifications: nil,
Jenkins: &v1alpha2.Jenkins{},
}
seedJobsClient := New(nil, config, nil)
seedJobsClient := New(nil, config)
t.Run("empty", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{}

Expand Down
Loading

0 comments on commit 419bd76

Please sign in to comment.