diff --git a/Makefile b/Makefile index 2964debff..3887e158e 100644 --- a/Makefile +++ b/Makefile @@ -108,9 +108,13 @@ E2E_FLAGS ?= "" e2e: $(GINKGO) #EXHELP Run the e2e tests. $(GINKGO) --tags $(GO_BUILD_TAGS) $(E2E_FLAGS) -trace -progress $(FOCUS) test/e2e +export REG_PKG_NAME=registry-operator +export PLAIN_PKG_NAME=plain-operator +export CATALOG_IMG=localhost/catalog:e2e .PHONY: test-op-dev-e2e -test-op-dev-e2e: $(GINKGO) #HELP Run operator create, upgrade and delete tests. - CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) $(GINKGO) --tags $(GO_BUILD_TAGS) $(E2E_FLAGS) -trace -progress $(FOCUS) test/operator-framework-e2e +test-op-dev-e2e: $(GINKGO) $(OPERATOR_SDK) $(KUSTOMIZE) $(KIND) #HELP Run operator create, upgrade and delete tests. + test/operator-framework-e2e/setup.sh $(OPERATOR_SDK) $(CONTAINER_RUNTIME) $(KUSTOMIZE) $(KIND) $(KIND_CLUSTER_NAME) + $(GINKGO) --tags $(GO_BUILD_TAGS) -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/') @@ -126,7 +130,7 @@ test-e2e: run kind-load-test-artifacts e2e e2e-coverage undeploy kind-clean #HEL .PHONY: operator-developer-e2e operator-developer-e2e: KIND_CLUSTER_NAME=operator-controller-op-dev-e2e #EXHELP Run operator-developer e2e on local kind cluster -operator-developer-e2e: run $(OPM) $(OPERATOR_SDK) $(KUSTOMIZE) deploy-local-registry test-op-dev-e2e cleanup-local-registry kind-clean +operator-developer-e2e: run test-op-dev-e2e kind-clean .PHONY: e2e-coverage e2e-coverage: @@ -172,23 +176,6 @@ kind-load-test-artifacts: $(KIND) #EXHELP Load the e2e testdata container images $(KIND) load docker-image localhost/testdata/bundles/plain-v0/plain:v0.1.0 --name $(KIND_CLUSTER_NAME) $(KIND) load docker-image localhost/testdata/catalogs/test-catalog:e2e --name $(KIND_CLUSTER_NAME) -.PHONY: deploy-local-registry -deploy-local-registry: #EXHELP Deploy local registry. - $(CONTAINER_RUNTIME) run -d -p 5001:5000 --restart=always --name local-registry registry:2 - -.PHONY: cleanup-local-registry -cleanup-local-registry: #EXHELP Stop and remove local registry. - $(CONTAINER_RUNTIME) container stop local-registry - $(CONTAINER_RUNTIME) container rm -v local-registry - -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)) #SECTION Build diff --git a/go.mod b/go.mod index 728c53524..06272bd24 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/go-logr/logr v1.3.0 github.com/onsi/ginkgo/v2 v2.13.0 github.com/onsi/gomega v1.29.0 - github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 github.com/operator-framework/catalogd v0.7.0 github.com/operator-framework/deppy v0.0.1 github.com/operator-framework/operator-registry v1.28.0 @@ -19,7 +18,6 @@ require ( golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.1 - 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 @@ -99,6 +97,7 @@ 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 @@ -137,6 +136,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.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 diff --git a/test/operator-framework-e2e/README.md b/test/operator-framework-e2e/README.md deleted file mode 100644 index c7b4dbb7c..000000000 --- a/test/operator-framework-e2e/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# Cross-component E2E for operator framework - -This is a cross-component demo with all OLM v1 repositories. The ginkgo test does the following: -- 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 named `operator-controller-op-dev-e2e`. - - Installing the operator controller onto the cluster. - - 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, to accept input for both `plain+v0` and `registry+v1` bundles: - - For getting bundle related inputs: - ``` - type BundleInfo struct { - baseFolderPath string - bundles []BundleContent - } - - type BundleContent struct { - bInputDir string - bundleVersion string - imageRef string - } - ``` - - `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 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 `/< operatorName>-bundle:v.` - - For getting catalog related inputs: - ``` - type CatalogDInfo struct { - baseFolderPath string - catalogDir string - operatorName string - desiredChannelName string - imageRef string - fbcFileName string - } - ``` - - `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 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: - ``` - type OperatorActionInfo struct { - installVersion string - upgradeVersion string - } - ``` - - `installVersion` - Version of the operator to be installed on the cluster. - - `upgradeVersion` - Version of the operator to be upgraded on the cluster. - - - The below inputs are used to form the bundle using operator-sdk. - - ``` - 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 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 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: - ``` - 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 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. - - -## 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 deleted file mode 100644 index 731fd39ad..000000000 --- a/test/operator-framework-e2e/create_fbc_helper.go +++ /dev/null @@ -1,152 +0,0 @@ -package operatore2e - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/property" -) - -const ( - 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, 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, channelName, bundleVersions) - - dBundle := formBundle(operatorName, bundleRefsVersions) - - fbc := declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{dPackage}, - Channels: []declcfg.Channel{dChannel}, - Bundles: dBundle, - } - return &fbc -} - -// Forms package schema for the FBC -func formPackage(pkgName string) declcfg.Package { - packageFormed := declcfg.Package{ - Schema: declcfg.SchemaPackage, - Name: pkgName, - } - return packageFormed -} - -// Forms channel schema for the FBC -func formChannel(pkgName, channelName string, bundleVersions []string) declcfg.Channel { - channelEntries := formChannelEntries(pkgName, bundleVersions) - channelFormed := declcfg.Channel{ - Schema: declcfg.SchemaChannel, - Name: channelName, - Package: pkgName, - Entries: channelEntries, - } - return channelFormed -} - -// 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 { - replace := "" - if i != 0 { - replace = pkgName + ".v" + bundleVersions[i-1] - } - - channelEntries = append(channelEntries, declcfg.ChannelEntry{ - Name: pkgName + ".v" + version, - Replaces: replace, - }) - } - return channelEntries -} - -// Forms bundle schema for the FBC -func formBundle(pkgName string, imgRefsVersions map[string]string) []declcfg.Bundle { - bundleFormed := make([]declcfg.Bundle, 0, len(imgRefsVersions)) - for imgRef, version := range imgRefsVersions { - var properties []property.Property - properties = append(properties, property.Property{ - Type: declcfg.SchemaPackage, - Value: json.RawMessage(fmt.Sprintf(`{"packageName": "%s", "version": "%s"}`, pkgName, version)), - }) - properties = append(properties, property.Property{ - Type: SchemaBundleMediatype, - Value: json.RawMessage(fmt.Sprintf(`"%s"`, BundleMediatype)), - }) - - bundleFormed = append(bundleFormed, declcfg.Bundle{ - Schema: declcfg.SchemaBundle, - Name: pkgName + ".v" + version, - Package: pkgName, - Image: imgRef, - Properties: properties, - }) - } - return bundleFormed -} - -// 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 { - return err - } - - if _, err := os.Stat(fbcFilePath); os.IsNotExist(err) { - if err := os.MkdirAll(fbcFilePath, 0755); err != nil { - return err - } - } - - file, err := os.Create(filepath.Join(fbcFilePath, fbcFileName)) - if err != nil { - return err - } - defer file.Close() - - err = os.WriteFile(filepath.Join(fbcFilePath, fbcFileName), buf.Bytes(), 0600) - return err -} - -// 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)) - } - - fileContent := fmt.Sprintf(`schema: olm.semver -generatemajorchannels: false -generateminorchannels: false -stable: - bundles: -%s -`, strings.Join(images, "\n")) - - file, err := os.Create(semverFileName) - if err != nil { - return fmt.Errorf("error creating the semver yaml file %v : %v", semverFileName, err) - } - defer file.Close() - - if _, err = file.WriteString(fileContent); err != nil { - return fmt.Errorf("error forming the semver yaml file %v : %v", semverFileName, err) - } - - return nil -} diff --git a/test/operator-framework-e2e/generate_dockerfile.go b/test/operator-framework-e2e/generate_dockerfile.go deleted file mode 100644 index c5d888fb8..000000000 --- a/test/operator-framework-e2e/generate_dockerfile.go +++ /dev/null @@ -1,41 +0,0 @@ -package operatore2e - -import ( - "os" - "text/template" -) - -// 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) - } - - file, err := os.Create(dockerFilePath) - if err != nil { - return err - } - defer file.Close() - - if _, err = file.WriteString("FROM scratch\n"); err != nil { - return err - } - - err = t.Execute(file, struct{ YamlDir string }{yamlFolderName}) - return err -} - -// 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 fb4b2db72..7a5087cfe 100644 --- a/test/operator-framework-e2e/operator_framework_test.go +++ b/test/operator-framework-e2e/operator_framework_test.go @@ -2,79 +2,30 @@ package operatore2e import ( "context" - "fmt" - "io" "os" - "os/exec" - "path/filepath" - "strings" "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/meta" 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/types" + "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" operatorv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" ) var ( - cfg *rest.Config - c client.Client - ctx context.Context - containerRuntime string -) - -type BundleInfo struct { - 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 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 // 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 catalog image reference - fbcFileName string // Name of the FBC template file -} - -type OperatorActionInfo struct { - installVersion string // Version of the operator to be installed on the cluster - upgradeVersion string // Version of the operator to be upgraded on the cluster -} - -type SdkProjectInfo struct { - projectName string // The operator-sdk project name - domainName string - group string - version string - kind string -} - -const ( - remoteRegistryRepo = "localhost:5001/" - kindServer = "operator-controller-op-dev-e2e" - installNamespaceSuffix = "-system" - operatorControllerHome = "../.." + cfg *rest.Config + c client.Client + ctx context.Context ) func TestOperatorFramework(t *testing.T) { @@ -83,904 +34,96 @@ func TestOperatorFramework(t *testing.T) { } var _ = BeforeSuite(func() { - - var err error cfg = ctrl.GetConfigOrDie() - scheme := runtime.NewScheme() + ctx = context.Background() - err = catalogd.AddToScheme(scheme) + err := catalogd.AddToScheme(scheme) Expect(err).ToNot(HaveOccurred()) err = operatorv1alpha1.AddToScheme(scheme) Expect(err).ToNot(HaveOccurred()) - err = rukpakv1alpha1.AddToScheme(scheme) - Expect(err).ToNot(HaveOccurred()) - c, err = client.New(cfg, client.Options{Scheme: scheme}) Expect(err).ToNot(HaveOccurred()) - - 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+v0 bundles", func() { - var ( - sdkInfo *SdkProjectInfo - bundleInfo *BundleInfo - catalogDInfo *CatalogDInfo - operatorAction *OperatorActionInfo - operatorCatalog *catalogd.Catalog - operator *operatorv1alpha1.Operator - err error - ) +var _ = Describe("Operator Framework E2E", func() { + var catalog *catalogd.Catalog 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{ - { - bundleVersion: "1.1.0", - }, - { - bundleVersion: "1.2.0", + catalog = &catalogd.Catalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog" + rand.String(10), + }, + Spec: catalogd.CatalogSpec{ + Source: catalogd.CatalogSource{ + Type: catalogd.SourceTypeImage, + Image: &catalogd.ImageSource{ + Ref: os.Getenv("CATALOG_IMG"), + }, }, }, } - catalogDInfo = &CatalogDInfo{ - baseFolderPath: "../../testdata/catalogs", - fbcFileName: "catalog.yaml", - operatorName: "plain-operator", - desiredChannelName: "beta", - } - operatorAction = &OperatorActionInfo{ - installVersion: "1.1.0", - upgradeVersion: "1.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" + Expect(c.Create(ctx, catalog)).NotTo(HaveOccurred()) }) - 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("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("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") - operatorCatalog, err = createCatalogCheckResources(operatorCatalog, catalogDInfo) - 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()) + When("Creating an Operator that references a package with a plain+v0 bundle type", func() { + var operator *operatorv1alpha1.Operator + BeforeEach(func() { + operator = &operatorv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "plainv0", + }, + Spec: operatorv1alpha1.OperatorSpec{ + PackageName: os.Getenv("PLAIN_PKG_NAME"), + }, + } - 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()) - }) - 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()) + Expect(c.Create(ctx, operator)).NotTo(HaveOccurred()) + }) + It("should have a status condition type of Installed with a status of True and a reason of Success", func() { + Eventually(func(g Gomega) { + op := &operatorv1alpha1.Operator{} + g.Expect(c.Get(ctx, client.ObjectKeyFromObject(operator), op)).NotTo(HaveOccurred()) + cond := meta.FindStatusCondition(op.Status.Conditions, operatorv1alpha1.TypeInstalled) + g.Expect(cond).NotTo(BeNil()) + g.Expect(cond.Status).Should(Equal(metav1.ConditionTrue)) + g.Expect(cond.Reason).Should(Equal(operatorv1alpha1.ReasonSuccess)) + }).WithTimeout(2 * time.Minute).Should(Succeed()) + }) + AfterEach(func() { + Expect(c.Delete(ctx, operator)).NotTo(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 - semverTemplateFileName string - err error - ) - BeforeEach(func() { - sdkInfo = &SdkProjectInfo{ - projectName: "registry-operator", - domainName: "example2.com", - group: "cache", - version: "v1alpha1", - kind: "Memcached2", - } - bundleInfo = &BundleInfo{ - baseFolderPath: "../../testdata/bundles/registry-v1", - bundles: []BundleContent{ - { - bundleVersion: "1.1.0", + When("Creating an Operator that references a package with a registry+v1 bundle type", func() { + var operator *operatorv1alpha1.Operator + BeforeEach(func() { + operator = &operatorv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "registryv1", }, - { - bundleVersion: "1.2.0", + Spec: operatorv1alpha1.OperatorSpec{ + PackageName: os.Getenv("REG_PKG_NAME"), }, - }, - } - catalogDInfo = &CatalogDInfo{ - baseFolderPath: "../../testdata/catalogs", - fbcFileName: "catalog.yaml", - operatorName: "registry-operator", - } - operatorAction = &OperatorActionInfo{ - installVersion: "1.1.0", - upgradeVersion: "1.2.0", - } - for i, b := range bundleInfo.bundles { - bundleInfo.bundles[i].bInputDir = sdkInfo.projectName + ".v" + b.bundleVersion - bundleInfo.bundles[i].imageRef = remoteRegistryRepo + sdkInfo.projectName + "-bundle:v" + b.bundleVersion - } - catalogDInfo.catalogDir = catalogDInfo.operatorName + "-catalog" - catalogDInfo.imageRef = remoteRegistryRepo + catalogDInfo.catalogDir + ":test" + } - semverTemplateFileName = "registry-semver.yaml" + Expect(c.Create(ctx, operator)).NotTo(HaveOccurred()) + }) + It("should have a status condition type of Installed with a status of True and a reason of Success", func() { + Eventually(func(g Gomega) { + op := &operatorv1alpha1.Operator{} + g.Expect(c.Get(ctx, client.ObjectKeyFromObject(operator), op)).NotTo(HaveOccurred()) + cond := meta.FindStatusCondition(op.Status.Conditions, operatorv1alpha1.TypeInstalled) + g.Expect(cond).NotTo(BeNil()) + g.Expect(cond.Status).Should(Equal(metav1.ConditionTrue)) + g.Expect(cond.Reason).Should(Equal(operatorv1alpha1.ReasonSuccess)) + }).WithTimeout(2 * time.Minute).Should(Succeed()) + }) + AfterEach(func() { + Expect(c.Delete(ctx, operator)).NotTo(HaveOccurred()) + }) }) - 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("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") - operatorCatalog, err = createCatalogCheckResources(operatorCatalog, catalogDInfo) - 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, fmt.Sprintf("%s%s", catalogDInfo.operatorName, installNamespaceSuffix)) - - 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, fmt.Sprintf("%s%s", catalogDInfo.operatorName, installNamespaceSuffix)) - - 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()) - }) 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()) + Expect(c.Delete(ctx, catalog)).NotTo(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 { - if err := os.Mkdir(sdkInfo.projectName, 0755); err != nil { - return fmt.Errorf("Error creating the sdk project %v:%v", sdkInfo.projectName, err) - } - - operatorSdkProjectAbsPath, _ := filepath.Abs(sdkInfo.projectName) - operatorSdkProjectPath := "OPERATOR_SDK_PROJECT_PATH=" + operatorSdkProjectAbsPath - operatorSdkArgs := "OPERATOR_SDK_ARGS= init --domain=" + sdkInfo.domainName - cmd := exec.Command("make", "operator-sdk", operatorSdkProjectPath, operatorSdkArgs) - cmd.Dir = operatorControllerHome - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("Error initializing the operator-sdk project %v: %v : %v", sdkInfo.projectName, string(output), err) - } - - 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() - 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, 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", fmt.Sprintf("%s_controller.go", strings.ToLower(sdkInfo.kind))) - Expect(controllerFilePath).To(BeAnExistingFile()) - - return nil -} - -// 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 - if err := cmd.Run(); err != nil { - return fmt.Errorf("Error updating generated code for the operator-sdk project %v:%v", sdkInfo.projectName, err) - } - - // Generate and update the CRD manifests - cmd = exec.Command("make", "manifests") - cmd.Dir = sdkInfo.projectName - if err := cmd.Run(); err != nil { - 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 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()) - - 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.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, string(output), err) - } - - // Checking if CRD manifests are generated - csvFilePath := filepath.Join(sdkInfo.projectName, "config", "manifests", "bases", sdkInfo.projectName+".clusterserviceversion.yaml") - Expect(csvFilePath).To(BeAnExistingFile()) - - return nil -} - -// 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 - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("Error generating bundle format for the bundle %v: %v :%v", bundleData.bInputDir, string(output), err) - } - - // Check if bundle manifests are created - bundleManifestPath := filepath.Join(sdkInfo.projectName, "bundle", "manifests") - Expect(bundleManifestPath).To(BeAnExistingFile()) - - // Check if bundle metadata is created - bundleMetadataPath := filepath.Join(sdkInfo.projectName, "bundle", "metadata") - Expect(bundleMetadataPath).To(BeAnExistingFile()) - - // Build the bundle image - bundleImg := "BUNDLE_IMG=" + bundleData.imageRef - cmd = exec.Command("make", "bundle-build", bundleImg) - cmd.Dir = sdkInfo.projectName - output, err = cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("Error building bundle image %v with tag %v :%v: %v", bundleData.imageRef, bundleData.bundleVersion, string(output), err) - } - - // Push the bundle image - cmd = exec.Command("make", "bundle-push", bundleImg) - cmd.Dir = sdkInfo.projectName - output, err = cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("Error pushing bundle image %v with tag %v : %v: %v", bundleData.imageRef, bundleData.bundleVersion, string(output), err) - } - - // Load the bundle image into test environment - if err = loadImages(GinkgoWriter, kindServer, bundleData.imageRef); err != nil { - return err - } - - // Move the bundle structure into correct testdata folder for bundles - err = moveFolderContents(filepath.Join(sdkInfo.projectName, "bundle"), filepath.Join(rootBundlePath, bundleData.bInputDir)) - Expect(err).ToNot(HaveOccurred()) - Expect(filepath.Join(rootBundlePath, bundleData.bInputDir)).To(BeAnExistingFile()) - - // Move the generated dockerfile to correct path - err = os.Rename(filepath.Join(sdkInfo.projectName, "bundle.Dockerfile"), filepath.Join(rootBundlePath, bundleData.bInputDir, "bundle.Dockerfile")) - Expect(err).ToNot(HaveOccurred()) - - return nil -} - -// buildPushLoadContainer function builds a Docker container image using the Docker -// command-line tool, pushes to a container registry and loads into a kind cluster. -// -// The function takes the following arguments and returns back the error if any: -// `tag`: container image tag/name. -// `dockerfilePath`: path to the Dockerfile that defines the container image. -// `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(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) - } - - 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) - } - - err := loadImages(w, kindServer, tag) - return err -} - -// loadImages loads container images into a Kubernetes kind cluster -// -// The function takes the `Writer` to which the standard output and standard error will be redirected, -// the kind cluster to which the image is to be loaded, the container image to be loaded. -// -// and returns back the error if any -func loadImages(w io.Writer, kindServerName string, images ...string) error { - for _, image := range images { - cmd := exec.Command("kind", "load", "docker-image", image, "--name", kindServerName) - 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 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) - if err != nil { - return fmt.Errorf("FBC validation error in absolute path %s: %s", fbcDirPath, err) - } - opmArgs := "OPM_ARGS=validate " + fbcDirAbsPath - cmd := exec.Command("make", "opm", opmArgs) - cmd.Dir = operatorControllerHome - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("FBC validation failed: %s", output) - } - return nil -} - -// Creates catalog CR -func createTestCatalog(ctx context.Context, name, imageRef string) (*catalogd.Catalog, error) { - catalog := &catalogd.Catalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: catalogd.CatalogSpec{ - Source: catalogd.CatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ImageSource{ - Ref: imageRef, - }, - }, - }, - } - - err := c.Create(ctx, catalog) - return catalog, err -} - -// 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{ - Name: opName, - }, - Spec: operatorv1alpha1.OperatorSpec{ - PackageName: opName, - Version: version, - }, - } - - 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 -func upgradeOperator(ctx context.Context, opName, version string) (*operatorv1alpha1.Operator, error) { - operator := &operatorv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{ - Name: opName, - }, - } - if err := c.Get(ctx, types.NamespacedName{Name: opName}, operator); err != nil { - return nil, err - } - operator.Spec.PackageName = opName - operator.Spec.Version = version - - err := c.Update(ctx, operator) - return operator, err -} - -// 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 { - return fmt.Errorf("Error deleting the operator %v for the version %v : %v", opName, operator.Spec.Version, err) - } - - err := c.Delete(ctx, operator) - 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 { - return fmt.Errorf("Expected condition %s to not be nil", expectedCond.Type) - } - if actualCond.Status != expectedCond.Status { - return fmt.Errorf("Expected status: %s, but got: %s", expectedCond.Status, actualCond.Status) - } - if actualCond.Reason != expectedCond.Reason { - return fmt.Errorf("Expected reason: %s but got: %s", expectedCond.Reason, actualCond.Reason) - } - if !strings.Contains(actualCond.Message, expectedCond.Message) { - return fmt.Errorf("Expected message: %s but got: %s", expectedCond.Message, actualCond.Message) - } - return nil -} - -// Checks if the catalog resource is successfully unpacked and returns error if not. -func validateCatalogUnpacking(operatorCatalog *catalogd.Catalog) error { - if err := c.Get(ctx, types.NamespacedName{Name: operatorCatalog.Name}, operatorCatalog); err != nil { - return fmt.Errorf("Error retrieving catalog %v:%v", operatorCatalog.Name, err) - } - - cond := apimeta.FindStatusCondition(operatorCatalog.Status.Conditions, catalogd.TypeUnpacked) - expectedCond := &metav1.Condition{ - Type: catalogd.TypeUnpacked, - Status: metav1.ConditionTrue, - Reason: catalogd.ReasonUnpackSuccessful, - Message: "successfully unpacked the catalog image", - } - if err := checkConditionEquals(cond, expectedCond); err != nil { - return fmt.Errorf("Status conditions for the catalog instance %v is not as expected:%v", operatorCatalog.Name, err) - } - return nil -} - -// 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) (*catalogd.Catalog, error) { - operatorCatalog, err := createTestCatalog(ctx, catalogDInfo.catalogDir, catalogDInfo.imageRef) - if err != nil { - return nil, fmt.Errorf("Error creating catalog %v : %v", catalogDInfo.catalogDir, err) - } - - // checking if catalog unpacking is successful - Eventually(func(g Gomega) { - err = validateCatalogUnpacking(operatorCatalog) - g.Expect(err).ToNot(HaveOccurred()) - }, 2*time.Minute, 1).Should(Succeed()) - - return operatorCatalog, nil -} - -// Checks if the operator operator succeeds following operator install or upgrade -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) - g.Expect(err).ToNot(HaveOccurred()) - }, 2*time.Minute, 1).Should(Succeed()) - - // checking for a successful operator installation - Eventually(func(g Gomega) { - err := validateOperatorInstallation(operator, opVersion) - g.Expect(err).ToNot(HaveOccurred()) - }, 2*time.Minute, 1).Should(Succeed()) - - // checking for a successful package installation - Eventually(func(g Gomega) { - err := validatePackageInstallation(operator) - g.Expect(err).ToNot(HaveOccurred()) - }, 2*time.Minute, 1).Should(Succeed()) - - // verifying the presence of relevant manifest from the bundle on cluster - Eventually(func(g Gomega) { - err := checkManifestPresence(bundlePath, pkgName, opVersion, nameSpace) - g.Expect(err).ToNot(HaveOccurred()) - }).Should(Succeed()) -} - -// Checks for a successful resolution and bundle path for the operator and returns error if not. -func validateResolutionAndBundlePath(operator *operatorv1alpha1.Operator) error { - if err := c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator); err != nil { - return fmt.Errorf("Error retrieving operator %v:%v", operator.Name, err) - } - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved) - expectedCond := &metav1.Condition{ - Type: operatorv1alpha1.TypeResolved, - Status: metav1.ConditionTrue, - Reason: operatorv1alpha1.ReasonSuccess, - Message: "resolved to", - } - if err := checkConditionEquals(cond, expectedCond); err != nil { - return fmt.Errorf("Status conditions for the operator %v for the version %v is not as expected:%v", operator.Name, operator.Spec.Version, err) - } - if operator.Status.ResolvedBundleResource == "" { - return fmt.Errorf("Resoved Bundle Resource is not found for the operator %v for the version %v", operator.Name, operator.Spec.Version) - } - return nil -} - -// Checks if the operator installation succeeded and returns error if not. -func validateOperatorInstallation(operator *operatorv1alpha1.Operator, operatorVersion string) error { - if err := c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator); err != nil { - return fmt.Errorf("Error retrieving operator %v:%v", operator.Name, err) - } - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeInstalled) - expectedCond := &metav1.Condition{ - Type: operatorv1alpha1.TypeInstalled, - Status: metav1.ConditionTrue, - Reason: operatorv1alpha1.ReasonSuccess, - Message: "installed from", - } - if err := checkConditionEquals(cond, expectedCond); err != nil { - return fmt.Errorf("Status conditions for the operator %v for the version %v is not as expected:%v", operator.Name, operator.Spec.Version, err) - } - if operator.Status.InstalledBundleResource == "" { - return fmt.Errorf("Installed Bundle Resource is not found for the operator %v for the version %v", operator.Name, operator.Spec.Version) - } - if operator.Spec.Version != operatorVersion { - return fmt.Errorf("Expected operator version: %s for the operator %v, but got: %s", operator.Spec.Version, operator.Name, operatorVersion) - } - return nil -} - -// Checks if bundle deployment succeeded and returns error if not. -func validatePackageInstallation(operator *operatorv1alpha1.Operator) error { - bd := rukpakv1alpha1.BundleDeployment{} - if err := c.Get(ctx, types.NamespacedName{Name: operator.Name}, &bd); err != nil { - return fmt.Errorf("Error retrieving the bundle deployments for the operator %v:%v", operator.Name, err) - } - cond := apimeta.FindStatusCondition(bd.Status.Conditions, rukpakv1alpha1.TypeHasValidBundle) - expectedCond := &metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionTrue, - Reason: rukpakv1alpha1.ReasonUnpackSuccessful, - Message: "Successfully unpacked", - } - if err := checkConditionEquals(cond, expectedCond); err != nil { - return fmt.Errorf("Status conditions of the bundle deployment for the operator %v for the version %v is not as expected:%v", operator.Name, operator.Spec.Version, err) - } - - cond = apimeta.FindStatusCondition(bd.Status.Conditions, rukpakv1alpha1.TypeInstalled) - expectedCond = &metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionTrue, - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - Message: "Instantiated bundle", - } - if err := checkConditionEquals(cond, expectedCond); err != nil { - return fmt.Errorf("Status conditions of the bundle deployment for the operator %v for the version %v is not as expected:%v", operator.Name, operator.Spec.Version, err) - } - - return nil -} - -// Checks the presence of operator manifests for the operator -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.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" { - continue - } - gvk := resource.GetObjectKind().GroupVersionKind() - obj := &unstructured.Unstructured{} - obj.SetGroupVersionKind(gvk) - - 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 -} - -// Moves the content from currentPath to newPath -func moveFolderContents(currentPath, newPath string) error { - files, err := os.ReadDir(currentPath) - if err != nil { - return fmt.Errorf("Failed to read the folder %s: %v", currentPath, err) - } - - for _, file := range files { - oldPath := filepath.Join(currentPath, file.Name()) - newFilePath := filepath.Join(newPath, file.Name()) - - if err = os.MkdirAll(filepath.Dir(newFilePath), os.ModePerm); err != nil { - return fmt.Errorf("Failed to create directory for file %s: %v", file.Name(), err) - } - - if err = os.Rename(oldPath, newFilePath); err != nil { - return fmt.Errorf("Failed to move file %s: %v", file.Name(), err) - } - } - - return nil -} - -// Delete the folders or file in the collection array -func deleteFolderFile(collection []string) error { - for _, c := range collection { - if err := os.RemoveAll(c); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("Error deleting %v:%v", c, err) - } - } - } - return nil -} diff --git a/test/operator-framework-e2e/read_manifests.go b/test/operator-framework-e2e/read_manifests.go deleted file mode 100644 index 887b9ca9e..000000000 --- a/test/operator-framework-e2e/read_manifests.go +++ /dev/null @@ -1,65 +0,0 @@ -package operatore2e - -import ( - "bytes" - "fmt" - "os" - "path/filepath" - - "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" -) - -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) ([]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 { - if err != nil { - return fmt.Errorf("error walking path %s: %v", filePath, err) - } - - if fileInfo.IsDir() { - return nil - } - - fileContent, err := os.ReadFile(filePath) - if err != nil { - return fmt.Errorf("error reading file %s: %v", filePath, err) - } - - 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 - }) - - if err != nil { - return nil, err - } - - return objects, nil -} diff --git a/test/operator-framework-e2e/setup.sh b/test/operator-framework-e2e/setup.sh new file mode 100755 index 000000000..8b7a090c4 --- /dev/null +++ b/test/operator-framework-e2e/setup.sh @@ -0,0 +1,216 @@ +#! /bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +help="setup.sh is used to build operators using the operator-sdk and +build the image + bundle image, and create a FBC image for the +following bundle formats: +- registry+v1 +- plain+v0 +This script will ensure that all images built are loaded onto +a KinD cluster with the name specified in the arguments. +The following environment variables are required for configuring this script: +- \$CATALOG_IMG - the tag for the catalog image that contains the plain+v0 and registry+v1 operator bundle. +- \$REG_PKG_NAME - the name of the package for the operator that uses the registry+v1 bundle format. +- \$PLAIN_PKG_NAME - the name of the package for the operator that uses the plain+v0 bundle format. +setup.sh also takes 5 arguments. + +Usage: + setup.sh [OPERATOR_SDK] [CONTAINER_RUNTIME] [KUSTOMIZE] [KIND] [KIND_CLUSTER_NAME] +" + +######################################## +# Input validation +######################################## + +if [[ "$#" -ne 5 ]]; then + echo "Illegal number of arguments passed" + echo "${help}" +fi + +if [[ -z "${CATALOG_IMG}" ]]; then + echo "\$CATALOG_IMG is required to be set" + echo "${help}" +fi + +if [[ -z "${REG_PKG_NAME}" ]]; then + echo "\$REG_PKG_NAME is required to be set" + echo "${help}" +fi + +if [[ -z "${PLAIN_PKG_NAME}" ]]; then + echo "\$PLAIN_PKG_NAME is required to be set" + echo "${help}" +fi + +######################################## +# Setup temp dir and local variables +######################################## + +# We're going to do file manipulation, so let's work in a temp dir +TMP_ROOT="$(mktemp -d ./tmp.XXXXXX)" +# Make sure to delete the temp dir when we exit +trap 'rm -rf ${TMP_ROOT}' EXIT + +DOMAIN=oc-opdev-e2e.operatorframework.io +REG_DIR="${TMP_ROOT}/registry" +mkdir -p "${REG_DIR}" + +PLAIN_DIR="${TMP_ROOT}/plain" +mkdir -p "${PLAIN_DIR}" + +operator_sdk=$1 +container_tool=$2 +kustomize=$3 +kind=$4 +kcluster_name=$5 + +reg_img="${DOMAIN}/registry:v0.0.1" +reg_bundle_img="${DOMAIN}/registry-bundle:v0.0.1" +plain_img="${DOMAIN}/plain:v0.0.1" +plain_bundle_img="${DOMAIN}/plain-bundle:v0.0.1" + +catalog_img="${CATALOG_IMG}" +reg_pkg_name="${REG_PKG_NAME}" +plain_pkg_name="${PLAIN_PKG_NAME}" + +######################################## +# Create the registry+v1 based operator +# and build + load images +######################################## + +( + cd "${REG_DIR}" && \ + $operator_sdk init --domain="${DOMAIN}" && \ + $operator_sdk create api \ + --group="${DOMAIN}" \ + --version v1alpha1 \ + --kind Registry \ + --resource --controller && \ + make generate manifests && \ + make docker-build IMG="${reg_img}" && \ + sed -i -e 's/$(OPERATOR_SDK) generate kustomize manifests -q/$(OPERATOR_SDK) generate kustomize manifests -q --interactive=false/g' Makefile && \ + make bundle IMG="${reg_img}" VERSION=0.0.1 && \ + make bundle-build BUNDLE_IMG="${reg_bundle_img}" +) + +$kind load docker-image "${reg_img}" --name "${kcluster_name}" +$kind load docker-image "${reg_bundle_img}" --name "${kcluster_name}" + +##################################### +# Create the plain+v0 based operator +# and build + load images +##################################### + +( + cd "${PLAIN_DIR}" && \ + $operator_sdk init --domain="${DOMAIN}" && \ + $operator_sdk create api \ + --group="${DOMAIN}" \ + --version v1alpha1 \ + --kind Plain \ + --resource --controller && \ + make generate manifests && \ + make docker-build IMG="${plain_img}" + mkdir -p manifests && \ + $kustomize build config/default > manifests/manifests.yaml +) + +cat << EOF > "${PLAIN_DIR}"/plainbundle.Dockerfile +FROM scratch +ADD manifests /manifests +EOF + +$container_tool build -t "${plain_bundle_img}" -f "${PLAIN_DIR}"/plainbundle.Dockerfile "${PLAIN_DIR}"/ + +$kind load docker-image "${plain_img}" --name "${kcluster_name}" +$kind load docker-image "${plain_bundle_img}" --name "${kcluster_name}" + +##################################### +# Create the FBC that contains both +# the plain+v0 and registry+v1 operators +##################################### + +cat << EOF > "${TMP_ROOT}"/catalog.Dockerfile +FROM scratch +ADD catalog /configs +EOF + +mkdir -p "${TMP_ROOT}/catalog" +cat < "${TMP_ROOT}"/catalog/index.yaml +{ + "schema": "olm.package", + "name": "${reg_pkg_name}" +} +{ + "schema": "olm.bundle", + "name": "${reg_pkg_name}.v0.0.1", + "package": "${reg_pkg_name}", + "image": "${reg_bundle_img}", + "properties": [ + { + "type": "olm.package", + "value": { + "packageName": "${reg_pkg_name}", + "version": "0.0.1" + } + } + ] +} +{ + "schema": "olm.channel", + "name": "preview", + "package": "${reg_pkg_name}", + "entries": [ + { + "name": "${reg_pkg_name}.v0.0.1" + } + ] +} +{ + "schema": "olm.package", + "name": "${plain_pkg_name}" +} +{ + "schema": "olm.bundle", + "name": "${plain_pkg_name}.v0.0.1", + "package": "${plain_pkg_name}", + "image": "$plain_bundle_img", + "properties": [ + { + "type": "olm.package", + "value": { + "packageName": "${plain_pkg_name}", + "version": "0.0.1" + } + }, + { + "type": "olm.bundle.mediatype", + "value": "plain+v0" + } + ] +} +{ + "schema": "olm.channel", + "name": "preview", + "package": "${plain_pkg_name}", + "entries": [ + { + "name": "${plain_pkg_name}.v0.0.1" + } + ] +} +EOF + +docker build -t "${catalog_img}" -f "${TMP_ROOT}"/catalog.Dockerfile "${TMP_ROOT}"/ +$kind load docker-image "${catalog_img}" --name "${kcluster_name}" + +# Make sure all files are removable. This is necessary because +# the Makefiles generated by the Operator-SDK have targets +# that install binaries under the bin/ directory. Those binaries +# don't have write permissions so they can't be removed unless +# we ensure they have the write permissions +chmod -R +w "${REG_DIR}/bin" +chmod -R +w "${PLAIN_DIR}/bin" \ No newline at end of file