diff --git a/cmd/config/configcobra/cmds.go b/cmd/config/configcobra/cmds.go index 0fd9c86bd..3e1126e1d 100644 --- a/cmd/config/configcobra/cmds.go +++ b/cmd/config/configcobra/cmds.go @@ -74,6 +74,7 @@ func NewConfigCommand(name string) *cobra.Command { root.AddCommand(commands.CatCommand(name)) root.AddCommand(commands.FmtCommand(name)) root.AddCommand(commands.MergeCommand(name)) + root.AddCommand(commands.Merge3Command(name)) root.AddCommand(commands.CountCommand(name)) root.AddCommand(commands.RunFnCommand(name)) root.AddCommand(commands.SubCommand(name)) diff --git a/cmd/config/docs/commands/merge3.md b/cmd/config/docs/commands/merge3.md new file mode 100644 index 000000000..7774a17d0 --- /dev/null +++ b/cmd/config/docs/commands/merge3.md @@ -0,0 +1,23 @@ +## merge3 + +[Alpha] Merge diff of Resource configuration files into a destination (3-way) + +### Synopsis + +[Alpha] Merge diff of Resource configuration files into a destination (3-way) + +Merge3 performs a 3-way merge by applying the diff between 2 sets of Resources to a 3rd set. + +Merge3 may be for rebasing changes to a forked set of configuration -- e.g. compute the difference between the original +set of Resources that was forked and an updated set of those Resources, then apply that difference to the fork. + +If a field value differs between the ORIGINAL_DIR and UPDATED_DIR, the value from the UPDATED_DIR is taken and applied +to the Resource in the DEST_DIR. + +For information on merge rules, run: + + kustomize config docs-merge3 + +### Examples + + kustomize config merge3 --ancestor a/ --from b/ --to c/ \ No newline at end of file diff --git a/cmd/config/internal/commands/merge3.go b/cmd/config/internal/commands/merge3.go new file mode 100644 index 000000000..405638463 --- /dev/null +++ b/cmd/config/internal/commands/merge3.go @@ -0,0 +1,55 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" + "sigs.k8s.io/kustomize/kyaml/kio/filters" +) + +func GetMerge3Runner(name string) *Merge3Runner { + r := &Merge3Runner{} + c := &cobra.Command{ + Use: "merge3 --ancestor [ORIGINAL_DIR] --from [UPDATED_DIR] --to [DESTINATION_DIR]", + Short: commands.Merge3Short, + Long: commands.Merge3Long, + Example: commands.Merge3Examples, + RunE: r.runE, + } + fixDocs(name, c) + c.Flags().StringVar(&r.ancestor, "ancestor", "", + "Path to original package") + c.Flags().StringVar(&r.fromDir, "from", "", + "Path to updated package") + c.Flags().StringVar(&r.toDir, "to", "", + "Path to destination package") + + r.Command = c + return r +} + +func Merge3Command(name string) *cobra.Command { + return GetMerge3Runner(name).Command +} + +// Merge3Runner contains the run function +type Merge3Runner struct { + Command *cobra.Command + ancestor string + fromDir string + toDir string +} + +func (r *Merge3Runner) runE(c *cobra.Command, args []string) error { + err := filters.Merge3{ + OriginalPath: r.ancestor, + UpdatedPath: r.fromDir, + DestPath: r.toDir, + }.Merge() + if err != nil { + return err + } + return nil +} diff --git a/cmd/config/internal/commands/merge3_test.go b/cmd/config/internal/commands/merge3_test.go new file mode 100644 index 000000000..171aec4d7 --- /dev/null +++ b/cmd/config/internal/commands/merge3_test.go @@ -0,0 +1,236 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/config/internal/commands" + "sigs.k8s.io/kustomize/kyaml/copyutil" +) + +// TestMerge3Command verifies the merge3 correctly applies the diff between 2 sets of resources into another +func TestMerge3Command(t *testing.T) { + datadir, err := ioutil.TempDir("", "test-data") + defer os.RemoveAll(datadir) + if !assert.NoError(t, err) { + return + } + + err = ioutil.WriteFile(filepath.Join(datadir, "java-deployment.resource.yaml"), []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: app + labels: + app: java +spec: + replicas: 1 + selector: + matchLabels: + app: java + template: + metadata: + labels: + app: java + spec: + restartPolicy: Always + containers: + - name: app + image: gcr.io/project/app:version + command: + - java + - -jar + - /app.jar + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: app-config + env: + - name: JAVA_OPTS + value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap + -Djava.security.egd=file:/dev/./urandom + imagePullPolicy: Always + minReadySeconds: 5 +`), 0600) + if !assert.NoError(t, err) { + return + } + + expected_dir, err := ioutil.TempDir("", "test-data-expected") + defer os.RemoveAll(expected_dir) + if !assert.NoError(t, err) { + return + } + + err = ioutil.WriteFile(filepath.Join(expected_dir, "java-deployment.resource.yaml"), []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: app + labels: + app: java + new-local: label + new-remote: label +spec: + replicas: 3 + selector: + matchLabels: + app: java + template: + metadata: + labels: + app: java + spec: + restartPolicy: Always + containers: + - name: app + image: gcr.io/project/app:version + command: + - java + - -jar + - /app.jar + - otherstuff + args: + - foo + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: app-config + env: + - name: JAVA_OPTS + value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap + -Djava.security.egd=file:/dev/./urandom + imagePullPolicy: Always + minReadySeconds: 20 +`), 0600) + if !assert.NoError(t, err) { + return + } + + updated_dir, err := ioutil.TempDir("", "test-data-updated") + defer os.RemoveAll(updated_dir) + if !assert.NoError(t, err) { + return + } + + err = ioutil.WriteFile(filepath.Join(updated_dir, "java-deployment.resource.yaml"), []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: app + labels: + app: java + new-remote: label +spec: + replicas: 3 + selector: + matchLabels: + app: java + template: + metadata: + labels: + app: java + spec: + restartPolicy: Always + containers: + - name: app + image: gcr.io/project/app:version + command: + - java + - -jar + - /app.jar + - otherstuff + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: app-config + env: + - name: JAVA_OPTS + value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap + -Djava.security.egd=file:/dev/./urandom + imagePullPolicy: Always + minReadySeconds: 5 +`), 0600) + if !assert.NoError(t, err) { + return + } + + dest_dir, err := ioutil.TempDir("", "test-data-dest") + defer os.RemoveAll(dest_dir) + if !assert.NoError(t, err) { + return + } + + err = ioutil.WriteFile(filepath.Join(dest_dir, "java-deployment.resource.yaml"), []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: app + labels: + app: java + new-local: label +spec: + replicas: 2 + selector: + matchLabels: + app: java + template: + metadata: + labels: + app: java + spec: + restartPolicy: Always + containers: + - name: app + image: gcr.io/project/app:version + command: + - java + - -jar + - /app.jar + args: + - foo + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: app-config + env: + - name: JAVA_OPTS + value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap + -Djava.security.egd=file:/dev/./urandom + imagePullPolicy: Always + minReadySeconds: 20 +`), 0600) + if !assert.NoError(t, err) { + return + } + + // Perform merge3 with newly created sets + r := commands.GetMerge3Runner("") + r.Command.SetArgs([]string{ + "--ancestor", + datadir, + "--from", + updated_dir, + "--to", + dest_dir, + }) + if !assert.NoError(t, r.Command.Execute()) { + return + } + + diffs, err := copyutil.Diff(dest_dir, expected_dir) + if !assert.NoError(t, err) { + t.FailNow() + } + + // Verify there are no diffs + if !assert.Empty(t, diffs.List()) { + t.FailNow() + } +} diff --git a/cmd/config/internal/generateddocs/commands/docs.go b/cmd/config/internal/generateddocs/commands/docs.go index 4ee4d845e..ece7df4d6 100644 --- a/cmd/config/internal/generateddocs/commands/docs.go +++ b/cmd/config/internal/generateddocs/commands/docs.go @@ -136,6 +136,25 @@ For information on merge rules, run: var MergeExamples = ` cat resources_and_patches.yaml | kustomize config merge > merged_resources.yaml` +var Merge3Short = `[Alpha] Merge diff of Resource configuration files into a destination (3-way)` +var Merge3Long = ` +[Alpha] Merge diff of Resource configuration files into a destination (3-way) + +Merge3 performs a 3-way merge by applying the diff between 2 sets of Resources to a 3rd set. + +Merge3 may be for rebasing changes to a forked set of configuration -- e.g. compute the difference between the original +set of Resources that was forked and an updated set of those Resources, then apply that difference to the fork. + +If a field value differs between the ORIGINAL_DIR and UPDATED_DIR, the value from the UPDATED_DIR is taken and applied +to the Resource in the DEST_DIR. + +For information on merge rules, run: + + kustomize config docs-merge3 +` +var Merge3Examples = ` + kustomize config merge3 --ancestor a/ --from b/ --to c/` + var RunFnsShort = `[Alpha] Reoncile config functions to Resources.` var RunFnsLong = ` [Alpha] Reconcile config functions to Resources.