diff --git a/Makefile b/Makefile index 6f31a8892..467e7ca21 100644 --- a/Makefile +++ b/Makefile @@ -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 stop-local-registry remove-local-registry kind-cluster-cleanup .PHONY: e2e-coverage e2e-coverage: @@ -151,7 +151,7 @@ kind-load-test-artifacts: $(KIND) ## Load the e2e testdata container images into .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 + $(CONTAINER_RUNTIME) run -d -p 5001:5000 --restart=always --name local-registry registry:2 .PHONY: stop-local-registry stop-local-registry: ## Stop local registry @@ -161,15 +161,15 @@ stop-local-registry: ## Stop 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..5f2ba13a5 100644 --- a/test/operator-framework-e2e/README.md +++ b/test/operator-framework-e2e/README.md @@ -1,11 +1,26 @@ # 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. @@ -14,10 +29,9 @@ This is a cross-component demo with all OLM v1 repositories. The ginkgo test doe - 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. - The following structs defined are required as input for both plain+v0 and registry+v1 bundles: @@ -34,11 +48,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 for the specific bundle type input data.[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. - `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 `localhost:5001/-bundle:v.` - For getting catalog related inputs: ``` type CatalogDInfo struct { @@ -50,11 +64,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 for 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` + - `imageRef` - This is formed. Stores the FBC image reference which will be of the format: `localhost:5001//: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: ``` @@ -67,20 +81,17 @@ This is a cross-component demo with all OLM v1 repositories. The ginkgo test doe - `upgradeVersion` - Version of the operator to be upgraded on the cluster. ### Plain bundles -- Plain bundle manifests are taken as input. - - - The plain bundle manifest directory taken as input should follow the below directory structure: +- The plain+v0 bundles are formed using `operator-sdk` and `kustomize`. + - The below input is used to form the bundle using operator-sdk. ``` - bundles/ - └── plain-v0/ - ├── plain.v/ - │ ├── manifests - │ └── Dockerfile - └── plain.v/ - ├── manifests - └── Dockerfile + type SdkProjectInfo struct { + projectName string + domainName string + group string + version string + kind string + } ``` - - 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. diff --git a/test/operator-framework-e2e/create_fbc_helper.go b/test/operator-framework-e2e/create_fbc_helper.go index 25a6e3860..9383535e4 100644 --- a/test/operator-framework-e2e/create_fbc_helper.go +++ b/test/operator-framework-e2e/create_fbc_helper.go @@ -125,8 +125,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 using the bundle images passed +func formOLMSemverFile(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..b58a35e1d 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 a given set of yaml files +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 +func generateCatalogDockerFile(dockerFilePath, yamlFolderName string) error { + return generateDockerFile(dockerFilePath, yamlFolderName, catalogDockerfileTmpl) +} + +// GenerateBundleDockerFile generates Dockerfile for the bundle +func generateBundleDockerFile(dockerFilePath, yamlFolderName string) error { + return generateDockerFile(dockerFilePath, yamlFolderName, 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..7ea0d36cf 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" @@ -73,7 +72,7 @@ type SdkProjectInfo struct { } const ( - remoteRegistryRepo = "localhost:5000/" + remoteRegistryRepo = "localhost:5001/" kindServer = "operator-controller-op-dev-e2e" deployedNameSpace = "rukpak-system" operatorControllerHome = "../.." @@ -107,8 +106,9 @@ var _ = BeforeSuite(func() { }) -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,15 +117,20 @@ 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", }, }, @@ -133,7 +138,7 @@ var _ = Describe("Operator Framework E2E for plain bundles", func() { catalogDInfo = &CatalogDInfo{ baseFolderPath: "../../testdata/catalogs", fbcFileName: "catalog.yaml", - operatorName: "plain", + operatorName: "plain-operator", desiredChannelName: "beta", } operatorAction = &OperatorActionInfo{ @@ -141,102 +146,102 @@ var _ = Describe("Operator Framework E2E for plain bundles", func() { upgradeVersion: "0.1.1", } 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 specified bundle versions + // Bundle content is same for the bundles 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) + Expect(err).ToNot(HaveOccurred()) + + By("building/pushing/kind loading the catalog images") + dockerContext := catalogDInfo.baseFolderPath + dockerfilePath := filepath.Join(dockerContext, catalogDInfo.catalogDir+".Dockerfile") + 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 := sdkInfo.projectName + "-system" + 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") + nameSpace = sdkInfo.projectName + "-system" + 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, 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, catalogDInfo.catalogDir+".Dockerfile")) // delete the catalog Dockerfile generated + err = deleteFolderFile(toDelete) + Expect(err).ToNot(HaveOccurred()) }) }) @@ -253,11 +258,11 @@ var _ = Describe("Operator Framework E2E for registry+v1 bundles", func() { ) 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", @@ -273,7 +278,7 @@ var _ = Describe("Operator Framework E2E for registry+v1 bundles", func() { catalogDInfo = &CatalogDInfo{ baseFolderPath: "../../testdata/catalogs", fbcFileName: "catalog.yaml", - operatorName: "example-operator", + operatorName: "registry-operator", } operatorAction = &OperatorActionInfo{ installVersion: "0.1.0", @@ -288,150 +293,99 @@ var _ = Describe("Operator Framework E2E for registry+v1 bundles", func() { 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()) - }) - }) - 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 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 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, semverFileName) + Expect(err).ToNot(HaveOccurred()) + + By("building/pushing/kind loading the catalog images") + dockerContext := catalogDInfo.baseFolderPath + dockerFilePath := filepath.Join(dockerContext, catalogDInfo.catalogDir+".Dockerfile") + 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, 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()) }) }) +// Creates new operator-sdk project with the name sdkInfo.projectName 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,12 +396,17 @@ 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) } @@ -463,7 +422,9 @@ func sdkInitialize(sdkInfo *SdkProjectInfo) error { 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 @@ -482,15 +443,19 @@ func sdkGenerateManifestsCSV(sdkInfo *SdkProjectInfo) error { crdFilePath := filepath.Join(sdkInfo.projectName, "config", "crd", "bases", sdkInfo.group+"."+sdkInfo.domainName+"_"+strings.ToLower(sdkInfo.kind)+"s.yaml") 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 +465,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) + } + + // Create 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 @@ -565,14 +561,14 @@ func buildPushLoadContainer(tag, dockerfilePath, dockerContext, kindServer strin 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.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 +587,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(yaml file) 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, catalogDInfo.catalogDir+".Dockerfile") + 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, semverFileName string) error { + // forming the semver template yaml file + sdkCatalogFile := filepath.Join(catalogDInfo.baseFolderPath, catalogDInfo.catalogDir, catalogDInfo.fbcFileName) + if err := formOLMSemverFile(semverFileName, bundleImageRefs); err != nil { + return fmt.Errorf("Error forming the semver template yaml file %v : %v", semverFileName, err) + } + + // generating the FBC using semver template") + semverFileAbsPath, err := filepath.Abs(semverFileName) + if err != nil { + return fmt.Errorf("Error forming the absolute path of the semver file %v : %v", semverFileName, err) + } + 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() + 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) @@ -647,6 +715,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 @@ -677,6 +747,26 @@ func deleteOperator(ctx context.Context, opName string) error { return err } +// 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 instance. +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 and returns error if not. +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 { @@ -742,7 +832,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 +853,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 +990,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