Add StdIn as an acceptable argument for apply, preview, destroy, and diff commands

This commit is contained in:
Sean R. Sullivan 2020-04-27 12:58:07 -07:00
parent b3a34cc049
commit 681ceda41d
6 changed files with 102 additions and 71 deletions

View File

@ -23,9 +23,9 @@ func GetApplyRunner(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *A
ioStreams: ioStreams,
}
cmd := &cobra.Command{
Use: "apply DIRECTORY",
Use: "apply (DIRECTORY | STDIN)",
DisableFlagsInUseLine: true,
Short: i18n.T("Apply a configuration to a resource by filename or stdin"),
Short: i18n.T("Apply a configuration to a resource by package directory or stdin"),
Run: r.Run,
}

View File

@ -20,7 +20,7 @@ func NewCmdDestroy(f util.Factory, ioStreams genericclioptions.IOStreams) *cobra
}
cmd := &cobra.Command{
Use: "destroy DIRECTORY",
Use: "destroy (DIRECTORY | STDIN)",
DisableFlagsInUseLine: true,
Short: i18n.T("Destroy all the resources related to configuration"),
Run: func(cmd *cobra.Command, args []string) {

View File

@ -19,7 +19,7 @@ import (
func NewCmdDiff(f util.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := diff.NewDiffOptions(ioStreams)
cmd := &cobra.Command{
Use: "diff DIRECTORY",
Use: "diff (DIRECTORY | STDIN)",
DisableFlagsInUseLine: true,
Short: i18n.T("Diff local config against cluster applied version"),
Args: cobra.MaximumNArgs(1),

View File

@ -24,7 +24,7 @@ func NewCmdPreview(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *co
}
cmd := &cobra.Command{
Use: "preview DIRECTORY",
Use: "preview (DIRECTORY | STDIN)",
DisableFlagsInUseLine: true,
Short: i18n.T("Preview the apply of a configuration"),
Args: cobra.MaximumNArgs(1),

View File

@ -13,32 +13,43 @@ import (
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
)
const stdinDash = "-"
func processPaths(paths []string) genericclioptions.FileNameFlags {
// No arguments means we are reading from StdIn
fileNameFlags := genericclioptions.FileNameFlags{}
if len(paths) == 0 {
fileNames := []string{"-"}
fileNames := []string{stdinDash}
fileNameFlags.Filenames = &fileNames
return fileNameFlags
}
// Must be a single directory here; set recursive flag.
t := true
fileNameFlags.Filenames = &paths
fileNameFlags.Recursive = &t
return fileNameFlags
}
// DemandOneDirectoryOrStdin processes "paths" to ensure the
// single argument in the array is a directory. Returns FileNameFlags
// populated with the directory (recursive flag set), or
// the StdIn dash. An empty array gets treated as StdIn
// (adding dash to the array). Returns an error if more than
// one element in the array or the filepath is not a directory.
func DemandOneDirectory(paths []string) (genericclioptions.FileNameFlags, error) {
result := processPaths(paths)
// alas, the things called file names should have been called paths.
if len(*result.Filenames) != 1 {
result := genericclioptions.FileNameFlags{}
if len(paths) == 1 {
dirPath := paths[0]
if !isPathADirectory(dirPath) {
return result, fmt.Errorf("argument '%s' is not but must be a directory", dirPath)
}
}
if len(paths) > 1 {
return result, fmt.Errorf(
"specify exactly one directory path argument; rejecting %v", paths)
}
path := (*result.Filenames)[0]
if !isPathADirectory(path) {
return result, fmt.Errorf("argument '%s' is not but must be a directory", path)
}
result = processPaths(paths)
return result, nil
}

View File

@ -13,57 +13,31 @@ import (
"sigs.k8s.io/cli-utils/pkg/testutil"
)
func TestProcessPaths(t *testing.T) {
trueVal := true
testCases := map[string]struct {
paths []string
expectedFileNameFlags genericclioptions.FileNameFlags
errFromDemandOneDirectory string
}{
"empty slice means reading from StdIn": {
paths: []string{},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{"-"},
},
errFromDemandOneDirectory: "argument '-' is not but must be a directory",
},
"single file in slice means reading from that path": {
paths: []string{"object.yaml"},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{"object.yaml"},
Recursive: &trueVal,
},
errFromDemandOneDirectory: "argument 'object.yaml' is not but must be a directory",
},
"single dir in slice": {
paths: []string{"/tmp"},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{"/tmp"},
Recursive: &trueVal,
},
},
"multiple elements in slice means reading from all files": {
paths: []string{"rs.yaml", "dep.yaml"},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{"rs.yaml", "dep.yaml"},
Recursive: &trueVal,
},
errFromDemandOneDirectory: "specify exactly one directory path argument; rejecting [rs.yaml dep.yaml]",
},
}
const (
packageDir = "test-pkg-dir"
inventoryFilename = "inventory.yaml"
podAFilename = "pod-a.yaml"
podBFilename = "pod-b.yaml"
)
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
var err error
fileNameFlags := processPaths(tc.paths)
assert.DeepEqual(t, tc.expectedFileNameFlags, fileNameFlags)
fileNameFlags, err = DemandOneDirectory(tc.paths)
assert.DeepEqual(t, tc.expectedFileNameFlags, fileNameFlags)
if err != nil && err.Error() != tc.errFromDemandOneDirectory {
assert.Equal(t, err.Error(), tc.errFromDemandOneDirectory)
}
})
}
var (
inventoryFilePath = filepath.Join(packageDir, inventoryFilename)
podAFilePath = filepath.Join(packageDir, podAFilename)
podBFilePath = filepath.Join(packageDir, podBFilename)
)
func setupTestFilesystem(t *testing.T) testutil.TestFilesystem {
// Create the test filesystem, and add package config files
// to it.
t.Log("Creating test filesystem")
tf := testutil.Setup(t, packageDir)
t.Logf("Adding File: %s", inventoryFilePath)
tf.WriteFile(t, inventoryFilePath, inventoryConfigMap)
t.Logf("Adding File: %s", podAFilePath)
tf.WriteFile(t, podAFilePath, podA)
t.Logf("Adding File: %s", podBFilePath)
tf.WriteFile(t, podBFilePath, podB)
return tf
}
var inventoryConfigMap = []byte(`
@ -104,17 +78,62 @@ spec:
image: k8s.gcr.io/pause:2.0
`)
func TestExpandDirErrors(t *testing.T) {
// Create the test filesystem, and add package config files
// to it.
packageDir := "test-pkg-dir"
tf := testutil.Setup(t, packageDir)
tf.WriteFile(t, filepath.Join(packageDir, "inventory.yaml"), inventoryConfigMap)
tf.WriteFile(t, filepath.Join(packageDir, "pod-a.yaml"), podA)
tf.WriteFile(t, filepath.Join(packageDir, "pod-b.yaml"), podB)
func TestProcessPaths(t *testing.T) {
tf := setupTestFilesystem(t)
defer tf.Clean()
trueVal := true
testCases := map[string]struct {
paths []string
expectedFileNameFlags genericclioptions.FileNameFlags
errFromDemandOneDirectory string
}{
"empty slice means reading from StdIn": {
paths: []string{},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{"-"},
},
},
"single file in slice is error; must be directory": {
paths: []string{podAFilePath},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: nil,
Recursive: nil,
},
errFromDemandOneDirectory: "argument 'test-pkg-dir/pod-a.yaml' is not but must be a directory",
},
"single dir in slice": {
paths: []string{tf.GetRootDir()},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{tf.GetRootDir()},
Recursive: &trueVal,
},
},
"multiple arguments is an error": {
paths: []string{podAFilePath, podBFilePath},
expectedFileNameFlags: genericclioptions.FileNameFlags{
Filenames: nil,
Recursive: nil,
},
errFromDemandOneDirectory: "specify exactly one directory path argument; rejecting [test-pkg-dir/pod-a.yaml test-pkg-dir/pod-b.yaml]",
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
fileNameFlags, err := DemandOneDirectory(tc.paths)
assert.DeepEqual(t, tc.expectedFileNameFlags, fileNameFlags)
if err != nil && err.Error() != tc.errFromDemandOneDirectory {
assert.Equal(t, err.Error(), tc.errFromDemandOneDirectory)
}
})
}
}
func TestExpandDirErrors(t *testing.T) {
tf := setupTestFilesystem(t)
defer tf.Clean()
testCases := map[string]struct {
packageDirPath []string
expandedPaths []string
@ -144,6 +163,7 @@ func TestExpandDirErrors(t *testing.T) {
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
trueVal := true
filenameFlags := genericclioptions.FileNameFlags{
Filenames: &tc.packageDirPath,
Recursive: &trueVal,