diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index c4f019a1aac..aaa069c2881 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -101,6 +101,7 @@ func NewSkaffoldCommand(out, err io.Writer) *cobra.Command { rootCmd.AddCommand(NewCmdConfig(out)) rootCmd.AddCommand(NewCmdInit(out)) rootCmd.AddCommand(NewCmdDiagnose(out)) + rootCmd.AddCommand(NewCmdFindConfigs(out)) rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", constants.DefaultLogLevel.String(), "Log level (debug, info, warn, error, fatal, panic)") rootCmd.PersistentFlags().IntVar(&defaultColor, "color", int(color.Default), "Specify the default output color in ANSI escape codes") diff --git a/cmd/skaffold/app/cmd/find_configs.go b/cmd/skaffold/app/cmd/find_configs.go new file mode 100644 index 00000000000..0eb0db2c03a --- /dev/null +++ b/cmd/skaffold/app/cmd/find_configs.go @@ -0,0 +1,113 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + directory string + format string +) + +// NewCmdFindConfigs list the skaffold config files in the specified directory. +func NewCmdFindConfigs(out io.Writer) *cobra.Command { + return NewCmd(out, "find-configs"). + WithDescription("Find in a given directory all skaffold yamls files that are parseable or upgradeable with their versions."). + WithFlags(func(f *pflag.FlagSet) { + // Default to current directory + f.StringVarP(&directory, "directory", "d", ".", "Root directory to lookup the config files.") + // Output format of this Command + f.StringVarP(&format, "output", "o", "table", "Result format, default to table. [(-o|--output=)json|table]") + }). + NoArgs(doFindConfigs) +} + +func doFindConfigs(out io.Writer) error { + pathToVersion, err := findConfigs(directory) + + if err != nil { + return err + } + + switch format { + case "json": + jsonBytes, err := json.Marshal(pathToVersion) + if err != nil { + return err + } + + var prettyJSON bytes.Buffer + err = json.Indent(&prettyJSON, jsonBytes, "", "\t") + if err != nil { + return err + } + fmt.Fprintln(out, prettyJSON.String()) + case "table": + pathOutLen, versionOutLen := 70, 30 + for p, v := range pathToVersion { + var c color.Color + if v == latest.Version { + c = color.Default + } else { + c = color.Green + } + c.Fprintf(out, fmt.Sprintf("%%-%ds\t%%-%ds\n", pathOutLen, versionOutLen), p, v) + } + default: + return errors.New("unsupported template") + } + + return nil +} + +func findConfigs(directory string) (map[string]string, error) { + pathToVersion := make(map[string]string) + + err := filepath.Walk(directory, + func(path string, info os.FileInfo, err error) error { + if err != nil { + fmt.Println(err) + return err + } + + // Find files ending in ".yaml" and parseable to skaffold config in the specified root directory recursively. + if !info.IsDir() && (strings.Contains(path, ".yaml") || strings.Contains(path, ".yml")) { + cfg, err := schema.ParseConfig(path, false) + if err == nil { + pathToVersion[path] = cfg.GetVersion() + } + } + return nil + }) + + return pathToVersion, err +} diff --git a/cmd/skaffold/app/cmd/find_configs_test.go b/cmd/skaffold/app/cmd/find_configs_test.go new file mode 100644 index 00000000000..04e1b88976b --- /dev/null +++ b/cmd/skaffold/app/cmd/find_configs_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "testing" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1beta7" + "github.com/GoogleContainerTools/skaffold/testutil" +) + +var ( + invalidFileName = "invalid-skaffold.yaml" + validFileName = "valid-skaffold.yaml" + upgradeableFileName = "upgradeable-skaffold.yaml" +) + +func TestFindConfigs(t *testing.T) { + testutil.Run(t, "", func(tt *testutil.T) { + latestVersion := latest.Version + upgradeableVersion := v1beta7.Version + tmpDir1, tmpDir2 := setUpTempFiles(tt, latestVersion, upgradeableVersion) + + tests := []struct { + flagDir *testutil.TempDir + resultCounts int + shouldContainsMappings map[string]string + }{ + { + flagDir: tmpDir1, + resultCounts: 2, + shouldContainsMappings: map[string]string{validFileName: latestVersion, upgradeableFileName: upgradeableVersion}, + }, + { + flagDir: tmpDir2, + resultCounts: 1, + shouldContainsMappings: map[string]string{validFileName: latestVersion}, + }, + } + for _, test := range tests { + pathToVersion, err := findConfigs(test.flagDir.Root()) + + tt.CheckErrorAndDeepEqual(false, err, len(test.shouldContainsMappings), len(pathToVersion)) + for f, v := range test.shouldContainsMappings { + version, ok := pathToVersion[test.flagDir.Path(f)] + tt.CheckDeepEqual(true, ok) + tt.CheckDeepEqual(version, v) + } + } + }) +} + +/* +This helper function will generate the following file tree for testing purpose +... +├── tmpDir1 +│   ├── valid-skaffold.yaml +| ├── upgradeable-skaffold.yaml +│   └── invalid-skaffold.yaml +└── tmpDir2 + ├── valid-skaffold.yaml + └── invalid-skaffold.yaml +*/ +func setUpTempFiles(tt *testutil.T, latestVersion, upgradeableVersion string) (*testutil.TempDir, *testutil.TempDir) { + validYaml := fmt.Sprintf(`apiVersion: %s +kind: Config +build: + artifacts: + - image: docker/image + docker: + dockerfile: dockerfile.test +`, latestVersion) + upgradeableYaml := fmt.Sprintf(`apiVersion: %s +kind: Config +build: + artifacts: + - image: docker/image + docker: + dockerfile: dockerfile.test +`, upgradeableVersion) + invalidYaml := `This is invalid` + + tmpDir1 := tt.NewTempDir() + tmpDir2 := tt.NewTempDir() + + files := []struct { + fileName string + content string + tmpDir *testutil.TempDir + }{ + { + fileName: invalidFileName, + content: invalidYaml, + tmpDir: tmpDir1, + }, + { + fileName: validFileName, + content: validYaml, + tmpDir: tmpDir1, + }, + { + fileName: upgradeableFileName, + content: upgradeableYaml, + tmpDir: tmpDir1, + }, + { + fileName: invalidFileName, + content: invalidYaml, + tmpDir: tmpDir2, + }, + { + fileName: validFileName, + content: validYaml, + tmpDir: tmpDir2, + }, + } + + for _, file := range files { + file.tmpDir.Write(file.fileName, file.content) + } + + return tmpDir1, tmpDir2 +} diff --git a/docs/content/en/docs/references/cli/_index.md b/docs/content/en/docs/references/cli/_index.md index eb31dcb71e7..a3ab6f4a973 100644 --- a/docs/content/en/docs/references/cli/_index.md +++ b/docs/content/en/docs/references/cli/_index.md @@ -63,18 +63,19 @@ Usage: skaffold [command] Available Commands: - build Builds the artifacts - completion Output shell completion for the given shell (bash or zsh) - config A set of commands for interacting with the Skaffold config. - debug Runs a pipeline file in debug mode - delete Delete the deployed resources - deploy Deploys the artifacts - dev Runs a pipeline file in development mode - diagnose Run a diagnostic on Skaffold - fix Converts old Skaffold config to newest schema version - init Automatically generate Skaffold configuration for deploying an application - run Runs a pipeline file - version Print the version information + build Builds the artifacts + completion Output shell completion for the given shell (bash or zsh) + config A set of commands for interacting with the Skaffold config. + debug Runs a pipeline file in debug mode + delete Delete the deployed resources + deploy Deploys the artifacts + dev Runs a pipeline file in development mode + diagnose Run a diagnostic on Skaffold + find-configs Find in a given directory all skaffold yamls files that are parseable or upgradeable with their versions. + fix Converts old Skaffold config to newest schema version + init Automatically generate Skaffold configuration for deploying an application + run Runs a pipeline file + version Print the version information Flags: --color int Specify the default output color in ANSI escape codes (default 34) @@ -467,6 +468,29 @@ Env vars: * `SKAFFOLD_FILENAME` (same as `--filename`) * `SKAFFOLD_PROFILE` (same as `--profile`) +### skaffold find-configs + +Find in a given directory all skaffold yamls files that are parseable or upgradeable with their versions. + +``` +Usage: + skaffold find-configs + +Flags: + -d, --directory string Root directory to lookup the config files. (default ".") + -o, --output string Result format, default to table. [(-o|--output=)json|table] (default "table") + +Global Flags: + --color int Specify the default output color in ANSI escape codes (default 34) + -v, --verbosity string Log level (debug, info, warn, error, fatal, panic) (default "warning") + + +``` +Env vars: + +* `SKAFFOLD_DIRECTORY` (same as `--directory`) +* `SKAFFOLD_OUTPUT` (same as `--output`) + ### skaffold fix Converts old Skaffold config to newest schema version