From 4d2fa446447ba6cd38d567ffd12518727f60e656 Mon Sep 17 00:00:00 2001 From: Sam Dowell Date: Tue, 19 Sep 2023 16:37:30 -0700 Subject: [PATCH] feat: add namespaceStrategy API (#883) This change adds a new namespaceStrategy API field which dictates how RootSync reconcilers handle undeclared namespaces. The default value is implicit, which is the same as the current behavior. This mode creates namespaces implicitly if they are not declared and do not exist. The new strategy is explicit, in which namespaces will only be created by the reconciler if they are declared in the source. --- clientgen/apis/clientset.go | 22 +- clientgen/apis/fake/clientset_generated.go | 10 +- clientgen/apis/fake/register.go | 2 +- clientgen/apis/scheme/register.go | 2 +- cmd/reconciler/main.go | 48 ++-- e2e/nomostest/testpredicates/predicates.go | 4 +- e2e/testcases/namespace_strategy_test.go | 257 ++++++++++++++++++ e2e/testcases/override_git_sync_depth_test.go | 4 +- .../override_resource_limits_test.go | 128 +++++---- e2e/testcases/reconciler_manager_test.go | 10 +- manifests/rootsync-crd.yaml | 24 ++ pkg/api/configsync/register.go | 13 + pkg/api/configsync/v1alpha1/getters.go | 12 +- pkg/api/configsync/v1alpha1/reposync_types.go | 2 +- .../configsync/v1alpha1/resource_override.go | 23 ++ pkg/api/configsync/v1alpha1/rootsync_types.go | 2 +- .../v1alpha1/zz_generated.conversion.go | 88 +++++- .../v1alpha1/zz_generated.deepcopy.go | 56 +++- pkg/api/configsync/v1beta1/getters.go | 12 +- pkg/api/configsync/v1beta1/reposync_types.go | 2 +- .../configsync/v1beta1/resource_override.go | 23 ++ pkg/api/configsync/v1beta1/rootsync_types.go | 2 +- .../v1beta1/zz_generated.deepcopy.go | 36 ++- pkg/parse/root.go | 13 +- pkg/parse/root_test.go | 76 ++++-- pkg/reconciler/reconciler.go | 6 +- pkg/reconcilermanager/constants.go | 4 + .../controllers/reposync_controller.go | 2 +- .../controllers/reposync_controller_test.go | 58 ++-- .../controllers/rootsync_controller.go | 15 +- .../controllers/rootsync_controller_test.go | 57 ++-- pkg/reconcilermanager/controllers/util.go | 11 + 32 files changed, 824 insertions(+), 200 deletions(-) create mode 100644 e2e/testcases/namespace_strategy_test.go diff --git a/clientgen/apis/clientset.go b/clientgen/apis/clientset.go index 039fd2acb1..fba3f9ffc9 100644 --- a/clientgen/apis/clientset.go +++ b/clientgen/apis/clientset.go @@ -17,8 +17,8 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface ConfigmanagementV1() configmanagementv1.ConfigmanagementV1Interface - ConfigsyncV1beta1() configsyncv1beta1.ConfigsyncV1beta1Interface ConfigsyncV1alpha1() configsyncv1alpha1.ConfigsyncV1alpha1Interface + ConfigsyncV1beta1() configsyncv1beta1.ConfigsyncV1beta1Interface HubV1() hubv1.HubV1Interface } @@ -27,8 +27,8 @@ type Interface interface { type Clientset struct { *discovery.DiscoveryClient configmanagementV1 *configmanagementv1.ConfigmanagementV1Client - configsyncV1beta1 *configsyncv1beta1.ConfigsyncV1beta1Client configsyncV1alpha1 *configsyncv1alpha1.ConfigsyncV1alpha1Client + configsyncV1beta1 *configsyncv1beta1.ConfigsyncV1beta1Client hubV1 *hubv1.HubV1Client } @@ -37,16 +37,16 @@ func (c *Clientset) ConfigmanagementV1() configmanagementv1.ConfigmanagementV1In return c.configmanagementV1 } -// ConfigsyncV1beta1 retrieves the ConfigsyncV1beta1Client -func (c *Clientset) ConfigsyncV1beta1() configsyncv1beta1.ConfigsyncV1beta1Interface { - return c.configsyncV1beta1 -} - // ConfigsyncV1alpha1 retrieves the ConfigsyncV1alpha1Client func (c *Clientset) ConfigsyncV1alpha1() configsyncv1alpha1.ConfigsyncV1alpha1Interface { return c.configsyncV1alpha1 } +// ConfigsyncV1beta1 retrieves the ConfigsyncV1beta1Client +func (c *Clientset) ConfigsyncV1beta1() configsyncv1beta1.ConfigsyncV1beta1Interface { + return c.configsyncV1beta1 +} + // HubV1 retrieves the HubV1Client func (c *Clientset) HubV1() hubv1.HubV1Interface { return c.hubV1 @@ -77,11 +77,11 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } - cs.configsyncV1beta1, err = configsyncv1beta1.NewForConfig(&configShallowCopy) + cs.configsyncV1alpha1, err = configsyncv1alpha1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } - cs.configsyncV1alpha1, err = configsyncv1alpha1.NewForConfig(&configShallowCopy) + cs.configsyncV1beta1, err = configsyncv1beta1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } @@ -102,8 +102,8 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset cs.configmanagementV1 = configmanagementv1.NewForConfigOrDie(c) - cs.configsyncV1beta1 = configsyncv1beta1.NewForConfigOrDie(c) cs.configsyncV1alpha1 = configsyncv1alpha1.NewForConfigOrDie(c) + cs.configsyncV1beta1 = configsyncv1beta1.NewForConfigOrDie(c) cs.hubV1 = hubv1.NewForConfigOrDie(c) cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) @@ -114,8 +114,8 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.configmanagementV1 = configmanagementv1.New(c) - cs.configsyncV1beta1 = configsyncv1beta1.New(c) cs.configsyncV1alpha1 = configsyncv1alpha1.New(c) + cs.configsyncV1beta1 = configsyncv1beta1.New(c) cs.hubV1 = hubv1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) diff --git a/clientgen/apis/fake/clientset_generated.go b/clientgen/apis/fake/clientset_generated.go index b1ba0513f2..1a7d27b651 100644 --- a/clientgen/apis/fake/clientset_generated.go +++ b/clientgen/apis/fake/clientset_generated.go @@ -74,16 +74,16 @@ func (c *Clientset) ConfigmanagementV1() configmanagementv1.ConfigmanagementV1In return &fakeconfigmanagementv1.FakeConfigmanagementV1{Fake: &c.Fake} } -// ConfigsyncV1beta1 retrieves the ConfigsyncV1beta1Client -func (c *Clientset) ConfigsyncV1beta1() configsyncv1beta1.ConfigsyncV1beta1Interface { - return &fakeconfigsyncv1beta1.FakeConfigsyncV1beta1{Fake: &c.Fake} -} - // ConfigsyncV1alpha1 retrieves the ConfigsyncV1alpha1Client func (c *Clientset) ConfigsyncV1alpha1() configsyncv1alpha1.ConfigsyncV1alpha1Interface { return &fakeconfigsyncv1alpha1.FakeConfigsyncV1alpha1{Fake: &c.Fake} } +// ConfigsyncV1beta1 retrieves the ConfigsyncV1beta1Client +func (c *Clientset) ConfigsyncV1beta1() configsyncv1beta1.ConfigsyncV1beta1Interface { + return &fakeconfigsyncv1beta1.FakeConfigsyncV1beta1{Fake: &c.Fake} +} + // HubV1 retrieves the HubV1Client func (c *Clientset) HubV1() hubv1.HubV1Interface { return &fakehubv1.FakeHubV1{Fake: &c.Fake} diff --git a/clientgen/apis/fake/register.go b/clientgen/apis/fake/register.go index e21d8ebb82..ff8990d705 100644 --- a/clientgen/apis/fake/register.go +++ b/clientgen/apis/fake/register.go @@ -19,8 +19,8 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ configmanagementv1.AddToScheme, - configsyncv1beta1.AddToScheme, configsyncv1alpha1.AddToScheme, + configsyncv1beta1.AddToScheme, hubv1.AddToScheme, } diff --git a/clientgen/apis/scheme/register.go b/clientgen/apis/scheme/register.go index d89c3b9d8e..7d166e6a3a 100644 --- a/clientgen/apis/scheme/register.go +++ b/clientgen/apis/scheme/register.go @@ -19,8 +19,8 @@ var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ configmanagementv1.AddToScheme, - configsyncv1beta1.AddToScheme, configsyncv1alpha1.AddToScheme, + configsyncv1beta1.AddToScheme, hubv1.AddToScheme, } diff --git a/cmd/reconciler/main.go b/cmd/reconciler/main.go index bce9debe88..15986303de 100644 --- a/cmd/reconciler/main.go +++ b/cmd/reconciler/main.go @@ -16,6 +16,7 @@ package main import ( "flag" + "fmt" "os" "strings" @@ -95,25 +96,30 @@ var ( "Enable debug mode, panicking in many scenarios where normally an InternalError would be logged. "+ "Do not use in production.") - renderingEnabled = flag.Bool("rendering-enabled", util.EnvBool(reconcilermanager.RenderingEnabled, false), "") + renderingEnabled = flag.Bool("rendering-enabled", util.EnvBool(reconcilermanager.RenderingEnabled, false), "") + namespaceStrategy = flag.String(flags.namespaceStrategy, util.EnvString(reconcilermanager.NamespaceStrategy, ""), + fmt.Sprintf("Set the namespace strategy for the reconciler. Must be %s or %s.", + configsync.NamespaceStrategyImplicit, configsync.NamespaceStrategyExplicit)) ) var flags = struct { - sourceDir string - repoRootDir string - hydratedRootDir string - clusterName string - sourceFormat string - statusMode string - reconcileTimeout string + sourceDir string + repoRootDir string + hydratedRootDir string + clusterName string + sourceFormat string + statusMode string + reconcileTimeout string + namespaceStrategy string }{ - repoRootDir: "repo-root", - sourceDir: "source-dir", - hydratedRootDir: "hydrated-root", - clusterName: "cluster-name", - sourceFormat: reconcilermanager.SourceFormat, - statusMode: "status-mode", - reconcileTimeout: "reconcile-timeout", + repoRootDir: "repo-root", + sourceDir: "source-dir", + hydratedRootDir: "hydrated-root", + clusterName: "cluster-name", + sourceFormat: reconcilermanager.SourceFormat, + statusMode: "status-mode", + reconcileTimeout: "reconcile-timeout", + namespaceStrategy: "namespace-strategy", } func main() { @@ -195,10 +201,16 @@ func main() { if format == "" { format = filesystem.SourceFormatHierarchy } + // Default to "implicit" if unset. + nsStrat := configsync.NamespaceStrategy(*namespaceStrategy) + if nsStrat == "" { + nsStrat = configsync.NamespaceStrategyImplicit + } klog.Info("Starting reconciler for: root") opts.RootOptions = &reconciler.RootOptions{ - SourceFormat: format, + SourceFormat: format, + NamespaceStrategy: nsStrat, } } else { klog.Infof("Starting reconciler for: %s", *scope) @@ -207,6 +219,10 @@ func main() { klog.Fatalf("Flag %s and Environment variable%q must not be passed to a Namespace reconciler", flags.sourceFormat, filesystem.SourceFormatKey) } + if *namespaceStrategy != "" { + klog.Fatalf("Flag %s and Environment variable%q must not be passed to a Namespace reconciler", + flags.namespaceStrategy, reconcilermanager.NamespaceStrategy) + } } reconciler.Run(opts) } diff --git a/e2e/nomostest/testpredicates/predicates.go b/e2e/nomostest/testpredicates/predicates.go index 6756cf1b5f..e7949ef9f5 100644 --- a/e2e/nomostest/testpredicates/predicates.go +++ b/e2e/nomostest/testpredicates/predicates.go @@ -1153,8 +1153,8 @@ func validateRootSyncCondition(actual *v1beta1.RootSyncCondition, expected *v1be } // RootSyncSpecOverrideEquals checks that the RootSync's spec.override matches -// the specified OverrideSpec. -func RootSyncSpecOverrideEquals(expected *v1beta1.OverrideSpec) Predicate { +// the specified RootSyncOverrideSpec. +func RootSyncSpecOverrideEquals(expected *v1beta1.RootSyncOverrideSpec) Predicate { return func(obj client.Object) error { if obj == nil { return ErrObjectNotFound diff --git a/e2e/testcases/namespace_strategy_test.go b/e2e/testcases/namespace_strategy_test.go new file mode 100644 index 0000000000..5dbf415e49 --- /dev/null +++ b/e2e/testcases/namespace_strategy_test.go @@ -0,0 +1,257 @@ +// Copyright 2023 Google LLC +// +// 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 e2e + +import ( + "fmt" + "testing" + + corev1 "k8s.io/api/core/v1" + "kpt.dev/configsync/e2e/nomostest" + "kpt.dev/configsync/e2e/nomostest/ntopts" + "kpt.dev/configsync/e2e/nomostest/taskgroup" + nomostesting "kpt.dev/configsync/e2e/nomostest/testing" + "kpt.dev/configsync/e2e/nomostest/testpredicates" + "kpt.dev/configsync/pkg/api/configsync" + "kpt.dev/configsync/pkg/applier" + "kpt.dev/configsync/pkg/core" + "kpt.dev/configsync/pkg/declared" + "kpt.dev/configsync/pkg/kinds" + "kpt.dev/configsync/pkg/metadata" + "kpt.dev/configsync/pkg/reconcilermanager" + "kpt.dev/configsync/pkg/testing/fake" + "sigs.k8s.io/cli-utils/pkg/common" +) + +// TestNamespaceStrategy focuses on the behavior of switching between modes +// of namespaceStrategy for a single RootSync. +// - Set namespaceStrategy: explicit +// - Declare resource in "foo-implicit" Namespace but not the Namespace itself (should error) +// - Set namespaceStrategy: implicit - should create the Namespace implicitly +// - Set namespaceStrategy: explicit - Namespace still exists but is unmanaged +// - Declare namespace "foo-implicit" in git. The Namespace should be managed explicitly +// - Prune "foo-implicit" from git. Should error because cm1 depends on the Namespace +// - Prune "cm1" from git. The Namespace and ConfigMap should be successfully pruned. +func TestNamespaceStrategy(t *testing.T) { + nt := nomostest.New(t, nomostesting.OverrideAPI, ntopts.Unstructured) + + rootSyncNN := nomostest.RootSyncNN(configsync.RootSyncName) + rootReconcilerNN := core.RootReconcilerObjectKey(rootSyncNN.Name) + rootSync := fake.RootSyncObjectV1Alpha1(rootSyncNN.Name) + // set the NamespaceStrategy to explicit + nt.MustMergePatch(rootSync, `{"spec": {"override": {"namespaceStrategy": "explicit"}}}`) + err := nt.Watcher.WatchObject( + kinds.Deployment(), rootReconcilerNN.Name, rootReconcilerNN.Namespace, + []testpredicates.Predicate{ + testpredicates.DeploymentHasEnvVar( + reconcilermanager.Reconciler, + reconcilermanager.NamespaceStrategy, + string(configsync.NamespaceStrategyExplicit), + ), + }, + ) + if err != nil { + nt.T.Fatal(err) + } + // add a resource for which the namespace is not declared/created + fooNamespace := fake.NamespaceObject("foo-implicit") + cm1 := fake.ConfigMapObject(core.Name("cm1"), core.Namespace(fooNamespace.Name)) + nt.Must(nt.RootRepos[configsync.RootSyncName].Add("acme/cm1.yaml", cm1)) + nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("Add cm1")) + // check for error + nt.WaitForRootSyncSyncError(rootSyncNN.Name, applier.ApplierErrorCode, + "failed to apply ConfigMap, foo-implicit/cm1: namespaces \"foo-implicit\" not found") + // switch the mode to implicit + nt.MustMergePatch(rootSync, `{"spec": {"override": {"namespaceStrategy": "implicit"}}}`) + // check for success + if err := nt.WatchForAllSyncs(); err != nil { + nt.T.Fatal(err) + } + // assert that implicit namespace was created + err = nt.Validate(fooNamespace.Name, fooNamespace.Namespace, &corev1.Namespace{}, + testpredicates.HasAnnotation(common.LifecycleDeleteAnnotation, common.PreventDeletion), + testpredicates.HasAnnotation(metadata.ResourceManagementKey, metadata.ResourceManagementEnabled), + testpredicates.HasAnnotation(metadata.ResourceManagerKey, string(declared.RootReconciler)), + ) + if err != nil { + nt.T.Fatal(err) + } + // switch mode back to explicit + nt.MustMergePatch(rootSync, `{"spec": {"override": {"namespaceStrategy": "explicit"}}}`) + // assert that namespace is no longer managed + err = nt.Watcher.WatchObject(kinds.Namespace(), fooNamespace.Name, fooNamespace.Namespace, + []testpredicates.Predicate{ + // still has PreventDeletion + testpredicates.HasAnnotation(common.LifecycleDeleteAnnotation, common.PreventDeletion), + // management annotations should be removed + testpredicates.MissingAnnotation(metadata.ResourceManagementKey), + testpredicates.MissingAnnotation(metadata.ResourceManagerKey), + }, + ) + if err != nil { + nt.T.Fatal(err) + } + // explicitly declare the namespace in git + nt.Must(nt.RootRepos[configsync.RootSyncName].Add("acme/namespace-foo.yaml", fooNamespace)) + nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("Explicitly manage fooNamespace")) + if err := nt.WatchForAllSyncs(); err != nil { + nt.T.Fatal(err) + } + // assert that namespace is managed + err = nt.Watcher.WatchObject(kinds.Namespace(), fooNamespace.Name, fooNamespace.Namespace, + []testpredicates.Predicate{ + // Config Sync uses a single field manager, so the PreventDeletion + // annotation is removed. + // Users can still declare the annotation in the explicit namespace. + testpredicates.MissingAnnotation(common.LifecycleDeleteAnnotation), + testpredicates.HasAnnotation(metadata.ResourceManagementKey, metadata.ResourceManagementEnabled), + testpredicates.HasAnnotation(metadata.ResourceManagerKey, string(declared.RootReconciler)), + }, + ) + if err != nil { + nt.T.Fatal(err) + } + // prune the namespace + nt.Must(nt.RootRepos[configsync.RootSyncName].Remove("acme/namespace-foo.yaml")) + nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("Prune namespace-foo")) + // check for error + nt.WaitForRootSyncSyncError(rootSyncNN.Name, applier.ApplierErrorCode, + "skipped delete of Namespace, /foo-implicit: namespace still in use: foo-implicit") + // prune the ConfigMap + nt.Must(nt.RootRepos[configsync.RootSyncName].Remove("acme/cm1.yaml")) + nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("Prune cm1")) + if err := nt.WatchForAllSyncs(); err != nil { + nt.T.Fatal(err) + } + // all resources should be pruned + tg := taskgroup.New() + tg.Go(func() error { + return nt.Watcher.WatchForNotFound(kinds.Namespace(), fooNamespace.Name, fooNamespace.Namespace) + }) + tg.Go(func() error { + return nt.Watcher.WatchForNotFound(kinds.ConfigMap(), cm1.Name, cm1.Namespace) + }) + if err := tg.Wait(); err != nil { + nt.T.Fatal(err) + } +} + +// TestNamespaceStrategyMultipleRootSyncs focuses on the behavior of using +// namespaceStrategy: explicit with multiple RootSyncs. +// When using multiple RootSyncs that declare resources in the same namespace, +// the namespace should only be created if declared explicitly in a sync source. +func TestNamespaceStrategyMultipleRootSyncs(t *testing.T) { + namespaceA := fake.NamespaceObject("namespace-a") + nt := nomostest.New(t, nomostesting.OverrideAPI, ntopts.Unstructured, + ntopts.RootRepo("sync-a"), // will declare namespace-a explicitly + ntopts.RootRepo("sync-x"), // will declare resources in namespace-a, but not namespace-a itself + ntopts.RootRepo("sync-y"), // will declare resources in namespace-a, but not namespace-a itself + ) + rootSyncA := nomostest.RootSyncObjectV1Beta1FromRootRepo(nt, "sync-a") + rootSyncX := nomostest.RootSyncObjectV1Beta1FromRootRepo(nt, "sync-x") + rootSyncY := nomostest.RootSyncObjectV1Beta1FromRootRepo(nt, "sync-y") + rootSyncA.Spec.SafeOverride().NamespaceStrategy = configsync.NamespaceStrategyExplicit + rootSyncX.Spec.SafeOverride().NamespaceStrategy = configsync.NamespaceStrategyExplicit + rootSyncY.Spec.SafeOverride().NamespaceStrategy = configsync.NamespaceStrategyExplicit + + // set the NamespaceStrategy to explicit + nt.Must(nt.RootRepos[configsync.RootSyncName].Add( + fmt.Sprintf("acme/namespaces/%s/%s.yaml", configsync.ControllerNamespace, rootSyncA.Name), + rootSyncA, + )) + nt.Must(nt.RootRepos[configsync.RootSyncName].Add( + fmt.Sprintf("acme/namespaces/%s/%s.yaml", configsync.ControllerNamespace, rootSyncX.Name), + rootSyncX, + )) + nt.Must(nt.RootRepos[configsync.RootSyncName].Add( + fmt.Sprintf("acme/namespaces/%s/%s.yaml", configsync.ControllerNamespace, rootSyncY.Name), + rootSyncY, + )) + nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush( + fmt.Sprintf("Adding RootSyncs (%s, %s, %s) with namespaceStrategy=explicit", + rootSyncA.Name, rootSyncX.Name, rootSyncY.Name), + )) + if err := nt.WatchForAllSyncs(); err != nil { + nt.T.Fatal(err) + } + // Assert that all reconcilers have NAMESPACE_STRATEGY=explicit set + tg := taskgroup.New() + for _, rsName := range []string{rootSyncA.Name, rootSyncX.Name, rootSyncY.Name} { + reconcilerNN := core.RootReconcilerObjectKey(rsName) + tg.Go(func() error { + return nt.Watcher.WatchObject( + kinds.Deployment(), reconcilerNN.Name, reconcilerNN.Namespace, + []testpredicates.Predicate{ + testpredicates.DeploymentHasEnvVar( + reconcilermanager.Reconciler, + reconcilermanager.NamespaceStrategy, + string(configsync.NamespaceStrategyExplicit), + ), + }, + ) + }) + } + if err := tg.Wait(); err != nil { + nt.T.Fatal(err) + } + // add a resource for which the namespace is not declared/created + cmX := fake.ConfigMapObject(core.Name("cm-x"), core.Namespace(namespaceA.Name)) + nt.Must(nt.RootRepos[rootSyncX.Name].Add("acme/cm-x.yaml", cmX)) + nt.Must(nt.RootRepos[rootSyncX.Name].CommitAndPush("Add cm-x")) + cmY := fake.ConfigMapObject(core.Name("cm-y"), core.Namespace(namespaceA.Name)) + nt.Must(nt.RootRepos[rootSyncY.Name].Add("acme/cm-y.yaml", cmY)) + nt.Must(nt.RootRepos[rootSyncY.Name].CommitAndPush("Add cm-y")) + // check for error + nt.WaitForRootSyncSyncError(rootSyncX.Name, applier.ApplierErrorCode, + "failed to apply ConfigMap, namespace-a/cm-x: namespaces \"namespace-a\" not found") + nt.WaitForRootSyncSyncError(rootSyncY.Name, applier.ApplierErrorCode, + "failed to apply ConfigMap, namespace-a/cm-y: namespaces \"namespace-a\" not found") + // declare the namespace in sync-a + nt.Must(nt.RootRepos[rootSyncA.Name].Add("acme/namespace-a.yaml", namespaceA)) + nt.Must(nt.RootRepos[rootSyncA.Name].CommitAndPush("Add namespace-a")) + // check for success + if err := nt.WatchForAllSyncs(); err != nil { + nt.T.Fatal(err) + } + // assert that all resources were created + tg = taskgroup.New() + tg.Go(func() error { + return nt.Watcher.WatchObject(kinds.Namespace(), namespaceA.Name, namespaceA.Namespace, + []testpredicates.Predicate{ + // Users can add PreventDeletion annotation to the declared namespace + // if they choose, but the reconciler does not add it by default. + testpredicates.MissingAnnotation(common.LifecycleDeleteAnnotation), + testpredicates.HasAnnotation(metadata.ResourceManagementKey, metadata.ResourceManagementEnabled), + testpredicates.HasAnnotation(metadata.ResourceManagerKey, declared.ResourceManager(declared.RootReconciler, rootSyncA.Name)), + }) + }) + tg.Go(func() error { + return nt.Watcher.WatchObject(kinds.ConfigMap(), cmX.Name, cmX.Namespace, + []testpredicates.Predicate{ + testpredicates.HasAnnotation(metadata.ResourceManagementKey, metadata.ResourceManagementEnabled), + testpredicates.HasAnnotation(metadata.ResourceManagerKey, declared.ResourceManager(declared.RootReconciler, rootSyncX.Name)), + }) + }) + tg.Go(func() error { + return nt.Watcher.WatchObject(kinds.ConfigMap(), cmY.Name, cmY.Namespace, + []testpredicates.Predicate{ + testpredicates.HasAnnotation(metadata.ResourceManagementKey, metadata.ResourceManagementEnabled), + testpredicates.HasAnnotation(metadata.ResourceManagerKey, declared.ResourceManager(declared.RootReconciler, rootSyncY.Name)), + }) + }) + if err := tg.Wait(); err != nil { + nt.T.Fatal(err) + } +} diff --git a/e2e/testcases/override_git_sync_depth_test.go b/e2e/testcases/override_git_sync_depth_test.go index 6d74ea1ef8..441a782595 100644 --- a/e2e/testcases/override_git_sync_depth_test.go +++ b/e2e/testcases/override_git_sync_depth_test.go @@ -119,7 +119,7 @@ func TestOverrideGitSyncDepthV1Alpha1(t *testing.T) { } // Clear `spec.override` from repoSyncBackend - repoSyncBackend.Spec.Override = &v1alpha1.OverrideSpec{} + repoSyncBackend.Spec.Override = &v1alpha1.RepoSyncOverrideSpec{} nt.Must(nt.RootRepos[configsync.RootSyncName].Add(nomostest.StructuredNSPath(backendNamespace, configsync.RepoSyncName), repoSyncBackend)) nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("Clear `spec.override` from repoSyncBackend")) if err := nt.WatchForAllSyncs(); err != nil { @@ -217,7 +217,7 @@ func TestOverrideGitSyncDepthV1Beta1(t *testing.T) { } // Clear `spec.override` from repoSyncBackend - repoSyncBackend.Spec.Override = &v1beta1.OverrideSpec{} + repoSyncBackend.Spec.Override = &v1beta1.RepoSyncOverrideSpec{} nt.Must(nt.RootRepos[configsync.RootSyncName].Add(nomostest.StructuredNSPath(backendNamespace, configsync.RepoSyncName), repoSyncBackend)) nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("Clear `spec.override` from repoSyncBackend")) if err := nt.WatchForAllSyncs(); err != nil { diff --git a/e2e/testcases/override_resource_limits_test.go b/e2e/testcases/override_resource_limits_test.go index a6094a24fc..59c15281a8 100644 --- a/e2e/testcases/override_resource_limits_test.go +++ b/e2e/testcases/override_resource_limits_test.go @@ -145,42 +145,46 @@ func TestOverrideReconcilerResourcesV1Alpha1(t *testing.T) { } // Override the CPU/memory requests and limits of the reconciler container of ns-reconciler-backend - repoSyncBackend.Spec.Override = &v1alpha1.OverrideSpec{ - Resources: []v1alpha1.ContainerResourcesSpec{ - { - ContainerName: "reconciler", - CPURequest: resource.MustParse("500m"), - CPULimit: resource.MustParse("1000m"), - MemoryRequest: resource.MustParse("500Mi"), - MemoryLimit: resource.MustParse("555Mi"), - }, - { - ContainerName: "git-sync", - CPURequest: resource.MustParse("600m"), - CPULimit: resource.MustParse("1"), - MemoryRequest: resource.MustParse("600Mi"), - MemoryLimit: resource.MustParse("666Mi"), + repoSyncBackend.Spec.Override = &v1alpha1.RepoSyncOverrideSpec{ + OverrideSpec: v1alpha1.OverrideSpec{ + Resources: []v1alpha1.ContainerResourcesSpec{ + { + ContainerName: "reconciler", + CPURequest: resource.MustParse("500m"), + CPULimit: resource.MustParse("1000m"), + MemoryRequest: resource.MustParse("500Mi"), + MemoryLimit: resource.MustParse("555Mi"), + }, + { + ContainerName: "git-sync", + CPURequest: resource.MustParse("600m"), + CPULimit: resource.MustParse("1"), + MemoryRequest: resource.MustParse("600Mi"), + MemoryLimit: resource.MustParse("666Mi"), + }, }, }, } nt.Must(nt.RootRepos[configsync.RootSyncName].Add(nomostest.StructuredNSPath(backendNamespace, configsync.RepoSyncName), repoSyncBackend)) // Override the CPU/memory requests and limits of the reconciler container of ns-reconciler-frontend - repoSyncFrontend.Spec.Override = &v1alpha1.OverrideSpec{ - Resources: []v1alpha1.ContainerResourcesSpec{ - { - ContainerName: "reconciler", - CPURequest: resource.MustParse("511m"), - CPULimit: resource.MustParse("2000m"), - MemoryRequest: resource.MustParse("511Mi"), - MemoryLimit: resource.MustParse("544Mi"), - }, - { - ContainerName: "git-sync", - CPURequest: resource.MustParse("611m"), - CPULimit: resource.MustParse("2"), - MemoryRequest: resource.MustParse("611Mi"), - MemoryLimit: resource.MustParse("644Mi"), + repoSyncFrontend.Spec.Override = &v1alpha1.RepoSyncOverrideSpec{ + OverrideSpec: v1alpha1.OverrideSpec{ + Resources: []v1alpha1.ContainerResourcesSpec{ + { + ContainerName: "reconciler", + CPURequest: resource.MustParse("511m"), + CPULimit: resource.MustParse("2000m"), + MemoryRequest: resource.MustParse("511Mi"), + MemoryLimit: resource.MustParse("544Mi"), + }, + { + ContainerName: "git-sync", + CPURequest: resource.MustParse("611m"), + CPULimit: resource.MustParse("2"), + MemoryRequest: resource.MustParse("611Mi"), + MemoryLimit: resource.MustParse("644Mi"), + }, }, }, } @@ -504,42 +508,46 @@ func TestOverrideReconcilerResourcesV1Beta1(t *testing.T) { } // Override the CPU/memory requests and limits of the reconciler container of ns-reconciler-backend - repoSyncBackend.Spec.Override = &v1beta1.OverrideSpec{ - Resources: []v1beta1.ContainerResourcesSpec{ - { - ContainerName: "reconciler", - CPURequest: resource.MustParse("500m"), - CPULimit: resource.MustParse("1000m"), - MemoryRequest: resource.MustParse("500Mi"), - MemoryLimit: resource.MustParse("555Mi"), - }, - { - ContainerName: "git-sync", - CPURequest: resource.MustParse("600m"), - CPULimit: resource.MustParse("1"), - MemoryRequest: resource.MustParse("600Mi"), - MemoryLimit: resource.MustParse("666Mi"), + repoSyncBackend.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: []v1beta1.ContainerResourcesSpec{ + { + ContainerName: "reconciler", + CPURequest: resource.MustParse("500m"), + CPULimit: resource.MustParse("1000m"), + MemoryRequest: resource.MustParse("500Mi"), + MemoryLimit: resource.MustParse("555Mi"), + }, + { + ContainerName: "git-sync", + CPURequest: resource.MustParse("600m"), + CPULimit: resource.MustParse("1"), + MemoryRequest: resource.MustParse("600Mi"), + MemoryLimit: resource.MustParse("666Mi"), + }, }, }, } nt.Must(nt.RootRepos[configsync.RootSyncName].Add(nomostest.StructuredNSPath(backendNamespace, configsync.RepoSyncName), repoSyncBackend)) // Override the CPU/memory requests and limits of the reconciler container of ns-reconciler-frontend - repoSyncFrontend.Spec.Override = &v1beta1.OverrideSpec{ - Resources: []v1beta1.ContainerResourcesSpec{ - { - ContainerName: "reconciler", - CPURequest: resource.MustParse("511m"), - CPULimit: resource.MustParse("2000m"), - MemoryRequest: resource.MustParse("511Mi"), - MemoryLimit: resource.MustParse("544Mi"), - }, - { - ContainerName: "git-sync", - CPURequest: resource.MustParse("611m"), - CPULimit: resource.MustParse("2"), - MemoryRequest: resource.MustParse("611Mi"), - MemoryLimit: resource.MustParse("644Mi"), + repoSyncFrontend.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: []v1beta1.ContainerResourcesSpec{ + { + ContainerName: "reconciler", + CPURequest: resource.MustParse("511m"), + CPULimit: resource.MustParse("2000m"), + MemoryRequest: resource.MustParse("511Mi"), + MemoryLimit: resource.MustParse("544Mi"), + }, + { + ContainerName: "git-sync", + CPURequest: resource.MustParse("611m"), + CPULimit: resource.MustParse("2"), + MemoryRequest: resource.MustParse("611Mi"), + MemoryLimit: resource.MustParse("644Mi"), + }, }, }, } diff --git a/e2e/testcases/reconciler_manager_test.go b/e2e/testcases/reconciler_manager_test.go index c721d7ac38..81876bca1b 100644 --- a/e2e/testcases/reconciler_manager_test.go +++ b/e2e/testcases/reconciler_manager_test.go @@ -712,9 +712,13 @@ func TestAutopilotReconcilerAdjustment(t *testing.T) { rootSyncObj := &v1beta1.RootSync{} err := nt.Validate(rootSyncNN.Name, rootSyncNN.Namespace, rootSyncObj, // Confirm there are no resource overrides - testpredicates.RootSyncSpecOverrideEquals(&v1beta1.OverrideSpec{ - ReconcileTimeout: &metav1.Duration{Duration: *nt.DefaultReconcileTimeout}, - }), + testpredicates.RootSyncSpecOverrideEquals( + &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + ReconcileTimeout: &metav1.Duration{Duration: *nt.DefaultReconcileTimeout}, + }, + }, + ), ) if err != nil { nt.T.Fatal(err) diff --git a/manifests/rootsync-crd.yaml b/manifests/rootsync-crd.yaml index d75b588589..190404d05b 100644 --- a/manifests/rootsync-crd.yaml +++ b/manifests/rootsync-crd.yaml @@ -374,6 +374,18 @@ spec: x-kubernetes-list-map-keys: - containerName x-kubernetes-list-type: map + namespaceStrategy: + description: namespaceStrategy controls how the reconciler handles + Namespaces which are used by resources in the source but not + declared. Must be "implicit" or "explicit" "implicit" means + that the reconciler will implicitly create Namespaces if they + do not exist, even if they are not declared in the source. "explicit" + means that the reconciler will not create Namespaces which are + not declared in the source. + enum: + - implicit + - explicit + type: string reconcileTimeout: description: 'reconcileTimeout allows one to override the threshold for how long to wait for all resources to reconcile before giving @@ -1427,6 +1439,18 @@ spec: x-kubernetes-list-map-keys: - containerName x-kubernetes-list-type: map + namespaceStrategy: + description: namespaceStrategy controls how the reconciler handles + Namespaces which are used by resources in the source but not + declared. Must be "implicit" or "explicit" "implicit" means + that the reconciler will implicitly create Namespaces if they + do not exist, even if they are not declared in the source. "explicit" + means that the reconciler will not create Namespaces which are + not declared in the source. + enum: + - implicit + - explicit + type: string reconcileTimeout: description: 'reconcileTimeout allows one to override the threshold for how long to wait for all resources to reconcile before giving diff --git a/pkg/api/configsync/register.go b/pkg/api/configsync/register.go index 10ba4fcfbb..340de37b33 100644 --- a/pkg/api/configsync/register.go +++ b/pkg/api/configsync/register.go @@ -107,3 +107,16 @@ const ( // Git or OCI or Helm, when GKE Workload Identity or Fleet Workload Identity is enabled. AuthGCPServiceAccount AuthType = "gcpserviceaccount" ) + +// NamespaceStrategy specifies the strategy used by the reconciler for undeclared +// namespaces. +type NamespaceStrategy string + +const ( + // NamespaceStrategyImplicit indicates that the reconciler should create + // "implicit" namespaces if they are not declared. Default + NamespaceStrategyImplicit NamespaceStrategy = "implicit" + // NamespaceStrategyExplicit indicates that namespaces must be explicitly + // declared to be created by the reconciler. + NamespaceStrategyExplicit NamespaceStrategy = "explicit" +) diff --git a/pkg/api/configsync/v1alpha1/getters.go b/pkg/api/configsync/v1alpha1/getters.go index e696bc91c1..363c9a537d 100644 --- a/pkg/api/configsync/v1alpha1/getters.go +++ b/pkg/api/configsync/v1alpha1/getters.go @@ -29,20 +29,20 @@ func GetPeriodSecs(g *Git) float64 { // SafeOverride creates an override or returns an existing one // use it if you need to ensure that you are assigning -// to an object, but not to test for nil (current existance) -func (rs *RepoSyncSpec) SafeOverride() *OverrideSpec { +// to an object, but not to test for nil (current existence) +func (rs *RepoSyncSpec) SafeOverride() *RepoSyncOverrideSpec { if rs.Override == nil { - rs.Override = &OverrideSpec{} + rs.Override = &RepoSyncOverrideSpec{} } return rs.Override } // SafeOverride creates an override or returns an existing one // use it if you need to ensure that you are assigning -// to an object, but not to test for nil (current existance) -func (rs *RootSyncSpec) SafeOverride() *OverrideSpec { +// to an object, but not to test for nil (current existence) +func (rs *RootSyncSpec) SafeOverride() *RootSyncOverrideSpec { if rs.Override == nil { - rs.Override = &OverrideSpec{} + rs.Override = &RootSyncOverrideSpec{} } return rs.Override } diff --git a/pkg/api/configsync/v1alpha1/reposync_types.go b/pkg/api/configsync/v1alpha1/reposync_types.go index a6f2100007..4d83164a55 100644 --- a/pkg/api/configsync/v1alpha1/reposync_types.go +++ b/pkg/api/configsync/v1alpha1/reposync_types.go @@ -75,7 +75,7 @@ type RepoSyncSpec struct { // override allows to override the settings for a reconciler. // +nullable // +optional - Override *OverrideSpec `json:"override,omitempty"` + Override *RepoSyncOverrideSpec `json:"override,omitempty"` } // RepoSyncStatus defines the observed state of a RepoSync. diff --git a/pkg/api/configsync/v1alpha1/resource_override.go b/pkg/api/configsync/v1alpha1/resource_override.go index 03ccc156ea..37a40a04b4 100644 --- a/pkg/api/configsync/v1alpha1/resource_override.go +++ b/pkg/api/configsync/v1alpha1/resource_override.go @@ -17,6 +17,7 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "kpt.dev/configsync/pkg/api/configsync" ) // OverrideSpec allows to override the settings for a reconciler pod @@ -75,6 +76,28 @@ type OverrideSpec struct { LogLevels []ContainerLogLevelOverride `json:"logLevels,omitempty"` } +// RootSyncOverrideSpec allows to override the settings for a RootSync reconciler pod +type RootSyncOverrideSpec struct { + OverrideSpec `json:",inline"` + + // namespaceStrategy controls how the reconciler handles Namespaces + // which are used by resources in the source but not declared. + // Must be "implicit" or "explicit" + // "implicit" means that the reconciler will implicitly create Namespaces + // if they do not exist, even if they are not declared in the source. + // "explicit" means that the reconciler will not create Namespaces which + // are not declared in the source. + // + // +kubebuilder:validation:Enum=implicit;explicit + // +optional + NamespaceStrategy configsync.NamespaceStrategy `json:"namespaceStrategy,omitempty"` +} + +// RepoSyncOverrideSpec allows to override the settings for a RepoSync reconciler pod +type RepoSyncOverrideSpec struct { + OverrideSpec `json:",inline"` +} + // ContainerResourcesSpec allows to override the resource requirements for a container type ContainerResourcesSpec struct { // containerName specifies the name of a container whose resource requirements will be overridden. diff --git a/pkg/api/configsync/v1alpha1/rootsync_types.go b/pkg/api/configsync/v1alpha1/rootsync_types.go index 9d0e1bdede..abd42a1ed9 100644 --- a/pkg/api/configsync/v1alpha1/rootsync_types.go +++ b/pkg/api/configsync/v1alpha1/rootsync_types.go @@ -75,7 +75,7 @@ type RootSyncSpec struct { // override allows to override the settings for a reconciler. // +nullable // +optional - Override *OverrideSpec `json:"override,omitempty"` + Override *RootSyncOverrideSpec `json:"override,omitempty"` } // RootSyncStatus defines the observed state of RootSync diff --git a/pkg/api/configsync/v1alpha1/zz_generated.conversion.go b/pkg/api/configsync/v1alpha1/zz_generated.conversion.go index 184bbd1736..0393aa64c5 100644 --- a/pkg/api/configsync/v1alpha1/zz_generated.conversion.go +++ b/pkg/api/configsync/v1alpha1/zz_generated.conversion.go @@ -138,6 +138,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*v1beta1.OverrideSpec)(nil), (*OverrideSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_OverrideSpec_To_v1alpha1_OverrideSpec(a.(*v1beta1.OverrideSpec), b.(*OverrideSpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*RenderingStatus)(nil), (*v1beta1.RenderingStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_RenderingStatus_To_v1beta1_RenderingStatus(a.(*RenderingStatus), b.(*v1beta1.RenderingStatus), scope) }); err != nil { @@ -178,6 +183,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*RepoSyncOverrideSpec)(nil), (*v1beta1.RepoSyncOverrideSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RepoSyncOverrideSpec_To_v1beta1_RepoSyncOverrideSpec(a.(*RepoSyncOverrideSpec), b.(*v1beta1.RepoSyncOverrideSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RepoSyncOverrideSpec)(nil), (*RepoSyncOverrideSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RepoSyncOverrideSpec_To_v1alpha1_RepoSyncOverrideSpec(a.(*v1beta1.RepoSyncOverrideSpec), b.(*RepoSyncOverrideSpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*RepoSyncSpec)(nil), (*v1beta1.RepoSyncSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_RepoSyncSpec_To_v1beta1_RepoSyncSpec(a.(*RepoSyncSpec), b.(*v1beta1.RepoSyncSpec), scope) }); err != nil { @@ -238,6 +253,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*RootSyncOverrideSpec)(nil), (*v1beta1.RootSyncOverrideSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RootSyncOverrideSpec_To_v1beta1_RootSyncOverrideSpec(a.(*RootSyncOverrideSpec), b.(*v1beta1.RootSyncOverrideSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RootSyncOverrideSpec)(nil), (*RootSyncOverrideSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RootSyncOverrideSpec_To_v1alpha1_RootSyncOverrideSpec(a.(*v1beta1.RootSyncOverrideSpec), b.(*RootSyncOverrideSpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*RootSyncSpec)(nil), (*v1beta1.RootSyncSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_RootSyncSpec_To_v1beta1_RootSyncSpec(a.(*RootSyncSpec), b.(*v1beta1.RootSyncSpec), scope) }); err != nil { @@ -318,11 +343,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddConversionFunc((*v1beta1.OverrideSpec)(nil), (*OverrideSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_OverrideSpec_To_v1alpha1_OverrideSpec(a.(*v1beta1.OverrideSpec), b.(*OverrideSpec), scope) - }); err != nil { - return err - } return nil } @@ -824,6 +844,30 @@ func Convert_v1beta1_RepoSyncList_To_v1alpha1_RepoSyncList(in *v1beta1.RepoSyncL return autoConvert_v1beta1_RepoSyncList_To_v1alpha1_RepoSyncList(in, out, s) } +func autoConvert_v1alpha1_RepoSyncOverrideSpec_To_v1beta1_RepoSyncOverrideSpec(in *RepoSyncOverrideSpec, out *v1beta1.RepoSyncOverrideSpec, s conversion.Scope) error { + if err := Convert_v1alpha1_OverrideSpec_To_v1beta1_OverrideSpec(&in.OverrideSpec, &out.OverrideSpec, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_RepoSyncOverrideSpec_To_v1beta1_RepoSyncOverrideSpec is an autogenerated conversion function. +func Convert_v1alpha1_RepoSyncOverrideSpec_To_v1beta1_RepoSyncOverrideSpec(in *RepoSyncOverrideSpec, out *v1beta1.RepoSyncOverrideSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_RepoSyncOverrideSpec_To_v1beta1_RepoSyncOverrideSpec(in, out, s) +} + +func autoConvert_v1beta1_RepoSyncOverrideSpec_To_v1alpha1_RepoSyncOverrideSpec(in *v1beta1.RepoSyncOverrideSpec, out *RepoSyncOverrideSpec, s conversion.Scope) error { + if err := Convert_v1beta1_OverrideSpec_To_v1alpha1_OverrideSpec(&in.OverrideSpec, &out.OverrideSpec, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_RepoSyncOverrideSpec_To_v1alpha1_RepoSyncOverrideSpec is an autogenerated conversion function. +func Convert_v1beta1_RepoSyncOverrideSpec_To_v1alpha1_RepoSyncOverrideSpec(in *v1beta1.RepoSyncOverrideSpec, out *RepoSyncOverrideSpec, s conversion.Scope) error { + return autoConvert_v1beta1_RepoSyncOverrideSpec_To_v1alpha1_RepoSyncOverrideSpec(in, out, s) +} + func autoConvert_v1alpha1_RepoSyncSpec_To_v1beta1_RepoSyncSpec(in *RepoSyncSpec, out *v1beta1.RepoSyncSpec, s conversion.Scope) error { out.SourceFormat = in.SourceFormat out.SourceType = in.SourceType @@ -838,7 +882,7 @@ func autoConvert_v1alpha1_RepoSyncSpec_To_v1beta1_RepoSyncSpec(in *RepoSyncSpec, } else { out.Helm = nil } - out.Override = (*v1beta1.OverrideSpec)(unsafe.Pointer(in.Override)) + out.Override = (*v1beta1.RepoSyncOverrideSpec)(unsafe.Pointer(in.Override)) return nil } @@ -861,7 +905,7 @@ func autoConvert_v1beta1_RepoSyncSpec_To_v1alpha1_RepoSyncSpec(in *v1beta1.RepoS } else { out.Helm = nil } - out.Override = (*OverrideSpec)(unsafe.Pointer(in.Override)) + out.Override = (*RepoSyncOverrideSpec)(unsafe.Pointer(in.Override)) return nil } @@ -1034,6 +1078,32 @@ func Convert_v1beta1_RootSyncList_To_v1alpha1_RootSyncList(in *v1beta1.RootSyncL return autoConvert_v1beta1_RootSyncList_To_v1alpha1_RootSyncList(in, out, s) } +func autoConvert_v1alpha1_RootSyncOverrideSpec_To_v1beta1_RootSyncOverrideSpec(in *RootSyncOverrideSpec, out *v1beta1.RootSyncOverrideSpec, s conversion.Scope) error { + if err := Convert_v1alpha1_OverrideSpec_To_v1beta1_OverrideSpec(&in.OverrideSpec, &out.OverrideSpec, s); err != nil { + return err + } + out.NamespaceStrategy = configsync.NamespaceStrategy(in.NamespaceStrategy) + return nil +} + +// Convert_v1alpha1_RootSyncOverrideSpec_To_v1beta1_RootSyncOverrideSpec is an autogenerated conversion function. +func Convert_v1alpha1_RootSyncOverrideSpec_To_v1beta1_RootSyncOverrideSpec(in *RootSyncOverrideSpec, out *v1beta1.RootSyncOverrideSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_RootSyncOverrideSpec_To_v1beta1_RootSyncOverrideSpec(in, out, s) +} + +func autoConvert_v1beta1_RootSyncOverrideSpec_To_v1alpha1_RootSyncOverrideSpec(in *v1beta1.RootSyncOverrideSpec, out *RootSyncOverrideSpec, s conversion.Scope) error { + if err := Convert_v1beta1_OverrideSpec_To_v1alpha1_OverrideSpec(&in.OverrideSpec, &out.OverrideSpec, s); err != nil { + return err + } + out.NamespaceStrategy = configsync.NamespaceStrategy(in.NamespaceStrategy) + return nil +} + +// Convert_v1beta1_RootSyncOverrideSpec_To_v1alpha1_RootSyncOverrideSpec is an autogenerated conversion function. +func Convert_v1beta1_RootSyncOverrideSpec_To_v1alpha1_RootSyncOverrideSpec(in *v1beta1.RootSyncOverrideSpec, out *RootSyncOverrideSpec, s conversion.Scope) error { + return autoConvert_v1beta1_RootSyncOverrideSpec_To_v1alpha1_RootSyncOverrideSpec(in, out, s) +} + func autoConvert_v1alpha1_RootSyncSpec_To_v1beta1_RootSyncSpec(in *RootSyncSpec, out *v1beta1.RootSyncSpec, s conversion.Scope) error { out.SourceFormat = in.SourceFormat out.SourceType = in.SourceType @@ -1048,7 +1118,7 @@ func autoConvert_v1alpha1_RootSyncSpec_To_v1beta1_RootSyncSpec(in *RootSyncSpec, } else { out.Helm = nil } - out.Override = (*v1beta1.OverrideSpec)(unsafe.Pointer(in.Override)) + out.Override = (*v1beta1.RootSyncOverrideSpec)(unsafe.Pointer(in.Override)) return nil } @@ -1071,7 +1141,7 @@ func autoConvert_v1beta1_RootSyncSpec_To_v1alpha1_RootSyncSpec(in *v1beta1.RootS } else { out.Helm = nil } - out.Override = (*OverrideSpec)(unsafe.Pointer(in.Override)) + out.Override = (*RootSyncOverrideSpec)(unsafe.Pointer(in.Override)) return nil } diff --git a/pkg/api/configsync/v1alpha1/zz_generated.deepcopy.go b/pkg/api/configsync/v1alpha1/zz_generated.deepcopy.go index 85f7952688..ae3ee73892 100644 --- a/pkg/api/configsync/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/configsync/v1alpha1/zz_generated.deepcopy.go @@ -31,6 +31,21 @@ func (in *ConfigSyncError) DeepCopy() *ConfigSyncError { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerLogLevelOverride) DeepCopyInto(out *ContainerLogLevelOverride) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerLogLevelOverride. +func (in *ContainerLogLevelOverride) DeepCopy() *ContainerLogLevelOverride { + if in == nil { + return nil + } + out := new(ContainerLogLevelOverride) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContainerResourcesSpec) DeepCopyInto(out *ContainerResourcesSpec) { *out = *in @@ -245,6 +260,11 @@ func (in *OverrideSpec) DeepCopyInto(out *OverrideSpec) { *out = new(bool) **out = **in } + if in.LogLevels != nil { + in, out := &in.LogLevels, &out.LogLevels + *out = make([]ContainerLogLevelOverride, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OverrideSpec. @@ -393,6 +413,22 @@ func (in *RepoSyncList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RepoSyncOverrideSpec) DeepCopyInto(out *RepoSyncOverrideSpec) { + *out = *in + in.OverrideSpec.DeepCopyInto(&out.OverrideSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepoSyncOverrideSpec. +func (in *RepoSyncOverrideSpec) DeepCopy() *RepoSyncOverrideSpec { + if in == nil { + return nil + } + out := new(RepoSyncOverrideSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RepoSyncSpec) DeepCopyInto(out *RepoSyncSpec) { *out = *in @@ -413,7 +449,7 @@ func (in *RepoSyncSpec) DeepCopyInto(out *RepoSyncSpec) { } if in.Override != nil { in, out := &in.Override, &out.Override - *out = new(OverrideSpec) + *out = new(RepoSyncOverrideSpec) (*in).DeepCopyInto(*out) } } @@ -560,6 +596,22 @@ func (in *RootSyncList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RootSyncOverrideSpec) DeepCopyInto(out *RootSyncOverrideSpec) { + *out = *in + in.OverrideSpec.DeepCopyInto(&out.OverrideSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RootSyncOverrideSpec. +func (in *RootSyncOverrideSpec) DeepCopy() *RootSyncOverrideSpec { + if in == nil { + return nil + } + out := new(RootSyncOverrideSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RootSyncSpec) DeepCopyInto(out *RootSyncSpec) { *out = *in @@ -580,7 +632,7 @@ func (in *RootSyncSpec) DeepCopyInto(out *RootSyncSpec) { } if in.Override != nil { in, out := &in.Override, &out.Override - *out = new(OverrideSpec) + *out = new(RootSyncOverrideSpec) (*in).DeepCopyInto(*out) } } diff --git a/pkg/api/configsync/v1beta1/getters.go b/pkg/api/configsync/v1beta1/getters.go index 1ff4493428..c659722442 100644 --- a/pkg/api/configsync/v1beta1/getters.go +++ b/pkg/api/configsync/v1beta1/getters.go @@ -39,20 +39,20 @@ func GetSecretName(secretRef *SecretReference) string { // SafeOverride creates an override or returns an existing one // use it if you need to ensure that you are assigning -// to an object, but not to test for nil (current existance) -func (rs *RepoSyncSpec) SafeOverride() *OverrideSpec { +// to an object, but not to test for nil (current existence) +func (rs *RepoSyncSpec) SafeOverride() *RepoSyncOverrideSpec { if rs.Override == nil { - rs.Override = &OverrideSpec{} + rs.Override = &RepoSyncOverrideSpec{} } return rs.Override } // SafeOverride creates an override or returns an existing one // use it if you need to ensure that you are assigning -// to an object, but not to test for nil (current existance) -func (rs *RootSyncSpec) SafeOverride() *OverrideSpec { +// to an object, but not to test for nil (current existence) +func (rs *RootSyncSpec) SafeOverride() *RootSyncOverrideSpec { if rs.Override == nil { - rs.Override = &OverrideSpec{} + rs.Override = &RootSyncOverrideSpec{} } return rs.Override } diff --git a/pkg/api/configsync/v1beta1/reposync_types.go b/pkg/api/configsync/v1beta1/reposync_types.go index 8f96f73440..80914874d5 100644 --- a/pkg/api/configsync/v1beta1/reposync_types.go +++ b/pkg/api/configsync/v1beta1/reposync_types.go @@ -76,7 +76,7 @@ type RepoSyncSpec struct { // override allows to override the settings for a namespace reconciler. // +nullable // +optional - Override *OverrideSpec `json:"override,omitempty"` + Override *RepoSyncOverrideSpec `json:"override,omitempty"` } // RepoSyncStatus defines the observed state of a RepoSync. diff --git a/pkg/api/configsync/v1beta1/resource_override.go b/pkg/api/configsync/v1beta1/resource_override.go index 6f404d02b4..2f4a154b98 100644 --- a/pkg/api/configsync/v1beta1/resource_override.go +++ b/pkg/api/configsync/v1beta1/resource_override.go @@ -17,6 +17,7 @@ package v1beta1 import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "kpt.dev/configsync/pkg/api/configsync" ) // OverrideSpec allows to override the settings for a reconciler pod @@ -75,6 +76,28 @@ type OverrideSpec struct { LogLevels []ContainerLogLevelOverride `json:"logLevels,omitempty"` } +// RootSyncOverrideSpec allows to override the settings for a RootSync reconciler pod +type RootSyncOverrideSpec struct { + OverrideSpec `json:",inline"` + + // namespaceStrategy controls how the reconciler handles Namespaces + // which are used by resources in the source but not declared. + // Must be "implicit" or "explicit" + // "implicit" means that the reconciler will implicitly create Namespaces + // if they do not exist, even if they are not declared in the source. + // "explicit" means that the reconciler will not create Namespaces which + // are not declared in the source. + // + // +kubebuilder:validation:Enum=implicit;explicit + // +optional + NamespaceStrategy configsync.NamespaceStrategy `json:"namespaceStrategy,omitempty"` +} + +// RepoSyncOverrideSpec allows to override the settings for a RepoSync reconciler pod +type RepoSyncOverrideSpec struct { + OverrideSpec `json:",inline"` +} + // ContainerResourcesSpec allows to override the resource requirements for a container type ContainerResourcesSpec struct { // containerName specifies the name of a container whose resource requirements will be overridden. diff --git a/pkg/api/configsync/v1beta1/rootsync_types.go b/pkg/api/configsync/v1beta1/rootsync_types.go index 6cfa7d6df3..be1aec3b6f 100644 --- a/pkg/api/configsync/v1beta1/rootsync_types.go +++ b/pkg/api/configsync/v1beta1/rootsync_types.go @@ -76,7 +76,7 @@ type RootSyncSpec struct { // override allows to override the settings for a root reconciler. // +nullable // +optional - Override *OverrideSpec `json:"override,omitempty"` + Override *RootSyncOverrideSpec `json:"override,omitempty"` } // RootSyncStatus defines the observed state of RootSync diff --git a/pkg/api/configsync/v1beta1/zz_generated.deepcopy.go b/pkg/api/configsync/v1beta1/zz_generated.deepcopy.go index f47cfbd350..b12d2948e5 100644 --- a/pkg/api/configsync/v1beta1/zz_generated.deepcopy.go +++ b/pkg/api/configsync/v1beta1/zz_generated.deepcopy.go @@ -413,6 +413,22 @@ func (in *RepoSyncList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RepoSyncOverrideSpec) DeepCopyInto(out *RepoSyncOverrideSpec) { + *out = *in + in.OverrideSpec.DeepCopyInto(&out.OverrideSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepoSyncOverrideSpec. +func (in *RepoSyncOverrideSpec) DeepCopy() *RepoSyncOverrideSpec { + if in == nil { + return nil + } + out := new(RepoSyncOverrideSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RepoSyncSpec) DeepCopyInto(out *RepoSyncSpec) { *out = *in @@ -433,7 +449,7 @@ func (in *RepoSyncSpec) DeepCopyInto(out *RepoSyncSpec) { } if in.Override != nil { in, out := &in.Override, &out.Override - *out = new(OverrideSpec) + *out = new(RepoSyncOverrideSpec) (*in).DeepCopyInto(*out) } } @@ -580,6 +596,22 @@ func (in *RootSyncList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RootSyncOverrideSpec) DeepCopyInto(out *RootSyncOverrideSpec) { + *out = *in + in.OverrideSpec.DeepCopyInto(&out.OverrideSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RootSyncOverrideSpec. +func (in *RootSyncOverrideSpec) DeepCopy() *RootSyncOverrideSpec { + if in == nil { + return nil + } + out := new(RootSyncOverrideSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RootSyncSpec) DeepCopyInto(out *RootSyncSpec) { *out = *in @@ -600,7 +632,7 @@ func (in *RootSyncSpec) DeepCopyInto(out *RootSyncSpec) { } if in.Override != nil { in, out := &in.Override, &out.Override - *out = new(OverrideSpec) + *out = new(RootSyncOverrideSpec) (*in).DeepCopyInto(*out) } } diff --git a/pkg/parse/root.go b/pkg/parse/root.go index 213796cf8d..ad1ee24085 100644 --- a/pkg/parse/root.go +++ b/pkg/parse/root.go @@ -55,7 +55,7 @@ import ( ) // NewRootRunner creates a new runnable parser for parsing a Root repository. -func NewRootRunner(clusterName, syncName, reconcilerName string, format filesystem.SourceFormat, fileReader reader.Reader, c client.Client, pollingPeriod, resyncPeriod, retryPeriod, statusUpdatePeriod time.Duration, fs FileSource, dc discovery.DiscoveryInterface, resources *declared.Resources, app applier.Applier, rem remediator.Interface, renderingEnabled bool) (Parser, error) { +func NewRootRunner(clusterName, syncName, reconcilerName string, format filesystem.SourceFormat, fileReader reader.Reader, c client.Client, pollingPeriod, resyncPeriod, retryPeriod, statusUpdatePeriod time.Duration, fs FileSource, dc discovery.DiscoveryInterface, resources *declared.Resources, app applier.Applier, rem remediator.Interface, renderingEnabled bool, namespaceStrategy configsync.NamespaceStrategy) (Parser, error) { converter, err := declared.NewValueConverter(dc) if err != nil { return nil, err @@ -84,7 +84,8 @@ func NewRootRunner(clusterName, syncName, reconcilerName string, format filesyst mux: &sync.Mutex{}, renderingEnabled: renderingEnabled, }, - sourceFormat: format, + sourceFormat: format, + namespaceStrategy: namespaceStrategy, }, nil } @@ -95,6 +96,10 @@ type root struct { // repository may be SourceFormatHierarchy; all others are implicitly // SourceFormatUnstructured. sourceFormat filesystem.SourceFormat + + // namespaceStrategy indicates the NamespaceStrategy to be used by this + // reconciler. + namespaceStrategy configsync.NamespaceStrategy } var _ Parser = &root{} @@ -141,7 +146,9 @@ func (p *root) parseSource(_ context.Context, state sourceState) ([]ast.FileObje options = OptionsForScope(options, p.scope) if p.sourceFormat == filesystem.SourceFormatUnstructured { - options.Visitors = append(options.Visitors, p.addImplicitNamespaces) + if p.namespaceStrategy == configsync.NamespaceStrategyImplicit { + options.Visitors = append(options.Visitors, p.addImplicitNamespaces) + } objs, err = validate.Unstructured(objs, options) } else { objs, err = validate.Hierarchical(objs, options) diff --git a/pkg/parse/root_test.go b/pkg/parse/root_test.go index 5ba709eba1..e3d470f3a1 100644 --- a/pkg/parse/root_test.go +++ b/pkg/parse/root_test.go @@ -106,19 +106,21 @@ func gitSpec(repo string, auth configsync.AuthType) core.MetaMutator { func TestRoot_Parse(t *testing.T) { testCases := []struct { - name string - format filesystem.SourceFormat - existingObjects []client.Object - parsed []ast.FileObject - want []ast.FileObject + name string + format filesystem.SourceFormat + namespaceStrategy configsync.NamespaceStrategy + existingObjects []client.Object + parsed []ast.FileObject + want []ast.FileObject }{ { name: "no objects", format: filesystem.SourceFormatUnstructured, }, { - name: "implicit namespace if unstructured and not present", - format: filesystem.SourceFormatUnstructured, + name: "implicit namespace if unstructured and not present", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, parsed: []ast.FileObject{ fake.Role(core.Namespace("foo")), }, @@ -150,8 +152,31 @@ func TestRoot_Parse(t *testing.T) { }, }, { - name: "implicit namespace if unstructured, present and self-managed", - format: filesystem.SourceFormatUnstructured, + name: "no implicit namespace if namespaceStrategy is explicit", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyExplicit, + parsed: []ast.FileObject{ + fake.Role(core.Namespace("foo")), + }, + want: []ast.FileObject{ + fake.Role(core.Namespace("foo"), + core.Label(metadata.ManagedByKey, metadata.ManagedByValue), + core.Label(metadata.DeclaredVersionLabel, "v1"), + core.Annotation(metadata.DeclaredFieldsKey, `{"f:metadata":{"f:annotations":{},"f:labels":{}},"f:rules":{}}`), + core.Annotation(metadata.SourcePathAnnotationKey, "namespaces/foo/role.yaml"), + core.Annotation(metadata.ResourceManagementKey, metadata.ResourceManagementEnabled), + core.Annotation(metadata.GitContextKey, nilGitContext), + core.Annotation(metadata.SyncTokenAnnotationKey, ""), + core.Annotation(metadata.OwningInventoryKey, applier.InventoryID(rootSyncName, configmanagement.ControllerNamespace)), + core.Annotation(metadata.ResourceIDKey, "rbac.authorization.k8s.io_role_foo_default-name"), + difftest.ManagedBy(declared.RootReconciler, rootSyncName), + ), + }, + }, + { + name: "implicit namespace if unstructured, present and self-managed", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, existingObjects: []client.Object{fake.NamespaceObject("foo", core.Label(metadata.ManagedByKey, metadata.ManagedByValue), core.Annotation(common.LifecycleDeleteAnnotation, common.PreventDeletion), @@ -192,8 +217,9 @@ func TestRoot_Parse(t *testing.T) { }, }, { - name: "no implicit namespace if unstructured, present, but managed by others", - format: filesystem.SourceFormatUnstructured, + name: "no implicit namespace if unstructured, present, but managed by others", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, existingObjects: []client.Object{fake.NamespaceObject("foo", core.Label(metadata.ManagedByKey, metadata.ManagedByValue), core.Annotation(common.LifecycleDeleteAnnotation, common.PreventDeletion), @@ -222,9 +248,10 @@ func TestRoot_Parse(t *testing.T) { }, }, { - name: "no implicit namespace if unstructured, present, but unmanaged", - format: filesystem.SourceFormatUnstructured, - existingObjects: []client.Object{fake.NamespaceObject("foo")}, + name: "no implicit namespace if unstructured, present, but unmanaged", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, + existingObjects: []client.Object{fake.NamespaceObject("foo")}, parsed: []ast.FileObject{ fake.Role(core.Namespace("foo")), }, @@ -244,8 +271,9 @@ func TestRoot_Parse(t *testing.T) { }, }, { - name: "no implicit namespace if unstructured and namespace is config-management-system", - format: filesystem.SourceFormatUnstructured, + name: "no implicit namespace if unstructured and namespace is config-management-system", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, parsed: []ast.FileObject{ fake.RootSyncV1Beta1("test", fake.WithRootSyncSourceType(v1beta1.GitSource), gitSpec("https://github.com/test/test.git", configsync.AuthNone)), }, @@ -266,8 +294,9 @@ func TestRoot_Parse(t *testing.T) { }, }, { - name: "multiple objects share a single implicit namespace", - format: filesystem.SourceFormatUnstructured, + name: "multiple objects share a single implicit namespace", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, parsed: []ast.FileObject{ fake.Role(core.Namespace("bar")), fake.ConfigMap(core.Namespace("bar")), @@ -312,8 +341,9 @@ func TestRoot_Parse(t *testing.T) { }, }, { - name: "multiple implicit namespaces", - format: filesystem.SourceFormatUnstructured, + name: "multiple implicit namespaces", + format: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, existingObjects: []client.Object{ fake.NamespaceObject("foo"), // foo exists but not managed, should NOT be added as an implicit namespace // bar not exists, should be added as an implicit namespace @@ -431,7 +461,8 @@ func TestRoot_Parse(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { parser := &root{ - sourceFormat: tc.format, + sourceFormat: tc.format, + namespaceStrategy: tc.namespaceStrategy, opts: opts{ parser: &fakeParser{parse: tc.parsed}, syncName: rootSyncName, @@ -676,7 +707,8 @@ func TestRoot_Parse_Discovery(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { parser := &root{ - sourceFormat: filesystem.SourceFormatUnstructured, + sourceFormat: filesystem.SourceFormatUnstructured, + namespaceStrategy: configsync.NamespaceStrategyImplicit, opts: opts{ parser: &fakeParser{parse: tc.parsed}, syncName: rootSyncName, diff --git a/pkg/reconciler/reconciler.go b/pkg/reconciler/reconciler.go index cb4ec7a98d..a366829379 100644 --- a/pkg/reconciler/reconciler.go +++ b/pkg/reconciler/reconciler.go @@ -22,6 +22,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" + "kpt.dev/configsync/pkg/api/configsync" "kpt.dev/configsync/pkg/api/configsync/v1beta1" "kpt.dev/configsync/pkg/applier" "kpt.dev/configsync/pkg/client/restconfig" @@ -119,6 +120,8 @@ type Options struct { type RootOptions struct { // SourceFormat is how the Root repository is structured. SourceFormat filesystem.SourceFormat + // NamespaceStrategy indicates the NamespaceStrategy used by this reconciler. + NamespaceStrategy configsync.NamespaceStrategy } // Run configures and starts the various components of a reconciler process. @@ -217,7 +220,8 @@ func Run(opts Options) { } if opts.ReconcilerScope == declared.RootReconciler { parser, err = parse.NewRootRunner(opts.ClusterName, opts.SyncName, opts.ReconcilerName, opts.SourceFormat, &reader.File{}, cl, - opts.PollingPeriod, opts.ResyncPeriod, opts.RetryPeriod, opts.StatusUpdatePeriod, fs, discoveryClient, decls, supervisor, rem, opts.RenderingEnabled) + opts.PollingPeriod, opts.ResyncPeriod, opts.RetryPeriod, opts.StatusUpdatePeriod, fs, discoveryClient, decls, supervisor, rem, opts.RenderingEnabled, + opts.NamespaceStrategy) if err != nil { klog.Fatalf("Instantiating Root Repository Parser: %v", err) } diff --git a/pkg/reconcilermanager/constants.go b/pkg/reconcilermanager/constants.go index 8a77fa7a04..e980243d56 100644 --- a/pkg/reconcilermanager/constants.go +++ b/pkg/reconcilermanager/constants.go @@ -88,6 +88,10 @@ const ( // RenderingEnabled tells the reconciler container whether the hydration-controller // container is running in the Pod. RenderingEnabled = "RENDERING_ENABLED" + + // NamespaceStrategy tells the reconciler container which NamespaceStrategy to + // use + NamespaceStrategy = "NAMESPACE_STRATEGY" ) const ( diff --git a/pkg/reconcilermanager/controllers/reposync_controller.go b/pkg/reconcilermanager/controllers/reposync_controller.go index 58fc49b3d4..1b4bdd1f73 100644 --- a/pkg/reconcilermanager/controllers/reposync_controller.go +++ b/pkg/reconcilermanager/controllers/reposync_controller.go @@ -1079,7 +1079,7 @@ func (r *RepoSyncReconciler) mutationsFor(ctx context.Context, rs *v1beta1.RepoS addContainer = false } else { container.Env = append(container.Env, containerEnvs[container.Name]...) - container.Image = updateHydrationControllerImage(container.Image, *rs.Spec.SafeOverride()) + container.Image = updateHydrationControllerImage(container.Image, rs.Spec.SafeOverride().OverrideSpec) } case reconcilermanager.OciSync: // Don't add the oci-sync container when sourceType is NOT oci. diff --git a/pkg/reconcilermanager/controllers/reposync_controller_test.go b/pkg/reconcilermanager/controllers/reposync_controller_test.go index c1f99eaccd..00ef388ab4 100644 --- a/pkg/reconcilermanager/controllers/reposync_controller_test.go +++ b/pkg/reconcilermanager/controllers/reposync_controller_test.go @@ -188,8 +188,10 @@ func reposyncGCPSAEmail(email string) func(sync *v1beta1.RepoSync) { func reposyncOverrideResources(containers []v1beta1.ContainerResourcesSpec) func(sync *v1beta1.RepoSync) { return func(sync *v1beta1.RepoSync) { - sync.Spec.Override = &v1beta1.OverrideSpec{ - Resources: containers, + sync.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: containers, + }, } } } @@ -407,8 +409,10 @@ func TestCreateAndUpdateNamespaceReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideReconcilerCPUAndGitSyncMemResources, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideReconcilerCPUAndGitSyncMemResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) @@ -447,7 +451,7 @@ func TestCreateAndUpdateNamespaceReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) } @@ -551,8 +555,10 @@ func TestUpdateNamespaceReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideReconcilerAndGitSyncResources, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideReconcilerAndGitSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) @@ -607,8 +613,10 @@ func TestUpdateNamespaceReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideReconcilerResources, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideReconcilerResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) @@ -655,8 +663,10 @@ func TestUpdateNamespaceReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideGitSyncResources, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideGitSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) @@ -694,7 +704,7 @@ func TestUpdateNamespaceReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) } @@ -1476,7 +1486,7 @@ func TestRepoSyncUpdateOverrideGitSyncDepth(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v, want error: nil", err) } @@ -1631,7 +1641,7 @@ func TestRepoSyncUpdateOverrideReconcileTimeout(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v, want error: nil", err) } @@ -1783,7 +1793,7 @@ func TestRepoSyncUpdateOverrideAPIServerTimeout(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v, want error: nil", err) } @@ -3108,8 +3118,10 @@ func TestRepoSyncWithHelm(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideHelmSyncResources, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideHelmSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v, want error: nil", err) @@ -3322,8 +3334,10 @@ func TestRepoSyncWithOCI(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideOciSyncResources, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideOciSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) @@ -3807,8 +3821,10 @@ func TestUpdateNamespaceReconcilerLogLevelWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the repo sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - LogLevels: overrideLogLevel, + rs.Spec.Override = &v1beta1.RepoSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + LogLevels: overrideLogLevel, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the repo sync request, got error: %v", err) diff --git a/pkg/reconcilermanager/controllers/rootsync_controller.go b/pkg/reconcilermanager/controllers/rootsync_controller.go index 79f5585315..e897b485a3 100644 --- a/pkg/reconcilermanager/controllers/rootsync_controller.go +++ b/pkg/reconcilermanager/controllers/rootsync_controller.go @@ -663,7 +663,18 @@ func (r *RootSyncReconciler) mapSecretToRootSyncs(secret client.Object) []reconc func (r *RootSyncReconciler) populateContainerEnvs(ctx context.Context, rs *v1beta1.RootSync, reconcilerName string) map[string][]corev1.EnvVar { result := map[string][]corev1.EnvVar{ reconcilermanager.HydrationController: hydrationEnvs(rs.Spec.SourceType, rs.Spec.Git, rs.Spec.Oci, declared.RootReconciler, reconcilerName, r.hydrationPollingPeriod.String()), - reconcilermanager.Reconciler: append(reconcilerEnvs(r.clusterName, rs.Name, rs.Generation, reconcilerName, declared.RootReconciler, rs.Spec.SourceType, rs.Spec.Git, rs.Spec.Oci, rootsync.GetHelmBase(rs.Spec.Helm), r.reconcilerPollingPeriod.String(), rs.Spec.SafeOverride().StatusMode, v1beta1.GetReconcileTimeout(rs.Spec.SafeOverride().ReconcileTimeout), v1beta1.GetAPIServerTimeout(rs.Spec.SafeOverride().APIServerTimeout), enableRendering(rs.GetAnnotations())), sourceFormatEnv(rs.Spec.SourceFormat)), + reconcilermanager.Reconciler: append( + reconcilerEnvs( + r.clusterName, rs.Name, rs.Generation, reconcilerName, declared.RootReconciler, + rs.Spec.SourceType, rs.Spec.Git, rs.Spec.Oci, rootsync.GetHelmBase(rs.Spec.Helm), + r.reconcilerPollingPeriod.String(), rs.Spec.SafeOverride().StatusMode, + v1beta1.GetReconcileTimeout(rs.Spec.SafeOverride().ReconcileTimeout), + v1beta1.GetAPIServerTimeout(rs.Spec.SafeOverride().APIServerTimeout), + enableRendering(rs.GetAnnotations()), + ), + sourceFormatEnv(rs.Spec.SourceFormat), + namespaceStrategyEnv(rs.Spec.SafeOverride().NamespaceStrategy), + ), } switch v1beta1.SourceType(rs.Spec.SourceType) { case v1beta1.GitSource: @@ -929,7 +940,7 @@ func (r *RootSyncReconciler) mutationsFor(ctx context.Context, rs *v1beta1.RootS addContainer = false } else { container.Env = append(container.Env, containerEnvs[container.Name]...) - container.Image = updateHydrationControllerImage(container.Image, *rs.Spec.SafeOverride()) + container.Image = updateHydrationControllerImage(container.Image, rs.Spec.SafeOverride().OverrideSpec) } case reconcilermanager.OciSync: // Don't add the oci-sync container when sourceType is NOT oci. diff --git a/pkg/reconcilermanager/controllers/rootsync_controller_test.go b/pkg/reconcilermanager/controllers/rootsync_controller_test.go index 090a5c8658..e98df869f9 100644 --- a/pkg/reconcilermanager/controllers/rootsync_controller_test.go +++ b/pkg/reconcilermanager/controllers/rootsync_controller_test.go @@ -191,8 +191,10 @@ func rootsyncGCPSAEmail(email string) func(sync *v1beta1.RootSync) { func rootsyncOverrideResources(containers []v1beta1.ContainerResourcesSpec) func(sync *v1beta1.RootSync) { return func(sync *v1beta1.RootSync) { - sync.Spec.Override = &v1beta1.OverrideSpec{ - Resources: containers, + sync.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: containers, + }, } } } @@ -359,8 +361,10 @@ func TestCreateAndUpdateRootReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideSelectedResources, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideSelectedResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) @@ -481,8 +485,10 @@ func TestUpdateRootReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideAllContainerResources, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideAllContainerResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) @@ -532,8 +538,10 @@ func TestUpdateRootReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideReconcilerAndHydrationResources, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideReconcilerAndHydrationResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) @@ -573,8 +581,10 @@ func TestUpdateRootReconcilerWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideGitSyncResources, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideGitSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) @@ -606,7 +616,7 @@ func TestUpdateRootReconcilerWithOverride(t *testing.T) { t.Fatalf("failed to get the root sync: %v", err) } // Clear rs.Spec.Override - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) } @@ -1168,7 +1178,7 @@ func TestRootSyncUpdateOverrideGitSyncDepth(t *testing.T) { t.Fatalf("failed to get the root sync: %v", err) } // Clear rs.Spec.Override - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) } @@ -1319,7 +1329,7 @@ func TestRootSyncUpdateOverrideReconcileTimeout(t *testing.T) { t.Fatalf("failed to get the root sync: %v", err) } // Clear rs.Spec.Override - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) } @@ -1465,7 +1475,7 @@ func TestRootSyncUpdateOverrideAPIServerTimeout(t *testing.T) { t.Fatalf("failed to get the root sync: %v", err) } // Clear rs.Spec.Override - rs.Spec.Override = &v1beta1.OverrideSpec{} + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{} if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) } @@ -2567,8 +2577,10 @@ func TestRootSyncWithHelm(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideHelmSyncResources, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideHelmSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) @@ -2775,8 +2787,10 @@ func TestRootSyncWithOCI(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - Resources: overrideOciSyncResources, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + Resources: overrideOciSyncResources, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) @@ -3098,6 +3112,7 @@ func TestPopulateRootContainerEnvs(t *testing.T) { reconcilermanager.SourceRepoKey: rootsyncRepo, reconcilermanager.SourceTypeKey: string(gitSource), filesystem.SourceFormatKey: "", + reconcilermanager.NamespaceStrategy: string(configsync.NamespaceStrategyImplicit), reconcilermanager.StatusMode: "enabled", reconcilermanager.SourceBranchKey: "master", reconcilermanager.SourceRevKey: "HEAD", @@ -3255,8 +3270,10 @@ func TestUpdateRootReconcilerLogLevelWithOverride(t *testing.T) { if err := fakeClient.Get(ctx, client.ObjectKeyFromObject(rs), rs); err != nil { t.Fatalf("failed to get the root sync: %v", err) } - rs.Spec.Override = &v1beta1.OverrideSpec{ - LogLevels: overrideLogLevel, + rs.Spec.Override = &v1beta1.RootSyncOverrideSpec{ + OverrideSpec: v1beta1.OverrideSpec{ + LogLevels: overrideLogLevel, + }, } if err := fakeClient.Update(ctx, rs); err != nil { t.Fatalf("failed to update the root sync request, got error: %v, want error: nil", err) diff --git a/pkg/reconcilermanager/controllers/util.go b/pkg/reconcilermanager/controllers/util.go index 97f048c7c4..88e8e362ef 100644 --- a/pkg/reconcilermanager/controllers/util.go +++ b/pkg/reconcilermanager/controllers/util.go @@ -204,6 +204,17 @@ func sourceFormatEnv(format string) corev1.EnvVar { } } +// namespaceStrategyEnv returns the environment variable for NAMESPACE_STRATEGY in the reconciler container. +func namespaceStrategyEnv(strategy configsync.NamespaceStrategy) corev1.EnvVar { + if strategy == "" { + strategy = configsync.NamespaceStrategyImplicit + } + return corev1.EnvVar{ + Name: reconcilermanager.NamespaceStrategy, + Value: string(strategy), + } +} + // ociSyncEnvs returns the environment variables for the oci-sync container. func ociSyncEnvs(image string, auth configsync.AuthType, period float64) []corev1.EnvVar { var result []corev1.EnvVar