diff --git a/.travis.yml b/.travis.yml index 5fa6439db..b3e30f7bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,9 @@ stages: # Builds are split into 'e2e-test' and 'build' to allow e2e tests to run first. If e2e fails, don't bother # building and pushing the images for the other architectures. - name: e2e-test - if: (branch = main OR tag =~ ^v) AND fork = false AND type != cron + if: (branch = main OR branch = 0.8.x OR tag =~ ^v) AND fork = false AND type != cron + - name: minikube-e2e-test + if: (branch = main OR branch = 0.8.x OR tag =~ ^v) AND fork = false AND type != cron - name: build if: (branch = main OR tag =~ ^v) AND fork = false AND type != pull_request AND type != cron - name: build-manifest @@ -39,6 +41,11 @@ jobs: os: linux arch: amd64 script: travis_wait 45 make setup test-e2e || travis_terminate 1 + - name: Build image on amd64 and test on Minikube + stage: minikube-e2e-test + os: linux + arch: amd64 + script: travis_wait 45 make setup minikube-test-e2e || travis_terminate 1 - name: Build image on ppc64le stage: build os: linux diff --git a/Dockerfile b/Dockerfile index e39d554e7..f50be67c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ FROM registry.access.redhat.com/ubi8/ubi-minimal:latest LABEL vendor="Runtime Component Community" \ name="Runtime Component Operator" \ - version="0.8.1" \ + version="0.8.2" \ summary="Image for Runtime Component Operator" \ description="This image contains the controller for Runtime Component Operator. See https://github.com/application-stacks/runtime-component-operator" diff --git a/Makefile b/Makefile index d05cddb7c..1afcf09a0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ OPERATOR_SDK_RELEASE_VERSION ?= v1.6.4 # Current Operator version -VERSION ?= 0.8.1 +VERSION ?= 0.8.2 OPERATOR_IMAGE ?= applicationstacks/operator @@ -164,6 +164,9 @@ build-manifest: setup-manifest setup-manifest: ./scripts/installers/install-manifest-tool.sh +minikube-test-e2e: + ./scripts/e2e-minikube.sh --test-tag "${TRAVIS_BUILD_NUMBER}" + test-e2e: ./scripts/e2e-release.sh --registry-name default-route --registry-namespace openshift-image-registry \ --test-tag "${TRAVIS_BUILD_NUMBER}" --target "${RELEASE_TARGET}" diff --git a/bundle/manifests/runtime-component.clusterserviceversion.yaml b/bundle/manifests/runtime-component.clusterserviceversion.yaml index 60d678ae9..0bef1b667 100644 --- a/bundle/manifests/runtime-component.clusterserviceversion.yaml +++ b/bundle/manifests/runtime-component.clusterserviceversion.yaml @@ -11,7 +11,7 @@ metadata: "name": "runtimecomponent-sample" }, "spec": { - "applicationImage": "registry.connect.redhat.com/ibm/open-liberty-samples:springPetClinic", + "applicationImage": "icr.io/appcafe/open-liberty/samples/getting-started", "expose": true, "replicas": 1, "service": { @@ -39,12 +39,12 @@ metadata: certified: "true" createdAt: "2022-02-25T09:00:00Z" description: Deploys any runtime component with dynamic and auto-tuning configuration - olm.skipRange: <0.8.1 + olm.skipRange: <0.8.2 operators.operatorframework.io/builder: operator-sdk-v1.6.1+git operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: https://github.com/application-stacks/runtime-component-operator support: Community - name: runtime-component.v0.8.1 + name: runtime-component.v0.8.2 namespace: placeholder spec: apiservicedefinitions: {} @@ -429,7 +429,7 @@ spec: resources: limits: cpu: 200m - memory: 256Mi + memory: 512Mi requests: cpu: 100m memory: 20Mi @@ -599,4 +599,4 @@ spec: maturity: beta provider: name: Community - version: 0.8.1 + version: 0.8.2 diff --git a/bundle/tests/scorecard/kuttl/affinity/00-node-no-affinity.yaml b/bundle/tests/scorecard/kuttl/affinity/00-node-no-affinity.yaml index 8a7e20700..84f021ba1 100644 --- a/bundle/tests/scorecard/kuttl/affinity/00-node-no-affinity.yaml +++ b/bundle/tests/scorecard/kuttl/affinity/00-node-no-affinity.yaml @@ -7,7 +7,7 @@ metadata: test: affinity spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 affinity: nodeAffinityLabels: diff --git a/bundle/tests/scorecard/kuttl/affinity/01-node-affinity-label.yaml b/bundle/tests/scorecard/kuttl/affinity/01-node-affinity-label.yaml index 16e9cd6ca..98c1f7aed 100644 --- a/bundle/tests/scorecard/kuttl/affinity/01-node-affinity-label.yaml +++ b/bundle/tests/scorecard/kuttl/affinity/01-node-affinity-label.yaml @@ -7,8 +7,8 @@ metadata: test: affinity spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 affinity: nodeAffinityLabels: - kuttlTest: test1 \ No newline at end of file + kuttlTest: test1 diff --git a/bundle/tests/scorecard/kuttl/affinity/02-pod-affinity.yaml b/bundle/tests/scorecard/kuttl/affinity/02-pod-affinity.yaml index 4305a6e10..9f631bd7f 100644 --- a/bundle/tests/scorecard/kuttl/affinity/02-pod-affinity.yaml +++ b/bundle/tests/scorecard/kuttl/affinity/02-pod-affinity.yaml @@ -7,7 +7,7 @@ metadata: test: affinity spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 affinity: podAffinity: @@ -22,4 +22,4 @@ spec: statefulSet: storage: size: "10Mi" - mountPath: "/mnt/data" \ No newline at end of file + mountPath: "/mnt/data" diff --git a/bundle/tests/scorecard/kuttl/affinity/03-pod-antiaffinity.yaml b/bundle/tests/scorecard/kuttl/affinity/03-pod-antiaffinity.yaml index 5ca963552..93c26cfa6 100644 --- a/bundle/tests/scorecard/kuttl/affinity/03-pod-antiaffinity.yaml +++ b/bundle/tests/scorecard/kuttl/affinity/03-pod-antiaffinity.yaml @@ -7,7 +7,7 @@ metadata: test: affinity spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 affinity: podAntiAffinity: @@ -20,4 +20,4 @@ spec: operator: In values: - node-affinity-label-rc - topologyKey: kubernetes.io/hostname \ No newline at end of file + topologyKey: kubernetes.io/hostname diff --git a/bundle/tests/scorecard/kuttl/affinity/04-node-affinity.yaml b/bundle/tests/scorecard/kuttl/affinity/04-node-affinity.yaml index 87b5e2638..44b9e1b1f 100644 --- a/bundle/tests/scorecard/kuttl/affinity/04-node-affinity.yaml +++ b/bundle/tests/scorecard/kuttl/affinity/04-node-affinity.yaml @@ -7,7 +7,7 @@ metadata: test: affinity spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 affinity: nodeAffinity: @@ -21,4 +21,4 @@ spec: statefulSet: storage: size: "10Mi" - mountPath: "/mnt/data" \ No newline at end of file + mountPath: "/mnt/data" diff --git a/bundle/tests/scorecard/kuttl/annotations/00-rc-deployment.yaml b/bundle/tests/scorecard/kuttl/annotations/00-rc-deployment.yaml index 7fdbf1ae9..185f75572 100644 --- a/bundle/tests/scorecard/kuttl/annotations/00-rc-deployment.yaml +++ b/bundle/tests/scorecard/kuttl/annotations/00-rc-deployment.yaml @@ -7,7 +7,7 @@ metadata: conflict: component spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 deployment: annotations: diff --git a/bundle/tests/scorecard/kuttl/annotations/01-rc-statefulset.yaml b/bundle/tests/scorecard/kuttl/annotations/01-rc-statefulset.yaml index 52694d5df..cc7470076 100644 --- a/bundle/tests/scorecard/kuttl/annotations/01-rc-statefulset.yaml +++ b/bundle/tests/scorecard/kuttl/annotations/01-rc-statefulset.yaml @@ -6,7 +6,7 @@ metadata: foo1: bar1 spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 statefulSet: annotations: diff --git a/bundle/tests/scorecard/kuttl/auto1/00-runtime-basic.yaml b/bundle/tests/scorecard/kuttl/auto1/00-runtime-basic.yaml index 2957fb359..37accb190 100644 --- a/bundle/tests/scorecard/kuttl/auto1/00-runtime-basic.yaml +++ b/bundle/tests/scorecard/kuttl/auto1/00-runtime-basic.yaml @@ -4,5 +4,5 @@ metadata: name: autoscaling1-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 diff --git a/bundle/tests/scorecard/kuttl/auto2/00-runtime-basic.yaml b/bundle/tests/scorecard/kuttl/auto2/00-runtime-basic.yaml index 966aa9dc1..5421623f1 100644 --- a/bundle/tests/scorecard/kuttl/auto2/00-runtime-basic.yaml +++ b/bundle/tests/scorecard/kuttl/auto2/00-runtime-basic.yaml @@ -4,5 +4,5 @@ metadata: name: scale2-runtime-component spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 diff --git a/bundle/tests/scorecard/kuttl/auto3/00-runtime-basic.yaml b/bundle/tests/scorecard/kuttl/auto3/00-runtime-basic.yaml index 2119fd03b..baeae1b29 100644 --- a/bundle/tests/scorecard/kuttl/auto3/00-runtime-basic.yaml +++ b/bundle/tests/scorecard/kuttl/auto3/00-runtime-basic.yaml @@ -4,5 +4,5 @@ metadata: name: scale3-runtime-component spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 diff --git a/bundle/tests/scorecard/kuttl/auto3/01-add-scaling.yaml b/bundle/tests/scorecard/kuttl/auto3/01-add-scaling.yaml index cdb7dc17d..379b17f07 100644 --- a/bundle/tests/scorecard/kuttl/auto3/01-add-scaling.yaml +++ b/bundle/tests/scorecard/kuttl/auto3/01-add-scaling.yaml @@ -5,7 +5,7 @@ metadata: spec: resources: requests: - cpu: "0.5" + cpu: "0.2" autoscaling: maxReplicas: 5 minReplicas: 3 diff --git a/bundle/tests/scorecard/kuttl/basic/00-runtime-basic.yaml b/bundle/tests/scorecard/kuttl/basic/00-runtime-basic.yaml index 28fdea291..30c82db7f 100644 --- a/bundle/tests/scorecard/kuttl/basic/00-runtime-basic.yaml +++ b/bundle/tests/scorecard/kuttl/basic/00-runtime-basic.yaml @@ -4,6 +4,6 @@ metadata: name: example-runtime-component spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 diff --git a/bundle/tests/scorecard/kuttl/day2operation/00-runtime.yaml b/bundle/tests/scorecard/kuttl/day2operation/00-runtime.yaml index b52e3b43f..d5c477c13 100644 --- a/bundle/tests/scorecard/kuttl/day2operation/00-runtime.yaml +++ b/bundle/tests/scorecard/kuttl/day2operation/00-runtime.yaml @@ -4,7 +4,7 @@ metadata: name: day2-operation-rc spec: # Add fields here - applicationImage: 'navidsh/demo-day' + applicationImage: navidsh/demo-day replicas: 1 statefulSet: storage: diff --git a/bundle/tests/scorecard/kuttl/day2operation/02-runtime-no-replicas.yaml b/bundle/tests/scorecard/kuttl/day2operation/02-runtime-no-replicas.yaml index 32b8ed776..e60151168 100644 --- a/bundle/tests/scorecard/kuttl/day2operation/02-runtime-no-replicas.yaml +++ b/bundle/tests/scorecard/kuttl/day2operation/02-runtime-no-replicas.yaml @@ -5,5 +5,5 @@ metadata: name: day2-operation-rc spec: # Add fields here - applicationImage: 'navidsh/demo-day' - replicas: 0 \ No newline at end of file + applicationImage: navidsh/demo-day + replicas: 0 diff --git a/bundle/tests/scorecard/kuttl/day2operation/04-runtime-new-replica.yaml b/bundle/tests/scorecard/kuttl/day2operation/04-runtime-new-replica.yaml index bf31ce08a..1eb53b32a 100644 --- a/bundle/tests/scorecard/kuttl/day2operation/04-runtime-new-replica.yaml +++ b/bundle/tests/scorecard/kuttl/day2operation/04-runtime-new-replica.yaml @@ -5,5 +5,5 @@ metadata: name: day2-operation-rc spec: # Add fields here - applicationImage: 'navidsh/demo-day' - replicas: 1 \ No newline at end of file + applicationImage: navidsh/demo-day + replicas: 1 diff --git a/bundle/tests/scorecard/kuttl/monitor/00-runtime-basic.yaml b/bundle/tests/scorecard/kuttl/monitor/00-runtime-basic.yaml index 2d984fe22..b5ac47677 100644 --- a/bundle/tests/scorecard/kuttl/monitor/00-runtime-basic.yaml +++ b/bundle/tests/scorecard/kuttl/monitor/00-runtime-basic.yaml @@ -4,7 +4,7 @@ metadata: name: service-monitor-rc spec: # Add fields here - applicationImage: 'navidsh/demo-day' + applicationImage: navidsh/demo-day service: type: "ClusterIP" port: 3000 diff --git a/bundle/tests/scorecard/kuttl/route-certificate/01-rc-with-cert.yaml b/bundle/tests/scorecard/kuttl/route-certificate/01-rc-with-cert.yaml index e35609069..cac904787 100644 --- a/bundle/tests/scorecard/kuttl/route-certificate/01-rc-with-cert.yaml +++ b/bundle/tests/scorecard/kuttl/route-certificate/01-rc-with-cert.yaml @@ -4,7 +4,7 @@ metadata: name: route-with-cert-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 expose: true route: diff --git a/bundle/tests/scorecard/kuttl/route-certificate/02-delete-route.yaml b/bundle/tests/scorecard/kuttl/route-certificate/02-delete-route.yaml index 5faa5fb9e..77d3974f9 100644 --- a/bundle/tests/scorecard/kuttl/route-certificate/02-delete-route.yaml +++ b/bundle/tests/scorecard/kuttl/route-certificate/02-delete-route.yaml @@ -4,7 +4,7 @@ metadata: name: route-with-cert-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 expose: true route: diff --git a/bundle/tests/scorecard/kuttl/routes/00-route.yaml b/bundle/tests/scorecard/kuttl/routes/00-route.yaml index fafa8de2e..f5c7c8739 100644 --- a/bundle/tests/scorecard/kuttl/routes/00-route.yaml +++ b/bundle/tests/scorecard/kuttl/routes/00-route.yaml @@ -4,7 +4,7 @@ metadata: name: route spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 expose: true route: diff --git a/bundle/tests/scorecard/kuttl/routes/01-delete-route.yaml b/bundle/tests/scorecard/kuttl/routes/01-delete-route.yaml index aa037c517..428b99839 100644 --- a/bundle/tests/scorecard/kuttl/routes/01-delete-route.yaml +++ b/bundle/tests/scorecard/kuttl/routes/01-delete-route.yaml @@ -4,7 +4,7 @@ metadata: name: route spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 expose: false diff --git a/bundle/tests/scorecard/kuttl/service-certificate/01-rc-with-cert.yaml b/bundle/tests/scorecard/kuttl/service-certificate/01-rc-with-cert.yaml index a1ee7cd30..18ca2c6ba 100644 --- a/bundle/tests/scorecard/kuttl/service-certificate/01-rc-with-cert.yaml +++ b/bundle/tests/scorecard/kuttl/service-certificate/01-rc-with-cert.yaml @@ -4,7 +4,7 @@ metadata: name: service-with-cert-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 service: certificateSecretRef: service-tls-secret diff --git a/bundle/tests/scorecard/kuttl/service-certificate/02-remove-cert.yaml b/bundle/tests/scorecard/kuttl/service-certificate/02-remove-cert.yaml index 07e1772b0..209fa663f 100644 --- a/bundle/tests/scorecard/kuttl/service-certificate/02-remove-cert.yaml +++ b/bundle/tests/scorecard/kuttl/service-certificate/02-remove-cert.yaml @@ -4,7 +4,7 @@ metadata: name: service-with-cert-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 service: certificateSecretRef: diff --git a/bundle/tests/scorecard/kuttl/storage/00-runtime-storage.yaml b/bundle/tests/scorecard/kuttl/storage/00-runtime-storage.yaml index d181ff923..ba6caa341 100644 --- a/bundle/tests/scorecard/kuttl/storage/00-runtime-storage.yaml +++ b/bundle/tests/scorecard/kuttl/storage/00-runtime-storage.yaml @@ -6,7 +6,7 @@ metadata: name: storage-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 statefulSet: storage: diff --git a/bundle/tests/scorecard/kuttl/storage/01-runtime-no-storage.yaml b/bundle/tests/scorecard/kuttl/storage/01-runtime-no-storage.yaml index baca84ca0..a2e857a5b 100644 --- a/bundle/tests/scorecard/kuttl/storage/01-runtime-no-storage.yaml +++ b/bundle/tests/scorecard/kuttl/storage/01-runtime-no-storage.yaml @@ -6,7 +6,7 @@ metadata: name: storage-rc spec: # Add fields here - applicationImage: 'k8s.gcr.io/pause:2.0' + applicationImage: k8s.gcr.io/pause:2.0 replicas: 1 statefulSet: diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/00-assert.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/00-assert.yaml new file mode 100644 index 000000000..3c30523e4 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/00-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: ingress-tls-secret +data: + ca.crt: Y2FjcnQK + destCA.crt: ZGVzdENBY3J0Cg== + tls.crt: dGxzY3J0Cg== + tls.key: dGxza2V5Cg== diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/00-secret.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/00-secret.yaml new file mode 100644 index 000000000..3ca51e383 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/00-secret.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Secret +metadata: + name: ingress-tls-secret +type: kubernetes.io/tls +data: + # 'tlscrt' + tls.crt: dGxzY3J0Cg== + # 'tlskey' + tls.key: dGxza2V5Cg== + # 'cacrt' + ca.crt: Y2FjcnQK + # 'destCAcrt' + destCA.crt: ZGVzdENBY3J0Cg== diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/01-assert.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/01-assert.yaml new file mode 100644 index 000000000..d9ad0730a --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/01-assert.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ingress-with-cert-rc +status: + replicas: 1 + readyReplicas: 1 + updatedReplicas: 1 +--- +kind: Ingress +apiVersion: extensions/v1beta1 +metadata: + name: ingress-with-cert-rc +spec: + tls: + - hosts: + - myapp.mycompany.com + secretName: ingress-tls-secret + rules: + - host: myapp.mycompany.com + http: + paths: + - path: / + pathType: Prefix diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/01-rc-with-cert.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/01-rc-with-cert.yaml new file mode 100644 index 000000000..0ef090bd2 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/01-rc-with-cert.yaml @@ -0,0 +1,16 @@ +apiVersion: rc.app.stacks/v1beta2 +kind: RuntimeComponent +metadata: + name: ingress-with-cert-rc +spec: + # Add fields here + applicationImage: k8s.gcr.io/pause:2.0 + replicas: 1 + expose: true + route: + host: myapp.mycompany.com + path: "/" + pathType: Prefix + certificateSecretRef: ingress-tls-secret + termination: reencrypt + diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-assert.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-assert.yaml new file mode 100644 index 000000000..27d90be55 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-assert.yaml @@ -0,0 +1,8 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ingress-with-cert-rc +status: + replicas: 1 + readyReplicas: 1 + updatedReplicas: 1 diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-delete-route.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-delete-route.yaml new file mode 100644 index 000000000..be138297b --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-delete-route.yaml @@ -0,0 +1,9 @@ +apiVersion: rc.app.stacks/v1beta2 +kind: RuntimeComponent +metadata: + name: ingress-with-cert-rc +spec: + # Add fields here + applicationImage: k8s.gcr.io/pause:2.0 + replicas: 1 + expose: false diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-errors.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-errors.yaml new file mode 100644 index 000000000..cc4e06719 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/02-errors.yaml @@ -0,0 +1,4 @@ +kind: Ingress +apiVersion: extensions/v1beta1 +metadata: + name: ingress-with-cert-rc diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/03-delete-secret.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/03-delete-secret.yaml new file mode 100644 index 000000000..309fd6c45 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress-certificate/03-delete-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: rc.app.stacks/v1beta2 + kind: RuntimeComponent +- apiVersion: v1 + kind: Secret + name: ingress-tls-secret + diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress/00-assert.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress/00-assert.yaml new file mode 100644 index 000000000..d0b747a71 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress/00-assert.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ingress +status: + replicas: 1 + readyReplicas: 1 + updatedReplicas: 1 +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: ingress +spec: + rules: + - host: myapp.mycompany.com + http: + paths: + - path: / + pathType: Prefix + diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress/00-ingress.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress/00-ingress.yaml new file mode 100644 index 000000000..8d99ca295 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress/00-ingress.yaml @@ -0,0 +1,13 @@ +apiVersion: rc.app.stacks/v1beta2 +kind: RuntimeComponent +metadata: + name: ingress +spec: + # Add fields here + applicationImage: k8s.gcr.io/pause:2.0 + replicas: 1 + expose: true + route: + host: myapp.mycompany.com + path: "/" + pathType: Prefix diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress/01-assert.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress/01-assert.yaml new file mode 100644 index 000000000..8902dd486 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress/01-assert.yaml @@ -0,0 +1,8 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ingress +status: + replicas: 1 + readyReplicas: 1 + updatedReplicas: 1 diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress/01-delete-ingress.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress/01-delete-ingress.yaml new file mode 100644 index 000000000..4bbd86c77 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress/01-delete-ingress.yaml @@ -0,0 +1,10 @@ +apiVersion: rc.app.stacks/v1beta2 +kind: RuntimeComponent +metadata: + name: ingress +spec: + # Add fields here + applicationImage: k8s.gcr.io/pause:2.0 + replicas: 1 + expose: false + diff --git a/bundle/tests/scorecard/minikube-kuttl/ingress/01-errors.yaml b/bundle/tests/scorecard/minikube-kuttl/ingress/01-errors.yaml new file mode 100644 index 000000000..2d98cf8b3 --- /dev/null +++ b/bundle/tests/scorecard/minikube-kuttl/ingress/01-errors.yaml @@ -0,0 +1,4 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: ingress diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 60bead4cc..0baf3f96f 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -42,7 +42,7 @@ spec: resources: limits: cpu: 200m - memory: 256Mi + memory: 512Mi requests: cpu: 100m memory: 20Mi diff --git a/config/manifests/bases/runtime-component.clusterserviceversion.yaml b/config/manifests/bases/runtime-component.clusterserviceversion.yaml index 64e9a401e..60c4f4317 100644 --- a/config/manifests/bases/runtime-component.clusterserviceversion.yaml +++ b/config/manifests/bases/runtime-component.clusterserviceversion.yaml @@ -8,7 +8,7 @@ metadata: certified: "true" createdAt: "2022-02-25T09:00:00Z" description: Deploys any runtime component with dynamic and auto-tuning configuration - olm.skipRange: <0.8.1 + olm.skipRange: <0.8.2 repository: https://github.com/application-stacks/runtime-component-operator support: Community name: runtime-component.v0.0.0 diff --git a/config/rbac/minikube-kuttl-rbac.yaml b/config/rbac/minikube-kuttl-rbac.yaml new file mode 100644 index 000000000..28e059ef5 --- /dev/null +++ b/config/rbac/minikube-kuttl-rbac.yaml @@ -0,0 +1,128 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: scorecard-kuttl +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: scorecard-kuttl +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: scorecard-kuttl +subjects: +- kind: ServiceAccount + name: scorecard-kuttl +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: scorecard-kuttl +rules: +- apiGroups: + - "" + resources: + - secrets + - serviceaccounts + - persistentvolumeclaims + verbs: + - get + - list + - create + - patch + - delete +- apiGroups: + - "" + resources: + - namespaces + - pods + - services + - routes + verbs: + - get + - list +- apiGroups: + - apps + resources: + - deployments + - statefulsets + verbs: + - get + - list + - create + - patch + - delete +- apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get +- apiGroups: + - image.openshift.io + resources: + - imagestreams + verbs: + - get + - list + - create + - patch + - delete +- apiGroups: + - events.k8s.io + resources: + - events + verbs: + - get + - list +- apiGroups: + - serving.knative.dev + resources: + - services + verbs: + - get +- apiGroups: + - rc.app.stacks + resources: + - runtimecomponents + - runtimeoperations + verbs: + - get + - list + - create + - patch + - delete +- apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - get + - list + - delete +- apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - get + - list + - create + - patch + - delete +- apiGroups: + - extensions + resources: + - ingresses + verbs: + - get + - list + - delete + diff --git a/config/samples/rc.app.stacks_v1beta2_runtimecomponent.yaml b/config/samples/rc.app.stacks_v1beta2_runtimecomponent.yaml index cd0b4082f..6fd11b6e4 100644 --- a/config/samples/rc.app.stacks_v1beta2_runtimecomponent.yaml +++ b/config/samples/rc.app.stacks_v1beta2_runtimecomponent.yaml @@ -4,8 +4,8 @@ metadata: name: runtimecomponent-sample spec: # Add fields here - applicationImage: registry.connect.redhat.com/ibm/open-liberty-samples:springPetClinic + applicationImage: icr.io/appcafe/open-liberty/samples/getting-started expose: true replicas: 1 service: - port: 9080 \ No newline at end of file + port: 9080 diff --git a/deploy/kustomize/daily/README.adoc b/deploy/kustomize/daily/README.adoc index 417edad5f..bf4254717 100644 --- a/deploy/kustomize/daily/README.adoc +++ b/deploy/kustomize/daily/README.adoc @@ -2,42 +2,47 @@ This directory contains kustomize files that can be used to install the operator in your cluster in various different configurations, and also some example overlays -which show how the installation can be customized +which show how the installation can be customized. -== base -The simplest configuration will install the operator into the default namespace, and the operator -will watch for RuntimeComponent instances only in its own namespace. To install, run: +== Install and watch own namespace + +=== base +The simplest configuration will install the operator into the 'default' namespace, and the operator +will watch for Runtime Component custom resource instances only in its own namespace. To install, run: `kubectl apply -k base` and to uninstall, run: `kubectl delete -k base` -== examples/watch-own-namespace +=== examples/watch-own-namespace This example overlay demonstrates how to modify the base configuration to install/watch a namespace other than 'default'. The example installs the operator to a namespace called 'rco-ns' which should already exist. To install, run `kubectl apply -k examples/watch-own-namespace` -== overlays/watch-another-namespace +== Install and watch another namespace + +=== overlays/watch-another-namespace This overlay installs the operator into the namespace 'rco-ns', but configures it to -watch for RuntimeComponent instances in a different namespace called 'rco-watched-ns'. As +watch for Runtime Component custom resource instances in a different namespace called 'rco-watched-ns'. As this overlay install resources into two different namespaces, the namespace must not be specified in the kustomize.yaml file. To install, run `kubectl apply -k overlays/watch-another-namespace` - -== examples/watch-another-namespace +=== examples/watch-another-namespace This example overlay builds on the previous one, but demonstrates how to change the install and watched namespaces. In this case, the operator is installed into 'rco-ns2' and it will watch for resources in 'rco-watched-ns2'. To install run `kubectl apply -k examples/watch-another-namespace` -== overlays/watch-all-namespaces +== Install and watch all namespaces + +=== overlays/watch-all-namespaces This overlay installs the operator into the default namespace, but configures it -to watch for RuntimeComponent instances in any namespace. Compared to the base, +to watch for Runtime Component custom resource instances in any namespace. Compared to the base, this requires additional ClusterRoles and ClusterRoleBindings. To install run: `kubectl apply -k overlays/watch-all-namespaces` -== examples/watch-all-namespaces +=== examples/watch-all-namespaces This example overlay builds on the previous one, and demonstrates how to change which namespace the operator is installed into. In this example, the operator is installed into a namespace called 'rco-ns', and will still watch for -RuntimeComponent instances in any namespace. To install, run: +Runtime Component custom resource instances in any namespace. To install, run: `kubectl apply -k examples/watch-all-namespaces` diff --git a/deploy/kustomize/daily/base/runtime-component-crd.yaml b/deploy/kustomize/daily/base/runtime-component-crd.yaml index 35ee7c736..fe2b3a791 100644 --- a/deploy/kustomize/daily/base/runtime-component-crd.yaml +++ b/deploy/kustomize/daily/base/runtime-component-crd.yaml @@ -1,10 +1,8 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.6.0 - creationTimestamp: null labels: app.kubernetes.io/instance: runtime-component-operator app.kubernetes.io/managed-by: olm @@ -72,7 +70,7 @@ spec: description: Defines the desired state of RuntimeComponent. properties: affinity: - description: Configures a Pod to run on particular Nodes. + description: Configure pods to run on particular Nodes. properties: architecture: description: An array of architectures to be considered for deployment. @@ -676,13 +674,14 @@ spec: type: object type: object applicationImage: - description: Application image to be installed. + description: Application image to deploy. type: string applicationName: - description: The name of the application this resource is part of. - If not specified, it defaults to the name of the CR. + description: Name of the application. Defaults to the name of this + custom resource. type: string applicationVersion: + description: Version of the application. type: string autoscaling: description: Configures the desired resource consumption of pods. @@ -690,7 +689,7 @@ spec: maxReplicas: description: Required field for autoscaling. Upper limit for the number of pods that can be set by the autoscaler. Parameter - spec.resourceConstraints.requests.cpu must also be specified. + .spec.resources.requests.cpu must also be specified. format: int32 minimum: 1 type: integer @@ -700,14 +699,13 @@ spec: format: int32 type: integer targetCPUUtilizationPercentage: - description: Target average CPU utilization (represented as a - percentage of requested CPU) over all the pods. + description: Target average CPU utilization, represented as a + percentage of requested CPU, over all the pods. format: int32 type: integer type: object createKnativeService: - description: A boolean to toggle the creation of Knative resources - and usage of Knative serving. + description: Create Knative resources and use Knative serving. type: boolean deployment: description: Defines the desired state and cycle of applications. @@ -771,8 +769,8 @@ spec: type: object type: object env: - description: An array of environment variables following the format - of {name, value}, where value is a simple string. + description: An array of environment variables for the application + container. items: description: EnvVar represents an environment variable present in a Container. @@ -878,8 +876,8 @@ spec: - name x-kubernetes-list-type: map envFrom: - description: An array of references to ConfigMap or Secret resources - containing environment variables. + description: List of sources to populate environment variables in + the application container. items: description: EnvFromSource represents the source of a set of ConfigMaps properties: @@ -913,11 +911,11 @@ spec: type: array x-kubernetes-list-type: atomic expose: - description: A boolean that toggles the external exposure of this - deployment via a Route or a Knative Route resource. + description: Expose the application externally via a Route, a Knative + Route or an Ingress resource. type: boolean initContainers: - description: List of containers that run before other containers in + description: List of containers to run before other containers in a pod. items: description: A single application container that you want to run @@ -2006,116 +2004,6 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map - livenessProbe: - description: Detects if the services need to be restarted. - properties: - exec: - description: One and only one of the following should be specified. - Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command is - simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe to be - considered failed after having succeeded. Defaults to 3. Minimum - value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the pod - IP. You probably want to set "Host" in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP allows - repeated headers. - items: - description: HTTPHeader describes a custom header to be - used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. Defaults - to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. Default - to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to be - considered successful after having failed. Defaults to 1. Must - be 1 for liveness and startup. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: 'TCPSocket specifies an action involving a TCP port. - TCP hooks not yet supported TODO: implement a realistic TCP - lifecycle hook' - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - timeoutSeconds: - description: 'Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object monitoring: description: Specifies parameters for Service Monitor. properties: @@ -2475,130 +2363,372 @@ spec: description: Labels to set on ServiceMonitor. type: object type: object - pullPolicy: - description: Policy for pulling container images. Defaults to IfNotPresent. - type: string - pullSecret: - description: Name of the Secret to use to pull images from the specified - repository. It is not required if the cluster is configured with - a global image pull secret. - type: string - readinessProbe: - description: Detects if the services are ready to serve. + probes: + description: Define health checks on application container to determine + whether it is alive or ready to receive traffic properties: - exec: - description: One and only one of the following should be specified. - Exec specifies the action to take. + liveness: + description: Periodic probe of container liveness. Container will + be restarted if the probe fails. properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command is - simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array + exec: + description: One and only one of the following should be specified. + Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe to + be considered failed after having succeeded. Defaults to + 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to the + pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has started + before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe to + be considered successful after having failed. Defaults to + 1. Must be 1 for liveness and startup. Minimum value is + 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP + port. TCP hooks not yet supported TODO: implement a realistic + TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe times + out. Defaults to 1 second. Minimum value is 1. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer type: object - failureThreshold: - description: Minimum consecutive failures for the probe to be - considered failed after having succeeded. Defaults to 3. Minimum - value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGet specifies the http request to perform. + readiness: + description: Periodic probe of container service readiness. Container + will be removed from service endpoints if the probe fails. properties: - host: - description: Host name to connect to, defaults to the pod - IP. You probably want to set "Host" in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP allows - repeated headers. - items: - description: HTTPHeader describes a custom header to be - used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value + exec: + description: One and only one of the following should be specified. + Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. Defaults - to HTTP. - type: string - required: - - port + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe to + be considered failed after having succeeded. Defaults to + 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to the + pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has started + before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe to + be considered successful after having failed. Defaults to + 1. Must be 1 for liveness and startup. Minimum value is + 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP + port. TCP hooks not yet supported TODO: implement a realistic + TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe times + out. Defaults to 1 second. Minimum value is 1. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer type: object - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. Default - to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to be - considered successful after having failed. Defaults to 1. Must - be 1 for liveness and startup. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: 'TCPSocket specifies an action involving a TCP port. - TCP hooks not yet supported TODO: implement a realistic TCP - lifecycle hook' + startup: + description: Probe to determine successful initialization. If + specified, other probes are not executed until this completes + successfully. properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port + exec: + description: One and only one of the following should be specified. + Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe to + be considered failed after having succeeded. Defaults to + 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to the + pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has started + before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe to + be considered successful after having failed. Defaults to + 1. Must be 1 for liveness and startup. Minimum value is + 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP + port. TCP hooks not yet supported TODO: implement a realistic + TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe times + out. Defaults to 1 second. Minimum value is 1. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer type: object - timeoutSeconds: - description: 'Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer type: object + pullPolicy: + description: Policy for pulling container images. Defaults to IfNotPresent. + type: string + pullSecret: + description: Name of the Secret to use to pull images from the specified + repository. It is not required if the cluster is configured with + a global image pull secret. + type: string replicas: - description: Number of pods to create. + description: Number of pods to create. Not applicable when .spec.autoscaling + or .spec.createKnativeService is specified. format: int32 type: integer - resourceConstraints: - description: Limits the amount of required resources. + resources: + description: Resource requests and limits for the application container. properties: limits: additionalProperties: @@ -2646,6 +2776,9 @@ spec: path: description: Path to be used for Route. type: string + pathType: + description: Path type to be used for Ingress. + type: string termination: description: TLS termination policy. Can be one of edge, reencrypt and passthrough. @@ -2753,12 +2886,13 @@ spec: type: string type: object serviceAccountName: - description: The name of the OpenShift service account to be used - during deployment. + description: Name of the service account to use for deploying the + application. A service account is automatically created if it's + not specified. type: string sidecarContainers: - description: The list of sidecar containers. These are additional - containers to be added to the pods. + description: List of sidecar containers. These are additional containers + to be added to the pods. items: description: A single application container that you want to run within a pod. @@ -3846,116 +3980,6 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map - startupProbe: - description: Protects slow starting containers from livenessProbe. - properties: - exec: - description: One and only one of the following should be specified. - Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command is - simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe to be - considered failed after having succeeded. Defaults to 3. Minimum - value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the pod - IP. You probably want to set "Host" in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP allows - repeated headers. - items: - description: HTTPHeader describes a custom header to be - used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. Defaults - to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. Default - to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to be - considered successful after having failed. Defaults to 1. Must - be 1 for liveness and startup. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: 'TCPSocket specifies an action involving a TCP port. - TCP hooks not yet supported TODO: implement a realistic TCP - lifecycle hook' - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - timeoutSeconds: - description: 'Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object statefulSet: description: Defines the desired state and cycle of stateful applications. properties: @@ -4241,7 +4265,8 @@ spec: type: object type: object volumeMounts: - description: Represents where to mount the volumes into containers. + description: Represents where to mount the volumes into the application + container. items: description: VolumeMount describes a mounting of a Volume within a container. @@ -4281,8 +4306,8 @@ spec: type: array x-kubernetes-list-type: atomic volumes: - description: Represents a pod volume with data that is accessible - to the containers. + description: Represents a volume with data that is accessible to the + application container. items: description: Volume represents a named volume in a pod that may be accessed by any container in the pod. @@ -5793,7 +5818,6 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.6.0 - creationTimestamp: null labels: app.kubernetes.io/instance: runtime-component-operator app.kubernetes.io/managed-by: olm @@ -5881,4 +5905,3 @@ status: plural: "" conditions: [] storedVersions: [] ---- diff --git a/deploy/kustomize/daily/base/runtime-component-operator.yaml b/deploy/kustomize/daily/base/runtime-component-operator.yaml index aa9e448fc..c59e8dcb7 100644 --- a/deploy/kustomize/daily/base/runtime-component-operator.yaml +++ b/deploy/kustomize/daily/base/runtime-component-operator.yaml @@ -1,4 +1,3 @@ ---- apiVersion: v1 kind: ServiceAccount metadata: @@ -222,17 +221,16 @@ spec: fieldRef: fieldPath: metadata.namespace - name: WATCH_NAMESPACE - value: default -# valueFrom: -# fieldRef: -# fieldPath: metadata.annotations['olm.targetNamespaces'] + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: applicationstacks/operator:daily imagePullPolicy: Always name: manager resources: limits: cpu: 200m - memory: 256Mi + memory: 512Mi requests: cpu: 100m memory: 20Mi diff --git a/deploy/kustomize/daily/examples/watch-another-namespace/watched-roles.yaml b/deploy/kustomize/daily/examples/watch-another-namespace/watched-roles.yaml index a4cf1978b..def5ae2fb 100644 --- a/deploy/kustomize/daily/examples/watch-another-namespace/watched-roles.yaml +++ b/deploy/kustomize/daily/examples/watch-another-namespace/watched-roles.yaml @@ -7,7 +7,7 @@ metadata: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: rco-namespace-rolebinding + name: rco-watched-rolebinding namespace: rco-watched-ns2 subjects: - kind: ServiceAccount diff --git a/deploy/kustomize/daily/examples/watch-own-namespace/kustomization.yaml b/deploy/kustomize/daily/examples/watch-own-namespace/kustomization.yaml index f15422a60..40beeea8a 100644 --- a/deploy/kustomize/daily/examples/watch-own-namespace/kustomization.yaml +++ b/deploy/kustomize/daily/examples/watch-own-namespace/kustomization.yaml @@ -6,5 +6,3 @@ namespace: rco-ns bases: - ../../base -patchesStrategicMerge: -- rco-test-project.yaml diff --git a/deploy/kustomize/daily/examples/watch-own-namespace/rco-test-project.yaml b/deploy/kustomize/daily/examples/watch-own-namespace/rco-test-project.yaml deleted file mode 100644 index bf3300583..000000000 --- a/deploy/kustomize/daily/examples/watch-own-namespace/rco-test-project.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: rco-controller-manager -spec: - template: - spec: - containers: - - name: manager - env: - - name: WATCH_NAMESPACE - value: rco-ns diff --git a/deploy/kustomize/daily/overlays/watch-all-namespaces/rco-all-namespaces.yaml b/deploy/kustomize/daily/overlays/watch-all-namespaces/rco-all-namespaces.yaml index 96c0f1762..599af7068 100644 --- a/deploy/kustomize/daily/overlays/watch-all-namespaces/rco-all-namespaces.yaml +++ b/deploy/kustomize/daily/overlays/watch-all-namespaces/rco-all-namespaces.yaml @@ -10,3 +10,4 @@ spec: env: - name: WATCH_NAMESPACE value: "" + valueFrom: diff --git a/deploy/kustomize/daily/overlays/watch-another-namespace/rco-deployment.yaml b/deploy/kustomize/daily/overlays/watch-another-namespace/rco-deployment.yaml index 524134e19..0fe4f0acb 100644 --- a/deploy/kustomize/daily/overlays/watch-another-namespace/rco-deployment.yaml +++ b/deploy/kustomize/daily/overlays/watch-another-namespace/rco-deployment.yaml @@ -11,3 +11,4 @@ spec: env: - name: WATCH_NAMESPACE value: rco-watched-ns + valueFrom: diff --git a/deploy/kustomize/daily/overlays/watch-another-namespace/watched-roles.yaml b/deploy/kustomize/daily/overlays/watch-another-namespace/watched-roles.yaml index dee5a639e..6d37a2af3 100644 --- a/deploy/kustomize/daily/overlays/watch-another-namespace/watched-roles.yaml +++ b/deploy/kustomize/daily/overlays/watch-another-namespace/watched-roles.yaml @@ -103,7 +103,7 @@ metadata: app.kubernetes.io/instance: runtime-component-operator app.kubernetes.io/managed-by: olm app.kubernetes.io/name: runtime-component-operator - name: rco-namespace-rolebinding + name: rco-watched-rolebinding namespace: rco-watched-ns roleRef: apiGroup: rbac.authorization.k8s.io diff --git a/scripts/e2e-minikube.sh b/scripts/e2e-minikube.sh new file mode 100755 index 000000000..e3e0b296e --- /dev/null +++ b/scripts/e2e-minikube.sh @@ -0,0 +1,171 @@ +#!/bin/bash + +readonly usage="Usage: e2e-minikube.sh --test-tag " + +readonly LOCAL_REGISTRY="localhost:5000" +readonly BUILD_IMAGE="runtime-operator:latest" +readonly DAILY_IMAGE="applicationstacks\/operator:daily" + +readonly RUNASUSER="\n securityContext:\n runAsUser: 1001" +readonly APPIMAGE='applicationImage:\s' +readonly IMAGES=('k8s.gcr.io\/pause:2.0' 'navidsh\/demo-day') + + +# setup_env: Download kubectl cli and Minikube, start Minikube, and create a test project +setup_env() { + # Install Minikube and Start a cluster + echo "****** Installing and starting Minikube" + scripts/installers/install-minikube.sh + + readonly TEST_NAMESPACE="rco-test-${TEST_TAG}" + + echo "****** Creating test namespace: ${TEST_NAMESPACE}" + kubectl create namespace "${TEST_NAMESPACE}" + kubectl config set-context $(kubectl config current-context) --namespace="${TEST_NAMESPACE}" + + ## Create service account for Kuttl tests + kubectl apply -f config/rbac/minikube-kuttl-rbac.yaml + + ## Add label to node for affinity test + kubectl label node "minikube" kuttlTest=test1 +} + +build_push() { + eval "$(minikube docker-env --profile=minikube)" && export DOCKER_CLI='docker' + ## Build Docker image and push to local registry + docker build -t "${LOCAL_REGISTRY}/${BUILD_IMAGE}" . + docker push "${LOCAL_REGISTRY}/${BUILD_IMAGE}" +} + +# install_rco: Kustomize and install Runtime-Component-Operator +install_rco() { + echo "****** Installing RCO in namespace: ${TEST_NAMESPACE}" + kubectl apply -f bundle/manifests/rc.app.stacks_runtimecomponents.yaml + kubectl apply -f bundle/manifests/rc.app.stacks_runtimeoperations.yaml + + sed -i "s/image: ${DAILY_IMAGE}/image: ${LOCAL_REGISTRY}\/${BUILD_IMAGE}/" deploy/kustomize/daily/base/runtime-component-operator.yaml + kubectl apply -f deploy/kustomize/daily/base/runtime-component-operator.yaml -n ${TEST_NAMESPACE} +} + +install_tools() { + echo "****** Installing Prometheus" + kubectl apply -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/main/bundle.yaml + + echo "****** Installing Knative" + kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.3.0/serving-crds.yaml + kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/eventing-crds.yaml + + echo "****** Installing Cert Manager" + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.0/cert-manager.yaml + + echo "****** Enabling Ingress" + minikube addons enable ingress +} + +## cleanup_env : Delete generated resources that are not bound to a test TEST_NAMESPACE. +cleanup_env() { + kubectl delete namespace "${TEST_NAMESPACE}" + minikube stop && minikube delete +} + +setup_test() { + echo "****** Installing kuttl" + mkdir krew && cd krew + curl -OL https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-linux_amd64.tar.gz \ + && tar -xvzf krew-linux_amd64.tar.gz \ + && ./krew-linux_amd64 install krew + cd .. && rm -rf krew + export PATH="$HOME/.krew/bin:$PATH" + kubectl krew install kuttl + + ## Add tests for minikube + mv bundle/tests/scorecard/minikube-kuttl/ingress bundle/tests/scorecard/kuttl/ + mv bundle/tests/scorecard/minikube-kuttl/ingress-certificate bundle/tests/scorecard/kuttl/ + + ## Remove tests that do not apply for minikube + mv bundle/tests/scorecard/kuttl/routes bundle/tests/scorecard/minikube-kuttl/ + mv bundle/tests/scorecard/kuttl/route-certificate bundle/tests/scorecard/minikube-kuttl/ + mv bundle/tests/scorecard/kuttl/stream bundle/tests/scorecard/minikube-kuttl/ + + for image in "${IMAGES[@]}" + do + files=($(grep -rwl 'bundle/tests/scorecard/kuttl/' -e $APPIMAGE$image)) + for file in "${files[@]}" + do + sed -i "s/$image/$image$RUNASUSER/" $file + done + done +} + +cleanup_test() { + ## Restore tests + mv bundle/tests/scorecard/kuttl/ingress bundle/tests/scorecard/minikube-kuttl/ + mv bundle/tests/scorecard/kuttl/ingress-certificate bundle/tests/scorecard/minikube-kuttl/ + + mv bundle/tests/scorecard/minikube-kuttl/routes bundle/tests/scorecard/kuttl/ + mv bundle/tests/scorecard/minikube-kuttl/route-certificate bundle/tests/scorecard/kuttl/ + mv bundle/tests/scorecard/minikube-kuttl/stream bundle/tests/scorecard/kuttl/ + + git restore bundle/tests/scorecard deploy/kustomize/daily/base/runtime-component-operator.yaml +} + +main() { + parse_args "$@" + + if [[ -z "${TEST_TAG}" ]]; then + echo "****** Missing test id, see usage" + echo "${usage}" + exit 1 + fi + + # echo "****** Setting up test environment..." + setup_env + build_push + install_rco + install_tools + + # Wait for operator deployment to be ready + while [[ $(kubectl get deploy rco-controller-manager -o jsonpath='{ .status.readyReplicas }') -ne "1" ]]; do + echo "****** Waiting for rco-controller-manager to be ready..." + sleep 10 + done + echo "****** rco-controller-manager deployment is ready..." + + setup_test + + echo "****** Starting minikube scorecard tests..." + operator-sdk scorecard --verbose --selector=suite=kuttlsuite --namespace "${TEST_NAMESPACE}" --service-account scorecard-kuttl --wait-time 30m ./bundle || { + echo "****** Scorecard tests failed..." + echo "****** Cleaning up test environment..." + cleanup_test + cleanup_env + exit 1 + } + result=$? + + echo "****** Cleaning up test environment..." + cleanup_test + cleanup_env + + return $result +} + +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + --test-tag) + shift + readonly TEST_TAG="${1}" + ;; + *) + echo "Error: Invalid argument - $1" + echo "$usage" + exit 1 + ;; + esac + shift + done +} + +main "$@" + diff --git a/scripts/installers/install-minikube.sh b/scripts/installers/install-minikube.sh index 7027dd1fe..c01939c55 100755 --- a/scripts/installers/install-minikube.sh +++ b/scripts/installers/install-minikube.sh @@ -3,51 +3,70 @@ set -e ## Some minikube job specific ENV variables -export CHANGE_MINIKUBE_NONE_USER=true export MINIKUBE_WANTUPDATENOTIFICATION=false export MINIKUBE_HOME=$HOME -export CHANGE_MINIKUBE_NONE_USER=true export KUBECONFIG=$HOME/.kube/config function main () { - echo "****** Installing minikube..." install_minikube echo "****** Verifying installation..." kubectl cluster-info wait_for_kube - ## Run tests below - echo "Minikube enabled job is running..." + + echo "****** Minikube enabled job is running..." } function install_minikube() { sudo apt-get update -y sudo apt-get -qq -y install conntrack + ## get kubectl - curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.18.1/bin/linux/amd64/kubectl \ - && chmod +x kubectl \ - && sudo mv kubectl /usr/local/bin/ + echo "****** Installing kubectl v1.19.4..." + curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.19.4/bin/linux/amd64/kubectl \ + && chmod +x kubectl \ + && sudo mv kubectl /usr/local/bin/ + ## Download minikube - curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.8.1/minikube-linux-amd64 \ - && chmod +x minikube \ - && sudo mv minikube /usr/local/bin/ + echo "****** Installing Minikube v1.21.0..." + curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.21.0/minikube-linux-amd64 \ + && chmod +x minikube \ + && sudo mv minikube /usr/local/bin/ + + ## Download docker + echo "****** Installing Docker..." + if test -f "/usr/share/keyrings/docker-archive-keyring.gpg"; then + rm /usr/share/keyrings/docker-archive-keyring.gpg + fi + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update -y + sudo apt-get install -y docker-ce docker-ce-cli containerd.io mkdir -p $HOME/.kube $HOME/.minikube touch $KUBECONFIG - sudo minikube start --profile=minikube --vm-driver=none --kubernetes-version=v1.18.1 + minikube start --profile=minikube --kubernetes-version=v1.19.4 --driver=docker --force minikube update-context --profile=minikube eval "$(minikube docker-env --profile=minikube)" && export DOCKER_CLI='docker' + + ## Run Local Registry + docker run -d -p 5000:5000 --restart=always --name local-registry registry } + function wait_for_kube() { local json_path='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}' + echo "****** Waiting for kube-controller-manager to be available..." - until kubectl -n kube-system get pods -l k8s-app=kube-dns -o jsonpath="$json_path" 2>&1 | grep -q "Ready=True"; do - sleep 5;echo "waiting for kube-dns to be available" - kubectl get pods --all-namespaces + until kubectl -n kube-system get pods -l component=kube-controller-manager -o jsonpath="$json_path" 2>&1 | grep -q "Ready=True"; do + sleep 5; done + + kubectl get pods --all-namespaces } main "$@" diff --git a/utils/utils.go b/utils/utils.go index 2de1a2e86..8488dd739 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1065,21 +1065,15 @@ func ServiceAccountPullSecretExists(ba common.BaseComponent, client client.Clien return getErr } secrets := sa.ImagePullSecrets - found := false if len(secrets) > 0 { // if this is our service account there will be one image pull secret // For others there could be more. either way, just use the first? sName := secrets[0].Name err := client.Get(context.TODO(), types.NamespacedName{Name: sName, Namespace: ns}, &corev1.Secret{}) if err != nil { - return err + saErr := errors.New("Service account " + saName + " isn't ready. Reason: " + err.Error()) + return saErr } - found = true - - } - if !found { - saErr := errors.New("Service account " + saName + " isn't ready") - return saErr } return nil }