Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add skaffold inspect command for adding config dependencies #9072

Merged
merged 10 commits into from
Sep 12, 2023
2 changes: 1 addition & 1 deletion cmd/skaffold/app/cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewCmdInspect() *cobra.Command {
WithPersistentFlagAdder(cmdInspectFlags).
Hidden().
WithCommands(cmdModules(), cmdProfiles(), cmdBuildEnv(), cmdTests(), cmdNamespaces(),
cmdJobManifestPaths(), cmdExecutionModes())
cmdJobManifestPaths(), cmdExecutionModes(), cmdConfigDependencies())
}

func cmdInspectFlags(f *pflag.FlagSet) {
Expand Down
68 changes: 68 additions & 0 deletions cmd/skaffold/app/cmd/inspect_config_dependencies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Copyright 2023 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 (
"context"
"errors"
"io"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
configDependencies "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect/configDependencies"
olog "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
)

func cmdConfigDependencies() *cobra.Command {
return NewCmd("config-dependencies").
WithDescription("Interact with skaffold config dependency definitions.").
WithCommands(cmdConfigDependenciesAdd())
}

func cmdConfigDependenciesAdd() *cobra.Command {
return NewCmd("add").
WithDescription("Add config dependencies").
WithExample("Add config dependency defined in `dependency.yaml`.", "inspect config-dependencies add dependency.yaml -f skaffold.yaml").
WithFlagAdder(cmdConfigDependenciesAddFlags).
WithArgs(func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
errMsg := "`config-dependencies add` requires exactly one file path argument"
olog.Entry(context.TODO()).Errorf(errMsg)
return errors.New(errMsg)
}
return nil
}, addConfigDependencies)
}

func cmdConfigDependenciesAddFlags(f *pflag.FlagSet) {
f.StringSliceVarP(&inspectFlags.modules, "module", "m", nil, "Names of modules to filter target action by.")
}

func addConfigDependencies(ctx context.Context, out io.Writer, args []string) error {
return configDependencies.AddConfigDependencies(ctx, out, addConfigDependenciesOptions(), args[0])
}

func addConfigDependenciesOptions() inspect.Options {
return inspect.Options{
Filename: inspectFlags.filename,
OutFormat: inspectFlags.outFormat,
RemoteCacheDir: inspectFlags.remoteCacheDir,
Modules: inspectFlags.modules,
}
}
66 changes: 66 additions & 0 deletions pkg/skaffold/inspect/configDependencies/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2023 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 inspect

import (
"context"
"io"
"os"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/yaml"
)

func AddConfigDependencies(ctx context.Context, out io.Writer, opts inspect.Options, inputFile string) error {
formatter := inspect.OutputFormatter(out, opts.OutFormat)

yamlFile, err := os.Open(inputFile)
if err != nil {
formatter.WriteErr(err)
return err
}
defer yamlFile.Close()
fileBytes, err := io.ReadAll(yamlFile)
if err != nil {
formatter.WriteErr(err)
return err
}
var cds []latest.ConfigDependency
if err = yaml.UnmarshalStrict(fileBytes, &cds); err != nil {
mattsanta marked this conversation as resolved.
Show resolved Hide resolved
formatter.WriteErr(err)
return err
}

cfgs, err := inspect.GetConfigSet(ctx, config.SkaffoldOptions{
ConfigurationFile: opts.Filename,
RemoteCacheDir: opts.RemoteCacheDir,
ConfigurationFilter: opts.Modules,
SkipConfigDefaults: true,
MakePathsAbsolute: util.Ptr(false),
})
if err != nil {
formatter.WriteErr(err)
return err
}
for _, cfg := range cfgs {
cfg.Dependencies = append(cfg.Dependencies, cds...)
}
return inspect.MarshalConfigSet(cfgs)
}
174 changes: 174 additions & 0 deletions pkg/skaffold/inspect/configDependencies/add_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
Copyright 2023 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 inspect

import (
"bytes"
"context"
"fmt"
"testing"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/v2/testutil"
)

const (
// Base config that can be used by config dependencies referenced.
baseCfg = `apiVersion: skaffold/v4beta5
kind: Config`
// We use the latest API version in the tests because the parser upgrades the version.
apiVersion = latest.Version
)

func TestAddConfigDependencies(t *testing.T) {
tests := []struct {
description string
config string
existingConfigDepFiles []string
input string
modules []string
expected string
shouldErr bool
}{
{
description: "adds remote config dependency",
config: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg1
`, apiVersion),
input: `
- configs: ["c1"]
path: add-dep.yaml
`,
expected: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg1
requires:
- configs:
- c1
path: add-dep.yaml
`, apiVersion),
},
{
description: "adds remote config dependencies when requires is present",
config: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg2
requires:
- path: existing-dep.yaml
`, apiVersion),
existingConfigDepFiles: []string{"existing-dep.yaml"},
input: `
- configs: ["c1"]
path: /add-dep.yaml
- path: /add-dep-2.yaml
`,
expected: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg2
requires:
- path: existing-dep.yaml
- configs:
- c1
path: /add-dep.yaml
- path: /add-dep-2.yaml
`, apiVersion),
},
{
description: "adds remote config dependency only to specified module",
config: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg1
---
apiVersion: %s
kind: Config
metadata:
name: cfg1_1
`, apiVersion, apiVersion),
input: `
- configs: ["c1"]
path: /add-dep.yaml
`,
modules: []string{"cfg1"},
expected: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg1
requires:
- configs:
- c1
path: /add-dep.yaml
---
apiVersion: %s
kind: Config
metadata:
name: cfg1_1
`, apiVersion, apiVersion),
},
{
description: "fails when specified module not present",
config: fmt.Sprintf(`apiVersion: %s
kind: Config
metadata:
name: cfg1
`, apiVersion),
input: `
- configs: ["c1"]
path: /add-dep.yaml
`,
modules: []string{"no"},
shouldErr: true,
},
{
description: "fails when input file is not list of config dependencies",
config: fmt.Sprintf(`apiVersion: %s
kind: Config
`, apiVersion),
input: `input`,
shouldErr: true,
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
td := t.NewTempDir()
td.Write("skaffold.yaml", test.config)
td.Write("input.yaml", test.input)
configFile := td.Root() + "/skaffold.yaml"
inputFile := td.Root() + "/input.yaml"

// The existing dependency files need to exist for the parser to resolve the config.
for _, s := range test.existingConfigDepFiles {
td.Write(s, baseCfg)
}

var b bytes.Buffer
err := AddConfigDependencies(context.Background(), &b, inspect.Options{Filename: configFile, Modules: test.modules}, inputFile)
t.CheckError(test.shouldErr, err)
if err == nil {
// The original config file is updated so check it's as expected.
t.CheckFileExistAndContent(configFile, []byte(test.expected))
}
})
}
}