From 33f59be26a6b2bbff229a769bac928dd5bb58daf Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Fri, 17 Mar 2023 14:57:24 +1100 Subject: [PATCH 01/13] Move drift specific code --- controllers/{git.go => drfit.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename controllers/{git.go => drfit.go} (100%) diff --git a/controllers/git.go b/controllers/drfit.go similarity index 100% rename from controllers/git.go rename to controllers/drfit.go From 2db1bb13cac8b8d5882e6526692b6bd6cf5b8239 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Fri, 17 Mar 2023 14:57:36 +1100 Subject: [PATCH 02/13] Restore the old go-git helpers --- controllers/git.go | 227 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 controllers/git.go diff --git a/controllers/git.go b/controllers/git.go new file mode 100644 index 000000000..f359f28bc --- /dev/null +++ b/controllers/git.go @@ -0,0 +1,227 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + // "log" + "fmt" + "os" + + "github.com/go-git/go-git/v5" + // . "github.com/go-git/go-git/v5/_examples" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/transport/http" +) + +// https://github.com/go-git/go-git/blob/master/_examples/commit/main.go + +func checkout(url, directory, token, branch, commit string) error { + + if err := cloneRepo(url, directory, token); err != nil { + return err + } + + if len(commit) == 0 { + // Nothing more to do + return nil + } + + if err := checkoutRevision(directory, token, commit); err != nil { + return err + } + + return nil +} + +func getHashFromReference(repo *git.Repository, name plumbing.ReferenceName) (plumbing.Hash, error) { + b, err := repo.Reference(name, true) + if err != nil { + return plumbing.ZeroHash, err + } + + if !b.Name().IsTag() { + return b.Hash(), nil + } + + o, err := repo.Object(plumbing.AnyObject, b.Hash()) + if err != nil { + return plumbing.ZeroHash, err + } + + switch o := o.(type) { + case *object.Tag: + if o.TargetType != plumbing.CommitObject { + return plumbing.ZeroHash, fmt.Errorf("unsupported tag object target %q", o.TargetType) + } + + return o.Target, nil + case *object.Commit: + return o.Hash, nil + } + + return plumbing.ZeroHash, fmt.Errorf("unsupported tag target %q", o.Type()) +} + +func getCommitFromTarget(repo *git.Repository, name string) (plumbing.Hash, error) { + if len(name) == 0 { + return getHashFromReference(repo, plumbing.NewBranchReferenceName("main")) + } + + // Try as commit hash + h := plumbing.NewHash(name) + _, err := repo.Object(plumbing.AnyObject, h) + if err == nil { + return h, err + } + + // Try various reference types... + + if h, err := getHashFromReference(repo, plumbing.NewBranchReferenceName(name)); err == nil { + return h, err + } + + if h, err := getHashFromReference(repo, plumbing.NewTagReferenceName(name)); err == nil { + return h, err + } + + if h, err := getHashFromReference(repo, plumbing.NewRemoteHEADReferenceName(name)); err == nil { + return h, err + } + + return plumbing.ZeroHash, fmt.Errorf("unknown target %q", name) +} + +func checkoutRevision(directory, token, commit string) error { + + fmt.Printf("Accessing %s\n", directory) + repo, err := git.PlainOpen(directory) + if err != nil { + return err + } + + var foptions = &git.FetchOptions{ + Force: true, + InsecureSkipTLS: true, + Tags: git.AllTags, + } + + if len(token) > 0 { + // The intended use of a GitHub personal access token is in replace of your password + // because access tokens can easily be revoked. + // https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ + foptions.Auth = &http.BasicAuth{ + Username: "abc123", // yes, this can be anything except an empty string + Password: token, + } + } + + if err := repo.Fetch(foptions); err != nil && err != git.NoErrAlreadyUpToDate { + fmt.Println("Error fetching") + return err + } + + w, err := repo.Worktree() + if err != nil { + fmt.Println("Error obtaining worktree") + return err + } + + h, err := getCommitFromTarget(repo, commit) + coptions := git.CheckoutOptions{ + Force: true, + Hash: h, + } + + if err != nil { + return err + } + + fmt.Printf("git checkout %s (%s)\n", h, commit) + + if err := w.Checkout(&coptions); err != nil && err != git.NoErrAlreadyUpToDate { + fmt.Printf("Error during checkout") + return err + } + + // ... retrieving the commit being pointed by HEAD, it shows that the + // repository is pointing to the giving commit in detached mode + fmt.Println("git show-ref --head HEAD") + ref, err := repo.Head() + if err != nil { + fmt.Println("Error obtaining HEAD") + return err + } + + fmt.Printf("%s\n", ref.Hash()) + return err + +} + +func cloneRepo(url string, directory string, token string) error { + + fmt.Printf("git clone %s into %s\n", url, directory) + + // Clone the given repository to the given directory + var options = &git.CloneOptions{ + URL: url, + Progress: os.Stdout, + } + + if len(token) > 0 { + // The intended use of a GitHub personal access token is in replace of your password + // because access tokens can easily be revoked. + // https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ + options.Auth = &http.BasicAuth{ + Username: "abc123", // yes, this can be anything except an empty string + Password: token, + } + } + + if err := os.MkdirAll(directory, os.ModePerm); err != nil { + return err + } + + repo, err := git.PlainClone(directory, false, options) + if err != nil { + return err + } + + // ... retrieving the commit being pointed by HEAD + fmt.Println("git show-ref --head HEAD") + if ref, err := repo.Head(); err != nil { + return err + } else { + fmt.Printf("%s\n", ref.Hash()) + } + return nil +} + +func repoHash(directory string) (error, string) { + repo, err := git.PlainOpen(directory) + if err != nil { + return err, "" + } + + // ... checking out to commit + ref, err := repo.Head() + if err != nil { + return err, "" + } + + return nil, ref.Hash().String() +} From 3629cc731b2e4ad23253fac08e6731a587f7533d Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Fri, 17 Mar 2023 14:57:53 +1100 Subject: [PATCH 03/13] We don't need history, minimise space used --- controllers/git.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controllers/git.go b/controllers/git.go index f359f28bc..26c7cda4d 100644 --- a/controllers/git.go +++ b/controllers/git.go @@ -180,6 +180,8 @@ func cloneRepo(url string, directory string, token string) error { var options = &git.CloneOptions{ URL: url, Progress: os.Stdout, + Depth: 0, + // ReferenceName: plumbing.ReferenceName, } if len(token) > 0 { From 8a58921abb28fab2a2acf2f07fa5f683950b4182 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Fri, 17 Mar 2023 15:01:27 +1100 Subject: [PATCH 04/13] Restore pattern checkout logic --- controllers/pattern_controller.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/controllers/pattern_controller.go b/controllers/pattern_controller.go index a738f4ce5..891895f91 100644 --- a/controllers/pattern_controller.go +++ b/controllers/pattern_controller.go @@ -149,6 +149,23 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return r.actionPerformed(qualifiedInstance, "applying defaults", err) } + var token = "" + if len(qualifiedInstance.Spec.GitConfig.TokenSecret) > 0 { + if err, token = r.authTokenFromSecret(qualifiedInstance.Spec.GitConfig.TokenSecretNamespace, qualifiedInstance.Spec.GitConfig.TokenSecret, qualifiedInstance.Spec.GitConfig.TokenSecretKey); err != nil { + return r.actionPerformed(qualifiedInstance, "obtaining git auth token", err) + } + } + + gitDir := filepath.Join(qualifiedInstance.Status.Path, ".git") + if _, err := os.Stat(gitDir); os.IsNotExist(err) { + err := cloneRepo(qualifiedInstance.Spec.GitConfig.TargetRepo, qualifiedInstance.Status.Path, token) + return r.actionPerformed(qualifiedInstance, "cloning pattern repo", err) + } + + if err := checkoutRevision(qualifiedInstance.Status.Path, token, qualifiedInstance.Spec.GitConfig.TargetRevision); err != nil { + return r.actionPerformed(qualifiedInstance, "checkout target revision", err) + } + if err := r.preValidation(qualifiedInstance); err != nil { return r.actionPerformed(qualifiedInstance, "prerequisite validation", err) } From 9fd635f54f92aada3e11f3445147535a0600b6ba Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Fri, 17 Mar 2023 15:41:42 +1100 Subject: [PATCH 05/13] Sketch out the code to disable pre-installed subscriptions --- controllers/subscription.go | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/controllers/subscription.go b/controllers/subscription.go index b45bdc2f5..c4368cb0e 100644 --- a/controllers/subscription.go +++ b/controllers/subscription.go @@ -22,6 +22,8 @@ import ( "log" "reflect" +"gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -145,3 +147,56 @@ func updateSubscription(client olmclient.Interface, target, current *operatorv1a return nil, changed } + + +func buildSubscriptionExclusions(p api.Pattern, client olmclient.Interface) []string { + disabled := []string + valueFiles := newApplicationValueFiles(p) + for _, f := range valueFiles { + valuesFile := filepath.Join(p.Status.Path, f) + if _, err := os.Stat(valuesFile); !os.IsNotExist(err) { + + parsedSubs := parseValueSubs(valuesFile) + for _, key := range parsedSubs.keys() { + // handle arrays too + targetSub := parsedSubs[key] + namespace := "openshift-operators" + if sub["namespace"] != nil { + namespace = sub["namespace"] + } + _, sub := getSubscription(client, targetSub["name"], namespace) + if sub != nil && !ownedBySame(targetSub, sub) { + disabled = append(disabled, fmt.Sprintf("clusterGroup.subscriptions.%s.disabled", key)) + } + } + } + } + return disabled +} + +func parseValueSubs(filename string) map[string]interface{} { + + yamlFile, err := ioutil.ReadFile(filename) + + if err != nil { + panic(err) + } + + var jsonconfig map[string]interface{} + + err = yaml.Unmarshal(yamlFile, &jsonconfig) + if err != nil { + panic(err) + } + + //subs := jsonconfig.clusterGroup.subscriptions + cg := jsonconfig["clusterGroup"].(map[string]interface{}) + subs := cg["subscriptions"].(map[string]interface{}) + + if substr, err = yaml.Marshal(subs); err != nil { + log.Println("Found subs: ", substr) + log.Println("Found sub keys: ", subs.keys()) + } + + return subs +} \ No newline at end of file From 6921bd8670bee24e7a0021dd720cf43aa19da33d Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Sun, 9 Apr 2023 07:17:24 +0000 Subject: [PATCH 06/13] Repair the old checkout code paths so they compile --- api/v1alpha1/groupversion_info.go | 4 ++-- api/v1alpha1/pattern_types.go | 9 ++++++--- controllers/pattern_controller.go | 30 +++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index 9ab50cd06..7c883c548 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -15,8 +15,8 @@ limitations under the License. */ // Package v1alpha1 contains API Schema definitions for the gitops v1alpha1 API group -//+kubebuilder:object:generate=true -//+groupName=gitops.hybrid-cloud-patterns.io +// +kubebuilder:object:generate=true +// +groupName=gitops.hybrid-cloud-patterns.io package v1alpha1 import ( diff --git a/api/v1alpha1/pattern_types.go b/api/v1alpha1/pattern_types.go index 61b5c5c30..9821922a8 100644 --- a/api/v1alpha1/pattern_types.go +++ b/api/v1alpha1/pattern_types.go @@ -78,9 +78,9 @@ type GitConfig struct { Hostname string `json:"hostname,omitempty"` //Account string `json:"account,omitempty"` - //TokenSecret string `json:"tokenSecret,omitempty"` - //TokenSecretNamespace string `json:"tokenSecretNamespace,omitempty"` - //TokenSecretKey string `json:"tokenSecretKey,omitempty"` + TokenSecret string `json:"tokenSecret,omitempty"` + TokenSecretNamespace string `json:"tokenSecretNamespace,omitempty"` + TokenSecretKey string `json:"tokenSecretKey,omitempty"` // Upstream git repo containing the pattern to deploy. Used when in-cluster fork to point to the upstream pattern repository //+operator-sdk:csv:customresourcedefinitions:type=spec @@ -149,6 +149,9 @@ type PatternStatus struct { //+operator-sdk:csv:customresourcedefinitions:type=status Version int `json:"version,omitempty"` + Path string `json:"path,omitempty"` + Revision string `json:"revision,omitempty"` + //+operator-sdk:csv:customresourcedefinitions:type=status ClusterName string `json:"clusterName,omitempty"` //+operator-sdk:csv:customresourcedefinitions:type=status diff --git a/controllers/pattern_controller.go b/controllers/pattern_controller.go index 891895f91..0382a201c 100644 --- a/controllers/pattern_controller.go +++ b/controllers/pattern_controller.go @@ -20,19 +20,24 @@ import ( "context" "fmt" "log" + "os" "strings" "time" + "path/filepath" + "github.com/Masterminds/semver/v3" "github.com/go-errors/errors" "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" - "k8s.io/client-go/dynamic" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -397,9 +402,32 @@ func (r *PatternReconciler) applyDefaults(input *api.Pattern) (error, *api.Patte output.Spec.GitConfig.PollInterval = 180 } + if len(output.Status.Path) == 0 { + output.Status.Path = filepath.Join(os.TempDir(), output.Namespace, output.Name) + } + return nil, output } +func (r *PatternReconciler) authTokenFromSecret(namespace, secret, key string) (error, string) { + if len(key) == 0 { + return nil, "" + } + tokenSecret := &corev1.Secret{} + err := r.Client.Get(context.TODO(), types.NamespacedName{Name: secret, Namespace: namespace}, tokenSecret) + if err != nil { + // if tokenSecret, err = r.Client.Core().Secrets(namespace).Get(secret); err != nil { + r.logger.Error(err, fmt.Sprintf("Could not obtain secret %s/%s", secret, namespace)) + return err, "" + } + + if val, ok := tokenSecret.Data[key]; ok { + // See also https://github.com/kubernetes/client-go/issues/198 + return nil, string(val) + } + return errors.New(fmt.Errorf("No key '%s' found in %s/%s", key, secret, namespace)), "" +} + func (r *PatternReconciler) finalizeObject(instance *api.Pattern) error { // Add finalizer when object is created From 81ac5ab198e75ef108ffcfe4c5eb153f755b34f1 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Sun, 9 Apr 2023 07:18:43 +0000 Subject: [PATCH 07/13] Move old code to simplify PR diff --- controllers/{git.go => checkout.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename controllers/{git.go => checkout.go} (99%) diff --git a/controllers/git.go b/controllers/checkout.go similarity index 99% rename from controllers/git.go rename to controllers/checkout.go index 26c7cda4d..845ece940 100644 --- a/controllers/git.go +++ b/controllers/checkout.go @@ -180,7 +180,7 @@ func cloneRepo(url string, directory string, token string) error { var options = &git.CloneOptions{ URL: url, Progress: os.Stdout, - Depth: 0, + Depth: 0, // ReferenceName: plumbing.ReferenceName, } From 775a06b0402b7502aca42ace4c4409d1478948d2 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Sun, 9 Apr 2023 08:11:28 +0000 Subject: [PATCH 08/13] Fix subscription check compilation --- controllers/subscription.go | 85 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/controllers/subscription.go b/controllers/subscription.go index c4368cb0e..7d02e9f16 100644 --- a/controllers/subscription.go +++ b/controllers/subscription.go @@ -20,17 +20,24 @@ import ( "context" "fmt" "log" + "os" "reflect" -"gopkg.in/yaml.v2" + "io/ioutil" + "path/filepath" + + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" olmclient "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" api "github.com/hybrid-cloud-patterns/patterns-operator/api/v1alpha1" - operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" ) const ( @@ -148,55 +155,61 @@ func updateSubscription(client olmclient.Interface, target, current *operatorv1a return nil, changed } - -func buildSubscriptionExclusions(p api.Pattern, client olmclient.Interface) []string { - disabled := []string +func buildSubscriptionExclusions(p api.Pattern, scheme *runtime.Scheme, client olmclient.Interface) []string { + disabled := []string{} valueFiles := newApplicationValueFiles(p) for _, f := range valueFiles { valuesFile := filepath.Join(p.Status.Path, f) if _, err := os.Stat(valuesFile); !os.IsNotExist(err) { parsedSubs := parseValueSubs(valuesFile) - for _, key := range parsedSubs.keys() { - // handle arrays too - targetSub := parsedSubs[key] - namespace := "openshift-operators" - if sub["namespace"] != nil { - namespace = sub["namespace"] - } - _, sub := getSubscription(client, targetSub["name"], namespace) - if sub != nil && !ownedBySame(targetSub, sub) { - disabled = append(disabled, fmt.Sprintf("clusterGroup.subscriptions.%s.disabled", key)) - } - } + for key := range parsedSubs { + // handle arrays too + patternSub := parsedSubs[key].(map[string]interface{}) + namespace := "openshift-operators" + if patternSub["namespace"] != nil { + namespace = patternSub["namespace"].(string) + } + + _, liveSub := getSubscription(client, patternSub["name"].(string), namespace) + if liveSub != nil { + targetSub := newSubscription(p) + _ = controllerutil.SetOwnerReference(&p, targetSub, scheme) + if !ownedBySame(targetSub, liveSub) { + disabled = append(disabled, fmt.Sprintf("clusterGroup.subscriptions.%s.disabled", key)) + } + } + } } } - return disabled + return disabled } func parseValueSubs(filename string) map[string]interface{} { - yamlFile, err := ioutil.ReadFile(filename) + yamlFile, err := ioutil.ReadFile(filename) - if err != nil { - panic(err) - } + if err != nil { + panic(err) + } - var jsonconfig map[string]interface{} + var jsonconfig map[string]interface{} - err = yaml.Unmarshal(yamlFile, &jsonconfig) - if err != nil { - panic(err) - } + err = yaml.Unmarshal(yamlFile, &jsonconfig) + if err != nil { + panic(err) + } - //subs := jsonconfig.clusterGroup.subscriptions - cg := jsonconfig["clusterGroup"].(map[string]interface{}) - subs := cg["subscriptions"].(map[string]interface{}) + //subs := jsonconfig.clusterGroup.subscriptions + cg := jsonconfig["clusterGroup"].(map[string]interface{}) + subs := cg["subscriptions"].(map[string]interface{}) - if substr, err = yaml.Marshal(subs); err != nil { - log.Println("Found subs: ", substr) - log.Println("Found sub keys: ", subs.keys()) - } + if substr, err := yaml.Marshal(subs); err != nil { + log.Println("Found subs: ", substr) + for key := range subs { + log.Println("Found sub: ", key) + } + } - return subs -} \ No newline at end of file + return subs +} From 143ac73c0fd4f2329b98821633c253b828f3d5b6 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Mon, 10 Apr 2023 09:41:22 +0000 Subject: [PATCH 09/13] cloneRepo() does nothing if already cloned --- controllers/checkout.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/controllers/checkout.go b/controllers/checkout.go index 845ece940..80bb9b2c6 100644 --- a/controllers/checkout.go +++ b/controllers/checkout.go @@ -21,6 +21,8 @@ import ( "fmt" "os" + "path/filepath" + "github.com/go-git/go-git/v5" // . "github.com/go-git/go-git/v5/_examples" "github.com/go-git/go-git/v5/plumbing" @@ -174,6 +176,11 @@ func checkoutRevision(directory, token, commit string) error { func cloneRepo(url string, directory string, token string) error { + gitDir := filepath.Join(directory, ".git") + if _, err := os.Stat(gitDir); err == nil { + fmt.Printf("%s already exists\n", gitDir) + return nil + } fmt.Printf("git clone %s into %s\n", url, directory) // Clone the given repository to the given directory From 5ffba8e74c7a0ebbd06e1bbdbd56298c310bcbd3 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Mon, 10 Apr 2023 09:41:44 +0000 Subject: [PATCH 10/13] Updated CR schema --- .../gitops.hybrid-cloud-patterns.io_patterns.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml b/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml index 01759ef4f..bd2dc4922 100644 --- a/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml +++ b/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml @@ -121,6 +121,13 @@ spec: description: 'Branch, tag, or commit to deploy. Does not support short-sha''s. Default: HEAD' type: string + tokenSecret: + description: Account string `json:"account,omitempty"` + type: string + tokenSecretKey: + type: string + tokenSecretNamespace: + type: string required: - targetRepo type: object @@ -177,6 +184,10 @@ spec: lastStep: description: Last action related to the pattern type: string + path: + type: string + revision: + type: string version: description: Number of updates to the pattern type: integer From 13391b5c1a8913df3119f1dfde159740252f96b7 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Mon, 10 Apr 2023 09:43:12 +0000 Subject: [PATCH 11/13] Subscription tests --- controllers/subscription_test.go | 125 +++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 controllers/subscription_test.go diff --git a/controllers/subscription_test.go b/controllers/subscription_test.go new file mode 100644 index 000000000..fa548d75c --- /dev/null +++ b/controllers/subscription_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + + // "github.com/go-logr/logr" + api "github.com/hybrid-cloud-patterns/patterns-operator/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("pattern controller", func() { + + var _ = Context("reconciliation", func() { + var ( + p *api.Pattern + qp *api.Pattern + reconciler *PatternReconciler + ) + BeforeEach(func() { + name := "subsTest" + nsOperators := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + reconciler = newFakeReconciler(nsOperators, buildNamedPatternManifest(name)) + + By("obtaining the pattern object") + p = &api.Pattern{} + Expect(reconciler.Client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, p)).To(Succeed()) + + By("obtaining applying defaults") + var err error + err, qp = reconciler.applyDefaults(p) + Expect(err).NotTo(HaveOccurred()) + + By(fmt.Sprintf("cloning %s", qp.Spec.GitConfig.TargetRepo)) + Expect(cloneRepo(qp.Spec.GitConfig.TargetRepo, qp.Status.Path, "")).To(Succeed()) + + By(fmt.Sprintf("checking out %s", qp.Status.Path)) + Expect(checkoutRevision(qp.Status.Path, "", qp.Spec.GitConfig.TargetRevision)).To(Succeed()) + + }) + It("pattern into empty cluster", func() { + + By("building the exclusion list") + propSet := buildSubscriptionExclusions(*qp, reconciler.Scheme, reconciler.olmClient) + + fmt.Println(propSet) + By(fmt.Sprintf("checking the exclusion list %s", propSet)) + Expect(propSet).To(HaveLen(0)) + }) + It("pattern into cluster with foreign ACM", func() { + By("creating an ACM subscription") + sub := namedSubscription("advanced-cluster-management", + "open-cluster-management", + "release-2.6", + "dummy-dummy", + "advanced-cluster-management.v2.6.1", + false, + false) + Expect(createSubscription(reconciler.olmClient, sub)).To(Succeed()) + + By("building the exclusion list") + propSet := buildSubscriptionExclusions(*qp, reconciler.Scheme, reconciler.olmClient) + fmt.Println(propSet) + + By(fmt.Sprintf("checking the exclusion list %s", propSet)) + Expect(propSet).To(HaveLen(1)) + }) + It("pattern into cluster with self-owned ACM", func() { + By("creating an owned ACM subscription") + sub := namedSubscription("advanced-cluster-management", + "open-cluster-management", + "release-2.6", + fmt.Sprintf("%s-%s", qp.Name, qp.Spec.ClusterGroupName), + "advanced-cluster-management.v2.6.1", + false, + false) + //Expect(controllerutil.SetOwnerReference(qp, sub, reconciler.Scheme)).To(Succeed()) + Expect(createSubscription(reconciler.olmClient, sub)).To(Succeed()) + + By("building the exclusion list") + propSet := buildSubscriptionExclusions(*qp, reconciler.Scheme, reconciler.olmClient) + fmt.Println(propSet) + + By(fmt.Sprintf("checking the exclusion list %s", propSet)) + Expect(propSet).To(HaveLen(0)) + }) + }) +}) + +func buildNamedPatternManifest(name string) *api.Pattern { + return &api.Pattern{ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Finalizers: []string{api.PatternFinalizer}, + }, + Spec: api.PatternSpec{ + ClusterGroupName: "hub", + GitConfig: api.GitConfig{ + TargetRevision: "main", + TargetRepo: "http://github.com/hybrid-cloud-patterns/multicloud-gitops", + }, + }, + } +} From a953a561ad86d0f8eab3f2c7f9c1eb861379f839 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Mon, 10 Apr 2023 09:47:46 +0000 Subject: [PATCH 12/13] Use labels to 'own' subscriptions to avoid cross namespace ownerRef limitations --- controllers/subscription.go | 77 ++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/controllers/subscription.go b/controllers/subscription.go index 7d02e9f16..170268cd1 100644 --- a/controllers/subscription.go +++ b/controllers/subscription.go @@ -30,10 +30,9 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + // "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" olmclient "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" @@ -46,6 +45,16 @@ const ( ) func newSubscription(p api.Pattern) *operatorv1alpha1.Subscription { + return namedSubscription("openshift-gitops-operator", + subscriptionNamespace, + p.Spec.GitOpsConfig.OperatorChannel, + fmt.Sprintf("%s-%s", p.Name, p.Spec.ClusterGroupName), // eg. multicloud-gitops-hub + p.Spec.GitOpsConfig.OperatorCSV, + p.Spec.GitOpsConfig.UseCSV, + p.Spec.GitOpsConfig.ManualApproval) +} + +func namedSubscription(name, namespace, channel, owner, operatorCSV string, useCSV, manualApproval bool) *operatorv1alpha1.Subscription { // apiVersion: operators.coreos.com/v1alpha1 // kind: Subscription // metadata: @@ -66,7 +75,7 @@ func newSubscription(p api.Pattern) *operatorv1alpha1.Subscription { spec := &operatorv1alpha1.SubscriptionSpec{ CatalogSource: "redhat-operators", CatalogSourceNamespace: "openshift-marketplace", - Channel: p.Spec.GitOpsConfig.OperatorChannel, + Channel: channel, Package: "openshift-gitops-operator", InstallPlanApproval: operatorv1alpha1.ApprovalAutomatic, Config: &operatorv1alpha1.SubscriptionConfig{ @@ -79,18 +88,20 @@ func newSubscription(p api.Pattern) *operatorv1alpha1.Subscription { }, } - if p.Spec.GitOpsConfig.UseCSV { - spec.StartingCSV = p.Spec.GitOpsConfig.OperatorCSV + if useCSV { + spec.StartingCSV = operatorCSV } - if p.Spec.GitOpsConfig.ManualApproval { + if manualApproval { spec.InstallPlanApproval = operatorv1alpha1.ApprovalManual } + labels := map[string]string{"app.kubernetes.io/instance": owner} return &operatorv1alpha1.Subscription{ ObjectMeta: metav1.ObjectMeta{ - Name: "openshift-gitops-operator", - Namespace: subscriptionNamespace, + Name: name, + Namespace: namespace, + Labels: labels, }, Spec: spec, } @@ -98,7 +109,7 @@ func newSubscription(p api.Pattern) *operatorv1alpha1.Subscription { func getSubscription(client olmclient.Interface, name, namespace string) (error, *operatorv1alpha1.Subscription) { - sub, err := client.OperatorsV1alpha1().Subscriptions(subscriptionNamespace).Get(context.Background(), name, metav1.GetOptions{}) + sub, err := client.OperatorsV1alpha1().Subscriptions(namespace).Get(context.Background(), name, metav1.GetOptions{}) if err != nil { return err, nil } @@ -106,7 +117,7 @@ func getSubscription(client olmclient.Interface, name, namespace string) (error, } func createSubscription(client olmclient.Interface, sub *operatorv1alpha1.Subscription) error { - _, err := client.OperatorsV1alpha1().Subscriptions(subscriptionNamespace).Create(context.Background(), sub, metav1.CreateOptions{}) + _, err := client.OperatorsV1alpha1().Subscriptions(sub.Namespace).Create(context.Background(), sub, metav1.CreateOptions{}) return err } @@ -158,6 +169,10 @@ func updateSubscription(client olmclient.Interface, target, current *operatorv1a func buildSubscriptionExclusions(p api.Pattern, scheme *runtime.Scheme, client olmclient.Interface) []string { disabled := []string{} valueFiles := newApplicationValueFiles(p) + + selfOwner := fmt.Sprintf("%s-%s", p.Name, p.Spec.ClusterGroupName) + ownerKey := "app.kubernetes.io/instance" + for _, f := range valueFiles { valuesFile := filepath.Join(p.Status.Path, f) if _, err := os.Stat(valuesFile); !os.IsNotExist(err) { @@ -165,19 +180,21 @@ func buildSubscriptionExclusions(p api.Pattern, scheme *runtime.Scheme, client o parsedSubs := parseValueSubs(valuesFile) for key := range parsedSubs { // handle arrays too - patternSub := parsedSubs[key].(map[string]interface{}) + patternSub := parsedSubs[key].(map[interface{}]interface{}) namespace := "openshift-operators" if patternSub["namespace"] != nil { namespace = patternSub["namespace"].(string) } _, liveSub := getSubscription(client, patternSub["name"].(string), namespace) - if liveSub != nil { - targetSub := newSubscription(p) - _ = controllerutil.SetOwnerReference(&p, targetSub, scheme) - if !ownedBySame(targetSub, liveSub) { - disabled = append(disabled, fmt.Sprintf("clusterGroup.subscriptions.%s.disabled", key)) - } + if liveSub == nil { + continue + } + // labels: + // app.kubernetes.io/instance: multicloud-gitops-hub + val, ok := liveSub.Labels[ownerKey] + if !ok || val != selfOwner { + disabled = append(disabled, fmt.Sprintf("clusterGroup.subscriptions.%s.disabled", key)) } } } @@ -185,8 +202,9 @@ func buildSubscriptionExclusions(p api.Pattern, scheme *runtime.Scheme, client o return disabled } -func parseValueSubs(filename string) map[string]interface{} { +func parseValueSubs(filename string) map[interface{}]interface{} { + //log.Println("Reading", filename) yamlFile, err := ioutil.ReadFile(filename) if err != nil { @@ -200,16 +218,23 @@ func parseValueSubs(filename string) map[string]interface{} { panic(err) } - //subs := jsonconfig.clusterGroup.subscriptions - cg := jsonconfig["clusterGroup"].(map[string]interface{}) - subs := cg["subscriptions"].(map[string]interface{}) + if jsonconfig["clusterGroup"] == nil { + return make(map[interface{}]interface{}, 0) + } + // log.Println("Found CG", reflect.TypeOf(jsonconfig["clusterGroup"]), jsonconfig["clusterGroup"]) - if substr, err := yaml.Marshal(subs); err != nil { - log.Println("Found subs: ", substr) - for key := range subs { - log.Println("Found sub: ", key) - } + cg := jsonconfig["clusterGroup"].(map[interface{}]interface{}) + if cg["subscriptions"] == nil { + return make(map[interface{}]interface{}, 0) } + subs := cg["subscriptions"].(map[interface{}]interface{}) + //if substr, err := yaml.Marshal(subs); err != nil { + // log.Println("Found subs: ", substr) + // for key := range subs { + // log.Println("Found sub: ", key) + // } + //} + return subs } From 501d6c0acc2ab5999fe466dedb945089860297d2 Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Tue, 11 Apr 2023 03:43:50 +0000 Subject: [PATCH 13/13] Hook up dynamic filtering of required subscriptions as optional --- api/v1alpha1/pattern_types.go | 3 ++ ...ops.hybrid-cloud-patterns.io_patterns.yaml | 4 ++ controllers/argo.go | 21 ++++++-- controllers/pattern_controller.go | 30 ++++++----- controllers/subscription.go | 6 +-- controllers/subscription_test.go | 52 ++++++++++++++----- 6 files changed, 80 insertions(+), 36 deletions(-) diff --git a/api/v1alpha1/pattern_types.go b/api/v1alpha1/pattern_types.go index 9821922a8..3d3a71af9 100644 --- a/api/v1alpha1/pattern_types.go +++ b/api/v1alpha1/pattern_types.go @@ -71,6 +71,9 @@ type PatternSpec struct { // Look for external changes every N minutes // ReconcileMinutes int `json:"reconcileMinutes,omitempty"` + + // Disable pre-existing subscriptions + DynamicSubscriptions bool `json:"dynamicSubscriptions"` } type GitConfig struct { diff --git a/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml b/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml index bd2dc4922..de0f3978a 100644 --- a/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml +++ b/config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml @@ -49,6 +49,9 @@ spec: properties: clusterGroupName: type: string + dynamicSubscriptions: + description: Disable pre-existing subscriptions + type: boolean extraParameters: description: '.Name is dot separated per the helm --set syntax, such as: global.something.field' @@ -133,6 +136,7 @@ spec: type: object required: - clusterGroupName + - dynamicSubscriptions - gitSpec type: object status: diff --git a/controllers/argo.go b/controllers/argo.go index 9347909a5..f7a7cec7e 100644 --- a/controllers/argo.go +++ b/controllers/argo.go @@ -26,11 +26,12 @@ import ( argoapi "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoclient "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" + olmclient "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" api "github.com/hybrid-cloud-patterns/patterns-operator/api/v1alpha1" ) -func newApplicationParameters(p api.Pattern) []argoapi.HelmParameter { +func newApplicationParameters(p api.Pattern, valueFiles []string, client olmclient.Interface) []argoapi.HelmParameter { parameters := []argoapi.HelmParameter{ { @@ -76,6 +77,17 @@ func newApplicationParameters(p api.Pattern) []argoapi.HelmParameter { }, } + if len(valueFiles) > 0 && client != nil && p.Spec.DynamicSubscriptions { + log.Println("Spec: ", p.Spec) + excludeList := buildLiveSubscriptionExclusions(p, valueFiles, client) + for _, excludeParam := range excludeList { + parameters = append(parameters, argoapi.HelmParameter{ + Name: excludeParam, + Value: "1", + }) + } + } + for _, extra := range p.Spec.ExtraParameters { if !updateHelmParameter(extra, parameters) { log.Printf("Parameter %q = %q added", extra.Name, extra.Value) @@ -114,12 +126,13 @@ func newApplicationValueFiles(p api.Pattern) []string { return files } -func newApplication(p api.Pattern) *argoapi.Application { +func newApplication(p api.Pattern, client olmclient.Interface) *argoapi.Application { // Argo uses... // r := regexp.MustCompile("(/|:)") // root := filepath.Join(os.TempDir(), r.ReplaceAllString(NormalizeGitURL(rawRepoURL), "_")) + valueFiles := newApplicationValueFiles(p) spec := argoapi.ApplicationSpec{ // Source is a reference to the location of the application's manifests or chart @@ -128,10 +141,10 @@ func newApplication(p api.Pattern) *argoapi.Application { Path: "common/clustergroup", TargetRevision: p.Spec.GitConfig.TargetRevision, Helm: &argoapi.ApplicationSourceHelm{ - ValueFiles: newApplicationValueFiles(p), + ValueFiles: valueFiles, // Parameters is a list of Helm parameters which are passed to the helm template command upon manifest generation - Parameters: newApplicationParameters(p), + Parameters: newApplicationParameters(p, valueFiles, client), // ReleaseName is the Helm release name to use. If omitted it will use the application name // ReleaseName string `json:"releaseName,omitempty" protobuf:"bytes,3,opt,name=releaseName"` diff --git a/controllers/pattern_controller.go b/controllers/pattern_controller.go index 0382a201c..c05dd93a0 100644 --- a/controllers/pattern_controller.go +++ b/controllers/pattern_controller.go @@ -154,21 +154,23 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return r.actionPerformed(qualifiedInstance, "applying defaults", err) } - var token = "" - if len(qualifiedInstance.Spec.GitConfig.TokenSecret) > 0 { - if err, token = r.authTokenFromSecret(qualifiedInstance.Spec.GitConfig.TokenSecretNamespace, qualifiedInstance.Spec.GitConfig.TokenSecret, qualifiedInstance.Spec.GitConfig.TokenSecretKey); err != nil { - return r.actionPerformed(qualifiedInstance, "obtaining git auth token", err) + if qualifiedInstance.Spec.DynamicSubscriptions { + var token = "" + if len(qualifiedInstance.Spec.GitConfig.TokenSecret) > 0 { + if err, token = r.authTokenFromSecret(qualifiedInstance.Spec.GitConfig.TokenSecretNamespace, qualifiedInstance.Spec.GitConfig.TokenSecret, qualifiedInstance.Spec.GitConfig.TokenSecretKey); err != nil { + return r.actionPerformed(qualifiedInstance, "obtaining git auth token", err) + } } - } - gitDir := filepath.Join(qualifiedInstance.Status.Path, ".git") - if _, err := os.Stat(gitDir); os.IsNotExist(err) { - err := cloneRepo(qualifiedInstance.Spec.GitConfig.TargetRepo, qualifiedInstance.Status.Path, token) - return r.actionPerformed(qualifiedInstance, "cloning pattern repo", err) - } + gitDir := filepath.Join(qualifiedInstance.Status.Path, ".git") + if _, err := os.Stat(gitDir); os.IsNotExist(err) { + err := cloneRepo(qualifiedInstance.Spec.GitConfig.TargetRepo, qualifiedInstance.Status.Path, token) + return r.actionPerformed(qualifiedInstance, "cloning pattern repo", err) + } - if err := checkoutRevision(qualifiedInstance.Status.Path, token, qualifiedInstance.Spec.GitConfig.TargetRevision); err != nil { - return r.actionPerformed(qualifiedInstance, "checkout target revision", err) + if err := checkoutRevision(qualifiedInstance.Status.Path, token, qualifiedInstance.Spec.GitConfig.TargetRevision); err != nil { + return r.actionPerformed(qualifiedInstance, "checkout target revision", err) + } } if err := r.preValidation(qualifiedInstance); err != nil { @@ -230,7 +232,7 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct logOnce("namespace found") // -- ArgoCD Application - targetApp := newApplication(*qualifiedInstance) + targetApp := newApplication(*qualifiedInstance, r.olmClient) _ = controllerutil.SetOwnerReference(qualifiedInstance, targetApp, r.Scheme) //log.Printf("Targeting: %s\n", objectYaml(targetApp)) @@ -443,7 +445,7 @@ func (r *PatternReconciler) finalizeObject(instance *api.Pattern) error { return nil } - targetApp := newApplication(*qualifiedInstance) + targetApp := newApplication(*qualifiedInstance, nil) _ = controllerutil.SetOwnerReference(qualifiedInstance, targetApp, r.Scheme) _, app := getApplication(r.argoClient, applicationName(*qualifiedInstance)) diff --git a/controllers/subscription.go b/controllers/subscription.go index 170268cd1..a2b99f369 100644 --- a/controllers/subscription.go +++ b/controllers/subscription.go @@ -30,8 +30,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - // "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" olmclient "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" @@ -166,9 +164,8 @@ func updateSubscription(client olmclient.Interface, target, current *operatorv1a return nil, changed } -func buildSubscriptionExclusions(p api.Pattern, scheme *runtime.Scheme, client olmclient.Interface) []string { +func buildLiveSubscriptionExclusions(p api.Pattern, valueFiles []string, client olmclient.Interface) []string { disabled := []string{} - valueFiles := newApplicationValueFiles(p) selfOwner := fmt.Sprintf("%s-%s", p.Name, p.Spec.ClusterGroupName) ownerKey := "app.kubernetes.io/instance" @@ -190,6 +187,7 @@ func buildSubscriptionExclusions(p api.Pattern, scheme *runtime.Scheme, client o if liveSub == nil { continue } + log.Println("Checking ownership", liveSub) // labels: // app.kubernetes.io/instance: multicloud-gitops-hub val, ok := liveSub.Labels[ownerKey] diff --git a/controllers/subscription_test.go b/controllers/subscription_test.go index fa548d75c..438da6511 100644 --- a/controllers/subscription_test.go +++ b/controllers/subscription_test.go @@ -60,13 +60,13 @@ var _ = Describe("pattern controller", func() { }) It("pattern into empty cluster", func() { - By("building the exclusion list") - propSet := buildSubscriptionExclusions(*qp, reconciler.Scheme, reconciler.olmClient) + valueFiles := newApplicationValueFiles(*qp) + paramList := newApplicationParameters(*qp, valueFiles, reconciler.olmClient) + fmt.Println(paramList) - fmt.Println(propSet) - By(fmt.Sprintf("checking the exclusion list %s", propSet)) - Expect(propSet).To(HaveLen(0)) + By("checking the parameter list") + Expect(paramList).To(HaveLen(10)) }) It("pattern into cluster with foreign ACM", func() { By("creating an ACM subscription") @@ -80,11 +80,33 @@ var _ = Describe("pattern controller", func() { Expect(createSubscription(reconciler.olmClient, sub)).To(Succeed()) By("building the exclusion list") - propSet := buildSubscriptionExclusions(*qp, reconciler.Scheme, reconciler.olmClient) - fmt.Println(propSet) + valueFiles := newApplicationValueFiles(*qp) + paramList := newApplicationParameters(*qp, valueFiles, reconciler.olmClient) + fmt.Println(paramList) + + By("checking the parameter list") + Expect(paramList).To(HaveLen(11)) + Expect(paramList[10].Name).To(Equal("clusterGroup.subscriptions.acm.disabled")) + }) + It("pattern into cluster with DynamicSubscriptions disabled", func() { + By("creating an ACM subscription") + sub := namedSubscription("advanced-cluster-management", + "open-cluster-management", + "release-2.6", + "dummy-dummy", + "advanced-cluster-management.v2.6.1", + false, + false) + Expect(createSubscription(reconciler.olmClient, sub)).To(Succeed()) + + By("building the exclusion list") + qp.Spec.DynamicSubscriptions = false + valueFiles := newApplicationValueFiles(*qp) + paramList := newApplicationParameters(*qp, valueFiles, reconciler.olmClient) + fmt.Println(paramList) - By(fmt.Sprintf("checking the exclusion list %s", propSet)) - Expect(propSet).To(HaveLen(1)) + By("checking the parameter list") + Expect(paramList).To(HaveLen(10)) }) It("pattern into cluster with self-owned ACM", func() { By("creating an owned ACM subscription") @@ -99,11 +121,12 @@ var _ = Describe("pattern controller", func() { Expect(createSubscription(reconciler.olmClient, sub)).To(Succeed()) By("building the exclusion list") - propSet := buildSubscriptionExclusions(*qp, reconciler.Scheme, reconciler.olmClient) - fmt.Println(propSet) + valueFiles := newApplicationValueFiles(*qp) + paramList := newApplicationParameters(*qp, valueFiles, reconciler.olmClient) + fmt.Println(paramList) - By(fmt.Sprintf("checking the exclusion list %s", propSet)) - Expect(propSet).To(HaveLen(0)) + By("checking the parameter list") + Expect(paramList).To(HaveLen(10)) }) }) }) @@ -115,7 +138,8 @@ func buildNamedPatternManifest(name string) *api.Pattern { Finalizers: []string{api.PatternFinalizer}, }, Spec: api.PatternSpec{ - ClusterGroupName: "hub", + ClusterGroupName: "hub", + DynamicSubscriptions: true, GitConfig: api.GitConfig{ TargetRevision: "main", TargetRepo: "http://github.com/hybrid-cloud-patterns/multicloud-gitops",