diff --git a/Makefile b/Makefile index 03a8c6df..9a1e36f4 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,7 @@ install: ## Installs the executable .PHONY: run run: export WATCH_NAMESPACE = $(NAMESPACE) run: export OPERATOR_NAME = $(NAME) -run: fmt vet manifests install build ## Run the executable, you can use EXTRA_ARGS +run: fmt vet manifests install-crds build ## Run the executable, you can use EXTRA_ARGS @echo "+ $@" ifeq ($(KUBERNETES_PROVIDER),minikube) kubectl config use-context $(KUBECTL_CONTEXT) @@ -422,7 +422,7 @@ generate-docs: ## Re-generate docs directory from the website directory ##################### FROM OPERATOR SDK ######################## #TODO rename # Install CRDs into a cluster -install: manifests kustomize +install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - # Uninstall CRDs from a cluster @@ -473,3 +473,10 @@ bundle: manifests kustomize .PHONY: bundle-build bundle-build: docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +#FIXME temporary target for running tests (test used above for go test) +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin +testing: generate fmt vet manifests + mkdir -p ${ENVTEST_ASSETS_DIR} + test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out \ No newline at end of file diff --git a/controllers/jenkins.go b/controllers/jenkins.go new file mode 100644 index 00000000..af11d172 --- /dev/null +++ b/controllers/jenkins.go @@ -0,0 +1,170 @@ +package controllers + +import ( + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + "github.com/jenkinsci/kubernetes-operator/pkg/constants" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +const ( + userConfigurationConfigMapName = "user-config" + userConfigurationSecretName = "user-secret" +) + +type seedJobConfig struct { + v1alpha2.SeedJob + JobNames []string `json:"jobNames,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` +} + +var ( + jenkinsCRName = "jenkins-example" + namespace = "default" + priorityClassName = "" + + mySeedJob = seedJobConfig{ + SeedJob: v1alpha2.SeedJob{ + ID: "jenkins-operator", + CredentialID: "jenkins-operator", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + Description: "Jenkins Operator repository", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + PollSCM: "1 1 1 1 1", + UnstableOnDeprecation: true, + BuildPeriodically: "1 1 1 1 1", + FailOnMissingPlugin: true, + IgnoreMissingFiles: true, + //AdditionalClasspath: can fail with the seed job agent + GitHubPushTrigger: true, + }, + } + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + Secret: v1alpha2.SecretRef{ + Name: userConfigurationSecretName, + }, + }, + } + + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + Secret: v1alpha2.SecretRef{ + Name: userConfigurationSecretName, + }, + }, + } +) + +func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { + var seedJobs []v1alpha2.SeedJob + if seedJob != nil { + seedJobs = append(seedJobs, *seedJob...) + } + + jenkins := &v1alpha2.Jenkins{ + TypeMeta: v1alpha2.JenkinsTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: v1alpha2.JenkinsSpec{ + GroovyScripts: groovyScripts, + ConfigurationAsCode: casc, + Master: v1alpha2.JenkinsMaster{ + Annotations: map[string]string{"test": "label"}, + Containers: []v1alpha2.Container{ + { + Name: resources.JenkinsMasterContainerName, + Env: []corev1.EnvVar{ + { + Name: "TEST_ENV", + Value: "test_env_value", + }, + }, + ReadinessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/login", + Port: intstr.FromString("http"), + Scheme: corev1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: int32(80), + TimeoutSeconds: int32(4), + FailureThreshold: int32(10), + }, + LivenessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/login", + Port: intstr.FromString("http"), + Scheme: corev1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: int32(80), + TimeoutSeconds: int32(4), + FailureThreshold: int32(10), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins-cache", + MountPath: "/usr/share/jenkins/ref/plugins", + }, + }, + }, + { + Name: "envoyproxy", + Image: "envoyproxy/envoy-alpine:v1.14.1", + }, + }, + Plugins: []v1alpha2.Plugin{ + {Name: "audit-trail", Version: "3.7"}, + {Name: "simple-theme-plugin", Version: "0.6"}, + {Name: "github", Version: "1.32.0"}, + {Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"}, + }, + PriorityClassName: priorityClassName, + NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, + Volumes: []corev1.Volume{ + { + Name: "plugins-cache", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + SeedJobs: seedJobs, + Service: v1alpha2.Service{ + Type: corev1.ServiceTypeNodePort, + Port: constants.DefaultHTTPPortInt32, + }, + }, + } + jenkins.Spec.Roles = []rbacv1.RoleRef{ + { + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: resources.GetResourceName(jenkins), + }, + } + return jenkins +} diff --git a/controllers/jenkins_controller_test.go b/controllers/jenkins_controller_test.go new file mode 100644 index 00000000..f0c25f5c --- /dev/null +++ b/controllers/jenkins_controller_test.go @@ -0,0 +1,40 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. +var _ = Describe("Jenkins controller", func() { + Describe("deploying Jenkins CR into a cluster", func() { + Context("when deploying CR to cluster", func() { + It("create Jenkins instance", func() { + ctx := context.Background() + jenkins := createJenkinsCR(jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) + Expect(k8sClient.Create(ctx, jenkins)).Should(Succeed()) + }) + }) + }) +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go new file mode 100644 index 00000000..8c1935be --- /dev/null +++ b/controllers/suite_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "path/filepath" + "testing" + + jenkinsiov1alpha2 "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + jenkinsClient "github.com/jenkinsci/kubernetes-operator/pkg/client" + e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + //cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = jenkinsiov1alpha2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + //setup manager + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).NotTo(HaveOccurred()) + + //setup controller + clientSet, err := kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + + notificationEvents := make(chan e.Event) + + // validate jenkins API connection + jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{} + + err = (&JenkinsReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + JenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, + ClientSet: *clientSet, + Config: *cfg, + NotificationEvents: ¬ificationEvents, + KubernetesClusterDomain: "", + }).SetupWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + go func() { + err = k8sManager.Start(ctrl.SetupSignalHandler()) + Expect(err).NotTo(HaveOccurred()) + }() + + k8sClient = k8sManager.GetClient() + Expect(k8sClient).NotTo(BeNil()) + close(done) + +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/go.mod b/go.mod index 4fa84ca9..847dbf14 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( github.com/go-logr/zapr v0.2.0 github.com/golang/mock v1.4.1 github.com/mailgun/mailgun-go/v3 v3.6.4 + github.com/onsi/ginkgo v1.14.1 + github.com/onsi/gomega v1.10.2 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/openshift/api v3.9.0+incompatible github.com/pkg/errors v0.9.1 @@ -20,7 +22,6 @@ require ( gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df k8s.io/api v0.20.2 k8s.io/apimachinery v0.20.2 - k8s.io/cli-runtime v0.20.2 // indirect k8s.io/client-go v0.20.2 k8s.io/utils v0.0.0-20201110183641-67b214c5f920 sigs.k8s.io/controller-runtime v0.7.0 diff --git a/operator-sdk.mk b/operator-sdk.mk index 18567712..10ea4673 100644 --- a/operator-sdk.mk +++ b/operator-sdk.mk @@ -16,7 +16,7 @@ run: generate fmt vet manifests go run ./main.go # Install CRDs into a cluster -install: manifests kustomize +install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - # Uninstall CRDs from a cluster