diff --git a/cmd/timoni/bundle_apply.go b/cmd/timoni/bundle_apply.go index 60805f88..781c2135 100644 --- a/cmd/timoni/bundle_apply.go +++ b/cmd/timoni/bundle_apply.go @@ -133,88 +133,108 @@ func runBundleApplyCmd(cmd *cobra.Command, _ []string) error { maps.Copy(runtimeValues, engine.GetEnv()) } - if len(bundleArgs.runtimeFiles) > 0 { - rt, err := buildRuntime(bundleArgs.runtimeFiles) - if err != nil { - return err - } + rt, err := buildRuntime(bundleArgs.runtimeFiles) + if err != nil { + return err + } + + clusters := rt.SelectClusters(bundleArgs.runtimeCluster, bundleArgs.runtimeClusterGroup) + if len(clusters) == 0 { + return fmt.Errorf("no cluster found") + } + ctxPull, cancel := context.WithTimeout(ctx, rootArgs.timeout) + defer cancel() + + for _, cluster := range clusters { + kubeconfigArgs.Context = &cluster.KubeContext + + clusterValues := make(map[string]string) + + // add values from env + maps.Copy(clusterValues, runtimeValues) + + // add values from cluster rm, err := runtime.NewResourceManager(kubeconfigArgs) if err != nil { return err } - reader := runtime.NewResourceReader(rm) rv, err := reader.Read(ctx, rt.Refs) if err != nil { return err } + maps.Copy(clusterValues, rv) - maps.Copy(runtimeValues, rv) - } - - if err := bm.InitWorkspace(tmpDir, runtimeValues); err != nil { - return err - } + // add cluster info + maps.Copy(clusterValues, cluster.NameGroupValues()) - v, err := bm.Build() - if err != nil { - return describeErr(tmpDir, "failed to build bundle", err) - } + // create cluster workspace + workspace := path.Join(tmpDir, cluster.Name) + if err := os.MkdirAll(workspace, os.ModePerm); err != nil { + return err + } - bundle, err := bm.GetBundle(v) - if err != nil { - return err - } + if err := bm.InitWorkspace(workspace, clusterValues); err != nil { + return describeErr(workspace, "failed to parse bundle", err) + } - log := LoggerBundle(cmd.Context(), bundle.Name, apiv1.RuntimeDefaultName) + v, err := bm.Build() + if err != nil { + return describeErr(tmpDir, "failed to build bundle", err) + } - if !bundleApplyArgs.overwriteOwnership { - err = bundleInstancesOwnershipConflicts(bundle.Instances) + bundle, err := bm.GetBundle(v) if err != nil { return err } - } - ctxPull, cancel := context.WithTimeout(ctx, rootArgs.timeout) - defer cancel() + log := LoggerBundle(cmd.Context(), bundle.Name, cluster.Name) - for _, instance := range bundle.Instances { - spin := StartSpinner(fmt.Sprintf("pulling %s", instance.Module.Repository)) - pullErr := fetchBundleInstanceModule(ctxPull, instance, tmpDir) - spin.Stop() - if pullErr != nil { - return pullErr + if !bundleApplyArgs.overwriteOwnership { + err = bundleInstancesOwnershipConflicts(bundle.Instances) + if err != nil { + return err + } } - } - kubeVersion, err := runtime.ServerVersion(kubeconfigArgs) - if err != nil { - return err - } - - if bundleApplyArgs.dryrun || bundleApplyArgs.diff { - log.Info(fmt.Sprintf("applying %v instance(s) %s", - len(bundle.Instances), colorizeDryRun("(server dry run)"))) - } else { - log.Info(fmt.Sprintf("applying %v instance(s)", - len(bundle.Instances))) - } + for _, instance := range bundle.Instances { + spin := StartSpinner(fmt.Sprintf("pulling %s", instance.Module.Repository)) + pullErr := fetchBundleInstanceModule(ctxPull, instance, tmpDir) + spin.Stop() + if pullErr != nil { + return pullErr + } + } - for _, instance := range bundle.Instances { - if err := applyBundleInstance(logr.NewContext(ctx, log), cuectx, instance, kubeVersion, tmpDir); err != nil { + kubeVersion, err := runtime.ServerVersion(kubeconfigArgs) + if err != nil { return err } - } - elapsed := time.Since(start) - if bundleApplyArgs.dryrun || bundleApplyArgs.diff { - log.Info(fmt.Sprintf("applied successfully %s", - colorizeDryRun("(server dry run)"))) - } else { - log.Info(fmt.Sprintf("applied successfully in %s", elapsed.Round(time.Second))) - } + if bundleApplyArgs.dryrun || bundleApplyArgs.diff { + log.Info(fmt.Sprintf("applying %v instance(s) %s", + len(bundle.Instances), colorizeDryRun("(server dry run)"))) + } else { + log.Info(fmt.Sprintf("applying %v instance(s)", + len(bundle.Instances))) + } + for _, instance := range bundle.Instances { + instance.Cluster = cluster.Name + if err := applyBundleInstance(logr.NewContext(ctx, log), cuectx, instance, kubeVersion, tmpDir); err != nil { + return err + } + } + + elapsed := time.Since(start) + if bundleApplyArgs.dryrun || bundleApplyArgs.diff { + log.Info(fmt.Sprintf("applied successfully %s", + colorizeDryRun("(server dry run)"))) + } else { + log.Info(fmt.Sprintf("applied successfully in %s", elapsed.Round(time.Second))) + } + } return nil } @@ -252,7 +272,7 @@ func fetchBundleInstanceModule(ctx context.Context, instance *engine.BundleInsta } func applyBundleInstance(ctx context.Context, cuectx *cue.Context, instance *engine.BundleInstance, kubeVersion string, rootDir string) error { - log := LoggerBundleInstance(ctx, instance.Bundle, apiv1.RuntimeDefaultName, instance.Name) + log := LoggerBundleInstance(ctx, instance.Bundle, instance.Cluster, instance.Name) modDir := path.Join(rootDir, instance.Name, "module") builder := engine.NewModuleBuilder( diff --git a/cmd/timoni/bundle_apply_test.go b/cmd/timoni/bundle_apply_test.go index da8169b4..272ea702 100644 --- a/cmd/timoni/bundle_apply_test.go +++ b/cmd/timoni/bundle_apply_test.go @@ -443,6 +443,8 @@ bundle: { values: client: enabled: true @timoni(runtime:bool:CLIENT) values: server: enabled: false @timoni(runtime:bool:ENABLED) values: domain: string @timoni(runtime:string:DOMAIN) + values: metadata: labels: "cluster": string @timoni(runtime:string:TIMONI_CLUSTER_NAME) + values: metadata: labels: "env": string @timoni(runtime:string:TIMONI_CLUSTER_GROUP) } } } @@ -452,6 +454,12 @@ bundle: { runtime: { apiVersion: "v1alpha1" name: "test" + clusters: { + "test": { + group: "testing" + kubeContext: "envtest" + } + } values: [ { query: "k8s:v1:Secret:%[1]s:%[2]s" @@ -518,6 +526,8 @@ runtime: { err = envTestClient.Get(context.Background(), client.ObjectKeyFromObject(scm), scm) g.Expect(err).ToNot(HaveOccurred()) g.Expect(scm.Data["hostname"]).To(BeEquivalentTo("test.local")) + g.Expect(scm.GetLabels()).To(HaveKeyWithValue("cluster", "test")) + g.Expect(scm.GetLabels()).To(HaveKeyWithValue("env", "testing")) }) t.Run("overrides env vars", func(t *testing.T) { @@ -557,4 +567,28 @@ runtime: { g.Expect(err).To(HaveOccurred()) g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) + + t.Run("fails for wrong cluster name selector", func(t *testing.T) { + g := NewWithT(t) + + cmd := fmt.Sprintf("bundle apply -p main --wait -f- -r=%s --runtime-cluster=prod", + runtimePath, + ) + + _, err := executeCommandWithIn(cmd, strings.NewReader(bundleData)) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("no cluster found")) + }) + + t.Run("fails for wrong cluster group selector", func(t *testing.T) { + g := NewWithT(t) + + cmd := fmt.Sprintf("bundle apply -p main --wait -f- -r=%s --runtime-group=prod", + runtimePath, + ) + + _, err := executeCommandWithIn(cmd, strings.NewReader(bundleData)) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("no cluster found")) + }) } diff --git a/internal/engine/bundle_builder.go b/internal/engine/bundle_builder.go index 0ac161f2..d467eedf 100644 --- a/internal/engine/bundle_builder.go +++ b/internal/engine/bundle_builder.go @@ -46,6 +46,7 @@ type Bundle struct { type BundleInstance struct { Bundle string + Cluster string Name string Namespace string Module apiv1.ModuleReference