Skip to content

Commit

Permalink
Add multi-cluster support to bundle apply cmd
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Prodan <[email protected]>
  • Loading branch information
stefanprodan committed Nov 18, 2023
1 parent 7d08ae9 commit 9a67eca
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 56 deletions.
132 changes: 76 additions & 56 deletions cmd/timoni/bundle_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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(
Expand Down
34 changes: 34 additions & 0 deletions cmd/timoni/bundle_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand All @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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"))
})
}
1 change: 1 addition & 0 deletions internal/engine/bundle_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Bundle struct {

type BundleInstance struct {
Bundle string
Cluster string
Name string
Namespace string
Module apiv1.ModuleReference
Expand Down

0 comments on commit 9a67eca

Please sign in to comment.