Skip to content

Commit

Permalink
Add localize command handle (kubernetes-sigs#4959)
Browse files Browse the repository at this point in the history
* Add localize command handle

* Align to kustomize command conventions

* Print success msg
  • Loading branch information
annasong20 authored Feb 1, 2023
1 parent 3370177 commit 2361660
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 0 deletions.
2 changes: 2 additions & 0 deletions kustomize/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down
98 changes: 98 additions & 0 deletions kustomize/commands/localize/localize.go
Original file line number Diff line number Diff line change
@@ -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
}
153 changes: 153 additions & 0 deletions kustomize/commands/localize/localize_test.go
Original file line number Diff line number Diff line change
@@ -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")
}

0 comments on commit 2361660

Please sign in to comment.