From 88776d0966a3e6331987d6886202be9758e070bc Mon Sep 17 00:00:00 2001 From: jubittajohn Date: Tue, 8 Aug 2023 23:22:30 -0400 Subject: [PATCH] Updated to generate plain manifests from an SDK project with kustomize;Restructured code to have a single It block in a describe block; Signed-off-by: jubittajohn Combined local-registry cleanup make target; Added support for container runtime in consideration Signed-off-by: jubittajohn Bumped the upgrade version from 0.1.1 to 0.2.0 Signed-off-by: jubittajohn Updated comments and README Signed-off-by: jubittajohn --- Makefile | 21 +- go.mod | 15 +- go.sum | 25 +- test/operator-framework-e2e/README.md | 174 +++-- .../create_fbc_helper.go | 24 +- .../generate_dockerfile.go | 20 +- .../operator_framework_test.go | 696 ++++++++++-------- test/operator-framework-e2e/read_manifests.go | 48 +- .../bundles/plain-v0/plain.v0.1.0/Dockerfile | 2 +- .../plain.v0.1.0/manifests/configmap.yaml | 2 +- .../bundles/plain-v0/plain.v0.1.1/Dockerfile | 2 - .../plain.v0.1.1/manifests/configmap.yaml | 19 - 12 files changed, 597 insertions(+), 451 deletions(-) delete mode 100644 testdata/bundles/plain-v0/plain.v0.1.1/Dockerfile delete mode 100644 testdata/bundles/plain-v0/plain.v0.1.1/manifests/configmap.yaml diff --git a/Makefile b/Makefile index 6f31a8892..9667eea95 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ test-e2e: $(GINKGO) ## Run the e2e tests .PHONY: test-op-dev-e2e test-op-dev-e2e: $(GINKGO) ## Run operator create, upgrade and delete tests - $(GINKGO) --tags $(GO_BUILD_TAGS) $(E2E_FLAGS) -trace -progress $(FOCUS) test/operator-framework-e2e + CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) $(GINKGO) --tags $(GO_BUILD_TAGS) $(E2E_FLAGS) -trace -progress $(FOCUS) test/operator-framework-e2e .PHONY: test-unit ENVTEST_VERSION = $(shell go list -m k8s.io/client-go | cut -d" " -f2 | sed 's/^v0\.\([[:digit:]]\{1,\}\)\.[[:digit:]]\{1,\}$$/1.\1.x/') @@ -117,7 +117,7 @@ e2e: run kind-load-test-artifacts test-e2e e2e-coverage kind-cluster-cleanup ## .PHONY: operator-developer-e2e operator-developer-e2e: KIND_CLUSTER_NAME=operator-controller-op-dev-e2e ## Run operator-developer e2e on local kind cluster -operator-developer-e2e: run setup-op-dev-e2e deploy-local-registry test-op-dev-e2e stop-local-registry remove-local-registry kind-cluster-cleanup +operator-developer-e2e: run $(OPM) $(OPERATOR_SDK) $(KUSTOMIZE) deploy-local-registry test-op-dev-e2e cleanup-local-registry kind-cluster-cleanup .PHONY: e2e-coverage e2e-coverage: @@ -150,26 +150,23 @@ kind-load-test-artifacts: $(KIND) ## Load the e2e testdata container images into $(KIND) load docker-image localhost/testdata/catalogs/test-catalog:e2e --name $(KIND_CLUSTER_NAME) .PHONY: deploy-local-registry -deploy-local-registry: ## Deploy local docker registry - $(CONTAINER_RUNTIME) run -d -p 5000:5000 --restart=always --name local-registry registry:2 +deploy-local-registry: ## Deploy local registry + $(CONTAINER_RUNTIME) run -d -p 5001:5000 --restart=always --name local-registry registry:2 -.PHONY: stop-local-registry -stop-local-registry: ## Stop local registry +.PHONY: cleanup-local-registry +cleanup-local-registry: ## Stop and remove local registry $(CONTAINER_RUNTIME) container stop local-registry - -.PHONY: remove-local-registry -remove-local-registry: ## Remove local registry $(CONTAINER_RUNTIME) container rm -v local-registry -.PHONY: setup-op-dev-e2e -setup-op-dev-e2e: $(OPM) $(OPERATOR_SDK) - opm: $(OPM) $(OPM) $(OPM_ARGS) operator-sdk: $(OPERATOR_SDK) (cd $(OPERATOR_SDK_PROJECT_PATH) && $(OPERATOR_SDK) $(OPERATOR_SDK_ARGS)) +kustomize: $(KUSTOMIZE) + (cd $(OPERATOR_SDK_PROJECT_PATH) && $(KUSTOMIZE) $(KUSTOMIZE_ARGS)) + ##@ Build export VERSION ?= $(shell git describe --tags --always --dirty) diff --git a/go.mod b/go.mod index d406ae0f3..1dba611ac 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 + github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 github.com/operator-framework/catalogd v0.4.1 github.com/operator-framework/deppy v0.0.0-20230629133131-bb7b6ae7b266 github.com/operator-framework/operator-registry v1.27.1 @@ -14,12 +15,12 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.25.0 + k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 k8s.io/component-base v0.26.1 k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 sigs.k8s.io/controller-runtime v0.14.4 - sigs.k8s.io/yaml v1.3.0 ) require ( @@ -82,7 +83,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.12.3 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-sqlite3 v1.14.10 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -96,7 +96,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect - github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 // indirect github.com/otiai10/copy v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -104,7 +103,6 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -120,13 +118,12 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/multierr v1.10.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.9.3 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect @@ -139,7 +136,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.26.1 // indirect - k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/apiserver v0.26.1 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect @@ -147,4 +143,5 @@ require ( sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index d265b1ad1..4c1fc6d2d 100644 --- a/go.sum +++ b/go.sum @@ -106,7 +106,6 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -632,9 +631,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -726,8 +724,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -778,7 +776,6 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -837,9 +834,6 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= -github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= @@ -892,10 +886,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1038,8 +1030,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1118,8 +1109,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/test/operator-framework-e2e/README.md b/test/operator-framework-e2e/README.md index 90aff4e80..c7b4dbb7c 100644 --- a/test/operator-framework-e2e/README.md +++ b/test/operator-framework-e2e/README.md @@ -1,26 +1,43 @@ # Cross-component E2E for operator framework This is a cross-component demo with all OLM v1 repositories. The ginkgo test does the following: -- Automates the creation of `plain+v0` bundles and FBCs for a set of bundle manifest directories. +- Uses operator-sdk and kustomize to build `plain+v0` bundles and create catalogs to include the bundles. - Installs, upgrades and deletes a `plain+v0` operator. - Uses operator-sdk to build `registry+v1` bundles and create catalogs to include the bundles. - Installs, upgrades and deletes a `registry+v1` operator. +The steps in the ginkgo test can be summarized as follows: + +1. start with an empty directory +2. call operator-sdk to initialize and generate an operator +3. generate a bundle directory +4. build/push/kind load bundle images from the bundle directories +5. repeat steps 2-4 as necessary to get bundles for multiple operator versions +6. generate a catalog directory +7. build/push/kind load the catalog +8. create a Catalog CR (with kubectl operator) +9. create an Operator CR (with kubectl operator) +10. trigger Operator upgrades (with kubectl operator) +11. delete the Operator CR (with kubectl operator) +12. delete the Catalog CR (with kubectl operator) +13. repeat steps 2-12 for each bundle format (e.g. registry+v1 and plain+v0) ## Objective - Development on OLM v1 is split across multiple repositories, and the list of relevant repositories may grow over time. While we have demos showing improvements in functionality of components over time, it can be difficult to have a picture of the state of OLM v1 at any given time for someone not following its development closely. Having a single source to look for OLM v1 behavior can provide more clarity about the state of the project. - With the scale of the OLM v1 project, it is useful to have a means to test components in the operator development + lifecycle pipeline together to create a more cohesive experience for all users. ## Getting Started - +- This test currently only works with the container runtime `docker`. - Building operator-controller, deploying it into the cluster and rest of the configuration is done in the `MakeFile` of this repo under the target `operator-developer-e2e`. This includes: - - Setting up a kind cluster. + - Setting up a kind cluster named `operator-controller-op-dev-e2e`. - Installing the operator controller onto the cluster. - - Downloading the opm tool. - - Installing the operator-sdk. + - Setting up `opm`, `operator-sdk` and `kustomize` using bingo. - Setting up a local registry server for building and loading images. +### Input Values used + +Below are the input values used in the test which is specified in the `operator_framework_test.go`. -- The following structs defined are required as input for both plain+v0 and registry+v1 bundles: +- The following structs defined are required, to accept input for both `plain+v0` and `registry+v1` bundles: - For getting bundle related inputs: ``` type BundleInfo struct { @@ -34,11 +51,11 @@ This is a cross-component demo with all OLM v1 repositories. The ginkgo test doe imageRef string } ``` - - `baseFolderPath` - Base path of the folder for the specific bundle type input data. + - `baseFolderPath` - Base/root path of the folder where the specific bundle type input data is stored.[root path to plain-v0 or registry-v1 bundles testdata] - `bundles` - Stores the data relevant to different versions of the bundle. - - `bInputDir` - The input directory containing the specific version of the bundle data. + - `bInputDir` - The directory that stores the specific version of the bundle data. The name of the directory is formed and is of the format `.v`. - `bundleVersion` - The specific version of the bundle data. - - `imageRef` - This is formed. Stores the bundle image reference which will be of the format `localhost:5000/-bundle:v.` + - `imageRef` - This is formed. Stores the bundle image reference which will be of the format `/< operatorName>-bundle:v.` - For getting catalog related inputs: ``` type CatalogDInfo struct { @@ -50,11 +67,11 @@ This is a cross-component demo with all OLM v1 repositories. The ginkgo test doe fbcFileName string } ``` - - `baseFolderPath` - Base path of the folder for the catalogs. + - `baseFolderPath` - Base/root path of the folder that stores the catalogs. - `operatorName` - Name of the operator to be installed from the bundles. - `desiredChannelName` - Desired channel name for the operator. - - `catalogDir` - This is formed. The directory to store the FBC. The formed value will be of the format: `-catalog` - - `imageRef` - This is formed. Stores the FBC image reference which will be of the format: `localhost:5000//:test` + - `catalogDir` - This is formed. The directory to store the catalog/FBC. The directory name will be of the format: `-catalog` + - `imageRef` - This is formed. Stores the FBC image reference which will be of the format: `/:test` - `fbcFileName` - Name of the FBC file. This is hard-coded as `catalog.yaml`. - For getting information related to the install/upgrade action for operators: ``` @@ -66,57 +83,120 @@ This is a cross-component demo with all OLM v1 repositories. The ginkgo test doe - `installVersion` - Version of the operator to be installed on the cluster. - `upgradeVersion` - Version of the operator to be upgraded on the cluster. -### Plain bundles -- Plain bundle manifests are taken as input. + - The below inputs are used to form the bundle using operator-sdk. - - The plain bundle manifest directory taken as input should follow the below directory structure: ``` - bundles/ - └── plain-v0/ - ├── plain.v/ - │ ├── manifests - │ └── Dockerfile - └── plain.v/ - ├── manifests - └── Dockerfile + type SdkProjectInfo struct { + projectName string + domainName string + group string + version string + kind string + } + ``` +## How to run +- Makefile target `operator-developer-e2e` : Runs the entire E2E setup. +- Makefile target `test-op-dev-e2e`: Runs the ginkgo test. +- Makefile target `deploy-local-registry`: Deploys local registry +- Makefile target `cleanup-local-registry` : Stops and removes local registry +- Makefile target `kind-cluster-cleanup`: Deletes the kind cluster + +## Bundle Types +### Plain bundles +- The `plain+v0` bundles are formed using `operator-sdk` and `kustomize`. + - The `kustomize` organizes the different resources present in the `operator-sdk` project into a single yaml file. + - The Dockerfile for the bundle is named `plainbundle.Dockerfile` and is generated using a custom routine. + - The generated bundle is stored in the format: + ``` + plain-v0 + └── .v + └── manifests + │ └── mainfest.yaml + └── plainbundle.Dockerfile ``` - - The bundles should be present in the testdata folder. -- After the bundle image is created and loaded, the FBC is formed by a custom routine by using the operatorName, desiredChannelName, bundle imageRefs and bundleVersions. - - - The generated FBC will be present in the file path: `testdata/catalogs/-catalog/catalog.yaml` and the Dockerfile will be formed in the file path: `testdata/catalogs/-catalog.Dockerfile` - - `Default channel` is not used in forming the FBC as it is not an OLMv1 concept. - - The package schema name will be the operatorName and the bundle name will be `.v`. -- The catalog resource is then formed with the name `-catalog`. +- The FBC template is formed by a custom routine by using the operatorName, desiredChannelName, bundle imageRefs and bundleVersions. + - `Default channel` is not used in forming the FBC as it is not an OLMv1 concept. + - Only one `olm.channel` is generated which is the given . + - Upgrade graph is formed with only replaces edge. + - The generated FBC is not validated using `opm` as the tool has no support for plain bundles. + - The Dockerfile for the catalog is named `-catalog.Dockerfile` and is generated using a custom routine. + - The generated catalog is stored in the format: + ``` + catalogs + └── -catalog + │ └── catalog.yaml + └── -catalog.Dockerfile + ``` +- The catalog CR is then formed with the name `-catalog`. - The operator is then installed and has the name ``. ### Registry Bundles - The registry+v1 bundles are formed using operator-sdk. - - The below input is used to form the bundle using operator-sdk. + - The generated CSV uses the default values. + - The bundle content is formed within the operator-sdk project directory in the folder `bundle`. This is moved to the bundle directory folder. + - The generated Dockerfile has the name `bundle.Dockerfile`. The Dockerfile and bundle structure is genearted by the `operator-sdk` tool. + - The generated bundle is stored in the format: ``` - type SdkProjectInfo struct { - projectName string - domainName string - group string - version string - kind string - } + registry-v1 + └── .v + └── manifests + └── metadata + └── bundle.Dockerfile + ``` + +- The FBC is formed using `opm alpha render-template semver` tool. + - The semver template named `registry-semver.yaml` is formed using a custom routine by passing the bundle imageRefs. + - `generatemajorchannels` and `generateminorchannels` is set to false in the semver template. + - The generated catalog is stored in the format: + ``` + catalogs + └── -catalog + │ └── catalog.yaml + └── -catalog.Dockerfile ``` - - The generated CSV uses the default values. - - The bundle content is formed in the operator-sdk project directory in the folder `bundle`. This is moved to the folder path: `testdata/bundles/registry-v1/.v`. - - The generated Dockerfile has the name `bundle.Dockerfile`. - - The same project directory needs to be updated for forming a different version of the `bundle`. -- After the bundle image is created and loaded, the FBC is formed using `semver` and `opm` tool. - - The semver named `registry-semver.yaml` is formed by passing the bundle imageRefs. - - The generated FBC will be present in the file path: `testdata/catalogs/-catalog/catalog.yaml` and the Dockerfile will be formed in the file path: `testdata/catalogs/-catalog.Dockerfile` - - The package schema name will be the operator-sdk projectName. - The catalog resource is then formed with the name `-catalog`. - The operator is then installed and has the name ``. -- After the e2e workflow, all the files formed are cleared and the catalog is deleted. +- After the e2e workflow, all the files formed are cleared. + + +## To-do +1. The resources are read from input manifests using universal decoder from `k8s.io/apimachinery/pkg/runtime/serializer`. + - However, in cases where a single file contains multiple YAML documents separated by `---,` the `UniversalDecoder` recognizes only the first resource. This situation is relevant as for `plain+v0` bundles generated through `kustomize,` the manifest has multiple YAML documents are combined into one file using --- separators. This is now handled by splitting the content of the YAML file and decoding each of them using the `UniversalDecoder`. + - This workaround can be improved using `YAMLToJSONDecoder` from `k8s.io/apimachinery/pkg/util/yaml`. And the kind, api version and name can be get by decoding into `Unstructured` from `k8s.io/apimachinery/pkg/apis/meta/v1/unstructured`. +2. All the tests pass and the operator is installed successfully. The bundledeployment succeeds and the resources are created. But the pod for the new operator failes due to `ImagePullBackOff`. + - This is because the `Deployment` controller-manager uses the image `controller:latest` which is not present on the cluster. + - The solution would be to replace `controller:latest` with the `busybox:latest` and then pulling and loading `busybox:latest` onto cluster. + - The replacement could possibly be achieved by adding the following to `config/default/kustomization.yaml` under `operator-sdk` project: + ``` + images: + - name: controller + newName: controller + newTag: latest + ``` +## Issues +1. This test currently only works with the container runtime `docker`. + - The default value of CONTAINER_RUNTIME defined in the Makefile is `docker`. Therefore the correct runtime has to be assigned to the variable `CONTAINER_RUNTIME` before calling the make target if docker is what is not being used. The test routine also assumes the runtime as `docker` if it is unable to retrieve the value of the environment variable. + - But this is only a partial fix to the problem. With this change the test for `plain+v0` bundles will pass but for `registry+v1` will fail for other container runtimes. This is because `registry+v1` uses `operator-sdk` support. Thus to mimic the user experience, the targets `bundle-build` and `bundle-push` from the generated Makefile by operator-sdk tool, which has docker being hard coded as the container runtime, is used to build and push the bundle images. This could be marked as an issue and addressed when hard coding docker as container runtime in the generated Makefile is addressed by operator-sdk. + +2. The `opm`,`operator-sdk` and `kustomize` binaries are added in operator-controller using `bingo`. + - But based on discussions, if required test should be changed so that it has `opm` and `operator-sdk` in `PATH` and simply runs it like `exec.Command("opm", ...)`. + - This will enable in running [a matrix](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) for the tests and to use different versions of `opm` and `operator-sdk`. + - This might help in emulating the user experience better. + +## Tooling gaps + +Following are the tooling gaps identified while testing `operator-framework` end-to-end: +- `opm` doesn't have plain bundle support. +- No tool for forming FBC for plain bundles. +- No tool for generating Dockerfile for plain bundles. +- No tool for generating Dockerfile for plain catalogs. +- Since `opm` doesn't have plain bundle support, there is no means to validate the FBC generated for plain bundles. + diff --git a/test/operator-framework-e2e/create_fbc_helper.go b/test/operator-framework-e2e/create_fbc_helper.go index 25a6e3860..731fd39ad 100644 --- a/test/operator-framework-e2e/create_fbc_helper.go +++ b/test/operator-framework-e2e/create_fbc_helper.go @@ -13,21 +13,18 @@ import ( ) const ( - SchemaPackage = "olm.package" - SchemaChannel = "olm.channel" - SchemaBundle = "olm.bundle" SchemaBundleMediatype = "olm.bundle.mediatype" BundleMediatype = "plain+v0" ) // Forms the FBC declartive config and creates the FBC by calling functions for forming the package, channel and bundles. -func CreateFBC(operatorName, defaultChannel string, bundleRefsVersions map[string]string) *declcfg.DeclarativeConfig { +func CreateFBC(operatorName, channelName string, bundleRefsVersions map[string]string) *declcfg.DeclarativeConfig { dPackage := formPackage(operatorName) bundleVersions := make([]string, 0) for _, bundleVersion := range bundleRefsVersions { bundleVersions = append(bundleVersions, bundleVersion) } - dChannel := formChannel(operatorName, defaultChannel, bundleVersions) + dChannel := formChannel(operatorName, channelName, bundleVersions) dBundle := formBundle(operatorName, bundleRefsVersions) @@ -42,7 +39,7 @@ func CreateFBC(operatorName, defaultChannel string, bundleRefsVersions map[strin // Forms package schema for the FBC func formPackage(pkgName string) declcfg.Package { packageFormed := declcfg.Package{ - Schema: SchemaPackage, + Schema: declcfg.SchemaPackage, Name: pkgName, } return packageFormed @@ -52,7 +49,7 @@ func formPackage(pkgName string) declcfg.Package { func formChannel(pkgName, channelName string, bundleVersions []string) declcfg.Channel { channelEntries := formChannelEntries(pkgName, bundleVersions) channelFormed := declcfg.Channel{ - Schema: SchemaChannel, + Schema: declcfg.SchemaChannel, Name: channelName, Package: pkgName, Entries: channelEntries, @@ -60,7 +57,8 @@ func formChannel(pkgName, channelName string, bundleVersions []string) declcfg.C return channelFormed } -// Forms the uprade graph for the FBC +// Forms the uprade graph for the FBC. Only forms replaces edge. For forming replaces edge, +// bundleVersions are assumed to be in increasing version number order. func formChannelEntries(pkgName string, bundleVersions []string) []declcfg.ChannelEntry { channelEntries := make([]declcfg.ChannelEntry, 0, len(bundleVersions)) for i, version := range bundleVersions { @@ -83,7 +81,7 @@ func formBundle(pkgName string, imgRefsVersions map[string]string) []declcfg.Bun for imgRef, version := range imgRefsVersions { var properties []property.Property properties = append(properties, property.Property{ - Type: SchemaPackage, + Type: declcfg.SchemaPackage, Value: json.RawMessage(fmt.Sprintf(`{"packageName": "%s", "version": "%s"}`, pkgName, version)), }) properties = append(properties, property.Property{ @@ -92,7 +90,7 @@ func formBundle(pkgName string, imgRefsVersions map[string]string) []declcfg.Bun }) bundleFormed = append(bundleFormed, declcfg.Bundle{ - Schema: SchemaBundle, + Schema: declcfg.SchemaBundle, Name: pkgName + ".v" + version, Package: pkgName, Image: imgRef, @@ -102,7 +100,7 @@ func formBundle(pkgName string, imgRefsVersions map[string]string) []declcfg.Bun return bundleFormed } -// Writes the formed FBC into catalog.yaml file +// Writes the formed FBC into catalog.yaml file in the path fbcFilePath func WriteFBC(fbc declcfg.DeclarativeConfig, fbcFilePath, fbcFileName string) error { var buf bytes.Buffer if err := declcfg.WriteYAML(fbc, &buf); err != nil { @@ -125,8 +123,8 @@ func WriteFBC(fbc declcfg.DeclarativeConfig, fbcFilePath, fbcFileName string) er return err } -// Generates the semver using the bundle images passed -func generateOLMSemverFile(semverFileName string, bundleImages []string) error { +// Forms the semver template using the bundle images passed +func formOLMSemverTemplateFile(semverFileName string, bundleImages []string) error { images := make([]string, 0, len(bundleImages)) for _, bundleImage := range bundleImages { images = append(images, fmt.Sprintf(" - image: %s", bundleImage)) diff --git a/test/operator-framework-e2e/generate_dockerfile.go b/test/operator-framework-e2e/generate_dockerfile.go index 86eb4795a..c5d888fb8 100644 --- a/test/operator-framework-e2e/generate_dockerfile.go +++ b/test/operator-framework-e2e/generate_dockerfile.go @@ -2,18 +2,16 @@ package operatore2e import ( "os" - "path/filepath" "text/template" ) -// generates Dockerfile and its contents for a given set of yaml files -func generateDockerFile(dockerFilePath, yamlFolderName, dockerFileName string) error { +// GenerateDockerFile generates Dockerfile and its contents for the data in yamlFolderName +func generateDockerFile(dockerFilePath, yamlFolderName, dockerfileTmpl string) error { t, err := template.New("dockerfile").Parse(dockerfileTmpl) if err != nil { panic(err) } - dockerFilePath = filepath.Join(dockerFilePath, dockerFileName) file, err := os.Create(dockerFilePath) if err != nil { return err @@ -28,4 +26,16 @@ func generateDockerFile(dockerFilePath, yamlFolderName, dockerFileName string) e return err } -const dockerfileTmpl = "ADD {{.YamlDir}} /configs/{{.YamlDir}}\n" +// GenerateCatalogDockerFile generates Dockerfile for the catalog content in catalogFolderName +func generateCatalogDockerFile(dockerFilePath, catalogFolderName string) error { + return generateDockerFile(dockerFilePath, catalogFolderName, catalogDockerfileTmpl) +} + +// GenerateBundleDockerFile generates Dockerfile for the bundle content in bundleFolderName +func generateBundleDockerFile(dockerFilePath, bundleFolderName string) error { + return generateDockerFile(dockerFilePath, bundleFolderName, bundleDockerfileTmpl) +} + +// Dockerfile templates +const catalogDockerfileTmpl = "ADD {{.YamlDir}} /configs/{{.YamlDir}}\n" +const bundleDockerfileTmpl = "ADD manifests /manifests\n" diff --git a/test/operator-framework-e2e/operator_framework_test.go b/test/operator-framework-e2e/operator_framework_test.go index 229c87bd1..db2e6a836 100644 --- a/test/operator-framework-e2e/operator_framework_test.go +++ b/test/operator-framework-e2e/operator_framework_test.go @@ -21,7 +21,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -34,29 +33,30 @@ import ( ) var ( - cfg *rest.Config - c client.Client - ctx context.Context + cfg *rest.Config + c client.Client + ctx context.Context + containerRuntime string ) type BundleInfo struct { - baseFolderPath string // Base path of the folder for the specific the bundle type input data - bundles []BundleContent // Stores the data relevant to different versions of the bundle + baseFolderPath string // root path of the folder where the specific bundle type input data is stored + bundles []BundleContent // stores the data relevant to different versions of the bundle } type BundleContent struct { - bInputDir string // The input directory containing the specific version of bundle data + bInputDir string // The directory that stores the specific version of bundle data bundleVersion string // The specific version of the bundle data imageRef string // Stores the bundle image reference } type CatalogDInfo struct { - baseFolderPath string // Base path of the folder for the catalogs - catalogDir string // The folder storing the FBC + baseFolderPath string // root path to the folder storing the catalogs + catalogDir string // The folder storing the FBC template operatorName string // Name of the operator to be installed from the bundles desiredChannelName string // Desired channel name for the operator - imageRef string // Stores the FBC image reference - fbcFileName string // Name of the FBC file + imageRef string // Stores the catalog image reference + fbcFileName string // Name of the FBC template file } type OperatorActionInfo struct { @@ -65,7 +65,7 @@ type OperatorActionInfo struct { } type SdkProjectInfo struct { - projectName string + projectName string // The operator-sdk project name domainName string group string version string @@ -73,7 +73,7 @@ type SdkProjectInfo struct { } const ( - remoteRegistryRepo = "localhost:5000/" + remoteRegistryRepo = "localhost:5001/" kindServer = "operator-controller-op-dev-e2e" deployedNameSpace = "rukpak-system" operatorControllerHome = "../.." @@ -105,10 +105,16 @@ var _ = BeforeSuite(func() { ctx = context.Background() + containerRuntime = os.Getenv("CONTAINER_RUNTIME") // This environment variable is set in the Makefile + if containerRuntime == "" { + containerRuntime = "docker" + } + }) -var _ = Describe("Operator Framework E2E for plain bundles", func() { +var _ = Describe("Operator Framework E2E for plain+v0 bundles", func() { var ( + sdkInfo *SdkProjectInfo bundleInfo *BundleInfo catalogDInfo *CatalogDInfo operatorAction *OperatorActionInfo @@ -117,147 +123,151 @@ var _ = Describe("Operator Framework E2E for plain bundles", func() { err error ) BeforeEach(func() { + sdkInfo = &SdkProjectInfo{ + projectName: "plain-example", + domainName: "plain.com", + group: "cache", + version: "v1alpha1", + kind: "Memcached1", + } bundleInfo = &BundleInfo{ baseFolderPath: "../../testdata/bundles/plain-v0", bundles: []BundleContent{ { - bInputDir: "plain.v0.1.0", bundleVersion: "0.1.0", }, { - bInputDir: "plain.v0.1.1", - bundleVersion: "0.1.1", + bundleVersion: "0.2.0", }, }, } catalogDInfo = &CatalogDInfo{ baseFolderPath: "../../testdata/catalogs", fbcFileName: "catalog.yaml", - operatorName: "plain", + operatorName: "plain-operator", desiredChannelName: "beta", } operatorAction = &OperatorActionInfo{ installVersion: "0.1.0", - upgradeVersion: "0.1.1", + upgradeVersion: "0.2.0", } for i, b := range bundleInfo.bundles { + bundleInfo.bundles[i].bInputDir = catalogDInfo.operatorName + ".v" + b.bundleVersion bundleInfo.bundles[i].imageRef = remoteRegistryRepo + catalogDInfo.operatorName + "-bundle:v" + b.bundleVersion } catalogDInfo.catalogDir = catalogDInfo.operatorName + "-catalog" catalogDInfo.imageRef = remoteRegistryRepo + catalogDInfo.catalogDir + ":test" }) - When("Build and load plain+v0 bundle images into the test environment", func() { - It("Build the plain bundle images and load them", func() { - for _, b := range bundleInfo.bundles { - dockerContext := filepath.Join(bundleInfo.baseFolderPath, b.bInputDir) - dockerfilePath := filepath.Join(dockerContext, "Dockerfile") - err = buildPushLoadContainer(b.imageRef, dockerfilePath, dockerContext, kindServer, GinkgoWriter) - Expect(err).ToNot(HaveOccurred()) - } - }) - }) - When("Create the FBC", func() { - It("Create a FBC", func() { - By("Creating FBC for plain bundle using custom routine") - imageRefsBundleVersions := make(map[string]string) - for _, b := range bundleInfo.bundles { - imageRefsBundleVersions[b.imageRef] = b.bundleVersion - } - fbc := CreateFBC(catalogDInfo.operatorName, catalogDInfo.desiredChannelName, imageRefsBundleVersions) - err = WriteFBC(*fbc, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir), catalogDInfo.fbcFileName) - Expect(err).ToNot(HaveOccurred()) - }) - }) - When("Build and load the FBC image into the test environment", func() { - It("Generate the docker file, build and load FBC image", func() { - By("Calling generate dockerfile function written") - err = generateDockerFile(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir, catalogDInfo.catalogDir+".Dockerfile") - Expect(err).ToNot(HaveOccurred()) - - By("Building the catalog image and loading into the test environment") - dockerContext := catalogDInfo.baseFolderPath - dockerfilePath := filepath.Join(dockerContext, catalogDInfo.catalogDir) + ".Dockerfile" - err = buildPushLoadContainer(catalogDInfo.imageRef, dockerfilePath, dockerContext, kindServer, GinkgoWriter) - Expect(err).ToNot(HaveOccurred()) - }) - }) - When("Create a catalog object and check if the resources are created", func() { - It("Create catalog object for the FBC and check if catalog, packages and bundle metadatas are created", func() { - bundleVersions := make([]string, len(bundleInfo.bundles)) - for i, bundle := range bundleInfo.bundles { - bundleVersions[i] = bundle.bundleVersion - } - operatorCatalog, err = createCatalogCheckResources(operatorCatalog, catalogDInfo, bundleVersions) - Expect(err).ToNot(HaveOccurred()) - }) - }) - When("Install an operator and check if the operator operations succeed", func() { - It("Create an operator object and install it", func() { - By("Creating an operator object") - operator, err = createOperator(ctx, catalogDInfo.operatorName, operatorAction.installVersion) - Expect(err).ToNot(HaveOccurred()) - - By("Checking if the operator operations succeeded") - checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.installVersion, bundleInfo.baseFolderPath) - }) - }) - When("Upgrade an operator to higher version and check if the operator operations succeed", func() { - It("Upgrade to a higher version of the operator", func() { - By("Upgrading the operator") - operator, err = upgradeOperator(ctx, catalogDInfo.operatorName, operatorAction.upgradeVersion) + It("should succeed", func() { + By("creating a new operator-sdk project") + err = sdkInitialize(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("creating a new api and controller") + err = sdkNewAPIAndController(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("generating CRD manifests") + err = sdkGenerateManifests(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("generating bundle directory using kustomize") + // Creates bundle structure for the given bundle versions + // Bundle content is same for the different versions of bundle now + for _, b := range bundleInfo.bundles { + err = kustomizeGenPlainBundleDirectory(sdkInfo, bundleInfo.baseFolderPath, b) Expect(err).ToNot(HaveOccurred()) + } - By("Checking if the operator operations succeeded") - checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.upgradeVersion, bundleInfo.baseFolderPath) - }) - }) - When("Delete an operator", func() { - It("Delete an operator", func() { - err = deleteOperator(ctx, catalogDInfo.operatorName) + By("building/pushing/kind loading bundle images from bundle directories") + for _, b := range bundleInfo.bundles { + dockerContext := filepath.Join(bundleInfo.baseFolderPath, b.bInputDir) + dockerfilePath := filepath.Join(dockerContext, "plainbundle.Dockerfile") + err = buildPushLoadContainer(b.imageRef, dockerfilePath, dockerContext, kindServer, GinkgoWriter) Expect(err).ToNot(HaveOccurred()) + } - By("Verifying the operator doesn't exist") - Eventually(func(g Gomega) { - err = validateOperatorDeletion(catalogDInfo.operatorName) - g.Expect(errors.IsNotFound(err)).To(BeTrue()) - }).Should(Succeed()) - }) + By("generating catalog directory by forming FBC and dockerfile using custom function") + imageRefsBundleVersions := make(map[string]string) + for _, b := range bundleInfo.bundles { + imageRefsBundleVersions[b.imageRef] = b.bundleVersion + } + err = genPlainCatalogDirectory(catalogDInfo, imageRefsBundleVersions) // the bundle image references and their respective versions are passed + Expect(err).ToNot(HaveOccurred()) + + By("building/pushing/kind loading the catalog images") + dockerContext := catalogDInfo.baseFolderPath + dockerfilePath := filepath.Join(dockerContext, fmt.Sprintf("%s.Dockerfile", catalogDInfo.catalogDir)) + err = buildPushLoadContainer(catalogDInfo.imageRef, dockerfilePath, dockerContext, kindServer, GinkgoWriter) + Expect(err).ToNot(HaveOccurred()) + + By("creating a Catalog CR and verifying the creation of respective packages and bundle metadata") + bundleVersions := make([]string, len(bundleInfo.bundles)) + for i, bundle := range bundleInfo.bundles { + bundleVersions[i] = bundle.bundleVersion + } + operatorCatalog, err = createCatalogCheckResources(operatorCatalog, catalogDInfo, bundleVersions) + Expect(err).ToNot(HaveOccurred()) + + By("creating an operator CR and verifying the operator operations") + namespace := fmt.Sprintf("%s-system", sdkInfo.projectName) + operator, err = createOperator(ctx, catalogDInfo.operatorName, operatorAction.installVersion) + Expect(err).ToNot(HaveOccurred()) + checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.installVersion, bundleInfo.baseFolderPath, namespace) + + By("upgrading an operator and verifying the operator operations") + operator, err = upgradeOperator(ctx, catalogDInfo.operatorName, operatorAction.upgradeVersion) + Expect(err).ToNot(HaveOccurred()) + checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.upgradeVersion, bundleInfo.baseFolderPath, namespace) + + By("deleting the operator CR and verifying the operator doesn't exist after deletion") + err = deleteOperator(ctx, catalogDInfo.operatorName) + Expect(err).ToNot(HaveOccurred()) + Eventually(func(g Gomega) { + err = validateOperatorDeletion(catalogDInfo.operatorName) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + + By("deleting the catalog CR and verifying the deletion") + err = deleteCatalog(operatorCatalog) + Expect(err).ToNot(HaveOccurred()) + Eventually(func(g Gomega) { + err = validateCatalogDeletion(operatorCatalog) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) }) - When("Clearing up catalog object and other files formed for the test", func() { - It("Clearing up data generated for the test", func() { - //Deleting the catalog object and checking if the deletion was successful - Eventually(func(g Gomega) { - err = deleteAndValidateCatalogDeletion(operatorCatalog) - g.Expect(errors.IsNotFound(err)).To(BeTrue()) - }).Should(Succeed()) - - var toDelete []string - toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) // delete the FBC formed - toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir+".Dockerfile")) // delete the catalog Dockerfile generated - err = deleteFolderFile(toDelete) - Expect(err).ToNot(HaveOccurred()) - }) + AfterEach(func() { + // Clearing up data generated for the test + var toDelete []string + for _, b := range bundleInfo.bundles { + toDelete = append(toDelete, filepath.Join(bundleInfo.baseFolderPath, b.bInputDir)) // delete the registry+v1 bundles formed + } + toDelete = append(toDelete, sdkInfo.projectName) //delete the sdk project directory + toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) // delete the FBC formed + toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, fmt.Sprintf("%s.Dockerfile", catalogDInfo.catalogDir))) // delete the catalog Dockerfile generated + err = deleteFolderFile(toDelete) + Expect(err).ToNot(HaveOccurred()) }) }) var _ = Describe("Operator Framework E2E for registry+v1 bundles", func() { var ( - sdkInfo *SdkProjectInfo - bundleInfo *BundleInfo - catalogDInfo *CatalogDInfo - operatorAction *OperatorActionInfo - operatorCatalog *catalogd.Catalog - operator *operatorv1alpha1.Operator - semverFileName string - err error + sdkInfo *SdkProjectInfo + bundleInfo *BundleInfo + catalogDInfo *CatalogDInfo + operatorAction *OperatorActionInfo + operatorCatalog *catalogd.Catalog + operator *operatorv1alpha1.Operator + semverTemplateFileName string + err error ) BeforeEach(func() { sdkInfo = &SdkProjectInfo{ - projectName: "example-operator", - domainName: "example.com", + projectName: "registry-operator", + domainName: "example2.com", group: "cache", version: "v1alpha1", - kind: "Memcached1", + kind: "Memcached2", } bundleInfo = &BundleInfo{ baseFolderPath: "../../testdata/bundles/registry-v1", @@ -266,18 +276,18 @@ var _ = Describe("Operator Framework E2E for registry+v1 bundles", func() { bundleVersion: "0.1.0", }, { - bundleVersion: "0.1.1", + bundleVersion: "0.2.0", }, }, } catalogDInfo = &CatalogDInfo{ baseFolderPath: "../../testdata/catalogs", fbcFileName: "catalog.yaml", - operatorName: "example-operator", + operatorName: "registry-operator", } operatorAction = &OperatorActionInfo{ installVersion: "0.1.0", - upgradeVersion: "0.1.1", + upgradeVersion: "0.2.0", } for i, b := range bundleInfo.bundles { bundleInfo.bundles[i].bInputDir = sdkInfo.projectName + ".v" + b.bundleVersion @@ -286,152 +296,102 @@ var _ = Describe("Operator Framework E2E for registry+v1 bundles", func() { catalogDInfo.catalogDir = catalogDInfo.operatorName + "-catalog" catalogDInfo.imageRef = remoteRegistryRepo + catalogDInfo.catalogDir + ":test" - semverFileName = "registry-semver.yaml" - }) - - When("Build registry+v1 bundles with operator-sdk", func() { - It("Initialize new operator-sdk project and create new api and controller", func() { - err = sdkInitialize(sdkInfo) - Expect(err).ToNot(HaveOccurred()) - }) - It("Generate manifests and CSV for the operator", func() { - err = sdkGenerateManifestsCSV(sdkInfo) - Expect(err).ToNot(HaveOccurred()) - }) - It("Generate and build registry+v1 bundle", func() { - // Creates bundle structure for the specified bundle versions - // Bundle content is same for the bundles now - for _, b := range bundleInfo.bundles { - err = sdkComplete(sdkInfo, bundleInfo.baseFolderPath, b) - Expect(err).ToNot(HaveOccurred()) - } - }) - }) - When("Create FBC and validate FBC", func() { - It("Create a FBC", func() { - sdkCatalogFile := filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir, catalogDInfo.fbcFileName) - By("Forming the semver yaml file") - bundleImageRefs := make([]string, len(bundleInfo.bundles)) - for i, bundle := range bundleInfo.bundles { - bundleImageRefs[i] = bundle.imageRef - } - err := generateOLMSemverFile(semverFileName, bundleImageRefs) - Expect(err).ToNot(HaveOccurred()) - - By("Forming the FBC using semver") - semverFileAbsPath, err := filepath.Abs(semverFileName) - Expect(err).ToNot(HaveOccurred()) - opmArgs := "OPM_ARGS=alpha render-template semver " + semverFileAbsPath + " -o yaml --use-http" - cmd := exec.Command("make", "-s", "opm", opmArgs) - cmd.Dir = operatorControllerHome - output, err := cmd.Output() - Expect(err).ToNot(HaveOccurred()) - - By("Saving the output under catalogs in testdata") - err = os.MkdirAll(filepath.Dir(sdkCatalogFile), os.ModePerm) - Expect(err).ToNot(HaveOccurred()) - - file, err := os.Create(sdkCatalogFile) - Expect(err).ToNot(HaveOccurred()) - defer file.Close() - _, err = file.Write(output) - Expect(err).ToNot(HaveOccurred()) - }) - It("Validate FBC", func() { - By("By validating the FBC using opm validate") - err = validateFBC(filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) - Expect(err).ToNot(HaveOccurred()) - }) - }) - When("Generate docker file and FBC image and load the FBC image into test environment", func() { - It("Create the docker file", func() { - By("By using opm generate tool") - dockerFolderAbsPath, err := filepath.Abs(filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) - Expect(err).ToNot(HaveOccurred()) - opmArgs := "OPM_ARGS=generate dockerfile " + dockerFolderAbsPath - cmd := exec.Command("make", "opm", opmArgs) - cmd.Dir = operatorControllerHome - err = cmd.Run() - Expect(err).ToNot(HaveOccurred()) - - By("Building the catalog image and loading into the test environment") - dockerContext := catalogDInfo.baseFolderPath - dockerFilePath := filepath.Join(dockerContext, catalogDInfo.catalogDir+".Dockerfile") - err = buildPushLoadContainer(catalogDInfo.imageRef, dockerFilePath, dockerContext, kindServer, GinkgoWriter) - Expect(err).ToNot(HaveOccurred()) - }) + semverTemplateFileName = "registry-semver.yaml" }) - When("Create a catalog object and check if the resources are created", func() { - It("Create catalog object for the FBC and check if catalog, packages and bundle metadatas are created", func() { - bundleVersions := make([]string, len(bundleInfo.bundles)) - for i, bundle := range bundleInfo.bundles { - bundleVersions[i] = bundle.bundleVersion - } - operatorCatalog, err = createCatalogCheckResources(operatorCatalog, catalogDInfo, bundleVersions) - Expect(err).ToNot(HaveOccurred()) - }) - }) - When("Install an operator and check if the operator operations succeed", func() { - It("Create an operator object and install it", func() { - By("Creating an operator object") - operator, err = createOperator(ctx, catalogDInfo.operatorName, operatorAction.installVersion) - Expect(err).ToNot(HaveOccurred()) - - By("Checking if the operator operations succeeded") - checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.installVersion, bundleInfo.baseFolderPath) - }) - }) - When("Upgrade an operator to higher version and check if the operator operations succeed", func() { - It("Upgrade to a higher version of the operator", func() { - By("Upgrading the operator") - operator, err = upgradeOperator(ctx, catalogDInfo.operatorName, operatorAction.upgradeVersion) - Expect(err).ToNot(HaveOccurred()) - - By("Checking if the operator operations succeeded") - checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.upgradeVersion, bundleInfo.baseFolderPath) - }) - }) - When("An operator is deleted", func() { - It("Delete and operator", func() { - err = deleteOperator(ctx, catalogDInfo.operatorName) + It("should succeed", func() { + By("creating a new operator-sdk project") + err = sdkInitialize(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("creating new api and controller") + err = sdkNewAPIAndController(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("generating CRD manifests") + err = sdkGenerateManifests(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("generating the CSV") + err = sdkGenerateCSV(sdkInfo) + Expect(err).ToNot(HaveOccurred()) + + By("generating bundle directory and building/pushing/kind loading bundle images from bundle directories") + // Creates bundle structure for the specified bundle versions + // Bundle content is same for the bundles now + for _, b := range bundleInfo.bundles { + err = sdkBundleComplete(sdkInfo, bundleInfo.baseFolderPath, b) Expect(err).ToNot(HaveOccurred()) + } - By("Eventually the operator should not exists") - Eventually(func(g Gomega) { - err = validateOperatorDeletion(catalogDInfo.operatorName) - g.Expect(errors.IsNotFound(err)).To(BeTrue()) - }).Should(Succeed()) - }) + By("generating catalog directory by forming FBC and dockerfile using opm tool, and validating the FBC formed") + bundleImageRefs := make([]string, len(bundleInfo.bundles)) + for i, bundle := range bundleInfo.bundles { + bundleImageRefs[i] = bundle.imageRef + } + err = genRegistryCatalogDirectory(catalogDInfo, bundleImageRefs, semverTemplateFileName) + Expect(err).ToNot(HaveOccurred()) + + By("building/pushing/kind loading the catalog images") + dockerContext := catalogDInfo.baseFolderPath + dockerFilePath := filepath.Join(dockerContext, fmt.Sprintf("%s.Dockerfile", catalogDInfo.catalogDir)) + err = buildPushLoadContainer(catalogDInfo.imageRef, dockerFilePath, dockerContext, kindServer, GinkgoWriter) + Expect(err).ToNot(HaveOccurred()) + + By("creating a Catalog CR and verifying the creation of respective packages and bundle metadata") + bundleVersions := make([]string, len(bundleInfo.bundles)) + for i, bundle := range bundleInfo.bundles { + bundleVersions[i] = bundle.bundleVersion + } + operatorCatalog, err = createCatalogCheckResources(operatorCatalog, catalogDInfo, bundleVersions) + Expect(err).ToNot(HaveOccurred()) + + By("creating an operator CR and verifying the operator operations") + operator, err = createOperator(ctx, catalogDInfo.operatorName, operatorAction.installVersion) + Expect(err).ToNot(HaveOccurred()) + checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.installVersion, bundleInfo.baseFolderPath, deployedNameSpace) + + By("upgrading an operator and verifying the operator operations") + operator, err = upgradeOperator(ctx, catalogDInfo.operatorName, operatorAction.upgradeVersion) + Expect(err).ToNot(HaveOccurred()) + checkOperatorOperationsSuccess(operator, catalogDInfo.operatorName, operatorAction.upgradeVersion, bundleInfo.baseFolderPath, deployedNameSpace) + + By("deleting the operator CR and verifying the operator doesn't exist") + err = deleteOperator(ctx, catalogDInfo.operatorName) + Expect(err).ToNot(HaveOccurred()) + Eventually(func(g Gomega) { + err = validateOperatorDeletion(catalogDInfo.operatorName) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + + By("deleting the catalog CR and verifying the deletion") + err = deleteCatalog(operatorCatalog) + Expect(err).ToNot(HaveOccurred()) + Eventually(func(g Gomega) { + err = validateCatalogDeletion(operatorCatalog) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) }) - When("Clearing up catalog object and other files formed for the test", func() { - It("Clearing up data generated for the test", func() { - //Deleting the catalog object and checking if the deletion was successful - Eventually(func(g Gomega) { - err = deleteAndValidateCatalogDeletion(operatorCatalog) - g.Expect(errors.IsNotFound(err)).To(BeTrue()) - }).Should(Succeed()) - - var toDelete []string - for _, b := range bundleInfo.bundles { - toDelete = append(toDelete, bundleInfo.baseFolderPath+"/"+b.bInputDir) // delete the registry+v1 bundles formed - } - toDelete = append(toDelete, sdkInfo.projectName) //delete the sdk project directory - toDelete = append(toDelete, semverFileName) // delete the semver yaml formed - toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) // delete the FBC formed - toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir+".Dockerfile")) // delete the catalog Dockerfile generated - err = deleteFolderFile(toDelete) - Expect(err).ToNot(HaveOccurred()) - }) + AfterEach(func() { + var toDelete []string + for _, b := range bundleInfo.bundles { + toDelete = append(toDelete, filepath.Join(bundleInfo.baseFolderPath, b.bInputDir)) // delete the registry+v1 bundles formed + } + toDelete = append(toDelete, sdkInfo.projectName) //delete the sdk project directory + toDelete = append(toDelete, semverTemplateFileName) // delete the semver template formed + toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) // delete the FBC formed + toDelete = append(toDelete, filepath.Join(catalogDInfo.baseFolderPath, fmt.Sprintf("%s.Dockerfile", catalogDInfo.catalogDir))) // delete the catalog Dockerfile generated + err = deleteFolderFile(toDelete) + Expect(err).ToNot(HaveOccurred()) }) }) +// Creates a new operator-sdk project with the name sdkInfo.projectName. +// A project folder is created with the name sdkInfo.projectName and operator-sdk is initialized. func sdkInitialize(sdkInfo *SdkProjectInfo) error { - // Create new project for the operator if err := os.Mkdir(sdkInfo.projectName, 0755); err != nil { return fmt.Errorf("Error creating the sdk project %v:%v", sdkInfo.projectName, err) } - // Initialize the operator-sdk project operatorSdkProjectAbsPath, _ := filepath.Abs(sdkInfo.projectName) operatorSdkProjectPath := "OPERATOR_SDK_PROJECT_PATH=" + operatorSdkProjectAbsPath operatorSdkArgs := "OPERATOR_SDK_ARGS= init --domain=" + sdkInfo.domainName @@ -442,28 +402,35 @@ func sdkInitialize(sdkInfo *SdkProjectInfo) error { return fmt.Errorf("Error initializing the operator-sdk project %v: %v : %v", sdkInfo.projectName, string(output), err) } - // Create new API and controller - operatorSdkProjectPath = "OPERATOR_SDK_PROJECT_PATH=" + operatorSdkProjectAbsPath - operatorSdkArgs = "OPERATOR_SDK_ARGS= create api --group=" + sdkInfo.group + " --version=" + sdkInfo.version + " --kind=" + sdkInfo.kind + " --resource --controller" - cmd = exec.Command("make", "operator-sdk", operatorSdkProjectPath, operatorSdkArgs) + return nil +} + +// Creates new API and controller for the given project with the name sdkInfo.projectName +func sdkNewAPIAndController(sdkInfo *SdkProjectInfo) error { + operatorSdkProjectAbsPath, _ := filepath.Abs(sdkInfo.projectName) + operatorSdkProjectPath := "OPERATOR_SDK_PROJECT_PATH=" + operatorSdkProjectAbsPath + operatorSdkArgs := "OPERATOR_SDK_ARGS= create api --group=" + sdkInfo.group + " --version=" + sdkInfo.version + " --kind=" + sdkInfo.kind + " --resource --controller" + cmd := exec.Command("make", "operator-sdk", operatorSdkProjectPath, operatorSdkArgs) cmd.Dir = operatorControllerHome - output, err = cmd.CombinedOutput() + output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("Error creating new API and controller for the operator-sdk project %v: %v : %v", sdkInfo.projectName, string(output), err) } // Checking if the API was created in the expected path - apiFilePath := filepath.Join(sdkInfo.projectName, "api", sdkInfo.version, strings.ToLower(sdkInfo.kind)+"_types.go") + apiFilePath := filepath.Join(sdkInfo.projectName, "api", sdkInfo.version, fmt.Sprintf("%s_types.go", strings.ToLower(sdkInfo.kind))) Expect(apiFilePath).To(BeAnExistingFile()) // Checking if the controller was created in the expected path") - controllerFilePath := filepath.Join(sdkInfo.projectName, "controllers", strings.ToLower(sdkInfo.kind)+"_controller.go") + controllerFilePath := filepath.Join(sdkInfo.projectName, "controllers", fmt.Sprintf("%s_controller.go", strings.ToLower(sdkInfo.kind))) Expect(controllerFilePath).To(BeAnExistingFile()) return nil } -func sdkGenerateManifestsCSV(sdkInfo *SdkProjectInfo) error { +// Updates the generated code if the API is changed. +// Generates and updates the CRD manifests +func sdkGenerateManifests(sdkInfo *SdkProjectInfo) error { // Update the generated code for the resources cmd := exec.Command("make", "generate") cmd.Dir = sdkInfo.projectName @@ -478,19 +445,23 @@ func sdkGenerateManifestsCSV(sdkInfo *SdkProjectInfo) error { return fmt.Errorf("Error generating and updating crd manifests for the operator-sdk project %v:%v", sdkInfo.projectName, err) } - // Checking if CRD manifests are generated - crdFilePath := filepath.Join(sdkInfo.projectName, "config", "crd", "bases", sdkInfo.group+"."+sdkInfo.domainName+"_"+strings.ToLower(sdkInfo.kind)+"s.yaml") + // Checking if CRD manifests are generated in the expected path + crdFilePath := filepath.Join(sdkInfo.projectName, "config", "crd", "bases", fmt.Sprintf("%s.%s_%ss.yaml", sdkInfo.group, sdkInfo.domainName, strings.ToLower(sdkInfo.kind))) Expect(crdFilePath).To(BeAnExistingFile()) - // Generate CSV for the bundle with default values + return nil +} + +// Generates CSV for the bundle with default values +func sdkGenerateCSV(sdkInfo *SdkProjectInfo) error { operatorSdkProjectAbsPath, _ := filepath.Abs(sdkInfo.projectName) operatorSdkProjectPath := "OPERATOR_SDK_PROJECT_PATH=" + operatorSdkProjectAbsPath operatorSdkArgs := "OPERATOR_SDK_ARGS= generate kustomize manifests --interactive=false" - cmd = exec.Command("make", "operator-sdk", operatorSdkProjectPath, operatorSdkArgs) + cmd := exec.Command("make", "operator-sdk", operatorSdkProjectPath, operatorSdkArgs) cmd.Dir = operatorControllerHome output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("Error generating CSV for the operator-sdk project %v: %v: %v", sdkInfo.projectName, output, err) + return fmt.Errorf("Error generating CSV for the operator-sdk project %v: %v: %v", sdkInfo.projectName, string(output), err) } // Checking if CRD manifests are generated @@ -500,8 +471,39 @@ func sdkGenerateManifestsCSV(sdkInfo *SdkProjectInfo) error { return nil } -func sdkComplete(sdkInfo *SdkProjectInfo, rootBundlePath string, bundleData BundleContent) error { - // Copy CRDs and other supported kinds, generate metadata, and in bundle format +// generates the bundle directory content for plain bundle format. The yaml files are formed using the kustomize tool +// and the bundle dockerfile is generated using a custom routine. +func kustomizeGenPlainBundleDirectory(sdkInfo *SdkProjectInfo, rootBundlePath string, bundleData BundleContent) error { + // Create the bundle directory structure + if err := os.MkdirAll(filepath.Join(rootBundlePath, bundleData.bInputDir, "manifests"), os.ModePerm); err != nil { + return fmt.Errorf("Failed to create directory for bundle structure %s: %v", bundleData.bInputDir, err) + } + + // Create the manifests for the plain+v0 bundle + operatorSdkProjectAbsPath, _ := filepath.Abs(sdkInfo.projectName) + operatorSdkProjectPath := "OPERATOR_SDK_PROJECT_PATH=" + operatorSdkProjectAbsPath + outputPlainBundlePath, _ := filepath.Abs(filepath.Join(rootBundlePath, bundleData.bInputDir, "manifests", "manifest.yaml")) + kustomizeArgs := "KUSTOMIZE_ARGS= build config/default > " + outputPlainBundlePath + cmd := exec.Command("make", "kustomize", operatorSdkProjectPath, kustomizeArgs) + cmd.Dir = operatorControllerHome + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("Error generating plain bundle directory %v: %v", string(output), err) + } + + // Creates bundle dockerfile + dockerFilePath := filepath.Join(rootBundlePath, bundleData.bInputDir, "plainbundle.Dockerfile") + if err = generateBundleDockerFile(dockerFilePath, bundleData.bInputDir); err != nil { + return fmt.Errorf("Error generating bundle dockerfile for the bundle %v: %v", bundleData.bInputDir, err) + } + return nil +} + +// Copies the CRDs. Generates metadata and manifest in registry+v1 bundle format. +// Build the bundle image and load into cluster. +// Copies the bundle to appropriate bundle format. +func sdkBundleComplete(sdkInfo *SdkProjectInfo, rootBundlePath string, bundleData BundleContent) error { + // Copy CRDs and other supported kinds and generate metadata and manifest in bundle format bundleGenFlags := "BUNDLE_GEN_FLAGS=-q --overwrite=false --version " + bundleData.bundleVersion + " $(BUNDLE_METADATA_OPTS)" cmd := exec.Command("make", "bundle", bundleGenFlags) cmd.Dir = sdkInfo.projectName @@ -561,18 +563,18 @@ func sdkComplete(sdkInfo *SdkProjectInfo, rootBundlePath string, bundleData Bund // `dockerContext`: context directory containing the files and resources referenced by the Dockerfile. // `w`: Writer to which the standard output and standard error will be redirected. func buildPushLoadContainer(tag, dockerfilePath, dockerContext, kindServer string, w io.Writer) error { - cmd := exec.Command("docker", "build", "-t", tag, "-f", dockerfilePath, dockerContext) + cmd := exec.Command(containerRuntime, "build", "-t", tag, "-f", dockerfilePath, dockerContext) cmd.Stderr = w cmd.Stdout = w if err := cmd.Run(); err != nil { - return fmt.Errorf("Error building Docker container image %s : %v", tag, err) + return fmt.Errorf("Error building Docker container image %s : %+v", tag, err) } - cmd = exec.Command("docker", "push", tag) + cmd = exec.Command(containerRuntime, "push", tag) cmd.Stderr = w cmd.Stdout = w if err := cmd.Run(); err != nil { - return fmt.Errorf("Error pushing Docker container image: %s to the registry: %v", tag, err) + return fmt.Errorf("Error pushing Docker container image: %s to the registry: %+v", tag, err) } err := loadImages(w, kindServer, tag) @@ -591,12 +593,84 @@ func loadImages(w io.Writer, kindServerName string, images ...string) error { cmd.Stderr = w cmd.Stdout = w if err := cmd.Run(); err != nil { - return fmt.Errorf("Error loading the container image %s into the cluster %s : %v", image, kindServerName, err) + return fmt.Errorf("Error loading the container image %s into the cluster %s : %+v", image, kindServerName, err) } } return nil } +// Generates catalog directory contents for the plain bundle format. The FBC template and the Dockerfile +// is formed using custom routines +func genPlainCatalogDirectory(catalogDInfo *CatalogDInfo, imageRefsBundleVersions map[string]string) error { + // forming the FBC using custom routine + fbc := CreateFBC(catalogDInfo.operatorName, catalogDInfo.desiredChannelName, imageRefsBundleVersions) + if err := WriteFBC(*fbc, filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir), catalogDInfo.fbcFileName); err != nil { + return fmt.Errorf("Error writing FBC content for the fbc %v : %v", catalogDInfo.fbcFileName, err) + } + + // generating dockerfile for the catalog using custom routine + dockerFilePath := filepath.Join(catalogDInfo.baseFolderPath, fmt.Sprintf("%s.Dockerfile", catalogDInfo.catalogDir)) + if err := generateCatalogDockerFile(dockerFilePath, catalogDInfo.catalogDir); err != nil { + return fmt.Errorf("Error generating catalog Dockerfile for the catalog directory %v : %v", catalogDInfo.catalogDir, err) + } + return nil +} + +// Generates catalog contents for the registry bundle format. The FBC(yaml file) and the Dockerfile +// is formed using opm tool. +func genRegistryCatalogDirectory(catalogDInfo *CatalogDInfo, bundleImageRefs []string, semverTemplateFileName string) error { + // forming the semver template yaml file + sdkCatalogFile := filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir, catalogDInfo.fbcFileName) + if err := formOLMSemverTemplateFile(semverTemplateFileName, bundleImageRefs); err != nil { + return fmt.Errorf("Error forming the semver template yaml file %v : %v", semverTemplateFileName, err) + } + + // generating the FBC using semver template") + semverTemplateFileAbsPath, err := filepath.Abs(semverTemplateFileName) + if err != nil { + return fmt.Errorf("Error forming the absolute path of the semver file %v : %v", semverTemplateFileName, err) + } + opmArgs := "OPM_ARGS=alpha render-template semver " + semverTemplateFileAbsPath + " -o yaml --use-http" + cmd := exec.Command("make", "-s", "opm", opmArgs) + cmd.Dir = operatorControllerHome + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("Error running opm command for FBC generation: %v", err) + } + + // saving the output of semver template under catalogs in testdata + if err = os.MkdirAll(filepath.Dir(sdkCatalogFile), os.ModePerm); err != nil { + return fmt.Errorf("Error forming the catalog directory structure: %v", err) + } + file, err := os.Create(sdkCatalogFile) + if err != nil { + return fmt.Errorf("Error creating the file %v: %v", sdkCatalogFile, err) + } + defer file.Close() + if _, err = file.Write(output); err != nil { + return fmt.Errorf("Error writing to the file %v: %v", sdkCatalogFile, err) + } + + // validating the FBC using opm validate + if err = validateFBC(filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)); err != nil { + return fmt.Errorf("Error validating the FBC %v: %v", sdkCatalogFile, err) + } + + // generating the dockerfile for catalog using opm generate tool + dockerFolderAbsPath, err := filepath.Abs(filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir)) + if err != nil { + return fmt.Errorf("Error forming the absolute path of the catalog dockerfile %v", err) + } + opmArgs = "OPM_ARGS=generate dockerfile " + dockerFolderAbsPath + cmd = exec.Command("make", "opm", opmArgs) + cmd.Dir = operatorControllerHome + output, err = cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("Error generating catalog dockerfile : %v :%v", string(output), err) + } + return nil +} + // Validates the FBC using opm tool func validateFBC(fbcDirPath string) error { fbcDirAbsPath, err := filepath.Abs(fbcDirPath) @@ -613,7 +687,7 @@ func validateFBC(fbcDirPath string) error { return nil } -// Creates catalog instance +// Creates catalog CR func createTestCatalog(ctx context.Context, name, imageRef string) (*catalogd.Catalog, error) { catalog := &catalogd.Catalog{ ObjectMeta: metav1.ObjectMeta{ @@ -633,7 +707,7 @@ func createTestCatalog(ctx context.Context, name, imageRef string) (*catalogd.Ca return catalog, err } -// Creates the operator opName for the version +// Creates the operator for opName for the version func createOperator(ctx context.Context, opName, version string) (*operatorv1alpha1.Operator, error) { operator := &operatorv1alpha1.Operator{ ObjectMeta: metav1.ObjectMeta{ @@ -647,6 +721,8 @@ func createOperator(ctx context.Context, opName, version string) (*operatorv1alp err := c.Create(ctx, operator) return operator, err + + // err := checkOperatorOperationsSuccess(opName, catalogDInfo.operatorName, operatorAction.installVersion, bundleInfo.baseFolderPath, nameSpace) } // Upgrades the operator opName for the version @@ -666,7 +742,7 @@ func upgradeOperator(ctx context.Context, opName, version string) (*operatorv1al return operator, err } -// Deletes the operator opName +// Deletes the operator CR with the name opName func deleteOperator(ctx context.Context, opName string) error { operator := &operatorv1alpha1.Operator{} if err := c.Get(ctx, types.NamespacedName{Name: opName}, operator); err != nil { @@ -677,6 +753,28 @@ func deleteOperator(ctx context.Context, opName string) error { return err } +// Checks if the operator was successfully deleted by trying to reteive the operator with the name opName. +// Error in retrieving indicates a successful deletion. +func validateOperatorDeletion(opName string) error { + err := c.Get(ctx, types.NamespacedName{Name: opName}, &operatorv1alpha1.Operator{}) + return err +} + +// Deletes the catalog CR. +func deleteCatalog(catalog *catalogd.Catalog) error { + if err := c.Delete(ctx, catalog); err != nil { + return fmt.Errorf("Error deleting the catalog instance: %v", err) + } + return nil +} + +// Checks if the catalog was successfully deleted by trying to reteive the catalog. +// Error in retrieving indicates a successful deletion. +func validateCatalogDeletion(catalog *catalogd.Catalog) error { + err := c.Get(ctx, types.NamespacedName{Name: catalog.Name}, &catalogd.Catalog{}) + return err +} + // Checks if the expected condition and actual condition for a resource matches and returns error if not. func checkConditionEquals(actualCond, expectedCond *metav1.Condition) error { if actualCond == nil { @@ -713,7 +811,7 @@ func validateCatalogUnpacking(operatorCatalog *catalogd.Catalog) error { return nil } -// Creates catalog instance and check if catalog unpackging is successul and if the packages and bundle metadatas are formed +// Creates catalog CR and checks if catalog unpackging is successful and if the packages and bundle metadatas are formed func createCatalogCheckResources(operatorCatalog *catalogd.Catalog, catalogDInfo *CatalogDInfo, bundleVersions []string) (*catalogd.Catalog, error) { operatorCatalog, err := createTestCatalog(ctx, catalogDInfo.catalogDir, catalogDInfo.imageRef) if err != nil { @@ -742,7 +840,7 @@ func createCatalogCheckResources(operatorCatalog *catalogd.Catalog, catalogDInfo } // Checks if the operator operator succeeds following operator install or upgrade -func checkOperatorOperationsSuccess(operator *operatorv1alpha1.Operator, pkgName, opVersion, bundlePath string) { +func checkOperatorOperationsSuccess(operator *operatorv1alpha1.Operator, pkgName, opVersion, bundlePath, nameSpace string) { // checking for a successful resolution and bundle path Eventually(func(g Gomega) { err := validateResolutionAndBundlePath(operator) @@ -763,7 +861,7 @@ func checkOperatorOperationsSuccess(operator *operatorv1alpha1.Operator, pkgName // verifying the presence of relevant manifest from the bundle on cluster Eventually(func(g Gomega) { - err := checkManifestPresence(bundlePath, pkgName, opVersion) + err := checkManifestPresence(bundlePath, pkgName, opVersion, nameSpace) g.Expect(err).ToNot(HaveOccurred()) }).Should(Succeed()) } @@ -900,45 +998,35 @@ func validatePackageInstallation(operator *operatorv1alpha1.Operator) error { } // Checks the presence of operator manifests for the operator -func checkManifestPresence(bundlePath, operatorName, version string) error { +func checkManifestPresence(bundlePath, operatorName, version, namespace string) error { resources, err := collectKubernetesObjects(bundlePath, operatorName, version) if err != nil { return err } for _, resource := range resources { - if resource.Kind == "ClusterServiceVersion" { + if resource.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" { continue } - gvk := schema.GroupVersionKind{ - Group: "", - Version: resource.APIVersion, - Kind: resource.Kind, - } - + gvk := resource.GetObjectKind().GroupVersionKind() obj := &unstructured.Unstructured{} obj.SetGroupVersionKind(gvk) - if err = c.Get(ctx, types.NamespacedName{Name: resource.Metadata.Name, Namespace: deployedNameSpace}, obj); err != nil { - return fmt.Errorf("Error retrieving the resources %v from the namespace %v: %v", resource.Metadata.Name, deployedNameSpace, err) + + objMeta, ok := resource.(metav1.Object) + if !ok { + return fmt.Errorf("Failed to convert resource to metav1.Object") + } + objName := objMeta.GetName() + namespacedName := types.NamespacedName{ + Name: objName, + Namespace: namespace, + } + if err = c.Get(ctx, namespacedName, obj); err != nil { + return fmt.Errorf("Error retrieving the resources %v from the namespace %v: %v", namespacedName.Name, namespace, err) } } return nil } -// Checks if the operator was successfully deleted and returns error if not. -func validateOperatorDeletion(opName string) error { - err := c.Get(ctx, types.NamespacedName{Name: opName}, &operatorv1alpha1.Operator{}) - return err -} - -// Deletes the catalog and checks if the deletion was successful. Returns error if not. -func deleteAndValidateCatalogDeletion(catalog *catalogd.Catalog) error { - if err := c.Delete(ctx, catalog); err != nil { - return fmt.Errorf("Error deleting the catalog instance: %v", err) - } - err := c.Get(ctx, types.NamespacedName{Name: catalog.Name}, &catalogd.Catalog{}) - return err -} - // Moves the content from currentPath to newPath func moveFolderContents(currentPath, newPath string) error { files, err := os.ReadDir(currentPath) diff --git a/test/operator-framework-e2e/read_manifests.go b/test/operator-framework-e2e/read_manifests.go index 7a1d17540..887b9ca9e 100644 --- a/test/operator-framework-e2e/read_manifests.go +++ b/test/operator-framework-e2e/read_manifests.go @@ -1,25 +1,34 @@ package operatore2e import ( + "bytes" "fmt" "os" "path/filepath" - "strings" - "sigs.k8s.io/yaml" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" ) -type Object struct { - Kind string `yaml:"kind"` - APIVersion string `yaml:"apiVersion"` - Metadata struct { - Name string `yaml:"name"` - } `yaml:"metadata"` +var ( + scheme = runtime.NewScheme() + + codecs = serializer.NewCodecFactory(scheme) +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) } // / collectKubernetesObjects collects the Kubernetes objects present in the bundle manifest folder for a particular package and its version -func collectKubernetesObjects(bundlePath, packageName, version string) ([]Object, error) { - objects := []Object{} +func collectKubernetesObjects(bundlePath, packageName, version string) ([]runtime.Object, error) { + var objects []runtime.Object bundleManifestPath := filepath.Join(bundlePath, packageName+".v"+version, "manifests") err := filepath.Walk(bundleManifestPath, func(filePath string, fileInfo os.FileInfo, err error) error { @@ -31,23 +40,20 @@ func collectKubernetesObjects(bundlePath, packageName, version string) ([]Object return nil } - content, err := os.ReadFile(filePath) + fileContent, err := os.ReadFile(filePath) if err != nil { return fmt.Errorf("error reading file %s: %v", filePath, err) } - documents := strings.Split(string(content), "---") - for _, doc := range documents { - obj := Object{} - if err := yaml.Unmarshal([]byte(doc), &obj); err != nil { - return fmt.Errorf("error parsing YAML in file %s: %v", filePath, err) - } - - if obj.Kind != "" && obj.APIVersion != "" && obj.Metadata.Name != "" { - objects = append(objects, obj) + decoder := codecs.UniversalDecoder(scheme.PrioritizedVersionsAllGroups()...) + yamlObjects := bytes.Split(fileContent, []byte("\n---\n")) + for _, yamlObject := range yamlObjects { + object, _, err := decoder.Decode(yamlObject, nil, nil) + if err != nil { + return fmt.Errorf("failed to decode file %s: %w", filePath, err) } + objects = append(objects, object) } - return nil }) diff --git a/testdata/bundles/plain-v0/plain.v0.1.0/Dockerfile b/testdata/bundles/plain-v0/plain.v0.1.0/Dockerfile index ac1b6dda5..bd54f66e6 100644 --- a/testdata/bundles/plain-v0/plain.v0.1.0/Dockerfile +++ b/testdata/bundles/plain-v0/plain.v0.1.0/Dockerfile @@ -1,2 +1,2 @@ FROM scratch -COPY manifests /manifests +COPY manifests /manifests \ No newline at end of file diff --git a/testdata/bundles/plain-v0/plain.v0.1.0/manifests/configmap.yaml b/testdata/bundles/plain-v0/plain.v0.1.0/manifests/configmap.yaml index d7d4f3a1c..268033efa 100644 --- a/testdata/bundles/plain-v0/plain.v0.1.0/manifests/configmap.yaml +++ b/testdata/bundles/plain-v0/plain.v0.1.0/manifests/configmap.yaml @@ -16,4 +16,4 @@ data: user-interface.properties: | color.good=purple color.bad=yellow - allow.textmode=true + allow.textmode=true \ No newline at end of file diff --git a/testdata/bundles/plain-v0/plain.v0.1.1/Dockerfile b/testdata/bundles/plain-v0/plain.v0.1.1/Dockerfile deleted file mode 100644 index ac1b6dda5..000000000 --- a/testdata/bundles/plain-v0/plain.v0.1.1/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM scratch -COPY manifests /manifests diff --git a/testdata/bundles/plain-v0/plain.v0.1.1/manifests/configmap.yaml b/testdata/bundles/plain-v0/plain.v0.1.1/manifests/configmap.yaml deleted file mode 100644 index 2376a6054..000000000 --- a/testdata/bundles/plain-v0/plain.v0.1.1/manifests/configmap.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# This configmap was pulled straight from the Kubernetes docs -# and can be found at https://kubernetes.io/docs/concepts/configuration/configmap/ -apiVersion: v1 -kind: ConfigMap -metadata: - name: game-demo -data: - # property-like keys; each key maps to a simple value - player_initial_lives: "5" - ui_properties_file_name: "user-interface.properties" - - # file-like keys - game.properties: | - enemy.types=aliens,monsters - player.maximum-lives=8 - user-interface.properties: | - color.good=purple - color.bad=yellow - allow.textmode=true