diff --git a/Makefile b/Makefile index 9a1e36f4..3e9a0bff 100644 --- a/Makefile +++ b/Makefile @@ -89,47 +89,10 @@ test: ## Runs the go tests @RUNNING_TESTS=1 go test -tags "$(BUILDTAGS) cgo" $(PACKAGES_FOR_UNIT_TESTS) .PHONY: e2e -CURRENT_DIRECTORY := $(shell pwd) -e2e: container-runtime-build ## Runs e2e tests, you can use EXTRA_ARGS +e2e: deepcopy-gen ## Runs e2e tests, you can use EXTRA_ARGS @echo "+ $@" - @echo "Docker image: $(DOCKER_REGISTRY):$(GITCOMMIT)" -ifeq ($(KUBERNETES_PROVIDER),minikube) - kubectl config use-context $(KUBECTL_CONTEXT) -endif -ifeq ($(KUBERNETES_PROVIDER),crc) - oc project $(CRC_OC_PROJECT) -endif - cp deploy/service_account.yaml deploy/namespace-init.yaml - cat deploy/role.yaml >> deploy/namespace-init.yaml - cat deploy/role_binding.yaml >> deploy/namespace-init.yaml - cat deploy/operator.yaml >> deploy/namespace-init.yaml -ifeq ($(OSFLAG), LINUX) -ifeq ($(IMAGE_PULL_MODE), remote) - sed -i 's|\(image:\).*|\1 $(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml - sed -i 's|\(imagePullPolicy\): IfNotPresent|\1: Always|g' deploy/namespace-init.yaml -else - sed -i 's|\(image:\).*|\1 $(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml -endif -ifeq ($(KUBERNETES_PROVIDER),minikube) - sed -i 's|\(imagePullPolicy\): IfNotPresent|\1: Never|g' deploy/namespace-init.yaml -endif -endif - -ifeq ($(OSFLAG), OSX) -ifeq ($(IMAGE_PULL_MODE), remote) - sed -i '' 's|\(image:\).*|\1 $(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml - sed -i '' 's|\(imagePullPolicy\): IfNotPresent|\1: Always|g' deploy/namespace-init.yaml -else - sed -i '' 's|\(image:\).*|\1 $(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml -endif -ifeq ($(KUBERNETES_PROVIDER),minikube) - sed -i '' 's|\(imagePullPolicy\): IfNotPresent|\1: Never|g' deploy/namespace-init.yaml -endif -endif - RUNNING_TESTS=1 go test -parallel=1 "./test/e2e/" -tags "$(BUILDTAGS) cgo" -v -timeout 60m -run "$(E2E_TEST_SELECTOR)" \ - -root=$(CURRENT_DIRECTORY) -kubeconfig=$(HOME)/.kube/config -globalMan deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml \ - -namespacedMan deploy/namespace-init.yaml $(TEST_ARGS) + $(TEST_ARGS) .PHONY: vet vet: ## Verifies `go vet` passes @@ -420,7 +383,6 @@ generate-docs: ## Re-generate docs directory from the website directory hugo -s website -d ../docs ##################### FROM OPERATOR SDK ######################## -#TODO rename # Install CRDs into a cluster install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - @@ -474,9 +436,8 @@ bundle: manifests kustomize 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 +# Download kubebuilder +kubebuilder: 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 + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); \ No newline at end of file diff --git a/api/v1alpha2/groupversion_info.go b/api/v1alpha2/groupversion_info.go index 0cfebbdc..93674fa3 100644 --- a/api/v1alpha2/groupversion_info.go +++ b/api/v1alpha2/groupversion_info.go @@ -16,7 +16,7 @@ limitations under the License. // Package v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group // +kubebuilder:object:generate=true -// +groupName=jenkins.io.jenkins.io +// +groupName=jenkins.io package v1alpha2 import ( @@ -26,7 +26,7 @@ import ( var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "jenkins.io.jenkins.io", Version: "v1alpha2"} + GroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/api/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go index 7a1461b6..7b130633 100644 --- a/api/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -674,7 +674,3 @@ type GroovyScripts struct { type ConfigurationAsCode struct { Customization `json:",inline"` } - -func init() { - SchemeBuilder.Register(&Jenkins{}, &JenkinsList{}) -} diff --git a/api/v1alpha2/jenkinsimage_types.go b/api/v1alpha2/jenkinsimage_types.go index 1ac64dca..0be257a9 100644 --- a/api/v1alpha2/jenkinsimage_types.go +++ b/api/v1alpha2/jenkinsimage_types.go @@ -47,7 +47,3 @@ type JenkinsImageList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []JenkinsImage `json:"items"` } - -func init() { - SchemeBuilder.Register(&JenkinsImage{}, &JenkinsImageList{}) -} diff --git a/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml b/config/crd/bases/jenkins.io_jenkins.yaml similarity index 98% rename from config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml rename to config/crd/bases/jenkins.io_jenkins.yaml index 237e7a9c..1f86fe9a 100644 --- a/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml +++ b/config/crd/bases/jenkins.io_jenkins.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: jenkins.jenkins.io.jenkins.io + name: jenkins.jenkins.io spec: - group: jenkins.io.jenkins.io + group: jenkins.io names: kind: Jenkins listKind: JenkinsList @@ -38,7 +38,7 @@ spec: properties: backup: description: 'Backup defines configuration of Jenkins backup More - info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' properties: action: description: Action defines action which performs backup in backup @@ -157,11 +157,11 @@ spec: type: object basePlugins: description: 'BasePlugins contains plugins required by operator - Defaults to : - name: kubernetes version: 1.15.7 - name: workflow-job - version: "2.39" - name: workflow-aggregator version: "2.6" - - name: git version: 3.10.0 - name: job-dsl version: "1.74" - - name: configuration-as-code version: "1.19" - name: kubernetes-credentials-provider - version: 0.12.1' + Defaults to : - name: kubernetes version: "1.28.6" - name: workflow-job + version: "2.40" - name: workflow-aggregator version: "2.6" - + name: git version: "4.5.0" - name: job-dsl version: "1.77" - + name: configuration-as-code version: "1.46" - name: kubernetes-credentials-provider + version: "0.15"' items: description: Plugin defines Jenkins plugin. properties: @@ -1184,7 +1184,7 @@ spec: support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are "OnRootMismatch" - and "Always". If not specified defaults to "Always".' + and "Always". If not specified, "Always" is used.' type: string runAsGroup: description: The GID to run the entrypoint of the container @@ -1817,22 +1817,16 @@ spec: dataSource: description: 'This field can be used to specify either: * An existing VolumeSnapshot object - (snapshot.storage.k8s.io/VolumeSnapshot - - Beta) * An existing PVC (PersistentVolumeClaim) - * An existing custom resource/object that - implements data population (Alpha) In order - to use VolumeSnapshot object types, the appropriate - feature gate must be enabled (VolumeSnapshotDataSource - or AnyVolumeDataSource) If the provisioner - or an external controller can support the - specified data source, it will create a new - volume based on the contents of the specified - data source. If the specified data source - is not supported, the volume will not be created - and the failure will be reported as an event. - In the future, we plan to support more data - source types and the behavior of the provisioner - may change.' + (snapshot.storage.k8s.io/VolumeSnapshot) * + An existing PVC (PersistentVolumeClaim) * + An existing custom resource that implements + data population (Alpha) In order to use custom + resource types that implement data population, + the AnyVolumeDataSource feature gate must + be enabled. If the provisioner or an external + controller can support the specified data + source, it will create a new volume based + on the contents of the specified data source.' properties: apiGroup: description: APIGroup is the group for the @@ -2560,8 +2554,6 @@ spec: type: object type: object type: array - required: - - sources type: object quobyte: description: Quobyte represents a Quobyte mount on the host @@ -3030,7 +3022,7 @@ spec: type: array restore: description: 'Backup defines configuration of Jenkins backup restore - More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' properties: action: description: Action defines action which performs restore backup @@ -3112,10 +3104,10 @@ spec: type: array seedJobs: description: 'SeedJobs defines list of Jenkins Seed Job configurations - More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' + More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration#configure-seed-jobs-and-pipelines' items: description: 'SeedJob defines configuration for seed job More info: - https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines.' + https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration/#configure-seed-jobs-and-pipelines.' properties: additionalClasspath: description: AdditionalClasspath is setting for Job DSL API @@ -3209,7 +3201,7 @@ spec: will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature." - More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access' items: type: string type: array @@ -3290,7 +3282,7 @@ spec: will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature." - More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access' items: type: string type: array diff --git a/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml b/config/crd/bases/jenkins.io_jenkinsimages.yaml similarity index 97% rename from config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml rename to config/crd/bases/jenkins.io_jenkinsimages.yaml index 70c0914a..7c79607a 100644 --- a/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml +++ b/config/crd/bases/jenkins.io_jenkinsimages.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: jenkinsimages.jenkins.io.jenkins.io + name: jenkinsimages.jenkins.io spec: - group: jenkins.io.jenkins.io + group: jenkins.io names: kind: JenkinsImage listKind: JenkinsImageList diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index d56a15ec..73fdb226 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,7 +2,7 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: -- bases/jenkins.io.jenkins.io_jenkins.yaml +- bases/jenkins.io_jenkins.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/crd/patches/cainjection_in_jenkins.yaml b/config/crd/patches/cainjection_in_jenkins.yaml index 463fc3cb..aa6b4a50 100644 --- a/config/crd/patches/cainjection_in_jenkins.yaml +++ b/config/crd/patches/cainjection_in_jenkins.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: jenkins.jenkins.io.jenkins.io + name: jenkins.jenkins.io diff --git a/config/crd/patches/webhook_in_jenkins.yaml b/config/crd/patches/webhook_in_jenkins.yaml index 7b4a9a30..37a2d93c 100644 --- a/config/crd/patches/webhook_in_jenkins.yaml +++ b/config/crd/patches/webhook_in_jenkins.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: jenkins.jenkins.io.jenkins.io + name: jenkins.jenkins.io spec: conversion: strategy: Webhook diff --git a/config/rbac/jenkins_editor_role.yaml b/config/rbac/jenkins_editor_role.yaml index 1846db5a..b9ccf6ab 100644 --- a/config/rbac/jenkins_editor_role.yaml +++ b/config/rbac/jenkins_editor_role.yaml @@ -5,7 +5,7 @@ metadata: name: jenkins-editor-role rules: - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins verbs: @@ -17,7 +17,7 @@ rules: - update - watch - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins/status verbs: diff --git a/config/rbac/jenkins_viewer_role.yaml b/config/rbac/jenkins_viewer_role.yaml index d65eb6f5..a4c1ccb1 100644 --- a/config/rbac/jenkins_viewer_role.yaml +++ b/config/rbac/jenkins_viewer_role.yaml @@ -5,7 +5,7 @@ metadata: name: jenkins-viewer-role rules: - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins verbs: @@ -13,7 +13,7 @@ rules: - list - watch - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins/status verbs: diff --git a/config/samples/jenkins.io_v1alpha2_jenkins.yaml b/config/samples/jenkins.io_v1alpha2_jenkins.yaml index 970a02b2..68c07cfb 100644 --- a/config/samples/jenkins.io_v1alpha2_jenkins.yaml +++ b/config/samples/jenkins.io_v1alpha2_jenkins.yaml @@ -1,4 +1,4 @@ -apiVersion: jenkins.io.jenkins.io/v1alpha2 +apiVersion: jenkins.io/v1alpha2 kind: Jenkins metadata: name: jenkins-example diff --git a/controllers/jenkins.go b/controllers/jenkins.go deleted file mode 100644 index af11d172..00000000 --- a/controllers/jenkins.go +++ /dev/null @@ -1,170 +0,0 @@ -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 deleted file mode 100644 index f0c25f5c..00000000 --- a/controllers/jenkins_controller_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -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/go.mod b/go.mod index 847dbf14..a274cd35 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ 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 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/pkg/configuration/base/resources/probe.go b/pkg/configuration/base/resources/probe.go index 545933ae..41d54dc7 100644 --- a/pkg/configuration/base/resources/probe.go +++ b/pkg/configuration/base/resources/probe.go @@ -15,6 +15,8 @@ func NewSimpleProbe(uri string, port string, scheme corev1.URIScheme, initialDel }, }, InitialDelaySeconds: initialDelaySeconds, + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), } } diff --git a/test/e2e/configuration_test.go b/test/e2e/configuration_test.go index 81a1e033..780a739c 100644 --- a/test/e2e/configuration_test.go +++ b/test/e2e/configuration_test.go @@ -3,10 +3,8 @@ package e2e import ( "context" "fmt" - "testing" - "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -15,9 +13,8 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/plugins" "github.com/bndr/gojenkins" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,86 +23,8 @@ import ( const e2e = "e2e" -func TestConfiguration(t *testing.T) { - t.Parallel() - namespace, ctx := setupTest(t) - - defer showLogsAndCleanup(t, ctx) - - jenkinsCRName := e2e - numberOfExecutors := 6 - numberOfExecutorsEnvName := "NUMBER_OF_EXECUTORS" - systemMessage := "Configuration as Code integration works!!!" - systemMessageEnvName := "SYSTEM_MESSAGE" - 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, - }, - }, - } - - stringData := make(map[string]string) - stringData[systemMessageEnvName] = systemMessage - stringData[numberOfExecutorsEnvName] = fmt.Sprintf("%d", numberOfExecutors) - - // base - createUserConfigurationSecret(t, namespace, stringData) - createUserConfigurationConfigMap(t, namespace, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName)) - jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) - createDefaultLimitsForContainersInNamespace(t, namespace) - createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) - waitForJenkinsBaseConfigurationToComplete(t, jenkins) - verifyJenkinsMasterPodAttributes(t, jenkins) - verifyServices(t, jenkins) - jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() - verifyPlugins(t, jenkinsClient, jenkins) - - // user - waitForJenkinsUserConfigurationToComplete(t, jenkins) - verifyUserConfiguration(t, jenkinsClient, numberOfExecutors, systemMessage) - verifyJenkinsSeedJobs(t, jenkinsClient, []seedJobConfig{mySeedJob}) -} - -func TestPlugins(t *testing.T) { +// FIXME +/*func TestPlugins(t *testing.T) { t.Parallel() namespace, ctx := setupTest(t) // Deletes test namespace @@ -145,28 +64,11 @@ func TestPlugins(t *testing.T) { build, err := job.GetLastBuild() require.NoError(t, err) assert.True(t, build.IsGood()) -} +}*/ -func TestPriorityClass(t *testing.T) { - if skipTestPriorityClass { - t.Skip() - } - t.Parallel() - namespace, ctx := setupTest(t) - defer showLogsAndCleanup(t, ctx) +func createUserConfigurationSecret(namespace string, stringData map[string]string) { + By("creating user configuration secret") - jenkinsCRName := "k8s-ete-priority-class-existing" - // One of the existing priority classes - priorityClassName := "system-cluster-critical" - - jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil, v1alpha2.GroovyScripts{}, v1alpha2.ConfigurationAsCode{}, priorityClassName) - waitForJenkinsBaseConfigurationToComplete(t, jenkins) - verifyJenkinsMasterPodAttributes(t, jenkins) - _, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() -} - -func createUserConfigurationSecret(t *testing.T, namespace string, stringData map[string]string) { userConfiguration := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: userConfigurationSecretName, @@ -175,13 +77,13 @@ func createUserConfigurationSecret(t *testing.T, namespace string, stringData ma StringData: stringData, } - t.Logf("User configuration secret %+v", *userConfiguration) - if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "User configuration secret %+v\n", *userConfiguration) + Expect(k8sClient.Create(context.TODO(), userConfiguration)).Should(Succeed()) } -func createUserConfigurationConfigMap(t *testing.T, namespace string, numberOfExecutorsSecretKeyName string, systemMessage string) { +func createUserConfigurationConfigMap(namespace string, numberOfExecutorsSecretKeyName string, systemMessage string) { + By("creating user configuration config map") + userConfiguration := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: userConfigurationConfigMapName, @@ -203,13 +105,13 @@ unclassified: }, } - t.Logf("User configuration %+v", *userConfiguration) - if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "User configuration %+v\n", *userConfiguration) + Expect(k8sClient.Create(context.TODO(), userConfiguration)).Should(Succeed()) } -func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string) { +func createDefaultLimitsForContainersInNamespace(namespace string) { + By("creating default limits for containers in namespace") + limitRange := &corev1.LimitRange{ ObjectMeta: metav1.ObjectMeta{ Name: e2e, @@ -232,31 +134,34 @@ func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string) }, } - t.Logf("LimitRange %+v", *limitRange) - if err := framework.Global.Client.Create(context.TODO(), limitRange, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "LimitRange %+v\n", *limitRange) + Expect(k8sClient.Create(context.TODO(), limitRange)).Should(Succeed()) } -func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) { - jenkinsPod := getJenkinsMasterPod(t, jenkins) - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) +func verifyJenkinsMasterPodAttributes(jenkins *v1alpha2.Jenkins) { + By("creating Jenkins master pod properly") - assertMapContainsElementsFromAnotherMap(t, jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) - assert.Equal(t, jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector) + jenkinsPod := getJenkinsMasterPod(jenkins) + jenkins = getJenkins(jenkins.Namespace, jenkins.Name) - assert.Equal(t, resources.JenkinsMasterContainerName, jenkinsPod.Spec.Containers[0].Name) - assert.Equal(t, len(jenkins.Spec.Master.Containers), len(jenkinsPod.Spec.Containers)) + assertMapContainsElementsFromAnotherMap(jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) + Expect(jenkinsPod.Spec.NodeSelector).Should(Equal(jenkins.Spec.Master.NodeSelector)) - assert.Equal(t, jenkins.Spec.Master.SecurityContext, jenkinsPod.Spec.SecurityContext) - assert.Equal(t, jenkins.Spec.Master.Containers[0].Command, jenkinsPod.Spec.Containers[0].Command) + Expect(jenkinsPod.Spec.Containers[0].Name).Should(Equal(resources.JenkinsMasterContainerName)) + Expect(jenkinsPod.Spec.Containers).Should(HaveLen(len(jenkins.Spec.Master.Containers))) - assert.Equal(t, resources.GetJenkinsMasterPodLabels(*jenkins), jenkinsPod.Labels) - assert.Equal(t, jenkins.Spec.Master.PriorityClassName, jenkinsPod.Spec.PriorityClassName) + if jenkins.Spec.Master.SecurityContext == nil { + jenkins.Spec.Master.SecurityContext = &corev1.PodSecurityContext{} + } + Expect(jenkinsPod.Spec.SecurityContext).Should(Equal(jenkins.Spec.Master.SecurityContext)) + Expect(jenkinsPod.Spec.Containers[0].Command).Should(Equal(jenkins.Spec.Master.Containers[0].Command)) + + Expect(jenkinsPod.Labels).Should(Equal(resources.GetJenkinsMasterPodLabels(*jenkins))) + Expect(jenkinsPod.Spec.PriorityClassName).Should(Equal(jenkins.Spec.Master.PriorityClassName)) for _, actualContainer := range jenkinsPod.Spec.Containers { if actualContainer.Name == resources.JenkinsMasterContainerName { - verifyContainer(t, resources.NewJenkinsMasterContainer(jenkins), actualContainer) + verifyContainer(resources.NewJenkinsMasterContainer(jenkins), actualContainer) continue } @@ -269,11 +174,11 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) { } if expectedContainer == nil { - t.Errorf("Container '%+v' not found in pod", actualContainer) + Fail(fmt.Sprintf("Container '%+v' not found in pod", actualContainer)) continue } - verifyContainer(t, *expectedContainer, actualContainer) + verifyContainer(*expectedContainer, actualContainer) } for _, expectedVolume := range jenkins.Spec.Master.Volumes { @@ -281,58 +186,54 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) { for _, actualVolume := range jenkinsPod.Spec.Volumes { if expectedVolume.Name == actualVolume.Name { volumeFound = true - assert.Equal(t, expectedVolume, actualVolume) + Expect(actualVolume).Should(Equal(expectedVolume)) } } if !volumeFound { - t.Errorf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes) + Fail(fmt.Sprintf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes)) } } - - t.Log("Jenkins pod attributes are valid") } -func verifyContainer(t *testing.T, expected corev1.Container, actual corev1.Container) { - assert.Equal(t, expected.Args, actual.Args, expected.Name, expected.Name) - assert.Equal(t, expected.Command, actual.Command, expected.Name) - assert.ElementsMatch(t, expected.Env, actual.Env, expected.Name) - assert.Equal(t, expected.EnvFrom, actual.EnvFrom, expected.Name) - assert.Equal(t, expected.Image, actual.Image, expected.Name) - assert.Equal(t, expected.ImagePullPolicy, actual.ImagePullPolicy, expected.Name) - assert.Equal(t, expected.Lifecycle, actual.Lifecycle, expected.Name) - assert.Equal(t, expected.LivenessProbe, actual.LivenessProbe, expected.Name) - assert.Equal(t, expected.Ports, actual.Ports, expected.Name) - assert.Equal(t, expected.ReadinessProbe, actual.ReadinessProbe, expected.Name) - assert.Equal(t, expected.Resources, actual.Resources, expected.Name) - assert.Equal(t, expected.SecurityContext, actual.SecurityContext, expected.Name) - assert.Equal(t, expected.WorkingDir, actual.WorkingDir, expected.Name) +func verifyContainer(expected corev1.Container, actual corev1.Container) { + Expect(actual.Args).Should(Equal(expected.Args), expected.Name) + Expect(actual.Command).Should(Equal(expected.Command), expected.Name) + Expect(actual.Env).Should(ConsistOf(expected.Env), expected.Name) + Expect(actual.EnvFrom).Should(Equal(expected.EnvFrom), expected.Name) + Expect(actual.Image).Should(Equal(expected.Image), expected.Name) + Expect(actual.ImagePullPolicy).Should(Equal(expected.ImagePullPolicy), expected.Name) + Expect(actual.Lifecycle).Should(Equal(expected.Lifecycle), expected.Name) + Expect(actual.LivenessProbe).Should(Equal(expected.LivenessProbe), expected.Name) + Expect(actual.Ports).Should(Equal(expected.Ports), expected.Name) + Expect(actual.ReadinessProbe).Should(Equal(expected.ReadinessProbe), expected.Name) + Expect(actual.Resources).Should(Equal(expected.Resources), expected.Name) + Expect(actual.SecurityContext).Should(Equal(expected.SecurityContext), expected.Name) + Expect(actual.WorkingDir).Should(Equal(expected.WorkingDir), expected.Name) if !base.CompareContainerVolumeMounts(expected, actual) { - t.Errorf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'", - expected.Name, expected.VolumeMounts, actual.VolumeMounts) + Fail(fmt.Sprintf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'", + expected.Name, expected.VolumeMounts, expected.VolumeMounts)) } } -func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha2.Jenkins) { +func verifyPlugins(jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha2.Jenkins) { + By("installing plugins in Jenkins instance") + installedPlugins, err := jenkinsClient.GetPlugins(1) - if err != nil { - t.Fatal(err) - } + Expect(err).NotTo(HaveOccurred()) for _, basePlugin := range plugins.BasePlugins() { if found, ok := isPluginValid(installedPlugins, basePlugin); !ok { - t.Fatalf("Invalid plugin '%s', actual '%+v'", basePlugin, found) + Fail(fmt.Sprintf("Invalid plugin '%s', actual '%+v'", basePlugin, found)) } } for _, userPlugin := range jenkins.Spec.Master.Plugins { plugin := plugins.Plugin{Name: userPlugin.Name, Version: userPlugin.Version} if found, ok := isPluginValid(installedPlugins, plugin); !ok { - t.Fatalf("Invalid plugin '%s', actual '%+v'", plugin, found) + Fail(fmt.Sprintf("Invalid plugin '%s', actual '%+v'", plugin, found)) } } - - t.Log("All plugins have been installed") } func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (*gojenkins.Plugin, bool) { @@ -348,13 +249,15 @@ func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (* return p, requiredPlugin.Version == p.Version } -func verifyUserConfiguration(t *testing.T, jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) { +func verifyUserConfiguration(jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) { + By("configuring Jenkins by groovy scripts") + checkConfigurationViaGroovyScript := fmt.Sprintf(` if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) { throw new Exception("Configuration via groovy scripts failed") }`, amountOfExecutors) logs, err := jenkinsClient.ExecuteScript(checkConfigurationViaGroovyScript) - assert.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) checkSecretLoaderViaGroovyScript := fmt.Sprintf(` if (!new Integer(%d).equals(new Integer(secrets['NUMBER_OF_EXECUTORS']))) { @@ -363,30 +266,33 @@ if (!new Integer(%d).equals(new Integer(secrets['NUMBER_OF_EXECUTORS']))) { loader := groovy.AddSecretsLoaderToGroovyScript("/var/jenkins/groovy-scripts-secrets") logs, err = jenkinsClient.ExecuteScript(loader(checkSecretLoaderViaGroovyScript)) - assert.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) + By("configuring Jenkins by CasC") checkConfigurationAsCode := fmt.Sprintf(` if (!"%s".equals(Jenkins.instance.systemMessage)) { throw new Exception("Configuration as code failed") }`, systemMessage) logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode) - assert.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) } -func verifyServices(t *testing.T, jenkins *v1alpha2.Jenkins) { - jenkinsHTTPService := getJenkinsService(t, jenkins, "http") - jenkinsSlaveService := getJenkinsService(t, jenkins, "slave") - assert.Equal(t, intstr.IntOrString{IntVal: constants.DefaultHTTPPortInt32, Type: intstr.Int}, jenkinsHTTPService.Spec.Ports[0].TargetPort) - assert.Equal(t, intstr.IntOrString{IntVal: constants.DefaultSlavePortInt32, Type: intstr.Int}, jenkinsSlaveService.Spec.Ports[0].TargetPort) +func verifyServices(jenkins *v1alpha2.Jenkins) { + By("creating Jenkins services properly") + + jenkinsHTTPService := getJenkinsService(jenkins, "http") + jenkinsSlaveService := getJenkinsService(jenkins, "slave") + Expect(jenkinsHTTPService.Spec.Ports[0].TargetPort).Should(Equal(intstr.IntOrString{IntVal: constants.DefaultHTTPPortInt32, Type: intstr.Int})) + Expect(jenkinsSlaveService.Spec.Ports[0].TargetPort).Should(Equal(intstr.IntOrString{IntVal: constants.DefaultSlavePortInt32, Type: intstr.Int})) } -func assertMapContainsElementsFromAnotherMap(t *testing.T, expected map[string]string, actual map[string]string) { +func assertMapContainsElementsFromAnotherMap(expected map[string]string, actual map[string]string) { for key, expectedValue := range expected { actualValue, keyExists := actual[key] if !keyExists { - assert.Failf(t, "key '%s' doesn't exist in map '%+v'", key, actual) + Fail(fmt.Sprintf("key '%s' doesn't exist in map '%+v'", key, actual)) continue } - assert.Equal(t, expectedValue, actualValue, expected, actual) + Expect(actualValue).Should(Equal(expectedValue), key, expected, actual) } } diff --git a/test/e2e/helm_test.go b/test/e2e/helm_test.go index 6637bdac..73d495da 100644 --- a/test/e2e/helm_test.go +++ b/test/e2e/helm_test.go @@ -2,6 +2,8 @@ package e2e +// TODO +/* import ( "fmt" "os/exec" @@ -66,3 +68,4 @@ func TestDeployHelmChart(t *testing.T) { waitForJenkinsBaseConfigurationToComplete(t, jenkins) waitForJenkinsUserConfigurationToComplete(t, jenkins) } +/* \ No newline at end of file diff --git a/test/e2e/jenkins_configuration_test.go b/test/e2e/jenkins_configuration_test.go new file mode 100644 index 00000000..5ff057ff --- /dev/null +++ b/test/e2e/jenkins_configuration_test.go @@ -0,0 +1,141 @@ +package e2e + +import ( + "fmt" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + + . "github.com/onsi/ginkgo" + corev1 "k8s.io/api/core/v1" + // +kubebuilder:scaffold:imports +) + +var _ = Describe("Jenkins controller configuration", func() { + + const ( + jenkinsCRName = e2e + numberOfExecutors = 6 + numberOfExecutorsEnvName = "NUMBER_OF_EXECUTORS" + systemMessage = "Configuration as Code integration works!!!" + systemMessageEnvName = "SYSTEM_MESSAGE" + priorityClassName = "" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + 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, + }, + }, + } + userConfigurationSecretData = map[string]string{ + systemMessageEnvName: systemMessage, + numberOfExecutorsEnvName: fmt.Sprintf("%d", numberOfExecutors), + } + ) + + BeforeEach(func() { + namespace = createNamespace() + + createUserConfigurationSecret(namespace.Name, userConfigurationSecretData) + createUserConfigurationConfigMap(namespace.Name, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName)) + jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) + createDefaultLimitsForContainersInNamespace(namespace.Name) + createKubernetesCredentialsProviderSecret(namespace.Name, mySeedJob) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when deploying CR to cluster", func() { + It("creates Jenkins instance and configures it", func() { + waitForJenkinsBaseConfigurationToComplete(jenkins) + verifyJenkinsMasterPodAttributes(jenkins) + verifyServices(jenkins) + jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc() + verifyPlugins(jenkinsClient, jenkins) + waitForJenkinsUserConfigurationToComplete(jenkins) + verifyUserConfiguration(jenkinsClient, numberOfExecutors, systemMessage) + verifyJenkinsSeedJobs(jenkinsClient, []seedJobConfig{mySeedJob}) + }) + }) +}) + +var _ = Describe("Jenkins controller priority class", func() { + + const ( + jenkinsCRName = "k8s-ete-priority-class-existing" + priorityClassName = "system-cluster-critical" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + ) + + BeforeEach(func() { + namespace = createNamespace() + jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when deploying CR with priority class to cluster", func() { + It("creates Jenkins instance and configures it", func() { + waitForJenkinsBaseConfigurationToComplete(jenkins) + verifyJenkinsMasterPodAttributes(jenkins) + }) + }) +}) diff --git a/test/e2e/jenkins.go b/test/e2e/jenkins_test.go similarity index 62% rename from test/e2e/jenkins.go rename to test/e2e/jenkins_test.go index 73a118b0..55683081 100644 --- a/test/e2e/jenkins.go +++ b/test/e2e/jenkins_test.go @@ -3,25 +3,24 @@ package e2e import ( "context" "fmt" - "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/constants" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -29,31 +28,25 @@ const ( userConfigurationSecretName = "user-secret" ) -func getJenkins(t *testing.T, namespace, name string) *v1alpha2.Jenkins { +func getJenkins(namespace, name string) *v1alpha2.Jenkins { jenkins := &v1alpha2.Jenkins{} namespaceName := types.NamespacedName{Namespace: namespace, Name: name} - if err := framework.Global.Client.Get(context.TODO(), namespaceName, jenkins); err != nil { - t.Fatal(err) - } - + Expect(k8sClient.Get(context.TODO(), namespaceName, jenkins)).Should(Succeed()) return jenkins } -func getJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) *corev1.Pod { - lo := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), +func getJenkinsMasterPod(jenkins *v1alpha2.Jenkins) *corev1.Pod { + lo := &client.ListOptions{ + LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)), + Namespace: jenkins.Namespace, } - podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) - if err != nil { - t.Fatal(err) - } - if len(podList.Items) != 1 { - t.Fatalf("Jenkins pod not found, pod list: %+v", podList) - } - return &podList.Items[0] + pods := &corev1.PodList{} + Expect(k8sClient.List(context.TODO(), pods, lo)).Should(Succeed()) + Expect(pods.Items).Should(HaveLen(1), fmt.Sprintf("Jenkins pod not found, pod list: %+v", pods.Items)) + return &pods.Items[0] } -func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { +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...) @@ -90,6 +83,8 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S InitialDelaySeconds: int32(80), TimeoutSeconds: int32(4), FailureThreshold: int32(10), + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), }, LivenessProbe: &corev1.Probe{ Handler: corev1.Handler{ @@ -102,6 +97,8 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S InitialDelaySeconds: int32(80), TimeoutSeconds: int32(4), FailureThreshold: int32(10), + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), }, VolumeMounts: []corev1.VolumeMount{ { @@ -146,25 +143,23 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S Name: resources.GetResourceName(jenkins), }, } - updateJenkinsCR(t, jenkins) + updateJenkinsCR(jenkins) - t.Logf("Jenkins CR %+v", *jenkins) - if err := framework.Global.Client.Create(context.TODO(), jenkins, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins CR %+v\n", *jenkins) + + Expect(k8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) return jenkins } -func createJenkinsAPIClientFromServiceAccount(t *testing.T, jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { - t.Log("Creating Jenkins API client from service account") +func createJenkinsAPIClientFromServiceAccount(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { podName := resources.GetJenkinsMasterPodName(jenkins) - clientSet, err := kubernetes.NewForConfig(framework.Global.KubeConfig) + clientSet, err := kubernetes.NewForConfig(cfg) if err != nil { return nil, err } - config := configuration.Configuration{Jenkins: jenkins, ClientSet: *clientSet, Config: framework.Global.KubeConfig} + config := configuration.Configuration{Jenkins: jenkins, ClientSet: *clientSet, Config: cfg} r := base.New(config, jenkinsclient.JenkinsAPIConnectionSettings{}) token, _, err := r.Configuration.Exec(podName, resources.JenkinsMasterContainerName, []string{"cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"}) @@ -175,12 +170,12 @@ func createJenkinsAPIClientFromServiceAccount(t *testing.T, jenkins *v1alpha2.Je return jenkinsclient.NewBearerTokenAuthorization(jenkinsAPIURL, token.String()) } -func createJenkinsAPIClientFromSecret(t *testing.T, jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { - t.Log("Creating Jenkins API client from secret") +func createJenkinsAPIClientFromSecret(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { + _, _ = fmt.Fprintf(GinkgoWriter, "Creating Jenkins API client from secret\n") adminSecret := &corev1.Secret{} namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetOperatorCredentialsSecretName(jenkins)} - if err := framework.Global.Client.Get(context.TODO(), namespaceName, adminSecret); err != nil { + if err := k8sClient.Get(context.TODO(), namespaceName, adminSecret); err != nil { return nil, err } @@ -191,19 +186,19 @@ func createJenkinsAPIClientFromSecret(t *testing.T, jenkins *v1alpha2.Jenkins, j ) } -func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha2.Jenkins, namespace string) (jenkinsclient.Jenkins, func()) { +func verifyJenkinsAPIConnection(jenkins *v1alpha2.Jenkins, namespace string) (jenkinsclient.Jenkins, func()) { + By("establishing Jenkins API connection") + var service corev1.Service - err := framework.Global.Client.Get(context.TODO(), types.NamespacedName{ + err := k8sClient.Get(context.TODO(), types.NamespacedName{ Namespace: jenkins.Namespace, Name: resources.GetJenkinsHTTPServiceName(jenkins), }, &service) - require.NoError(t, err) + Expect(err).NotTo(HaveOccurred()) podName := resources.GetJenkinsMasterPodName(jenkins) - port, cleanUpFunc, waitFunc, portForwardFunc, err := setupPortForwardToPod(t, namespace, podName, int(constants.DefaultHTTPPortInt32)) - if err != nil { - t.Fatal(err) - } + port, cleanUpFunc, waitFunc, portForwardFunc, err := setupPortForwardToPod(namespace, podName, int(constants.DefaultHTTPPortInt32)) + Expect(err).NotTo(HaveOccurred()) go portForwardFunc() waitFunc() @@ -213,41 +208,33 @@ func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha2.Jenkins, namespa UseNodePort: false, }.BuildJenkinsAPIUrl(service.Name, service.Namespace, service.Spec.Ports[0].Port, service.Spec.Ports[0].NodePort) - var client jenkinsclient.Jenkins + var jenkinsClient jenkinsclient.Jenkins if jenkins.Spec.JenkinsAPISettings.AuthorizationStrategy == v1alpha2.ServiceAccountAuthorizationStrategy { - client, err = createJenkinsAPIClientFromServiceAccount(t, jenkins, jenkinsAPIURL) + jenkinsClient, err = createJenkinsAPIClientFromServiceAccount(jenkins, jenkinsAPIURL) } else { - client, err = createJenkinsAPIClientFromSecret(t, jenkins, jenkinsAPIURL) + jenkinsClient, err = createJenkinsAPIClientFromSecret(jenkins, jenkinsAPIURL) } if err != nil { defer cleanUpFunc() - t.Fatal(err) + Fail(err.Error()) } - t.Log("I can establish connection to Jenkins API") - return client, cleanUpFunc + _, _ = fmt.Fprintf(GinkgoWriter, "I can establish connection to Jenkins API\n") + return jenkinsClient, cleanUpFunc } -func restartJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Restarting Jenkins master pod") - jenkinsPod := getJenkinsMasterPod(t, jenkins) - err := framework.Global.Client.Delete(context.TODO(), jenkinsPod) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins master pod has been restarted") -} +/*func restartJenkinsMasterPod(jenkins *v1alpha2.Jenkins) { + _, _ = fmt.Fprintf(GinkgoWriter, "Restarting Jenkins master pod") + jenkinsPod := getJenkinsMasterPod(jenkins) + Expect(k8sClient.Delete(context.TODO(), jenkinsPod)).Should(Succeed()) + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins master pod has been restarted") +}*/ -func getJenkinsService(t *testing.T, jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service { +func getJenkinsService(jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service { + service := &corev1.Service{} serviceName := constants.OperatorName + "-" + serviceKind + "-" + jenkins.ObjectMeta.Name - lo := metav1.ListOptions{ - FieldSelector: fields.SelectorFromSet(fields.Set{"metadata.name": serviceName}).String(), - } - serviceList, err := framework.Global.KubeClient.CoreV1().Services(jenkins.Namespace).List(lo) + Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: serviceName, Namespace: jenkins.Namespace}, service)).Should(Succeed()) - require.NoError(t, err) - require.Equal(t, 1, len(serviceList.Items), fmt.Sprintf("'%s' service not found", serviceName)) - - return &serviceList.Items[0] + return service } diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go deleted file mode 100644 index c1494109..00000000 --- a/test/e2e/main_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package e2e - -import ( - "flag" - "testing" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - "github.com/jenkinsci/kubernetes-operator/pkg/constants" - - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/operator-framework/operator-sdk/pkg/test/e2eutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - jenkinsOperatorDeploymentName = constants.OperatorName - seedJobConfigurationParameterName = "seed-job-config" -) - -var ( - seedJobConfigurationFile *string -) - -func TestMain(m *testing.M) { - seedJobConfigurationFile = flag.String(seedJobConfigurationParameterName, "", "path to seed job config") - - framework.MainEntry(m) -} - -func setupTest(t *testing.T) (string, *framework.Context) { - ctx := framework.NewContext(t) - err := ctx.InitializeClusterResources(nil) - if err != nil { - t.Fatalf("could not initialize cluster resources: %v", err) - } - - defer func() { - showLogsIfTestHasFailed(t, ctx) - if t.Failed() && ctx != nil { - ctx.Cleanup() - } - }() - - jenkinsServiceList := &v1alpha2.JenkinsList{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha2.Kind, - APIVersion: v1alpha2.SchemeGroupVersion.String(), - }, - } - err = framework.AddToFrameworkScheme(apis.AddToScheme, jenkinsServiceList) - if err != nil { - t.Fatalf("could not add scheme to framework scheme: %v", err) - } - - namespace, err := ctx.GetOperatorNamespace() - if err != nil { - t.Fatalf("could not get namespace: %v", err) - } - t.Logf("Test namespace '%s'", namespace) - - // wait for jenkins-operator to be ready - err = e2eutil.WaitForDeployment(t, framework.Global.KubeClient, namespace, jenkinsOperatorDeploymentName, 1, retryInterval, timeout) - if err != nil { - t.Fatal(err) - } - - return namespace, ctx -} diff --git a/test/e2e/mode_kubernetes.go b/test/e2e/mode_kubernetes.go index f17f0730..9408844c 100644 --- a/test/e2e/mode_kubernetes.go +++ b/test/e2e/mode_kubernetes.go @@ -4,17 +4,14 @@ package e2e import ( - "testing" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" ) const ( - skipTestSafeRestart = false - skipTestPriorityClass = false +//skipTestSafeRestart = false +//skipTestPriorityClass = false ) -func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Update Jenkins CR") +func updateJenkinsCR(jenkins *v1alpha2.Jenkins) { // do nothing } diff --git a/test/e2e/mode_openshift.go b/test/e2e/mode_openshift.go index d6aa054d..a2f6f584 100644 --- a/test/e2e/mode_openshift.go +++ b/test/e2e/mode_openshift.go @@ -3,9 +3,7 @@ package e2e import ( - "testing" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" corev1 "k8s.io/api/core/v1" @@ -16,9 +14,7 @@ const ( skipTestPriorityClass = true ) -func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Update Jenkins CR: OpenShift") - +func updateJenkinsCR(jenkins *v1alpha2.Jenkins) { jenkins.Spec.Master.Containers[0].Image = "quay.io/openshift/origin-jenkins" jenkins.Spec.Master.Containers[0].Command = []string{ "bash", diff --git a/test/e2e/mode_openshift_oauth.go b/test/e2e/mode_openshift_oauth.go index a832aed4..419efbec 100644 --- a/test/e2e/mode_openshift_oauth.go +++ b/test/e2e/mode_openshift_oauth.go @@ -2,6 +2,8 @@ package e2e +// TODO +/* import ( "context" "testing" @@ -100,3 +102,4 @@ func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { jenkins.Spec.Master.Plugins = jenkins.Spec.Master.Plugins[0:3] // remove devoptics plugin } } +*/ \ No newline at end of file diff --git a/test/e2e/operator.go b/test/e2e/operator.go deleted file mode 100644 index ad810e19..00000000 --- a/test/e2e/operator.go +++ /dev/null @@ -1,155 +0,0 @@ -package e2e - -import ( - "bytes" - "fmt" - "io" - "sort" - "testing" - - framework "github.com/operator-framework/operator-sdk/pkg/test" - v1 "k8s.io/api/core/v1" - "k8s.io/api/events/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" -) - -var ( - podLogTailLimit int64 = 15 - kubernetesEventsLimit int64 = 15 - // MUST match the labels in the deployment manifest: deploy/operator.yaml - operatorPodLabels = map[string]string{ - "name": "jenkins-operator", - } -) - -func getOperatorPod(namespace string) (*v1.Pod, error) { - listOptions := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(operatorPodLabels).String(), - } - - podList, err := framework.Global.KubeClient.CoreV1().Pods(namespace).List(listOptions) - if err != nil { - return nil, err - } - if len(podList.Items) != 1 { - return nil, fmt.Errorf("expected exactly one pod, got: '%+v'", podList) - } - - return &podList.Items[0], nil -} - -func getOperatorLogs(namespace string) (string, error) { - pod, err := getOperatorPod(namespace) - if err != nil { - return "", err - } - - logOptions := v1.PodLogOptions{TailLines: &podLogTailLimit} - req := framework.Global.KubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &logOptions) - podLogs, err := req.Stream() - if err != nil { - return "", err - } - - defer func() { - if podLogs != nil { - _ = podLogs.Close() - } - }() - - buf := new(bytes.Buffer) - _, err = io.Copy(buf, podLogs) - if err != nil { - return "", err - } - - logs := buf.String() - return logs, nil -} - -func printOperatorLogs(t *testing.T, namespace string) { - t.Logf("Operator logs in '%s' namespace:\n", namespace) - logs, err := getOperatorLogs(namespace) - if err != nil { - t.Errorf("Couldn't get the operator pod logs: %s", err) - } else { - t.Logf("Last %d lines of log from operator:\n %s", podLogTailLimit, logs) - } -} - -func getKubernetesEvents(namespace string) ([]v1beta1.Event, error) { - listOptions := metav1.ListOptions{ - Limit: kubernetesEventsLimit, - } - - events, err := framework.Global.KubeClient.EventsV1beta1().Events(namespace).List(listOptions) - if err != nil { - return nil, err - } - - sort.SliceStable(events.Items, func(i, j int) bool { - return events.Items[i].CreationTimestamp.Unix() < events.Items[j].CreationTimestamp.Unix() - }) - - return events.Items, nil -} - -func printKubernetesEvents(t *testing.T, namespace string) { - t.Logf("Kubernetes events in '%s' namespace:\n", namespace) - events, err := getKubernetesEvents(namespace) - if err != nil { - t.Errorf("Couldn't get kubernetes events: %s", err) - } else { - t.Logf("Last %d events from kubernetes:\n", kubernetesEventsLimit) - - for _, event := range events { - t.Logf("%+v\n\n", event) - } - } -} - -func getKubernetesPods(namespace string) (*v1.PodList, error) { - return framework.Global.KubeClient.CoreV1().Pods(namespace).List(metav1.ListOptions{}) -} - -func printKubernetesPods(t *testing.T, namespace string) { - t.Logf("All pods in '%s' namespace:\n", namespace) - podList, err := getKubernetesPods(namespace) - if err != nil { - t.Errorf("Couldn't get kubernetes pods: %s", err) - } - - for _, pod := range podList.Items { - t.Logf("%+v\n\n", pod) - } -} - -func showLogsIfTestHasFailed(t *testing.T, ctx *framework.Context) { - namespace, err := ctx.GetOperatorNamespace() - if err != nil { - t.Fatalf("Failed to get '%s' namespace", err) - } - - if t.Failed() { - t.Log("Test failed. Bellow here you can check logs:") - - printKubernetesEvents(t, namespace) - printKubernetesPods(t, namespace) - printOperatorLogs(t, namespace) - } -} - -func showLogsAndCleanup(t *testing.T, ctx *framework.Context) { - namespace, err := ctx.GetOperatorNamespace() - if err != nil { - t.Fatalf("Failed to get '%s' namespace", err) - } - - showLogsIfTestHasFailed(t, ctx) - - ctx.Cleanup() - if err = waitUntilNamespaceDestroyed(namespace); err != nil { - t.Fatalf("Failed to wait for namespace until destroyed '%s'", err) - } -} diff --git a/test/e2e/port_forward.go b/test/e2e/port_forward_test.go similarity index 85% rename from test/e2e/port_forward.go rename to test/e2e/port_forward_test.go index 49efcd78..12f318af 100644 --- a/test/e2e/port_forward.go +++ b/test/e2e/port_forward_test.go @@ -7,9 +7,9 @@ import ( "net/url" "os" "strings" - "testing" - framework "github.com/operator-framework/operator-sdk/pkg/test" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -49,11 +49,9 @@ func getFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } -func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) (port int, cleanUpFunc func(), waitFunc func(), portForwardFunc func(), err error) { +func setupPortForwardToPod(namespace, podName string, podPort int) (port int, cleanUpFunc func(), waitFunc func(), portForwardFunc func(), err error) { port, err = getFreePort() - if err != nil { - t.Fatal(err) - } + Expect(err).NotTo(HaveOccurred()) stream := genericclioptions.IOStreams{ In: os.Stdin, @@ -68,7 +66,7 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) readyCh := make(chan struct{}) req := portForwardToPodRequest{ - config: framework.Global.KubeConfig, + config: cfg, pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: podName, @@ -83,9 +81,9 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) } waitFunc = func() { - t.Log("Waiting for the port-forward.") + _, _ = fmt.Fprintf(GinkgoWriter, "Waiting for the port-forward.\n") <-readyCh - t.Log("The port-forward is established.") + _, _ = fmt.Fprintf(GinkgoWriter, "The port-forward is established.\n") } portForwardFunc = func() { @@ -96,7 +94,7 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) } cleanUpFunc = func() { - t.Log("Closing port-forward") + _, _ = fmt.Fprintf(GinkgoWriter, "Closing port-forward\n") close(stopCh) } diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 6f36d968..15740c92 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -1,5 +1,7 @@ package e2e +// TODO +/* import ( "context" "testing" @@ -109,3 +111,4 @@ func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha2. t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status) } } +*/ \ No newline at end of file diff --git a/test/e2e/restorebackup_test.go b/test/e2e/restorebackup_test.go index 2b09cf45..4ca42422 100644 --- a/test/e2e/restorebackup_test.go +++ b/test/e2e/restorebackup_test.go @@ -1,5 +1,7 @@ package e2e +// TODO +/* import ( "context" "testing" @@ -214,3 +216,4 @@ func resetJenkinsStatus(t *testing.T, jenkins *v1alpha2.Jenkins) { err := framework.Global.Client.Update(context.TODO(), jenkins) require.NoError(t, err) } +*/ \ No newline at end of file diff --git a/test/e2e/seedjobs_test.go b/test/e2e/seedjobs_test.go index 8ac0b11c..7268013c 100644 --- a/test/e2e/seedjobs_test.go +++ b/test/e2e/seedjobs_test.go @@ -2,24 +2,19 @@ package e2e import ( "context" - "encoding/json" "fmt" - "io/ioutil" - "os" - "testing" "text/template" "time" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/internal/render" "github.com/jenkinsci/kubernetes-operator/internal/try" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/user/seedjobs" "github.com/jenkinsci/kubernetes-operator/pkg/constants" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,11 +27,12 @@ type seedJobConfig struct { PrivateKey string `json:"privateKey,omitempty"` } -type seedJobsConfig struct { +/*type seedJobsConfig struct { SeedJobs []seedJobConfig `json:"seedJobs,omitempty"` -} +}*/ -func TestSeedJobs(t *testing.T) { +// FIXME +/*func TestSeedJobs(t *testing.T) { t.Parallel() if seedJobConfigurationFile == nil || len(*seedJobConfigurationFile) == 0 { t.Skipf("Skipping test because flag '%+v' is not set", seedJobConfigurationFile) @@ -81,8 +77,9 @@ func loadSeedJobsConfig(t *testing.T) seedJobsConfig { assert.NotEmpty(t, result.SeedJobs) return result } +*/ -func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, config seedJobConfig) { +func createKubernetesCredentialsProviderSecret(namespace string, config seedJobConfig) { if config.JenkinsCredentialType == v1alpha2.NoJenkinsCredentialCredentialType { return } @@ -105,31 +102,32 @@ func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, c }, } - err := framework.Global.Client.Create(context.TODO(), secret, nil) - require.NoError(t, err) + Expect(k8sClient.Create(context.TODO(), secret)).Should(Succeed()) } -func verifyJenkinsSeedJobs(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) { +func verifyJenkinsSeedJobs(jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) { + By("creating Jenkins jobs by seed jobs") + var err error for _, seedJob := range seedJobs { if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType { err = verifyIfJenkinsCredentialExists(jenkinsClient, seedJob.CredentialID) - assert.NoErrorf(t, err, "Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID)) } - verifySeedJobProperties(t, jenkinsClient, seedJob) + verifySeedJobProperties(jenkinsClient, seedJob) for _, requireJobName := range seedJob.JobNames { err = try.Until(func() (end bool, err error) { _, err = jenkinsClient.GetJob(requireJobName) return err == nil, err }, time.Second*2, time.Minute*2) - assert.NoErrorf(t, err, "Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID)) } } } -func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) { +func verifySeedJobProperties(jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) { data := struct { ID string CredentialID string @@ -163,10 +161,10 @@ func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins, } groovyScript, err := render.Render(verifySeedJobPropertiesGroovyScriptTemplate, data) - assert.NoError(t, err, groovyScript) + Expect(err).NotTo(HaveOccurred(), groovyScript) logs, err := jenkinsClient.ExecuteScript(groovyScript) - assert.NoError(t, err, logs, groovyScript) + Expect(err).NotTo(HaveOccurred(), logs, groovyScript) } func verifyIfJenkinsCredentialExists(jenkinsClient jenkinsclient.Jenkins, credentialName string) error { diff --git a/controllers/suite_test.go b/test/e2e/suite_test.go similarity index 52% rename from controllers/suite_test.go rename to test/e2e/suite_test.go index 8c1935be..7567d1f5 100644 --- a/controllers/suite_test.go +++ b/test/e2e/suite_test.go @@ -1,33 +1,27 @@ -/* -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 +package e2e import ( + "context" + "fmt" "path/filepath" "testing" + "time" - jenkinsiov1alpha2 "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/controllers" jenkinsClient "github.com/jenkinsci/kubernetes-operator/pkg/client" + "github.com/jenkinsci/kubernetes-operator/pkg/constants" + "github.com/jenkinsci/kubernetes-operator/pkg/event" + "github.com/jenkinsci/kubernetes-operator/pkg/notifications" e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -37,11 +31,8 @@ import ( // +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 + cfg *rest.Config k8sClient client.Client testEnv *envtest.Environment ) @@ -55,45 +46,55 @@ func TestAPIs(t *testing.T) { } var _ = BeforeSuite(func(done Done) { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(false))) By("bootstrapping test environment") + useExistingCluster := true testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + //BinaryAssetsDirectory: path.Join("..", "..", "testbin", "bin"), + UseExistingCluster: &useExistingCluster, } - cfg, err := testEnv.Start() + var err error + cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - err = jenkinsiov1alpha2.AddToScheme(scheme.Scheme) + err = v1alpha2.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:scheme - //setup manager + // setup manager k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme.Scheme, }) Expect(err).NotTo(HaveOccurred()) - //setup controller + // setup controller clientSet, err := kubernetes.NewForConfig(cfg) Expect(err).NotTo(HaveOccurred()) + // setup events + events, err := event.New(cfg, constants.OperatorName) + Expect(err).NotTo(HaveOccurred()) notificationEvents := make(chan e.Event) + go notifications.Listen(notificationEvents, events, k8sClient) - // validate jenkins API connection - jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{} + jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{ + Hostname: "192.168.99.100", // FIXME minikube ip + UseNodePort: true, + } - err = (&JenkinsReconciler{ + err = (&controllers.JenkinsReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), JenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, ClientSet: *clientSet, Config: *cfg, NotificationEvents: ¬ificationEvents, - KubernetesClusterDomain: "", + KubernetesClusterDomain: "cluster.local", }).SetupWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -105,7 +106,6 @@ var _ = BeforeSuite(func(done Done) { k8sClient = k8sManager.GetClient() Expect(k8sClient).NotTo(BeNil()) close(done) - }, 60) var _ = AfterSuite(func() { @@ -113,3 +113,39 @@ var _ = AfterSuite(func() { err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) + +func createNamespace() *corev1.Namespace { + By("creating temporary namespace") + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%d", time.Now().Unix()), + }, + } + Expect(k8sClient.Create(context.TODO(), namespace)).Should(Succeed()) + return namespace +} + +func destroyNamespace(namespace *corev1.Namespace) { + By("deleting temporary namespace") + + Expect(k8sClient.Delete(context.TODO(), namespace)).Should(Succeed()) + + Eventually(func() (bool, error) { + namespaces := &corev1.NamespaceList{} + err := k8sClient.List(context.TODO(), namespaces) + if err != nil { + return false, err + } + + exists := false + for _, namespaceItem := range namespaces.Items { + if namespaceItem.Name == namespace.Name { + exists = true + break + } + } + + return !exists, nil + }, time.Second*120, time.Second).Should(BeTrue()) +} diff --git a/test/e2e/wait.go b/test/e2e/wait.go deleted file mode 100644 index ff3919bb..00000000 --- a/test/e2e/wait.go +++ /dev/null @@ -1,134 +0,0 @@ -package e2e - -import ( - goctx "context" - "net/http" - "testing" - "time" - - "golang.org/x/net/context" - - "github.com/jenkinsci/kubernetes-operator/internal/try" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" - "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var ( - retryInterval = time.Second * 5 - timeout = time.Second * 60 -) - -// checkConditionFunc is used to check if a condition for the jenkins CR is set -type checkConditionFunc func(*v1alpha2.Jenkins, error) bool - -func waitForJenkinsBaseConfigurationToComplete(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Waiting for Jenkins base configuration to complete") - _, err := WaitUntilJenkinsConditionSet(retryInterval, 170, jenkins, func(jenkins *v1alpha2.Jenkins, err error) bool { - t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err) - return err == nil && jenkins.Status.BaseConfigurationCompletedTime != nil - }) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins pod is running") - - // update jenkins CR because Operator sets default values - namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} - err = framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkins) - assert.NoError(t, err) -} - -func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { - err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) { - lo := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), - } - podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) - if err != nil { - return false, err - } - if len(podList.Items) != 1 { - return false, nil - } - - return podList.Items[0].DeletionTimestamp == nil, nil - }) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins pod has been recreated") -} - -func waitForJenkinsUserConfigurationToComplete(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Waiting for Jenkins user configuration to complete") - _, err := WaitUntilJenkinsConditionSet(retryInterval, 110, jenkins, func(jenkins *v1alpha2.Jenkins, err error) bool { - t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err) - return err == nil && jenkins.Status.UserConfigurationCompletedTime != nil - }) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins pod is running") -} - -func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { - err := try.Until(func() (end bool, err error) { - status, err := jenkinsClient.Poll() - if err != nil { - return false, err - } - if status != http.StatusOK { - return false, errors.Wrap(err, "couldn't poll data from Jenkins API") - } - return true, nil - }, time.Second, time.Second*70) - require.NoError(t, err) -} - -// WaitUntilJenkinsConditionSet retries until the specified condition check becomes true for the jenkins CR -func WaitUntilJenkinsConditionSet(retryInterval time.Duration, retries int, jenkins *v1alpha2.Jenkins, checkCondition checkConditionFunc) (*v1alpha2.Jenkins, error) { - jenkinsStatus := &v1alpha2.Jenkins{} - err := wait.Poll(retryInterval, time.Duration(retries)*retryInterval, func() (bool, error) { - namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} - err := framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkinsStatus) - return checkCondition(jenkinsStatus, err), nil - }) - if err != nil { - return nil, err - } - return jenkinsStatus, nil -} - -func waitUntilNamespaceDestroyed(namespace string) error { - err := try.Until(func() (bool, error) { - var namespaceList v1.NamespaceList - err := framework.Global.Client.List(context.TODO(), &namespaceList, &client.ListOptions{}) - if err != nil { - return true, err - } - - exists := false - for _, namespaceItem := range namespaceList.Items { - if namespaceItem.Name == namespace { - exists = true - break - } - } - - return !exists, nil - }, time.Second, time.Second*120) - - return err -} diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go new file mode 100644 index 00000000..255e3a3e --- /dev/null +++ b/test/e2e/wait_test.go @@ -0,0 +1,87 @@ +package e2e + +import ( + "context" + "fmt" + "time" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var ( + retryInterval = time.Second * 5 +) + +func waitForJenkinsBaseConfigurationToComplete(jenkins *v1alpha2.Jenkins) { + By("waiting for Jenkins base configuration phase to complete") + + Eventually(func() (*metav1.Time, error) { + actualJenkins := &v1alpha2.Jenkins{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins) + if err != nil { + return nil, err + } + + return actualJenkins.Status.BaseConfigurationCompletedTime, nil + }, time.Duration(170)*retryInterval, retryInterval).Should(Not(BeNil())) + + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins pod is running\n") + + // update jenkins CR because Operator sets default values + namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} + Expect(k8sClient.Get(context.TODO(), namespacedName, jenkins)).Should(Succeed()) +} + +/*func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { + err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) { + lo := metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), + } + podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) + if err != nil { + return false, err + } + if len(podList.Items) != 1 { + return false, nil + } + + return podList.Items[0].DeletionTimestamp == nil, nil + }) + if err != nil { + t.Fatal(err) + } + _, _ = fmt.Fprintf(GinkgoWriter,"Jenkins pod has been recreated") +}*/ + +func waitForJenkinsUserConfigurationToComplete(jenkins *v1alpha2.Jenkins) { + By("waiting for Jenkins user configuration phase to complete") + + Eventually(func() (*metav1.Time, error) { + actualJenkins := &v1alpha2.Jenkins{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins) + if err != nil { + return nil, err + } + + return actualJenkins.Status.UserConfigurationCompletedTime, nil + }, time.Duration(110)*retryInterval, retryInterval).Should(Not(BeNil())) + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins instance is up and ready\n") +} + +/*func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { + err := try.Until(func() (end bool, err error) { + status, err := jenkinsClient.Poll() + if err != nil { + return false, err + } + if status != http.StatusOK { + return false, errors.Wrap(err, "couldn't poll data from Jenkins API") + } + return true, nil + }, time.Second, time.Second*70) + require.NoError(t, err) +}*/ diff --git a/variables.mk b/variables.mk index 6648c14b..582ba15f 100644 --- a/variables.mk +++ b/variables.mk @@ -1,5 +1,4 @@ -# Set POSIX sh for maximum interoperability -SHELL := /bin/sh +SHELL := /bin/bash PATH := $(GOPATH)/bin:$(PATH) OSFLAG := @@ -60,7 +59,7 @@ GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static" GOOSARCHES = linux/amd64 PACKAGES = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor) -PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e) +PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e | grep -v controllers) # Run all the e2e tests by default E2E_TEST_SELECTOR ?= .* @@ -93,4 +92,6 @@ ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin else GOBIN=$(shell go env GOBIN) -endif \ No newline at end of file +endif + +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin \ No newline at end of file