Hyphenate flags and add more build command tests.

This commit is contained in:
monopole
2021-02-13 08:59:11 -08:00
parent 8cf7bc67bb
commit 6cf48442df
10 changed files with 352 additions and 177 deletions

View File

@@ -8,7 +8,6 @@ import (
"io"
"log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
@@ -16,20 +15,20 @@ import (
"sigs.k8s.io/kustomize/api/types"
)
// Options contain the options for running a build
type Options struct {
var theArgs struct {
kustomizationPath string
outputPath string
outOrder reorderOutput
fnOptions types.FnPluginLoadingOptions
}
// NewOptions creates a Options object
func NewOptions(p, o string) *Options {
return &Options{
kustomizationPath: p,
outputPath: o,
var theFlags struct {
outputPath string
enable struct {
resourceIdChanges bool
plugins bool
managedByLabel bool
}
loadRestrictor string
reorderOutput string
fnOptions types.FnPluginLoadingOptions
}
type Help struct {
@@ -39,16 +38,16 @@ type Help struct {
Example string
}
func MakeHelp(pgmName, cmdName string) Help {
func MakeHelp(pgmName, cmdName string) *Help {
fN := konfig.DefaultKustomizationFileName()
return Help{
Use: cmdName + " <dir>",
return &Help{
Use: cmdName + " DIR",
Short: "Build a kustomization target from a directory or URL.",
Long: fmt.Sprintf(`Build a set of KRM resources using a '%s' file.
The <dir> argument must be a path to a directory containing
The DIR argument must be a path to a directory containing
'%s', or a git repository URL with a path suffix
specifying same with respect to the repository root.
If <dir> is omitted, '.' is assumed.
If DIR is omitted, '.' is assumed.
`, fN, fN),
Example: fmt.Sprintf(`# Build the current working directory
%s %s
@@ -57,15 +56,14 @@ If <dir> is omitted, '.' is assumed.
%s %s /home/config/production
# Build from github
%s %s \
https://github.com/kubernetes-sigs/kustomize.git/examples/helloWorld?ref=v1.0.6
%s %s https://github.com/kubernetes-sigs/kustomize.git/examples/helloWorld?ref=v1.0.6
`, pgmName, cmdName, pgmName, cmdName, pgmName, cmdName),
}
}
// NewCmdBuild creates a new build command.
func NewCmdBuild(help Help, out io.Writer) *cobra.Command {
var o Options
func NewCmdBuild(
fSys filesys.FileSystem, help *Help, writer io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: help.Use,
Short: help.Short,
@@ -73,40 +71,36 @@ func NewCmdBuild(help Help, out io.Writer) *cobra.Command {
Example: help.Example,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Validate(args); err != nil {
if err := Validate(args); err != nil {
return err
}
k := krusty.MakeKustomizer(
o.ModifyKrustyOptions(krusty.MakeDefaultOptions()),
HonorKustomizeFlags(krusty.MakeDefaultOptions()),
)
fSys := filesys.MakeFsOnDisk()
m, err := k.Run(fSys, o.kustomizationPath)
m, err := k.Run(fSys, theArgs.kustomizationPath)
if err != nil {
return err
}
if o.outputPath != "" && fSys.IsDir(o.outputPath) {
// Ignore io.Writer; write to o.outputPath directly.
return MakeWriter(fSys).WriteIndividualFiles(o.outputPath, m)
if theFlags.outputPath != "" && fSys.IsDir(theFlags.outputPath) {
// Ignore writer; write to o.outputPath directly.
return MakeWriter(fSys).WriteIndividualFiles(
theFlags.outputPath, m)
}
yml, err := m.AsYaml()
if err != nil {
return err
}
if o.outputPath != "" {
// Ignore io.Writer; write to o.outputPath directly.
return fSys.WriteFile(o.outputPath, yml)
if theFlags.outputPath != "" {
// Ignore writer; write to o.outputPath directly.
return fSys.WriteFile(theFlags.outputPath, yml)
}
_, err = out.Write(yml)
_, err = writer.Write(yml)
return err
},
}
cmd.Flags().StringVarP(
&o.outputPath,
"output", "o", "",
"If specified, write output to this path.")
AddFunctionFlags(cmd.Flags(), &o.fnOptions)
AddFlagOutputPath(cmd.Flags())
AddFunctionFlags(cmd.Flags())
AddFlagLoadRestrictor(cmd.Flags())
AddFlagEnablePlugins(cmd.Flags())
AddFlagReorderOutput(cmd.Flags())
@@ -116,40 +110,39 @@ func NewCmdBuild(help Help, out io.Writer) *cobra.Command {
return cmd
}
// Validate validates build command.
func (o *Options) Validate(args []string) (err error) {
// Validate validates build command args and flags.
func Validate(args []string) (err error) {
if len(args) > 1 {
return errors.New(
return fmt.Errorf(
"specify one path to " +
konfig.DefaultKustomizationFileName())
}
if len(args) == 0 {
o.kustomizationPath = filesys.SelfDir
theArgs.kustomizationPath = filesys.SelfDir
} else {
o.kustomizationPath = args[0]
theArgs.kustomizationPath = args[0]
}
err = validateFlagLoadRestrictor()
if err != nil {
return err
}
o.outOrder, err = validateFlagReorderOutput()
return
return validateFlagReorderOutput()
}
// ModifyKrustyOptions feeds command line data into the krusty options.
func (o *Options) ModifyKrustyOptions(kOpts *krusty.Options) *krusty.Options {
kOpts.DoLegacyResourceSort = o.outOrder == legacy
// HonorKustomizeFlags feeds command line data to the krusty options.
// Flags and such are held in private package variables.
func HonorKustomizeFlags(kOpts *krusty.Options) *krusty.Options {
kOpts.DoLegacyResourceSort = getFlagReorderOutput() == legacy
kOpts.LoadRestrictions = getFlagLoadRestrictorValue()
if isFlagEnablePluginsSet() {
if theFlags.enable.plugins {
c, err := konfig.EnabledPluginConfig(types.BploUseStaticallyLinked)
if err != nil {
log.Fatal(err)
}
c.FnpLoadingOptions = o.fnOptions
c.FnpLoadingOptions = theFlags.fnOptions
kOpts.PluginConfig = c
}
kOpts.AddManagedbyLabel = isManagedbyLabelEnabled()
kOpts.UseKyaml = konfig.FlagEnableKyamlDefaultValue
kOpts.AllowResourceIdChanges = flagAllowResourceIdChangesValue
kOpts.AddManagedbyLabel = isManagedByLabelEnabled()
kOpts.AllowResourceIdChanges = theFlags.enable.resourceIdChanges
return kOpts
}

View File

@@ -1,54 +1,250 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package build
package build_test
import (
"bytes"
"strings"
"testing"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
. "sigs.k8s.io/kustomize/kustomize/v4/commands/build"
)
func TestNewOptionsToSilenceCodeInspectionError(t *testing.T) {
if NewOptions("foo", "bar") == nil {
t.Fatal("could not make new options")
func loadFileSystem(fSys filesys.FileSystem) {
fSys.WriteFile(konfig.DefaultKustomizationFileName(), []byte(`
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: foo-
nameSuffix: -bar
namespace: ns1
commonLabels:
app: nginx
commonAnnotations:
note: This is a test annotation
resources:
- deployment.yaml
- namespace.yaml
configMapGenerator:
- name: literalConfigMap
literals:
- DB_USERNAME=admin
- DB_PASSWORD=somepw
secretGenerator:
- name: secret
literals:
- DB_USERNAME=admin
- DB_PASSWORD=somepw
type: Opaque
patchesJson6902:
- target:
group: apps
version: v1
kind: Deployment
name: dply1
path: jsonpatch.json
`))
fSys.WriteFile("deployment.yaml", []byte(`
apiVersion: apps/v1
metadata:
name: dply1
kind: Deployment
`))
fSys.WriteFile("namespace.yaml", []byte(`
apiVersion: v1
kind: Namespace
metadata:
name: ns1
`))
fSys.WriteFile("jsonpatch.json", []byte(`[
{"op": "add", "path": "/spec/replica", "value": "3"}
]`))
}
const expectedContent = `apiVersion: v1
kind: Namespace
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
name: ns1
---
apiVersion: v1
data:
DB_PASSWORD: somepw
DB_USERNAME: admin
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
name: foo-literalConfigMap-bar-g5f6t456f5
namespace: ns1
---
apiVersion: v1
data:
DB_PASSWORD: c29tZXB3
DB_USERNAME: YWRtaW4=
kind: Secret
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
name: foo-secret-bar-82c2g5f8f6
namespace: ns1
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
name: foo-dply1-bar
namespace: ns1
spec:
replica: "3"
selector:
matchLabels:
app: nginx
template:
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
`
func TestBuild(t *testing.T) {
fSys := filesys.MakeFsInMemory()
loadFileSystem(fSys)
buffy := new(bytes.Buffer)
cmd := NewCmdBuild(fSys, MakeHelp("foo", "bar"), buffy)
if err := cmd.RunE(cmd, []string{}); err != nil {
t.Fatal(err)
}
if buffy.String() != expectedContent {
t.Fatalf("Expected output:\n%s\n But got output:\n%s", expectedContent, buffy)
}
}
func TestBuildValidate(t *testing.T) {
var cases = []struct {
name string
func TestBuildWithShardedOutput(t *testing.T) {
var err error
fSys := filesys.MakeFsInMemory()
loadFileSystem(fSys)
fSys.Mkdir("someDir")
buffy := new(bytes.Buffer)
cmd := NewCmdBuild(fSys, MakeHelp("foo", "bar"), buffy)
cmd.Flags().Set("output", "someDir")
cmd.Flags().Set("enable-managedby-label", "true")
if err = cmd.RunE(cmd, []string{}); err != nil {
t.Fatal(err)
}
if buffy.String() != "" {
t.Fatalf("Expected:\n%s\nBut got:\n%s\n", expectedContent, buffy)
}
if !fSys.IsDir("someDir") {
t.Fatal("expected dir")
}
var data []byte
if data, err = fSys.ReadFile(
"someDir/v1_namespace_ns1.yaml"); err != nil {
t.Fatal(err)
}
expected := `apiVersion: v1
kind: Namespace
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
app.kubernetes.io/managed-by: kustomize-unknown
name: ns1
`
if string(data) != expected {
t.Fatalf("Expected:\n%s\nBut got:\n%s\n", expected, string(data))
}
if data, err = fSys.ReadFile(
"someDir/v1_secret_foo-secret-bar-82c2g5f8f6.yaml"); err != nil {
t.Fatal(err)
}
expected = `apiVersion: v1
data:
DB_PASSWORD: c29tZXB3
DB_USERNAME: YWRtaW4=
kind: Secret
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
app.kubernetes.io/managed-by: kustomize-unknown
name: foo-secret-bar-82c2g5f8f6
namespace: ns1
type: Opaque
`
if string(data) != expected {
t.Fatalf("Expected:\n%s\nBut got:\n%s\n", expected, string(data))
}
}
func TestHelp(t *testing.T) {
fSys := filesys.MakeFsInMemory()
buffy := new(bytes.Buffer)
cmd := NewCmdBuild(fSys, MakeHelp("foo", "bar"), buffy)
if cmd.Use != "bar DIR" {
t.Fatalf("Unexpected usage: %s\n", cmd.Use)
}
if cmd.Short != "Build a kustomization target from a directory or URL." {
t.Fatalf("Unexpected short help: %s\n", cmd.Short)
}
if !strings.Contains(cmd.Long, "If DIR is omitted, '.' is assumed.") {
t.Fatalf("Unexpected long help: %s\n", cmd.Long)
}
if !strings.Contains(cmd.Example, "foo bar /home/config/production") {
t.Fatalf("Unexpected example: %s\n", cmd.Example)
}
}
func TestValidation(t *testing.T) {
var cases = map[string]struct {
args []string
path string
erMsg string
}{
{"noargs", []string{}, filesys.SelfDir, ""},
{"file", []string{"beans"}, "beans", ""},
{"path", []string{"a/b/c"}, "a/b/c", ""},
{"path", []string{"too", "many"},
"",
"noArgs": {[]string{}, "unable to find one of "},
"dotArg": {[]string{"."}, "unable to find one of "},
"file": {[]string{"beans"}, "'beans' doesn't exist"},
"directory": {[]string{"a/b/c"}, "'a/b/c' doesn't exist"},
"tooManyArgs": {[]string{"too", "many"},
"specify one path to " +
konfig.DefaultKustomizationFileName()},
}
for _, mycase := range cases {
opts := Options{}
e := opts.Validate(mycase.args)
if len(mycase.erMsg) > 0 {
if e == nil {
t.Errorf("%s: Expected an error %v", mycase.name, mycase.erMsg)
for n := range cases {
tc := cases[n]
t.Run(n, func(t *testing.T) {
fSys := filesys.MakeFsInMemory()
buffy := new(bytes.Buffer)
cmd := NewCmdBuild(fSys, MakeHelp("foo", "bar"), buffy)
err := cmd.RunE(cmd, tc.args)
if len(tc.erMsg) > 0 {
if err == nil {
t.Errorf("%s: Expected an error %v", n, tc.erMsg)
}
if !strings.Contains(err.Error(), tc.erMsg) {
t.Errorf("%s: Expected error %s, but got %v",
n, tc.erMsg, err)
}
} else {
if err != nil {
t.Errorf("%s: unknown error: %v", n, err)
}
}
if e.Error() != mycase.erMsg {
t.Errorf("%s: Expected error %s, but got %v", mycase.name, mycase.erMsg, e)
}
continue
}
if e != nil {
t.Errorf("%s: unknown error: %v", mycase.name, e)
continue
}
if opts.kustomizationPath != mycase.path {
t.Errorf("%s: expected path '%s', got '%s'", mycase.name, mycase.path, opts.kustomizationPath)
}
})
}
}

View File

@@ -10,28 +10,18 @@ import (
"sigs.k8s.io/kustomize/api/konfig"
)
const (
flagEnableManagedbyLabelName = "enable_managedby_label"
flagEnableManagedbyLabelHelp = `enable adding ` + konfig.ManagedbyLabelKey
)
var (
flagEnableManagedbyLabelValue = false
)
func AddFlagEnableManagedbyLabel(set *pflag.FlagSet) {
set.BoolVar(
&flagEnableManagedbyLabelValue, flagEnableManagedbyLabelName,
false, flagEnableManagedbyLabelHelp)
&theFlags.enable.managedByLabel,
"enable-managedby-label",
false,
`enable adding `+konfig.ManagedbyLabelKey)
}
func isManagedbyLabelEnabled() bool {
if flagEnableManagedbyLabelValue {
func isManagedByLabelEnabled() bool {
if theFlags.enable.managedByLabel {
return true
}
enableLabel, isSet := os.LookupEnv(konfig.EnableManagedbyLabelEnv)
if isSet && enableLabel == "on" {
return true
}
return false
return isSet && enableLabel == "on"
}

View File

@@ -7,17 +7,10 @@ import (
"github.com/spf13/pflag"
)
const (
flagAllowResourceIdChangesName = "allow_id_changes"
flagAllowResourceIdChangesHelp = `enable changes to a resourceId`
)
var (
flagAllowResourceIdChangesValue = false
)
func AddFlagAllowResourceIdChanges(set *pflag.FlagSet) {
set.BoolVar(
&flagAllowResourceIdChangesValue, flagAllowResourceIdChangesName,
false, flagAllowResourceIdChangesHelp)
&theFlags.enable.resourceIdChanges,
"allow-id-changes",
false,
`enable changes to a resourceId`)
}

View File

@@ -7,23 +7,10 @@ import (
"github.com/spf13/pflag"
)
const (
flagEnablePluginsName = "enable_alpha_plugins"
flagEnablePluginsHelp = `enable plugins, an alpha feature.
See https://github.com/kubernetes-sigs/kustomize/blob/master/docs/plugins/README.md
`
)
var (
flagPluginsEnabledValue = false
)
func AddFlagEnablePlugins(set *pflag.FlagSet) {
set.BoolVar(
&flagPluginsEnabledValue, flagEnablePluginsName,
false, flagEnablePluginsHelp)
}
func isFlagEnablePluginsSet() bool {
return flagPluginsEnabledValue
&theFlags.enable.plugins,
"enable-alpha-plugins",
false,
"enable kustomize plugins")
}

View File

@@ -10,42 +10,38 @@ import (
"sigs.k8s.io/kustomize/api/types"
)
const (
flagName = "load_restrictor"
)
var (
flagLrValue = types.LoadRestrictionsRootOnly.String()
flagLrHelp = "if set to '" + types.LoadRestrictionsNone.String() +
"', local kustomizations may load files from outside their root. " +
"This does, however, break the relocatability of the kustomization."
)
const flagLoadRestrictorName = "load-restrictor"
func AddFlagLoadRestrictor(set *pflag.FlagSet) {
set.StringVar(
&flagLrValue, flagName,
types.LoadRestrictionsRootOnly.String(), flagLrHelp)
&theFlags.loadRestrictor,
flagLoadRestrictorName,
types.LoadRestrictionsRootOnly.String(),
"if set to '"+types.LoadRestrictionsNone.String()+
"', local kustomizations may load files from outside their root. "+
"This does, however, break the "+
"relocatability of the kustomization.")
}
func validateFlagLoadRestrictor() error {
switch getFlagLoadRestrictorValue() {
case types.LoadRestrictionsRootOnly, types.LoadRestrictionsNone:
switch theFlags.loadRestrictor {
case types.LoadRestrictionsRootOnly.String(),
types.LoadRestrictionsNone.String(), "":
return nil
default:
return fmt.Errorf(
"illegal flag value --%s %s; legal values: %v",
flagName, flagLrValue,
[]string{types.LoadRestrictionsRootOnly.String(), types.LoadRestrictionsNone.String()})
flagLoadRestrictorName, theFlags.loadRestrictor,
[]string{types.LoadRestrictionsRootOnly.String(),
types.LoadRestrictionsNone.String()})
}
}
func getFlagLoadRestrictorValue() types.LoadRestrictions {
switch flagLrValue {
case types.LoadRestrictionsRootOnly.String(), "rootOnly":
return types.LoadRestrictionsRootOnly
switch theFlags.loadRestrictor {
case types.LoadRestrictionsNone.String(), "none":
return types.LoadRestrictionsNone
default:
return types.LoadRestrictionsUnknown
return types.LoadRestrictionsRootOnly
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package build
import (
"github.com/spf13/pflag"
)
func AddFlagOutputPath(set *pflag.FlagSet) {
set.StringVarP(
&theFlags.outputPath,
"output",
"o", // abbreviation
"", // default
"If specified, write output to this path.")
}

View File

@@ -5,26 +5,25 @@ package build
import (
"github.com/spf13/pflag"
"sigs.k8s.io/kustomize/api/types"
)
func AddFunctionFlags(set *pflag.FlagSet, o *types.FnPluginLoadingOptions) {
func AddFunctionFlags(set *pflag.FlagSet) {
set.BoolVar(
&o.EnableExec, "enable-exec", false, /*do not change!*/
&theFlags.fnOptions.EnableExec, "enable-exec", false, /*do not change!*/
"enable support for exec functions -- note: exec functions run arbitrary code -- do not use for untrusted configs!!! (Alpha)")
set.BoolVar(
&o.EnableStar, "enable-star", false,
&theFlags.fnOptions.EnableStar, "enable-star", false,
"enable support for starlark functions. (Alpha)")
set.BoolVar(
&o.Network, "network", false,
&theFlags.fnOptions.Network, "network", false,
"enable network access for functions that declare it")
set.StringVar(
&o.NetworkName, "network-name", "bridge",
&theFlags.fnOptions.NetworkName, "network-name", "bridge",
"the docker network to run the container in")
set.StringArrayVar(
&o.Mounts, "mount", []string{},
&theFlags.fnOptions.Mounts, "mount", []string{},
"a list of storage options read from the filesystem")
set.StringArrayVarP(
&o.Env, "env", "e", []string{},
&theFlags.fnOptions.Env, "env", "e", []string{},
"a list of environment variables to be used by functions")
}

View File

@@ -18,33 +18,37 @@ const (
legacy
)
const (
flagReorderOutputName = "reorder"
)
var (
flagReorderOutputValue = legacy.String()
flagReorderOutputHelp = "Reorder the resources just before output. " +
"Use '" + legacy.String() + "' to apply a legacy reordering (Namespaces first, Webhooks last, etc). " +
"Use '" + none.String() + "' to suppress a final reordering."
)
const flagReorderOutputName = "reorder"
func AddFlagReorderOutput(set *pflag.FlagSet) {
set.StringVar(
&flagReorderOutputValue, flagReorderOutputName,
legacy.String(), flagReorderOutputHelp)
&theFlags.reorderOutput, flagReorderOutputName,
legacy.String(),
"Reorder the resources just before output. "+
"Use '"+legacy.String()+"' to apply a legacy reordering "+
"(Namespaces first, Webhooks last, etc). "+
"Use '"+none.String()+"' to suppress a final reordering.")
}
func validateFlagReorderOutput() (reorderOutput, error) {
switch flagReorderOutputValue {
case none.String():
return none, nil
case legacy.String():
return legacy, nil
func validateFlagReorderOutput() error {
switch theFlags.reorderOutput {
case none.String(), legacy.String():
return nil
default:
return unspecified, fmt.Errorf(
return fmt.Errorf(
"illegal flag value --%s %s; legal values: %v",
flagReorderOutputName, flagReorderOutputValue,
flagReorderOutputName, theFlags.reorderOutput,
[]string{legacy.String(), none.String()})
}
}
func getFlagReorderOutput() reorderOutput {
switch theFlags.reorderOutput {
case none.String():
return none
case legacy.String():
return legacy
default:
return unspecified
}
}

View File

@@ -38,7 +38,7 @@ See https://sigs.k8s.io/kustomize
c.AddCommand(
completion.NewCommand(),
build.NewCmdBuild(
build.MakeHelp(konfig.ProgramName, "build"), stdOut),
fSys, build.MakeHelp(konfig.ProgramName, "build"), stdOut),
edit.NewCmdEdit(
fSys, pvd.GetFieldValidator(), pvd.GetKunstructuredFactory()),
create.NewCmdCreate(fSys, pvd.GetKunstructuredFactory()),