Skip to content

Commit

Permalink
Showing 24 changed files with 1,071 additions and 251 deletions.
75 changes: 73 additions & 2 deletions cmd/skaffold/app/cmd/inspect_build_env.go
Original file line number Diff line number Diff line change
@@ -24,13 +24,23 @@ import (
"github.com/spf13/pflag"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
buildEnv "github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect/buildEnv"
)

var buildEnvFlags = struct {
profile string
projectID string
diskSizeGb int64
machineType string
timeout string
concurrency int
}{}

func cmdBuildEnv() *cobra.Command {
return NewCmd("build-env").
WithDescription("Interact with skaffold build environment definitions.").
WithPersistentFlagAdder(cmdBuildEnvFlags).
WithCommands(cmdBuildEnvList())
WithCommands(cmdBuildEnvList(), cmdBuildEnvAdd())
}

func cmdBuildEnvList() *cobra.Command {
@@ -41,8 +51,43 @@ func cmdBuildEnvList() *cobra.Command {
NoArgs(listBuildEnv)
}

func cmdBuildEnvAdd() *cobra.Command {
return NewCmd("add").
WithDescription("Add a new build environment to the default pipeline or to a new or existing profile.").
WithPersistentFlagAdder(cmdBuildEnvAddFlags).
WithCommands(cmdBuildEnvAddGcb())
}

func cmdBuildEnvAddGcb() *cobra.Command {
return NewCmd("googleCloudBuild").
WithDescription("Add a new GoogleCloudBuild build environment definition").
WithLongDescription(`Add a new GoogleCloudBuild build environment definition.
Without the '--profile' flag the new environment definition is added to the default pipeline. With the '--profile' flag it will create a new profile with this build env definition.
In these respective scenarios, it will fail if the build env definition for the default pipeline or the named profile already exists. To override an existing definition use 'skaffold inspect build-env modify' command instead.
Use the '--module' filter to specify the individual module to target. Otherwise, it'll be applied to all modules defined in the target file. Also, with the '--profile' flag if the target config imports other configs as dependencies, then the new profile will be recursively created in all the imported configs also.`).
WithExample("Add a new profile named 'gcb' targeting the builder 'googleCloudBuild' against the GCP project ID '1234'.", "inspect build-env add googleCloudBuild --profile gcb --projectID 1234 -f skaffold.yaml").
WithFlagAdder(cmdBuildEnvAddGcbFlags).
NoArgs(addGcbBuildEnv)
}

func listBuildEnv(ctx context.Context, out io.Writer) error {
return inspect.PrintBuildEnvsList(ctx, out, inspect.Options{Filename: inspectFlags.fileName, OutFormat: inspectFlags.outFormat, Modules: inspectFlags.modules, BuildEnvOptions: inspect.BuildEnvOptions{Profiles: inspectFlags.profiles}})
return buildEnv.PrintBuildEnvsList(ctx, out, printBuildEnvsListOptions())
}

func addGcbBuildEnv(ctx context.Context, out io.Writer) error {
return buildEnv.AddGcbBuildEnv(ctx, out, addGcbBuildEnvOptions())
}

func cmdBuildEnvAddFlags(f *pflag.FlagSet) {
f.StringVarP(&buildEnvFlags.profile, "profile", "p", "", `Profile name to add the new build env definition in. If the profile name doesn't exist then the profile will be created in all the target configs. If this flag is not specified then the build env is added to the default pipeline of the target configs.`)
}

func cmdBuildEnvAddGcbFlags(f *pflag.FlagSet) {
f.StringVar(&buildEnvFlags.projectID, "projectId", "", `ID of the Cloud Platform Project.`)
f.Int64Var(&buildEnvFlags.diskSizeGb, "diskSizeGb", 0, `Disk size of the VM that runs the build`)
f.StringVar(&buildEnvFlags.machineType, "machineType", "", `Type of VM that runs the build`)
f.StringVar(&buildEnvFlags.timeout, "timeout", "", `Build timeout (in seconds)`)
f.IntVar(&buildEnvFlags.concurrency, "concurrency", -1, `number of artifacts to build concurrently. 0 means "no-limit"`)
}

func cmdBuildEnvFlags(f *pflag.FlagSet) {
@@ -52,3 +97,29 @@ func cmdBuildEnvFlags(f *pflag.FlagSet) {
func cmdBuildEnvListFlags(f *pflag.FlagSet) {
f.StringSliceVarP(&inspectFlags.profiles, "profile", "p", nil, `Profile names to activate`)
}

func printBuildEnvsListOptions() inspect.Options {
return inspect.Options{
Filename: inspectFlags.fileName,
OutFormat: inspectFlags.outFormat,
Modules: inspectFlags.modules,
BuildEnvOptions: inspect.BuildEnvOptions{
Profiles: inspectFlags.profiles,
},
}
}
func addGcbBuildEnvOptions() inspect.Options {
return inspect.Options{
Filename: inspectFlags.fileName,
OutFormat: inspectFlags.outFormat,
Modules: inspectFlags.modules,
BuildEnvOptions: inspect.BuildEnvOptions{
Profile: buildEnvFlags.profile,
ProjectID: buildEnvFlags.projectID,
DiskSizeGb: buildEnvFlags.diskSizeGb,
MachineType: buildEnvFlags.machineType,
Timeout: buildEnvFlags.timeout,
Concurrency: buildEnvFlags.concurrency,
},
}
}
3 changes: 2 additions & 1 deletion cmd/skaffold/app/cmd/inspect_modules.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
modules "github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect/modules"
)

func cmdModules() *cobra.Command {
@@ -39,5 +40,5 @@ func cmdModulesList() *cobra.Command {
}

func listModules(ctx context.Context, out io.Writer) error {
return inspect.PrintModulesList(ctx, out, inspect.Options{Filename: inspectFlags.fileName, OutFormat: inspectFlags.outFormat})
return modules.PrintModulesList(ctx, out, inspect.Options{Filename: inspectFlags.fileName, OutFormat: inspectFlags.outFormat})
}
3 changes: 2 additions & 1 deletion cmd/skaffold/app/cmd/inspect_profiles.go
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import (
"github.com/spf13/pflag"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
profiles "github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect/profiles"
)

func cmdProfiles() *cobra.Command {
@@ -43,7 +44,7 @@ func cmdProfilesList() *cobra.Command {
}

func listProfiles(ctx context.Context, out io.Writer) error {
return inspect.PrintProfilesList(ctx, out, inspect.Options{Filename: inspectFlags.fileName, OutFormat: inspectFlags.outFormat, Modules: inspectFlags.modules, ProfilesOptions: inspect.ProfilesOptions{BuildEnv: inspect.BuildEnv(inspectFlags.buildEnv)}})
return profiles.PrintProfilesList(ctx, out, inspect.Options{Filename: inspectFlags.fileName, OutFormat: inspectFlags.outFormat, Modules: inspectFlags.modules, ProfilesOptions: inspect.ProfilesOptions{BuildEnv: inspect.BuildEnv(inspectFlags.buildEnv)}})
}

func cmdProfilesFlags(f *pflag.FlagSet) {
87 changes: 58 additions & 29 deletions docs/content/en/api/skaffold.swagger.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/content/en/docs/references/api/grpc.md
Original file line number Diff line number Diff line change
@@ -1008,6 +1008,8 @@ For Cancelled Error code, use range 800 to 850.<br>
| CONFIG_FILE_PATHS_SUBSTITUTION_ERR | 1210 | Failed to substitute absolute file paths in config |
| CONFIG_MULTI_IMPORT_PROFILE_CONFLICT_ERR | 1211 | Same config imported at least twice with different set of profiles |
| CONFIG_PROFILES_NOT_FOUND_ERR | 1212 | Profile selection did not match known profile names |
| INSPECT_UNKNOWN_ERR | 1301 | Catch-all `skaffold inspect` command error |
| INSPECT_BUILD_ENV_ALREADY_EXISTS_ERR | 1302 | Trying to add new build environment that already exists |



@@ -1065,6 +1067,7 @@ Enum for Suggestion codes
| CONFIG_CHECK_PROFILE_DEFINITION | 704 | Check profile definition in current config |
| CONFIG_CHECK_DEPENDENCY_PROFILES_SELECTION | 705 | Check active profile selection for dependency config |
| CONFIG_CHECK_PROFILE_SELECTION | 706 | Check profile selection flag |
| INSPECT_DEDUP_NEW_BUILD_ENV | 800 | `skaffold inspect` command error suggestion codes |
| OPEN_ISSUE | 900 | Open an issue so this situation can be diagnosed |
| CHECK_CUSTOM_COMMAND | 1000 | Test error suggestion codes |
| FIX_CUSTOM_COMMAND_TIMEOUT | 1001 | |
2 changes: 1 addition & 1 deletion pkg/skaffold/config/options.go
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@ type SkaffoldOptions struct {
ProfileAutoActivation bool
DryRun bool
SkipRender bool
SkipConfigDefaults bool

// Add Skaffold-specific labels including runID, deployer labels, etc.
// `CustomLabels` are still applied if this is false. Must only be used in
@@ -91,7 +92,6 @@ type SkaffoldOptions struct {
RPCHTTPPort int
BuildConcurrency int
MakePathsAbsolute *bool

// TODO(https://github.com/GoogleContainerTools/skaffold/issues/3668):
// remove minikubeProfile from here and instead detect it by matching the
// kubecontext API Server to minikube profiles
113 changes: 113 additions & 0 deletions pkg/skaffold/inspect/buildEnv/add_gcb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
Copyright 2021 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"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
)

func AddGcbBuildEnv(ctx context.Context, out io.Writer, opts inspect.Options) error {
formatter := inspect.OutputFormatter(out, opts.OutFormat)
cfgs, err := inspect.ConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename, ConfigurationFilter: opts.Modules, SkipConfigDefaults: true, MakePathsAbsolute: util.BoolPtr(false)})
if err != nil {
return formatter.WriteErr(err)
}
if opts.Profile == "" {
// empty profile flag implies that the new build env needs to be added to the default pipeline.
// for these cases, don't add the new env definition to any configs imported as dependencies.
cfgs = cfgs.SelectRootConfigs()
for _, cfg := range cfgs {
if cfg.Build.GoogleCloudBuild != nil && (*cfg.Build.GoogleCloudBuild != latestV1.GoogleCloudBuild{}) {
return formatter.WriteErr(inspect.BuildEnvAlreadyExists(inspect.BuildEnvs.GoogleCloudBuild, cfg.SourceFile, ""))
}
cfg.Build.GoogleCloudBuild = constructGcbDefinition(cfg.Build.GoogleCloudBuild, opts.BuildEnvOptions)
cfg.Build.LocalBuild = nil
cfg.Build.Cluster = nil
}
} else {
for _, cfg := range cfgs {
index := -1
for i := range cfg.Profiles {
if cfg.Profiles[i].Name == opts.Profile {
index = i
break
}
}
if index < 0 {
index = len(cfg.Profiles)
cfg.Profiles = append(cfg.Profiles, latestV1.Profile{Name: opts.Profile})
}
if cfg.Profiles[index].Build.GoogleCloudBuild != nil && (*cfg.Profiles[index].Build.GoogleCloudBuild != latestV1.GoogleCloudBuild{}) {
return formatter.WriteErr(inspect.BuildEnvAlreadyExists(inspect.BuildEnvs.GoogleCloudBuild, cfg.SourceFile, opts.Profile))
}
cfg.Profiles[index].Build.GoogleCloudBuild = constructGcbDefinition(cfg.Profiles[index].Build.GoogleCloudBuild, opts.BuildEnvOptions)
cfg.Profiles[index].Build.LocalBuild = nil
cfg.Profiles[index].Build.Cluster = nil

addProfileActivationStanza(cfg, opts.Profile)
}
}
return inspect.MarshalConfigSet(cfgs)
}

func constructGcbDefinition(existing *latestV1.GoogleCloudBuild, opts inspect.BuildEnvOptions) *latestV1.GoogleCloudBuild {
var b latestV1.GoogleCloudBuild
if existing != nil {
b = *existing
}
if opts.Concurrency >= 0 {
b.Concurrency = opts.Concurrency
}
if opts.DiskSizeGb > 0 {
b.DiskSizeGb = opts.DiskSizeGb
}
if opts.MachineType != "" {
b.MachineType = opts.MachineType
}
if opts.ProjectID != "" {
b.ProjectID = opts.ProjectID
}
if opts.Timeout != "" {
b.Timeout = opts.Timeout
}
return &b
}

func addProfileActivationStanza(cfg *parser.SkaffoldConfigEntry, profileName string) {
for i := range cfg.Dependencies {
if cfg.Dependencies[i].GitRepo != nil {
// setup profile activation stanza only for local config dependencies
continue
}
for j := range cfg.Dependencies[i].ActiveProfiles {
if cfg.Dependencies[i].ActiveProfiles[j].Name == profileName {
if !util.StrSliceContains(cfg.Dependencies[i].ActiveProfiles[j].ActivatedBy, profileName) {
cfg.Dependencies[i].ActiveProfiles[j].ActivatedBy = append(cfg.Dependencies[i].ActiveProfiles[j].ActivatedBy, profileName)
}
return
}
}
cfg.Dependencies[i].ActiveProfiles = append(cfg.Dependencies[i].ActiveProfiles, latestV1.ProfileDependency{Name: profileName, ActivatedBy: []string{profileName}})
}
}
383 changes: 383 additions & 0 deletions pkg/skaffold/inspect/buildEnv/add_gcb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,383 @@
/*
Copyright 2021 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"
"errors"
"fmt"
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestAddGcbBuildEnv(t *testing.T) {
tests := []struct {
description string
profile string
modules []string
buildEnvOpts inspect.BuildEnvOptions
expectedConfigs []string
err error
expectedErrMsg string
}{
{
description: "add to default pipeline",
buildEnvOpts: inspect.BuildEnvOptions{ProjectID: "project1", DiskSizeGb: 2, MachineType: "machine1", Timeout: "128", Concurrency: 2},
expectedConfigs: []string{
`apiVersion: ""
kind: ""
metadata:
name: cfg1_0
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
profiles:
- name: p1
build:
cluster: {}
---
apiVersion: ""
kind: ""
metadata:
name: cfg1_1
requires:
- path: path/to/cfg2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
profiles:
- name: p1
build:
cluster: {}
`, ``,
},
},
{
description: "add to existing profile",
buildEnvOpts: inspect.BuildEnvOptions{ProjectID: "project1", DiskSizeGb: 2, MachineType: "machine1", Timeout: "128", Concurrency: 2, Profile: "p1"},
expectedConfigs: []string{
`apiVersion: ""
kind: ""
metadata:
name: cfg1_0
build:
local: {}
profiles:
- name: p1
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
---
apiVersion: ""
kind: ""
metadata:
name: cfg1_1
requires:
- path: path/to/cfg2
activeProfiles:
- name: p1
activatedBy:
- p1
build:
local: {}
profiles:
- name: p1
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`, `apiVersion: ""
kind: ""
metadata:
name: cfg2
build:
googleCloudBuild: {}
profiles:
- name: p1
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`,
},
},
{
description: "add to new profile",
buildEnvOpts: inspect.BuildEnvOptions{ProjectID: "project1", DiskSizeGb: 2, MachineType: "machine1", Timeout: "128", Concurrency: 2, Profile: "p2"},
expectedConfigs: []string{
`apiVersion: ""
kind: ""
metadata:
name: cfg1_0
build:
local: {}
profiles:
- name: p1
build:
cluster: {}
- name: p2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
---
apiVersion: ""
kind: ""
metadata:
name: cfg1_1
requires:
- path: path/to/cfg2
activeProfiles:
- name: p2
activatedBy:
- p2
build:
local: {}
profiles:
- name: p1
build:
cluster: {}
- name: p2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`, `apiVersion: ""
kind: ""
metadata:
name: cfg2
build:
googleCloudBuild: {}
profiles:
- name: p1
build:
local: {}
- name: p2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`,
},
},
{
description: "add to new profile in selected modules",
modules: []string{"cfg1_1"},
buildEnvOpts: inspect.BuildEnvOptions{ProjectID: "project1", DiskSizeGb: 2, MachineType: "machine1", Timeout: "128", Concurrency: 2, Profile: "p2"},
expectedConfigs: []string{
`apiVersion: ""
kind: ""
metadata:
name: cfg1_0
build:
local: {}
profiles:
- name: p1
build:
cluster: {}
---
apiVersion: ""
kind: ""
metadata:
name: cfg1_1
requires:
- path: path/to/cfg2
activeProfiles:
- name: p2
activatedBy:
- p2
build:
local: {}
profiles:
- name: p1
build:
cluster: {}
- name: p2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`, `apiVersion: ""
kind: ""
metadata:
name: cfg2
build:
googleCloudBuild: {}
profiles:
- name: p1
build:
local: {}
- name: p2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`, "",
},
},
{
description: "add to new profile in nested module",
modules: []string{"cfg2"},
buildEnvOpts: inspect.BuildEnvOptions{ProjectID: "project1", DiskSizeGb: 2, MachineType: "machine1", Timeout: "128", Concurrency: 2, Profile: "p2"},
expectedConfigs: []string{"",
`apiVersion: ""
kind: ""
metadata:
name: cfg2
build:
googleCloudBuild: {}
profiles:
- name: p1
build:
local: {}
- name: p2
build:
googleCloudBuild:
projectId: project1
diskSizeGb: 2
machineType: machine1
timeout: "128"
concurrency: 2
`,
},
},
{
description: "actionable error",
err: sErrors.MainConfigFileNotFoundErr("path/to/skaffold.yaml", fmt.Errorf("failed to read file : %q", "skaffold.yaml")),
expectedErrMsg: `{"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",
},
{
description: "generic error",
err: errors.New("some error occurred"),
expectedErrMsg: `{"errorCode":"INSPECT_UNKNOWN_ERR","errorMessage":"some error occurred"}` + "\n",
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
configSet := parser.SkaffoldConfigSet{
&parser.SkaffoldConfigEntry{SkaffoldConfig: &v1.SkaffoldConfig{
Metadata: v1.Metadata{Name: "cfg1_0"},
Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{LocalBuild: &v1.LocalBuild{}}}},
Profiles: []v1.Profile{
{Name: "p1", Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{Cluster: &v1.ClusterDetails{}}}}},
}}, SourceFile: "path/to/cfg1", IsRootConfig: true, SourceIndex: 0},
&parser.SkaffoldConfigEntry{SkaffoldConfig: &v1.SkaffoldConfig{
Metadata: v1.Metadata{Name: "cfg1_1"},
Dependencies: []v1.ConfigDependency{{Path: "path/to/cfg2"}},
Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{LocalBuild: &v1.LocalBuild{}}}},
Profiles: []v1.Profile{
{Name: "p1", Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{Cluster: &v1.ClusterDetails{}}}}},
}}, SourceFile: "path/to/cfg1", IsRootConfig: true, SourceIndex: 1},
&parser.SkaffoldConfigEntry{SkaffoldConfig: &v1.SkaffoldConfig{
Metadata: v1.Metadata{Name: "cfg2"},
Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{GoogleCloudBuild: &v1.GoogleCloudBuild{}}}},
Profiles: []v1.Profile{
{Name: "p1", Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{LocalBuild: &v1.LocalBuild{}}}}},
}}, SourceFile: "path/to/cfg2", SourceIndex: 0},
}
t.Override(&inspect.ConfigSetFunc, func(opts config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
if test.err != nil {
return nil, test.err
}
var sets parser.SkaffoldConfigSet
if len(opts.ConfigurationFilter) == 0 || util.StrSliceContains(opts.ConfigurationFilter, "cfg2") || util.StrSliceContains(opts.ConfigurationFilter, "cfg1_1") {
sets = append(sets, configSet[2])
}
if len(opts.ConfigurationFilter) == 0 || util.StrSliceContains(opts.ConfigurationFilter, "cfg1_0") {
sets = append(sets, configSet[0])
}
if len(opts.ConfigurationFilter) == 0 || util.StrSliceContains(opts.ConfigurationFilter, "cfg1_1") {
sets = append(sets, configSet[1])
}
return sets, nil
})
t.Override(&inspect.ReadFileFunc, func(filename string) ([]byte, error) {
if filename == "path/to/cfg1" {
return yaml.MarshalWithSeparator([]*v1.SkaffoldConfig{configSet[0].SkaffoldConfig, configSet[1].SkaffoldConfig})
} else if filename == "path/to/cfg2" {
return yaml.MarshalWithSeparator([]*v1.SkaffoldConfig{configSet[2].SkaffoldConfig})
}
t.FailNow()
return nil, nil
})
var actualCfg1, actualCfg2 string
t.Override(&inspect.WriteFileFunc, func(filename string, data []byte) error {
switch filename {
case "path/to/cfg1":
actualCfg1 = string(data)
case "path/to/cfg2":
actualCfg2 = string(data)
default:
t.FailNow()
}
return nil
})

var buf bytes.Buffer
err := AddGcbBuildEnv(context.Background(), &buf, inspect.Options{OutFormat: "json", Modules: test.modules, BuildEnvOptions: test.buildEnvOpts})
t.CheckNoError(err)
if test.err == nil {
t.CheckDeepEqual(test.expectedConfigs[0], actualCfg1)
t.CheckDeepEqual(test.expectedConfigs[1], actualCfg2)
} else {
t.CheckDeepEqual(test.expectedErrMsg, buf.String())
}
})
}
}
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ import (
"io"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
)

type buildEnvList struct {
@@ -34,19 +34,16 @@ type buildEnvEntry struct {
Module string `json:"module,omitempty"`
}

func PrintBuildEnvsList(ctx context.Context, out io.Writer, opts Options) error {
formatter := getOutputFormatter(out, opts.OutFormat)
cfgs, err := getConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename, Profiles: opts.Profiles})
func PrintBuildEnvsList(ctx context.Context, out io.Writer, opts inspect.Options) error {
formatter := inspect.OutputFormatter(out, opts.OutFormat)
cfgs, err := inspect.ConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename, Profiles: opts.Profiles, ConfigurationFilter: opts.Modules})
if err != nil {
return formatter.WriteErr(err)
}

l := &buildEnvList{BuildEnvs: []buildEnvEntry{}}
for _, c := range cfgs {
if len(opts.Modules) > 0 && !util.StrSliceContains(opts.Modules, c.Metadata.Name) {
continue
}
buildEnv := GetBuildEnv(&c.Build.BuildType)
buildEnv := inspect.GetBuildEnv(&c.Build.BuildType)
l.BuildEnvs = append(l.BuildEnvs, buildEnvEntry{Type: string(buildEnv), Path: c.SourceFile, Module: c.Metadata.Name})
}
return formatter.Write(l)
Original file line number Diff line number Diff line change
@@ -24,9 +24,11 @@ import (
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/testutil"
)

@@ -78,7 +80,7 @@ func TestPrintBuildEnvsList(t *testing.T) {
{
description: "generic error",
err: errors.New("some error occurred"),
expected: `{"errorCode":"UNKNOWN_ERROR","errorMessage":"some error occurred"}` + "\n",
expected: `{"errorCode":"INSPECT_UNKNOWN_ERR","errorMessage":"some error occurred"}` + "\n",
},
}

@@ -98,22 +100,27 @@ func TestPrintBuildEnvsList(t *testing.T) {
{Name: "local", Pipeline: v1.Pipeline{Build: v1.BuildConfig{BuildType: v1.BuildType{LocalBuild: &v1.LocalBuild{}}}}},
}}, SourceFile: "path/to/cfg2"},
}
t.Override(&getConfigSetFunc, func(config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
t.Override(&inspect.ConfigSetFunc, func(opts config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
// mock profile activation
var set parser.SkaffoldConfigSet
for _, c := range configSet {
for _, pName := range test.profiles {
if len(opts.ConfigurationFilter) > 0 && !util.StrSliceContains(opts.ConfigurationFilter, c.Metadata.Name) {
continue
}
for _, pName := range opts.Profiles {
for _, profile := range c.Profiles {
if profile.Name != pName {
continue
}
c.Build.BuildType = profile.Build.BuildType
}
}
set = append(set, c)
}
return configSet, test.err
return set, test.err
})
var buf bytes.Buffer
err := PrintBuildEnvsList(context.Background(), &buf, Options{OutFormat: "json", Modules: test.module, BuildEnvOptions: BuildEnvOptions{Profiles: test.profiles}})
err := PrintBuildEnvsList(context.Background(), &buf, inspect.Options{OutFormat: "json", Modules: test.module, BuildEnvOptions: inspect.BuildEnvOptions{Profiles: test.profiles}})
t.CheckNoError(err)
t.CheckDeepEqual(test.expected, buf.String())
})
45 changes: 45 additions & 0 deletions pkg/skaffold/inspect/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright 2021 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 (
"fmt"

sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
"github.com/GoogleContainerTools/skaffold/proto/v1"
)

// BuildEnvAlreadyExists specifies that there's an existing build environment definition for the same type.
func BuildEnvAlreadyExists(b BuildEnv, filename string, profile string) error {
var msg string
if profile == "" {
msg = fmt.Sprintf("trying to create a %q build environment definition that already exists, in file %s", b, filename)
} else {
msg = fmt.Sprintf("trying to create a %q build environment definition that already exists, in profile %q in file %s", b, profile, filename)
}
return sErrors.NewError(fmt.Errorf(msg),
proto.ActionableErr{
Message: msg,
ErrCode: proto.StatusCode_INSPECT_BUILD_ENV_ALREADY_EXISTS_ERR,
Suggestions: []*proto.Suggestion{
{
SuggestionCode: proto.SuggestionCode_INSPECT_DEDUP_NEW_BUILD_ENV,
Action: "Use the `modify` command instead of the `add` command to overwrite fields for an already existing build environment type. Otherwise pass the `--profile` flag with a unique name to create the new build environment definition in a new profile instead",
},
},
})
}
89 changes: 89 additions & 0 deletions pkg/skaffold/inspect/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright 2021 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"
"fmt"
"io"
"io/ioutil"

yamlv3 "gopkg.in/yaml.v3"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
)

var (
ReadFileFunc = util.ReadConfiguration
WriteFileFunc = func(filename string, data []byte) error {
return ioutil.WriteFile(filename, data, 0644)
}
)

// MarshalConfigSet marshals out the slice of skaffold configs into the respective source `skaffold.yaml` files.
// It ensures that the unmodified configs are copied over as-is in their original positions in the file.
func MarshalConfigSet(cfgs parser.SkaffoldConfigSet) error {
m := make(map[string]parser.SkaffoldConfigSet)
for _, cfg := range cfgs {
m[cfg.SourceFile] = append(m[cfg.SourceFile], cfg)
}
for file, set := range m {
if err := marshalConfigSetForFile(file, set); err != nil {
return err
}
}
return nil
}

func marshalConfigSetForFile(filename string, cfgs parser.SkaffoldConfigSet) error {
buf, err := ReadFileFunc(filename)
if err != nil {
return sErrors.ConfigParsingError(err)
}
in := bytes.NewReader(buf)
decoder := yamlv3.NewDecoder(in)
decoder.KnownFields(true)
var sl []interface{}
for {
var parsed yamlv3.Node
err := decoder.Decode(&parsed)
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("unable to parse YAML: %w", err)
}
// parsed content is a document so the `Content` slice has exactly one element
sl = append(sl, parsed.Content[0])
}

for i, cfg := range cfgs {
sl[cfg.SourceIndex] = cfgs[i].SkaffoldConfig
}

newCfgs, err := yaml.MarshalWithSeparator(sl)
if err != nil {
return fmt.Errorf("marshaling new configs: %w", err)
}
if err := WriteFileFunc(filename, newCfgs); err != nil {
return fmt.Errorf("writing config file: %w", err)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import (
"io"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
)

type moduleList struct {
@@ -32,9 +33,9 @@ type moduleEntry struct {
Path string `json:"path"`
}

func PrintModulesList(ctx context.Context, out io.Writer, opts Options) error {
formatter := getOutputFormatter(out, opts.OutFormat)
cfgs, err := getConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename})
func PrintModulesList(ctx context.Context, out io.Writer, opts inspect.Options) error {
formatter := inspect.OutputFormatter(out, opts.OutFormat)
cfgs, err := inspect.ConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename})
if err != nil {
return formatter.WriteErr(err)
}
Original file line number Diff line number Diff line change
@@ -24,9 +24,11 @@ import (
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/testutil"
)

@@ -53,17 +55,27 @@ func TestPrintModulesList(t *testing.T) {
{
description: "generic error",
err: errors.New("some error occurred"),
expected: `{"errorCode":"UNKNOWN_ERROR","errorMessage":"some error occurred"}` + "\n",
expected: `{"errorCode":"INSPECT_UNKNOWN_ERR","errorMessage":"some error occurred"}` + "\n",
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
t.Override(&getConfigSetFunc, func(config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
return test.configSet, test.err
t.Override(&inspect.ConfigSetFunc, func(opts config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
if len(opts.ConfigurationFilter) == 0 {
return test.configSet, test.err
}
var set parser.SkaffoldConfigSet
if util.StrSliceContains(opts.ConfigurationFilter, "cfg1") {
set = append(set, test.configSet[0])
}
if util.StrSliceContains(opts.ConfigurationFilter, "cfg2") {
set = append(set, test.configSet[1])
}
return set, test.err
})
var buf bytes.Buffer
err := PrintModulesList(context.Background(), &buf, Options{OutFormat: "json"})
err := PrintModulesList(context.Background(), &buf, inspect.Options{OutFormat: "json"})
t.CheckNoError(err)
t.CheckDeepEqual(test.expected, buf.String())
})
6 changes: 3 additions & 3 deletions pkg/skaffold/inspect/output.go
Original file line number Diff line number Diff line change
@@ -25,12 +25,12 @@ import (
"github.com/GoogleContainerTools/skaffold/proto/v1"
)

type formatter interface {
type Formatter interface {
Write(interface{}) error
WriteErr(error) error
}

func getOutputFormatter(out io.Writer, _ string) formatter {
func OutputFormatter(out io.Writer, _ string) Formatter {
// TODO: implement other output formatters. Currently only JSON is implemented
return jsonFormatter{out: out}
}
@@ -54,7 +54,7 @@ func (j jsonFormatter) WriteErr(err error) error {
if errors.As(err, &sErr) {
jsonErr = jsonErrorOutput{ErrorCode: sErr.StatusCode().String(), ErrorMessage: sErr.Error()}
} else {
jsonErr = jsonErrorOutput{ErrorCode: proto.StatusCode_UNKNOWN_ERROR.String(), ErrorMessage: err.Error()}
jsonErr = jsonErrorOutput{ErrorCode: proto.StatusCode_INSPECT_UNKNOWN_ERR.String(), ErrorMessage: err.Error()}
}
return json.NewEncoder(j.out).Encode(jsonErr)
}
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ import (
"io"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
)

type profileList struct {
@@ -34,20 +34,17 @@ type profileEntry struct {
Module string `json:"module,omitempty"`
}

func PrintProfilesList(ctx context.Context, out io.Writer, opts Options) error {
formatter := getOutputFormatter(out, opts.OutFormat)
cfgs, err := getConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename})
func PrintProfilesList(ctx context.Context, out io.Writer, opts inspect.Options) error {
formatter := inspect.OutputFormatter(out, opts.OutFormat)
cfgs, err := inspect.ConfigSetFunc(config.SkaffoldOptions{ConfigurationFile: opts.Filename, ConfigurationFilter: opts.Modules})
if err != nil {
return formatter.WriteErr(err)
}

l := &profileList{Profiles: []profileEntry{}}
for _, c := range cfgs {
if len(opts.Modules) > 0 && !util.StrSliceContains(opts.Modules, c.Metadata.Name) {
continue
}
for _, p := range c.Profiles {
if opts.BuildEnv != BuildEnvs.Unspecified && GetBuildEnv(&p.Build.BuildType) != opts.BuildEnv {
if opts.BuildEnv != inspect.BuildEnvs.Unspecified && inspect.GetBuildEnv(&p.Build.BuildType) != opts.BuildEnv {
continue
}
l.Profiles = append(l.Profiles, profileEntry{Name: p.Name, Path: c.SourceFile, Module: c.Metadata.Name})
Original file line number Diff line number Diff line change
@@ -24,17 +24,19 @@ import (
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/parser"
sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestPrintProfilesList(t *testing.T) {
tests := []struct {
description string
configSet parser.SkaffoldConfigSet
buildEnv BuildEnv
buildEnv inspect.BuildEnv
module []string
err error
expected string
@@ -96,7 +98,7 @@ func TestPrintProfilesList(t *testing.T) {
`{"name":"p4","path":"path/to/cfg2","module":"cfg2"}` +
"]}\n",
module: []string{"cfg2"},
buildEnv: BuildEnvs.GoogleCloudBuild,
buildEnv: inspect.BuildEnvs.GoogleCloudBuild,
},
{
description: "actionable error",
@@ -106,17 +108,27 @@ func TestPrintProfilesList(t *testing.T) {
{
description: "generic error",
err: errors.New("some error occurred"),
expected: `{"errorCode":"UNKNOWN_ERROR","errorMessage":"some error occurred"}` + "\n",
expected: `{"errorCode":"INSPECT_UNKNOWN_ERR","errorMessage":"some error occurred"}` + "\n",
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
t.Override(&getConfigSetFunc, func(config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
return test.configSet, test.err
t.Override(&inspect.ConfigSetFunc, func(opts config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
if len(opts.ConfigurationFilter) == 0 {
return test.configSet, test.err
}
var set parser.SkaffoldConfigSet
if util.StrSliceContains(opts.ConfigurationFilter, "cfg1") {
set = append(set, test.configSet[0])
}
if util.StrSliceContains(opts.ConfigurationFilter, "cfg2") {
set = append(set, test.configSet[1])
}
return set, test.err
})
var buf bytes.Buffer
err := PrintProfilesList(context.Background(), &buf, Options{OutFormat: "json", Modules: test.module, ProfilesOptions: ProfilesOptions{BuildEnv: test.buildEnv}})
err := PrintProfilesList(context.Background(), &buf, inspect.Options{OutFormat: "json", Modules: test.module, ProfilesOptions: inspect.ProfilesOptions{BuildEnv: test.buildEnv}})
t.CheckNoError(err)
t.CheckDeepEqual(test.expected, buf.String())
})
16 changes: 14 additions & 2 deletions pkg/skaffold/inspect/types.go
Original file line number Diff line number Diff line change
@@ -44,13 +44,25 @@ type ProfilesOptions struct {
type BuildEnvOptions struct {
// Profiles is the slice of profile names to activate.
Profiles []string
// Profile is a target profile to create or edit
Profile string
// ProjectID is the GCP project ID
ProjectID string
// DiskSizeGb is the disk size of the VM that runs the build
DiskSizeGb int64
// MachineType is the type of VM that runs the build
MachineType string
// Timeout is the build timeout (in seconds)
Timeout string
// Concurrency is the number of artifacts to build concurrently. 0 means "no-limit"
Concurrency int
}

type BuildEnv string

var (
getConfigSetFunc = parser.GetConfigSet
BuildEnvs = struct {
ConfigSetFunc = parser.GetConfigSet
BuildEnvs = struct {
Unspecified BuildEnv
Local BuildEnv
GoogleCloudBuild BuildEnv
10 changes: 6 additions & 4 deletions pkg/skaffold/parser/config.go
Original file line number Diff line number Diff line change
@@ -166,14 +166,16 @@ func processEachConfig(config *latestV1.SkaffoldConfig, cfgOpts configOpts, opts
if err != nil {
return nil, sErrors.ConfigProfileActivationErr(config.Metadata.Name, cfgOpts.file, err)
}
if err := defaults.Set(config); err != nil {
return nil, sErrors.ConfigSetDefaultValuesErr(config.Metadata.Name, cfgOpts.file, err)
if !opts.SkipConfigDefaults {
if err := defaults.Set(config); err != nil {
return nil, sErrors.ConfigSetDefaultValuesErr(config.Metadata.Name, cfgOpts.file, err)
}
}
// if `opts.MakePathsAbsolute` is not set, convert relative file paths to absolute for all configs that are not invoked explicitly.
// This avoids maintaining multiple root directory information since the dependency skaffold configs would have their own root directory.
// if `opts.MakePathsAbsolute` is set, use that as condition to decide on making file paths absolute for all configs or none at all.
// This is used when the parsed config is marshalled out (for commands like `skaffold diagnose` or `skaffold inspect`), we want to retain the original relative paths in the output files.
if (opts.MakePathsAbsolute != nil && *opts.MakePathsAbsolute) || (opts.MakePathsAbsolute == nil && cfgOpts.isDependency) {
if (opts.MakePathsAbsolute != nil && (*opts.MakePathsAbsolute)) || (opts.MakePathsAbsolute == nil && cfgOpts.isDependency) {
if err := tags.MakeFilePathsAbsolute(config, filepath.Dir(cfgOpts.file)); err != nil {
return nil, sErrors.ConfigSetAbsFilePathsErr(config.Metadata.Name, cfgOpts.file, err)
}
@@ -195,7 +197,7 @@ func processEachConfig(config *latestV1.SkaffoldConfig, cfgOpts configOpts, opts
}

if required {
configs = append(configs, &SkaffoldConfigEntry{SkaffoldConfig: config, SourceFile: cfgOpts.file, SourceIndex: index})
configs = append(configs, &SkaffoldConfigEntry{SkaffoldConfig: config, SourceFile: cfgOpts.file, SourceIndex: index, IsRootConfig: !cfgOpts.isDependency})
}
return configs, nil
}
16 changes: 14 additions & 2 deletions pkg/skaffold/parser/types.go
Original file line number Diff line number Diff line change
@@ -24,6 +24,18 @@ type SkaffoldConfigSet []*SkaffoldConfigEntry
// SkaffoldConfigEntry encapsulates a single skaffold configuration, along with the source filename and its index in that file.
type SkaffoldConfigEntry struct {
*latestV1.SkaffoldConfig
SourceFile string
SourceIndex int
SourceFile string
SourceIndex int
IsRootConfig bool
}

// SelectRootConfigs filters SkaffoldConfigSet to only configs read from the root skaffold.yaml file
func (s SkaffoldConfigSet) SelectRootConfigs() SkaffoldConfigSet {
var filteredSet SkaffoldConfigSet
for _, entry := range s {
if entry.IsRootConfig {
filteredSet = append(filteredSet, entry)
}
}
return filteredSet
}
359 changes: 187 additions & 172 deletions proto/enums/enums.pb.go

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions proto/enums/enums.proto
Original file line number Diff line number Diff line change
@@ -403,6 +403,13 @@ enum StatusCode {
CONFIG_MULTI_IMPORT_PROFILE_CONFLICT_ERR = 1211;
// Profile selection did not match known profile names
CONFIG_PROFILES_NOT_FOUND_ERR = 1212;

// Inspect command errors

// Catch-all `skaffold inspect` command error
INSPECT_UNKNOWN_ERR = 1301;
// Trying to add new build environment that already exists
INSPECT_BUILD_ENV_ALREADY_EXISTS_ERR = 1302;
}

// Enum for Suggestion codes
@@ -532,6 +539,11 @@ enum SuggestionCode {
CONFIG_CHECK_DEPENDENCY_PROFILES_SELECTION = 705;
// Check profile selection flag
CONFIG_CHECK_PROFILE_SELECTION = 706;

// `skaffold inspect` command error suggestion codes
INSPECT_DEDUP_NEW_BUILD_ENV = 800;


// Open an issue so this situation can be diagnosed
OPEN_ISSUE = 900;

3 changes: 3 additions & 0 deletions proto/v1/skaffold.pb.go
3 changes: 3 additions & 0 deletions proto/v2/skaffold.pb.go

0 comments on commit d9b397e

Please sign in to comment.