Skip to content

Commit d14f34d

Browse files
authored
Add skaffold inspect command (#5765)
* Add `skaffold inspect` command * add tests
1 parent 91c4480 commit d14f34d

File tree

12 files changed

+361
-9
lines changed

12 files changed

+361
-9
lines changed

cmd/skaffold/app/cmd/cmd.go

+1
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func NewSkaffoldCommand(out, err io.Writer) *cobra.Command {
200200

201201
rootCmd.AddCommand(NewCmdGeneratePipeline())
202202
rootCmd.AddCommand(NewCmdSurvey())
203+
rootCmd.AddCommand(NewCmdInspect())
203204

204205
templates.ActsAsRootCommand(rootCmd, nil, groups...)
205206
rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", constants.DefaultLogLevel.String(), "Log level (debug, info, warn, error, fatal, panic)")

cmd/skaffold/app/cmd/commands.go

+13
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type Builder interface {
3838
Hidden() Builder
3939
ExactArgs(argCount int, action func(context.Context, io.Writer, []string) error) *cobra.Command
4040
NoArgs(action func(context.Context, io.Writer) error) *cobra.Command
41+
WithCommands(cmds ...*cobra.Command) *cobra.Command
42+
WithPersistentFlagAdder(adder func(*pflag.FlagSet)) Builder
4143
}
4244

4345
type builder struct {
@@ -94,7 +96,11 @@ func (b *builder) WithFlags(flags []*Flag) Builder {
9496
b.cmd.PreRun = func(cmd *cobra.Command, args []string) {
9597
ResetFlagDefaults(cmd, flags)
9698
}
99+
return b
100+
}
97101

102+
func (b *builder) WithPersistentFlagAdder(adder func(*pflag.FlagSet)) Builder {
103+
adder(b.cmd.PersistentFlags())
98104
return b
99105
}
100106

@@ -144,3 +150,10 @@ func (b *builder) WithArgs(f cobra.PositionalArgs, action func(context.Context,
144150
}
145151
return &b.cmd
146152
}
153+
154+
func (b *builder) WithCommands(cmds ...*cobra.Command) *cobra.Command {
155+
for _, c := range cmds {
156+
b.cmd.AddCommand(c)
157+
}
158+
return &b.cmd
159+
}

cmd/skaffold/app/cmd/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
func NewCmdConfig() *cobra.Command {
2727
cmd := &cobra.Command{
2828
Use: "config",
29-
Short: "Interact with the Skaffold configuration",
29+
Short: "Interact with the global skaffold config file (defaults to `$HOME/.skaffold/config`)",
3030
}
3131

3232
cmd.AddCommand(NewCmdSet())

cmd/skaffold/app/cmd/inspect.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"github.com/spf13/cobra"
21+
"github.com/spf13/pflag"
22+
)
23+
24+
var inspectFlags = struct {
25+
fileName string
26+
outFormat string
27+
}{
28+
fileName: "skaffold.yaml",
29+
}
30+
31+
func NewCmdInspect() *cobra.Command {
32+
return NewCmd("inspect").
33+
WithDescription("Helper commands for Cloud Code IDEs to interact with and modify skaffold configuration files.").
34+
WithPersistentFlagAdder(cmdInspectFlags).
35+
Hidden().
36+
WithCommands(cmdModules())
37+
}
38+
39+
func cmdInspectFlags(f *pflag.FlagSet) {
40+
f.StringVarP(&inspectFlags.fileName, "filename", "f", "skaffold.yaml", "Path to the local Skaffold config file. Defaults to `skaffold.yaml`")
41+
f.StringVarP(&inspectFlags.outFormat, "format", "o", "json", "Output format. One of: json(default)")
42+
}
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"context"
21+
"io"
22+
23+
"github.com/spf13/cobra"
24+
25+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
26+
)
27+
28+
func cmdModules() *cobra.Command {
29+
return NewCmd("modules").
30+
WithDescription("Interact with configuration modules").
31+
WithCommands(cmdModulesList())
32+
}
33+
34+
func cmdModulesList() *cobra.Command {
35+
return NewCmd("list").
36+
WithExample("Get list of modules", "skaffold inspect modules list --format json").
37+
WithDescription("Print the list of module names that can be invoked with the --module flag in other skaffold commands.").
38+
NoArgs(listModules)
39+
}
40+
41+
func listModules(ctx context.Context, out io.Writer) error {
42+
return inspect.PrintModulesList(ctx, out, inspect.Options{Filename: inspectFlags.fileName, OutFormat: inspectFlags.outFormat})
43+
}

docs/content/en/docs/references/cli/_index.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Getting started with a new project:
8585
8686
Other Commands:
8787
completion Output shell completion for the given shell (bash or zsh)
88-
config Interact with the Skaffold configuration
88+
config Interact with the global skaffold config file (defaults to `$HOME/.skaffold/config`)
8989
credits Export third party notices to given path (./skaffold-credits by default)
9090
diagnose Run a diagnostic on Skaffold
9191
schema List and print json schemas used to validate skaffold.yaml configuration
@@ -268,7 +268,7 @@ Use "skaffold options" for a list of global command-line options (applies to all
268268

269269
### skaffold config
270270

271-
Interact with the Skaffold configuration
271+
Interact with the global skaffold config file (defaults to `$HOME/.skaffold/config`)
272272

273273
```
274274

pkg/skaffold/inspect/modules.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inspect
18+
19+
import (
20+
"context"
21+
"io"
22+
23+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
24+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
25+
)
26+
27+
var (
28+
getConfigSetFunc = parser.GetConfigSet
29+
)
30+
31+
type moduleList struct {
32+
Modules []moduleEntry `json:"modules"`
33+
}
34+
35+
type moduleEntry struct {
36+
Name string `json:"name"`
37+
Path string `json:"path"`
38+
}
39+
40+
func PrintModulesList(ctx context.Context, out io.Writer, opts Options) error {
41+
formatter := getOutputFormatter(out, opts.OutFormat)
42+
cfgs, err := getConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename})
43+
if err != nil {
44+
return formatter.WriteErr(err)
45+
}
46+
47+
l := &moduleList{}
48+
for _, c := range cfgs {
49+
if c.Metadata.Name != "" {
50+
l.Modules = append(l.Modules, moduleEntry{Name: c.Metadata.Name, Path: c.SourceFile})
51+
}
52+
}
53+
return formatter.Write(l)
54+
}

pkg/skaffold/inspect/modules_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inspect
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"errors"
23+
"fmt"
24+
"testing"
25+
26+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
27+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
28+
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
29+
v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
30+
"github.com/GoogleContainerTools/skaffold/testutil"
31+
)
32+
33+
func TestPrintModulesList(t *testing.T) {
34+
tests := []struct {
35+
description string
36+
configSet parser.SkaffoldConfigSet
37+
err error
38+
expected string
39+
}{
40+
{
41+
description: "print modules",
42+
configSet: parser.SkaffoldConfigSet{
43+
&parser.SkaffoldConfigEntry{SkaffoldConfig: &v1.SkaffoldConfig{Metadata: v1.Metadata{Name: "cfg1"}}, SourceFile: "path/to/cfg1"},
44+
&parser.SkaffoldConfigEntry{SkaffoldConfig: &v1.SkaffoldConfig{Metadata: v1.Metadata{Name: "cfg2"}}, SourceFile: "path/to/cfg2"},
45+
},
46+
expected: `{"modules":[{"name":"cfg1","path":"path/to/cfg1"},{"name":"cfg2","path":"path/to/cfg2"}]}` + "\n",
47+
},
48+
{
49+
description: "actionable error",
50+
err: sErrors.MainConfigFileNotFoundErr("path/to/skaffold.yaml", fmt.Errorf("failed to read file : %q", "skaffold.yaml")),
51+
expected: `{"errorCode":"CONFIG_FILE_NOT_FOUND_ERR","errorMessage":"unable to find configuration file \"path/to/skaffold.yaml\": failed to read file : \"skaffold.yaml\". Check that the specified configuration file exists at \"path/to/skaffold.yaml\"."}` + "\n",
52+
},
53+
{
54+
description: "generic error",
55+
err: errors.New("some error occurred"),
56+
expected: `{"errorCode":"UNKNOWN_ERROR","errorMessage":"some error occurred"}` + "\n",
57+
},
58+
}
59+
60+
for _, test := range tests {
61+
testutil.Run(t, test.description, func(t *testutil.T) {
62+
t.Override(&getConfigSetFunc, func(config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
63+
return test.configSet, test.err
64+
})
65+
var buf bytes.Buffer
66+
err := PrintModulesList(context.Background(), &buf, Options{OutFormat: "json"})
67+
t.CheckNoError(err)
68+
t.CheckDeepEqual(test.expected, buf.String())
69+
})
70+
}
71+
}

pkg/skaffold/inspect/output.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inspect
18+
19+
import (
20+
"encoding/json"
21+
"errors"
22+
"io"
23+
24+
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
25+
"github.com/GoogleContainerTools/skaffold/proto/v1"
26+
)
27+
28+
type formatter interface {
29+
Write(interface{}) error
30+
WriteErr(error) error
31+
}
32+
33+
func getOutputFormatter(out io.Writer, _ string) formatter {
34+
// TODO: implement other output formatters. Currently only JSON is implemented
35+
return jsonFormatter{out: out}
36+
}
37+
38+
type jsonFormatter struct {
39+
out io.Writer
40+
}
41+
42+
func (j jsonFormatter) Write(data interface{}) error {
43+
return json.NewEncoder(j.out).Encode(data)
44+
}
45+
46+
type jsonErrorOutput struct {
47+
ErrorCode string `json:"errorCode"`
48+
ErrorMessage string `json:"errorMessage"`
49+
}
50+
51+
func (j jsonFormatter) WriteErr(err error) error {
52+
var sErr sErrors.Error
53+
var jsonErr jsonErrorOutput
54+
if errors.As(err, &sErr) {
55+
jsonErr = jsonErrorOutput{ErrorCode: sErr.StatusCode().String(), ErrorMessage: sErr.Error()}
56+
} else {
57+
jsonErr = jsonErrorOutput{ErrorCode: proto.StatusCode_UNKNOWN_ERROR.String(), ErrorMessage: err.Error()}
58+
}
59+
return json.NewEncoder(j.out).Encode(jsonErr)
60+
}

pkg/skaffold/inspect/types.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inspect
18+
19+
// Options holds flag values for the various `skaffold inspect` commands
20+
type Options struct {
21+
// Filename is the `skaffold.yaml` file path
22+
Filename string
23+
// OutFormat is the output format. One of: json
24+
OutFormat string
25+
}

0 commit comments

Comments
 (0)