Skip to content

Commit 807316b

Browse files
committed
add exists validation to kubectl manifests specified in skaffold config
1 parent 9cd80d3 commit 807316b

File tree

21 files changed

+424
-228
lines changed

21 files changed

+424
-228
lines changed

cmd/skaffold/app/cmd/fix.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func fix(out io.Writer, configFile string, toVersion string, overwrite bool) err
8989
SourceFile: configFile,
9090
IsRootConfig: true})
9191
}
92-
if err := validation.Process(cfgs); err != nil {
92+
if err := validation.Process(cfgs, validation.Config{CheckSourceFiles: false}); err != nil {
9393
return fmt.Errorf("validating upgraded config: %w", err)
9494
}
9595
}

cmd/skaffold/app/cmd/runner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func runContext(out io.Writer, opts config.SkaffoldOptions) (*runcontext.RunCont
7979
}
8080
setDefaultDeployer(cfgSet)
8181

82-
if err := validation.Process(cfgSet); err != nil {
82+
if err := validation.Process(cfgSet, validation.DefaultConfig); err != nil {
8383
return nil, nil, fmt.Errorf("invalid skaffold config: %w", err)
8484
}
8585
var configs []*latestV1.SkaffoldConfig

cmd/skaffold/app/cmd/runner_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
2727
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
2828
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
29+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/validation"
2930
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/update"
3031
"github.com/GoogleContainerTools/skaffold/testutil"
3132
)
@@ -86,6 +87,7 @@ func TestCreateNewRunner(t *testing.T) {
8687
}
8788
for _, test := range tests {
8889
testutil.Run(t, test.description, func(t *testutil.T) {
90+
t.Override(&validation.DefaultConfig, validation.Config{CheckSourceFiles: false})
8991
t.Override(&docker.NewAPIClient, func(docker.Config) (docker.LocalDaemon, error) {
9092
return docker.NewLocalDaemon(&testutil.FakeAPIClient{
9193
ErrVersion: true,

docs/content/en/api/skaffold.swagger.json

+30-15
Large diffs are not rendered by default.

docs/content/en/docs/references/api/grpc.md

+2
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,7 @@ For Cancelled Error code, use range 800 to 850.<br>
10191019
| CONFIG_UNKNOWN_API_VERSION_ERR | 1213 | Config API version not found |
10201020
| CONFIG_UNKNOWN_VALIDATOR | 1214 | The validator is not allowed in skaffold-managed mode. |
10211021
| CONFIG_UNKNOWN_TRANSFORMER | 1215 | The transformer is not allowed in skaffold-managed mode. |
1022+
| CONFIG_MISSING_MANIFEST_FILE_ERR | 1216 | Manifest file not found |
10221023
| INSPECT_UNKNOWN_ERR | 1301 | Catch-all `skaffold inspect` command error |
10231024
| INSPECT_BUILD_ENV_ALREADY_EXISTS_ERR | 1302 | Trying to add new build environment that already exists |
10241025
| INSPECT_BUILD_ENV_INCORRECT_TYPE_ERR | 1303 | Trying to modify build environment that doesn't exist |
@@ -1087,6 +1088,7 @@ Enum for Suggestion codes
10871088
| CONFIG_FIX_API_VERSION | 707 | Fix config API version or upgrade the skaffold binary |
10881089
| CONFIG_ALLOWLIST_VALIDATORS | 708 | Only the allow listed validators are acceptable in skaffold-managed mode. |
10891090
| CONFIG_ALLOWLIST_transformers | 709 | Only the allow listed transformers are acceptable in skaffold-managed mode. |
1091+
| CONFIG_FIX_MISSING_MANIFEST_FILE | 710 | Check mising manifest file section of config and fix as needed. |
10901092
| INSPECT_USE_MODIFY_OR_NEW_PROFILE | 800 | Create new build env in a profile instead, or use the 'modify' command |
10911093
| INSPECT_USE_ADD_BUILD_ENV | 801 | Check profile selection, or use the 'add' command instead |
10921094
| INSPECT_CHECK_INPUT_PROFILE | 802 | Check profile flag value |

integration/testdata/build/k8s/dummy.yaml

Whitespace-only changes.

integration/testdata/generate_pipeline/existing_oncluster/k8s-yaml/dummy.yaml

Whitespace-only changes.

integration/testdata/generate_pipeline/existing_other/k8s-yaml/dummy.yaml

Whitespace-only changes.

integration/testdata/generate_pipeline/k8s-yaml/dummy.yaml

Whitespace-only changes.

integration/testdata/generate_pipeline/multiple_configs/k8s-yaml/dummy.yaml

Whitespace-only changes.

integration/testdata/generate_pipeline/multiple_configs/sub-app/k8s-yaml/dummy.yaml

Whitespace-only changes.

integration/testdata/generate_pipeline/no_profiles/k8s-yaml/dummy.yaml

Whitespace-only changes.

integration/testdata/tagPolicy/k8s/dummy.yaml

Whitespace-only changes.

pkg/skaffold/parser/config.go

+41-10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ func GetAllConfigs(opts config.SkaffoldOptions) ([]*latestV1.SkaffoldConfig, err
7373
return cfgs, nil
7474
}
7575

76+
// GetBase returns the correct base address (work dir) for a given config file
77+
func GetBase(isDependency bool, cfgFile string) (string, error) {
78+
if isDependency {
79+
logrus.Tracef("found %s base dir for absolute path substitution within skaffold config %s", filepath.Dir(cfgFile), cfgFile)
80+
return filepath.Dir(cfgFile), nil
81+
}
82+
logrus.Tracef("found cwd as base for absolute path substitution within skaffold config %s", cfgFile)
83+
return util.RealWorkDir()
84+
}
85+
7686
// GetConfigSet returns the list of all skaffold configurations parsed from the target config file in addition to all resolved dependency configs as a `SkaffoldConfigSet`.
7787
// This struct additionally contains the file location that each skaffold configuration is parsed from.
7888
func GetConfigSet(opts config.SkaffoldOptions) (SkaffoldConfigSet, error) {
@@ -129,6 +139,36 @@ func getConfigs(cfgOpts configOpts, opts config.SkaffoldOptions, r *record) (Ska
129139
seen[cfgName] = true
130140
}
131141

142+
// // validate that manifest files referenced in config exist
143+
// for _, cfg := range parsed {
144+
// if cfg.(*latestV1.SkaffoldConfig).Deploy.KubectlDeploy == nil {
145+
// break
146+
// }
147+
// manifests := cfg.(*latestV1.SkaffoldConfig).Deploy.KubectlDeploy.Manifests
148+
// // TODO(6050) validate for other deploy types - helm, kpt, etc.
149+
// for _, pattern := range manifests {
150+
// if util.IsURL(pattern) {
151+
// continue
152+
// }
153+
// // handle absolute manifest paths
154+
// base := ""
155+
// if !filepath.IsAbs(pattern) {
156+
// base, err = getBase(cfgOpts)
157+
// if err != nil {
158+
// return nil, err
159+
// }
160+
// }
161+
162+
// expanded, err := filepath.Glob(filepath.Join(base, pattern))
163+
// if err != nil {
164+
// return nil, err
165+
// }
166+
// if len(expanded) == 0 {
167+
// return nil, sErrors.ConfigHasMissingManifestFileErr(cfgOpts.file, pattern, base)
168+
// }
169+
// }
170+
// }
171+
132172
var configs SkaffoldConfigSet
133173
for i, cfg := range parsed {
134174
config := cfg.(*latestV1.SkaffoldConfig)
@@ -176,7 +216,7 @@ func processEachConfig(config *latestV1.SkaffoldConfig, cfgOpts configOpts, opts
176216
// if `opts.MakePathsAbsolute` is set, use that as condition to decide on making file paths absolute for all configs or none at all.
177217
// 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.
178218
if isMakePathsAbsoluteSet(opts) || (opts.MakePathsAbsolute == nil && cfgOpts.isDependency) {
179-
base, err := getBase(cfgOpts)
219+
base, err := GetBase(cfgOpts.isDependency, cfgOpts.file)
180220
if err != nil {
181221
return nil, sErrors.ConfigSetAbsFilePathsErr(config.Metadata.Name, cfgOpts.file, err)
182222
}
@@ -359,15 +399,6 @@ func isMakePathsAbsoluteSet(opts config.SkaffoldOptions) bool {
359399
return opts.MakePathsAbsolute != nil && (*opts.MakePathsAbsolute)
360400
}
361401

362-
func getBase(cfgOpts configOpts) (string, error) {
363-
if cfgOpts.isDependency {
364-
logrus.Tracef("found %s base dir for absolute path substitution within skaffold config %s", filepath.Dir(cfgOpts.file), cfgOpts.file)
365-
return filepath.Dir(cfgOpts.file), nil
366-
}
367-
logrus.Tracef("found cwd as base for absolute path substitution within skaffold config %s", cfgOpts.file)
368-
return util.RealWorkDir()
369-
}
370-
371402
func isRemoteConfig(file string, opts config.SkaffoldOptions) (bool, error) {
372403
dir, err := git.GetRepoCacheDir(opts)
373404
if err != nil {

pkg/skaffold/schema/validation/samples_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func checkSkaffoldConfig(t *testutil.T, yaml []byte) {
8888
t.CheckNoError(err)
8989
cfgs = append(cfgs, cfg)
9090
}
91-
err = Process(cfgs)
91+
err = Process(cfgs, Config{CheckSourceFiles: false})
9292
t.CheckNoError(err)
9393
}
9494

pkg/skaffold/schema/validation/validation.go

+59-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package validation
1919
import (
2020
"context"
2121
"fmt"
22+
"path/filepath"
2223
"reflect"
2324
"regexp"
2425
"strings"
@@ -41,11 +42,16 @@ import (
4142
var (
4243
// for testing
4344
validateYamltags = yamltags.ValidateStruct
45+
DefaultConfig = Config{CheckSourceFiles: true}
4446
dependencyAliasPattern = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
4547
)
4648

49+
type Config struct {
50+
CheckSourceFiles bool
51+
}
52+
4753
// Process checks if the Skaffold pipeline is valid and returns all encountered errors as a concatenated string
48-
func Process(configs parser.SkaffoldConfigSet) error {
54+
func Process(configs parser.SkaffoldConfigSet, validateConfig Config) error {
4955
var errs = validateImageNames(configs)
5056
for _, config := range configs {
5157
var cfgErrs []error
@@ -63,6 +69,10 @@ func Process(configs parser.SkaffoldConfigSet) error {
6369
}
6470
errs = append(errs, validateArtifactDependencies(configs)...)
6571
errs = append(errs, validateSingleKubeContext(configs)...)
72+
if validateConfig.CheckSourceFiles {
73+
// TODO(6050) validate for other deploy types - helm, kpt, etc.
74+
errs = append(errs, validateKubectlManifests(configs)...)
75+
}
6676
if len(errs) == 0 {
6777
return nil
6878
}
@@ -581,3 +591,51 @@ func wrapWithContext(config *parser.SkaffoldConfigEntry, errs ...error) []error
581591
}
582592
return errs
583593
}
594+
595+
// validateKubectlManifests
596+
// - validates that kubectl manifest files specified in the skaffold config exist
597+
func validateKubectlManifests(configs parser.SkaffoldConfigSet) (errs []error) {
598+
for _, c := range configs {
599+
if c.IsRemote {
600+
continue
601+
}
602+
if c.Deploy.KubectlDeploy == nil {
603+
continue
604+
}
605+
// validate that manifest files referenced in config exist
606+
for _, pattern := range c.Deploy.KubectlDeploy.Manifests {
607+
if util.IsURL(pattern) {
608+
continue
609+
}
610+
// handle absolute manifest paths
611+
base := ""
612+
var err error
613+
if !filepath.IsAbs(pattern) {
614+
base, err = parser.GetBase(!c.IsRootConfig, c.SourceFile)
615+
if err != nil {
616+
errs = append(errs, err)
617+
}
618+
}
619+
620+
expanded, err := filepath.Glob(filepath.Join(base, pattern))
621+
if err != nil {
622+
errs = append(errs, err)
623+
}
624+
if len(expanded) == 0 {
625+
msg := fmt.Sprintf("skaffold config named %q referenced file %q that could not be found", c.SourceFile, pattern)
626+
errs = append(errs, sErrors.NewError(fmt.Errorf(msg),
627+
proto.ActionableErr{
628+
Message: msg,
629+
ErrCode: proto.StatusCode_CONFIG_MISSING_MANIFEST_FILE_ERR,
630+
Suggestions: []*proto.Suggestion{
631+
{
632+
SuggestionCode: proto.SuggestionCode_CONFIG_FIX_MISSING_MANIFEST_FILE,
633+
Action: fmt.Sprintf("Verify that file %q referenced in config %q exists at %q and the path and naming are correct", pattern, c.SourceFile, filepath.Join(base, pattern)),
634+
},
635+
},
636+
}))
637+
}
638+
}
639+
}
640+
return errs
641+
}

pkg/skaffold/schema/validation/validation_test.go

+78-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"os"
24+
"path/filepath"
2325
"testing"
2426

2527
"github.com/docker/docker/api/types"
@@ -90,7 +92,8 @@ func TestValidateSchema(t *testing.T) {
9092
}
9193
for _, test := range tests {
9294
testutil.Run(t, test.description, func(t *testutil.T) {
93-
err := Process(parser.SkaffoldConfigSet{&parser.SkaffoldConfigEntry{SkaffoldConfig: test.cfg}})
95+
err := Process(parser.SkaffoldConfigSet{&parser.SkaffoldConfigEntry{SkaffoldConfig: test.cfg}},
96+
Config{CheckSourceFiles: false})
9497

9598
t.CheckError(test.shouldErr, err)
9699
})
@@ -511,7 +514,7 @@ func TestValidateNetworkMode(t *testing.T) {
511514
Artifacts: test.artifacts,
512515
},
513516
},
514-
}}})
517+
}}}, Config{CheckSourceFiles: false})
515518

516519
t.CheckError(test.shouldErr, err)
517520
})
@@ -812,7 +815,7 @@ func TestValidateSyncRules(t *testing.T) {
812815
Artifacts: test.artifacts,
813816
},
814817
},
815-
}}})
818+
}}}, Config{CheckSourceFiles: false})
816819

817820
t.CheckError(test.shouldErr, err)
818821
})
@@ -970,7 +973,7 @@ func TestValidateImageNames(t *testing.T) {
970973
},
971974
},
972975
},
973-
})
976+
}, Config{CheckSourceFiles: false})
974977

975978
t.CheckError(test.shouldErr, err)
976979
})
@@ -1074,7 +1077,7 @@ func TestValidateJibPluginType(t *testing.T) {
10741077
},
10751078
},
10761079
},
1077-
}})
1080+
}}, Config{CheckSourceFiles: false})
10781081

10791082
t.CheckError(test.shouldErr, err)
10801083
})
@@ -1108,7 +1111,7 @@ func TestValidateLogsConfig(t *testing.T) {
11081111
},
11091112
},
11101113
},
1111-
}}})
1114+
}}}, Config{CheckSourceFiles: false})
11121115

11131116
t.CheckError(test.shouldErr, err)
11141117
})
@@ -1441,7 +1444,7 @@ func TestValidateTaggingPolicy(t *testing.T) {
14411444
},
14421445
},
14431446
},
1444-
})
1447+
}, Config{CheckSourceFiles: false})
14451448

14461449
t.CheckError(test.shouldErr, err)
14471450
})
@@ -1507,3 +1510,71 @@ func TestValidateCustomTest(t *testing.T) {
15071510
})
15081511
}
15091512
}
1513+
1514+
func TestValidateKubectlManifests(t *testing.T) {
1515+
tempDir := t.TempDir()
1516+
tests := []struct {
1517+
description string
1518+
configs []*latestV1.SkaffoldConfig
1519+
files []string
1520+
shouldErr bool
1521+
}{
1522+
{
1523+
description: "specified manifest file exists",
1524+
configs: []*latestV1.SkaffoldConfig{
1525+
{
1526+
Pipeline: latestV1.Pipeline{
1527+
Deploy: latestV1.DeployConfig{
1528+
DeployType: latestV1.DeployType{
1529+
KubectlDeploy: &latestV1.KubectlDeploy{
1530+
Manifests: []string{filepath.Join(tempDir, "validation-test-exists.yaml")},
1531+
},
1532+
},
1533+
},
1534+
},
1535+
},
1536+
},
1537+
files: []string{"validation-test-exists.yaml"},
1538+
},
1539+
{
1540+
description: "specified manifest file does not exist",
1541+
configs: []*latestV1.SkaffoldConfig{
1542+
{
1543+
Pipeline: latestV1.Pipeline{
1544+
Deploy: latestV1.DeployConfig{
1545+
DeployType: latestV1.DeployType{
1546+
KubectlDeploy: &latestV1.KubectlDeploy{
1547+
Manifests: []string{filepath.Join(tempDir, "validation-test-missing.yaml")},
1548+
},
1549+
},
1550+
},
1551+
},
1552+
},
1553+
},
1554+
files: []string{},
1555+
shouldErr: true,
1556+
},
1557+
}
1558+
1559+
for _, test := range tests {
1560+
testutil.Run(t, test.description, func(t *testutil.T) {
1561+
for _, file := range test.files {
1562+
_, err := os.Create(filepath.Join(tempDir, file))
1563+
if err != nil {
1564+
t.Errorf("error creating manifest file %s: %v", file, err)
1565+
}
1566+
}
1567+
1568+
set := parser.SkaffoldConfigSet{}
1569+
for _, c := range test.configs {
1570+
set = append(set, &parser.SkaffoldConfigEntry{SkaffoldConfig: c})
1571+
}
1572+
errs := validateKubectlManifests(set)
1573+
var err error
1574+
if len(errs) > 0 {
1575+
err = errs[0]
1576+
}
1577+
t.CheckError(test.shouldErr, err)
1578+
})
1579+
}
1580+
}

0 commit comments

Comments
 (0)