Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject abstractions by using interfaces #391

Merged
merged 2 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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 reconcile User Configuration
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