From 236166097e528c2a2d5d678866f9d5a7dec08e83 Mon Sep 17 00:00:00 2001 From: Anna Song Date: Wed, 1 Feb 2023 08:11:07 -0800 Subject: [PATCH] Add localize command handle (#4959) * Add localize command handle * Align to kustomize command conventions * Print success msg --- kustomize/commands/commands.go | 2 + kustomize/commands/localize/localize.go | 98 ++++++++++++ kustomize/commands/localize/localize_test.go | 153 +++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 kustomize/commands/localize/localize.go create mode 100644 kustomize/commands/localize/localize_test.go diff --git a/kustomize/commands/commands.go b/kustomize/commands/commands.go index 3f5e197cd..184da4e37 100644 --- a/kustomize/commands/commands.go +++ b/kustomize/commands/commands.go @@ -17,6 +17,7 @@ import ( "sigs.k8s.io/kustomize/kustomize/v4/commands/build" "sigs.k8s.io/kustomize/kustomize/v4/commands/create" "sigs.k8s.io/kustomize/kustomize/v4/commands/edit" + "sigs.k8s.io/kustomize/kustomize/v4/commands/localize" "sigs.k8s.io/kustomize/kustomize/v4/commands/openapi" "sigs.k8s.io/kustomize/kustomize/v4/commands/version" "sigs.k8s.io/kustomize/kyaml/filesys" @@ -53,6 +54,7 @@ See https://sigs.k8s.io/kustomize create.NewCmdCreate(fSys, pvd.GetResourceFactory()), version.NewCmdVersion(stdOut), openapi.NewCmdOpenAPI(stdOut), + localize.NewCmdLocalize(fSys, stdOut), ) configcobra.AddCommands(c, konfig.ProgramName) diff --git a/kustomize/commands/localize/localize.go b/kustomize/commands/localize/localize.go new file mode 100644 index 000000000..fe9747a30 --- /dev/null +++ b/kustomize/commands/localize/localize.go @@ -0,0 +1,98 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package localize + +import ( + "fmt" + "io" + "log" + + "github.com/spf13/cobra" + lclzr "sigs.k8s.io/kustomize/api/krusty/localizer" + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +const numArgs = 2 + +type arguments struct { + target string + dest string +} + +type flags struct { + scope string +} + +// NewCmdLocalize returns a new localize command. +func NewCmdLocalize(fs filesys.FileSystem, writer io.Writer) *cobra.Command { + log.SetOutput(writer) + var f flags + cmd := &cobra.Command{ + Use: "localize [target [destination]]", + Short: "[Alpha] Creates localized copy of target kustomization root at destination", + Long: `[Alpha] Creates copy of target kustomization directory or +versioned URL at destination, where remote references in the original +are replaced by local references to the downloaded remote content. + +If target is not specified, the current working directory will be used. +Destination is a path to a new directory in an existing directory. If +destination is not specified, a new directory will be created in the current +working directory. + +For details, see: https://kubectl.docs.kubernetes.io/references/kustomize/cmd/ + +Disclaimer: +This command does not yet localize helm or KRM plugin fields. This command also +alphabetizes kustomization fields in the localized copy. +`, + Example: ` +# Localize the current working directory, with default scope and destination +kustomize localize + +# Localize some local directory, with scope and default destination +kustomize localize /home/path/scope/target --scope /home/path/scope + +# Localize remote at set destination relative to working directory +kustomize localize https://github.com/kubernetes-sigs/kustomize//api/krusty/testdata/localize/simple?ref=v4.5.7 path/non-existing-dir +`, + SilenceUsage: true, + Args: cobra.MaximumNArgs(numArgs), + RunE: func(cmd *cobra.Command, rawArgs []string) error { + args := matchArgs(rawArgs) + dst, err := lclzr.Run(fs, args.target, f.scope, args.dest) + if err != nil { + return errors.Wrap(err) + } + successMsg := fmt.Sprintf("SUCCESS: localized %q to directory %s\n", args.target, dst) + _, err = writer.Write([]byte(successMsg)) + return errors.Wrap(err) + }, + } + // no shorthand to avoid conflation with other flags + cmd.Flags().StringVar(&f.scope, + "scope", + "", + `Path to directory inside of which localize is limited to running. +Cannot specify for remote targets, as scope is by default the containing repo. +If not specified for local target, scope defaults to target. +`) + return cmd +} + +// matchArgs matches user-entered userArgs, which cannot exceed max length, with +// arguments. +func matchArgs(rawArgs []string) arguments { + var args arguments + switch len(rawArgs) { + case numArgs: + args.dest = rawArgs[1] + fallthrough + case 1: + args.target = rawArgs[0] + case 0: + args.target = filesys.SelfDir + } + return args +} diff --git a/kustomize/commands/localize/localize_test.go b/kustomize/commands/localize/localize_test.go new file mode 100644 index 000000000..7e43093e7 --- /dev/null +++ b/kustomize/commands/localize/localize_test.go @@ -0,0 +1,153 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package localize_test + +import ( + "bytes" + "fmt" + "log" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + loctest "sigs.k8s.io/kustomize/api/testutils/localizertest" + "sigs.k8s.io/kustomize/kustomize/v4/commands/localize" +) + +const deployment = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +` + +func TestScopeFlag(t *testing.T) { + kustomizations := map[string]string{ + filepath.Join("target", "kustomization.yaml"): fmt.Sprintf(`resources: +- %s +`, filepath.Join("..", "base")), + filepath.Join("base", "kustomization.yaml"): `resources: +- deployment.yaml +`, + filepath.Join("base", "deployment.yaml"): deployment, + } + expected, actual, testDir := loctest.PrepareFs(t, []string{ + "target", + "base", + }, kustomizations) + + cmd := localize.NewCmdLocalize(actual, new(bytes.Buffer)) + require.NoError(t, cmd.Flags().Set("scope", testDir.String())) + err := cmd.RunE(cmd, []string{ + testDir.Join("target"), + testDir.Join("dst"), + }) + require.NoError(t, err) + + loctest.SetupDir(t, expected, testDir.Join("dst"), kustomizations) + loctest.CheckFs(t, testDir.String(), expected, actual) +} + +func TestOptionalArgs(t *testing.T) { + for name, args := range map[string][]string{ + "no_target": {}, + "no_dst": {"."}, + } { + t.Run(name, func(t *testing.T) { + kust := map[string]string{ + "kustomization.yaml": `resources: +- deployment.yaml +`, + "deployment.yaml": deployment, + } + expected, actual, testDir := loctest.PrepareFs(t, []string{ + "target", + }, nil) + target := testDir.Join("target") + loctest.SetupDir(t, actual, target, kust) + loctest.SetWorkingDir(t, target) + + buffy := new(bytes.Buffer) + cmd := localize.NewCmdLocalize(actual, buffy) + err := cmd.RunE(cmd, args) + require.NoError(t, err) + + loctest.SetupDir(t, expected, target, kust) + dst := filepath.Join(target, "localized-target") + loctest.SetupDir(t, expected, dst, kust) + loctest.CheckFs(t, testDir.String(), expected, actual) + + successMsg := fmt.Sprintf(`SUCCESS: localized "." to directory %s +`, dst) + require.Equal(t, successMsg, buffy.String()) + }) + } +} + +func TestOutput(t *testing.T) { + kustomization := map[string]string{ + "kustomization.yaml": `namePrefix: test- +`, + } + expected, actual, target := loctest.PrepareFs(t, nil, kustomization) + + buffy := new(bytes.Buffer) + cmd := localize.NewCmdLocalize(actual, buffy) + err := cmd.RunE(cmd, []string{ + target.String(), + target.Join("dst"), + }) + require.NoError(t, err) + + loctest.SetupDir(t, expected, target.Join("dst"), kustomization) + loctest.CheckFs(t, target.String(), expected, actual) + + successMsg := fmt.Sprintf(`SUCCESS: localized "%s" to directory %s +`, target.String(), target.Join("dst")) + require.Equal(t, successMsg, buffy.String()) + + const msg = "Check that cmd log output is hooked to buffy." + log.Print(msg) + require.Contains(t, buffy.String(), msg) +} + +func TestAlpha(t *testing.T) { + _, actual, _ := loctest.PrepareFs(t, nil, map[string]string{ + "kustomization.yaml": `namePrefix: test-`, + }) + + cmd := localize.NewCmdLocalize(actual, new(bytes.Buffer)) + require.Contains(t, cmd.Short, "[Alpha]") + require.Contains(t, cmd.Long, "[Alpha]") +} + +func TestTooManyArgs(t *testing.T) { + _, actual, target := loctest.PrepareFs(t, nil, map[string]string{ + "kustomization.yaml": `namePrefix: test-`, + }) + + cmd := localize.NewCmdLocalize(actual, new(bytes.Buffer)) + err := cmd.Args(cmd, []string{ + target.String(), + target.Join("dst"), + target.String(), + }) + require.EqualError(t, err, "accepts at most 2 arg(s), received 3") +}