diff --git a/go.mod b/go.mod index 813a2a6e63f..9ee5211347e 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,7 @@ require ( go.uber.org/multierr v1.4.0 // indirect go.uber.org/zap v1.12.0 // indirect golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 + golang.org/x/mod v0.3.0 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sys v0.0.0-20200523222454-059865788121 diff --git a/go.sum b/go.sum index da560b812cd..60d36c085ca 100644 --- a/go.sum +++ b/go.sum @@ -941,6 +941,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= diff --git a/pkg/skaffold/deploy/kpt/kpt.go b/pkg/skaffold/deploy/kpt/kpt.go index 982f8a888d9..8d038c8b148 100644 --- a/pkg/skaffold/deploy/kpt/kpt.go +++ b/pkg/skaffold/deploy/kpt/kpt.go @@ -28,6 +28,7 @@ import ( "regexp" "strings" + "golang.org/x/mod/semver" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/kustomize/kyaml/fn/framework" k8syaml "sigs.k8s.io/yaml" @@ -49,6 +50,13 @@ const ( tmpKustomizeDir = ".kustomize" kptFnAnnotation = "config.kubernetes.io/function" kptFnLocalConfig = "config.kubernetes.io/local-config" + + kptDownloadLink = "https://googlecontainertools.github.io/kpt/installation/" + kptMinVersion = "0.34.0" + + kustomizeDownloadLink = "https://kubernetes-sigs.github.io/kustomize/installation/" + kustomizeMinVersion = "v3.2.3" + kustomizeVersionRegexP = `{Version:(\S+) GitCommit:\S+ BuildDate:\d{4}-\d{2}-\d{2}T\d\d:\d\d:\d\dZ GoOs:\S+ GoArch:\S+}` ) // Deployer deploys workflows with kpt CLI @@ -70,10 +78,65 @@ func NewDeployer(cfg types.Config, labels map[string]string) *Deployer { } } +var sanityCheck = versionCheck + +// sanityCheck guarantees the kpt and kustomize versions are compatible with skaffold. +func versionCheck(dir string) error { + kptCmd := exec.Command("kpt", "version") + out, err := util.RunCmdOut(kptCmd) + if err != nil { + return fmt.Errorf("kpt is not installed yet\nSee kpt installation: %v", + kptDownloadLink) + } + version := strings.TrimSuffix(string(out), "\n") + // kpt follows semver but does not have "v" prefix. + if !semver.IsValid("v" + version) { + return fmt.Errorf("unknown kpt version %v\nPlease upgrade your "+ + "local kpt CLI to a version >= %v\nSee kpt installation: %v", + string(out), kptMinVersion, kptDownloadLink) + } + if semver.Compare("v"+version, "v"+kptMinVersion) < 0 { + return fmt.Errorf("you are using kpt %q\nPlease update your kpt version to"+ + " >= %v\nSee kpt installation: %v", version[0], kptMinVersion, kptDownloadLink) + } + + // Users can choose not to use kustomize in kpt deployer mode. We only check the kustomize + // version when kustomization.yaml config is directed under .deploy.kpt.dir path. + _, err = kustomize.FindKustomizationConfig(dir) + if err == nil { + kustomizeCmd := exec.Command("kustomize", "version") + out, err := util.RunCmdOut(kustomizeCmd) + if err != nil { + return fmt.Errorf("kustomize is not installed yet\nSee kpt installation: %v", + kustomizeDownloadLink) + } + versionInfo := strings.TrimSuffix(string(out), "\n") + // Kustomize version information is in the form of + // {Version:$VERSION GitCommit:$COMMIT BuildDate:1970-01-01T00:00:00Z GoOs:darwin GoArch:amd64} + re := regexp.MustCompile(kustomizeVersionRegexP) + match := re.FindStringSubmatch(versionInfo) + if len(match) != 2 { + return fmt.Errorf("unknown kustomize version %v\nPlease upgrade your "+ + "local kustomize CLI to a version >= %v\nSee kustomize installation: %v", + string(out), kustomizeMinVersion, kustomizeDownloadLink) + } + if !semver.IsValid(match[1]) || semver.Compare(match[1], kustomizeMinVersion) < 0 { + return fmt.Errorf("you are using kustomize %q\n"+ + "Please update your kustomize version to >= %v\n"+ + "See kustomize installation: %v", match[1], kustomizeMinVersion, + kustomizeDownloadLink) + } + } + return nil +} + // Deploy hydrates the manifests using kustomizations and kpt functions as described in the render method, // outputs them to the applyDir, and runs `kpt live apply` against applyDir to create resources in the cluster. // `kpt live apply` supports automated pruning declaratively via resources in the applyDir. func (k *Deployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact) ([]string, error) { + if err := sanityCheck(k.Dir); err != nil { + return nil, err + } flags, err := k.getKptFnRunArgs() if err != nil { return []string{}, err @@ -166,6 +229,9 @@ func (k *Deployer) Cleanup(ctx context.Context, out io.Writer) error { // Render hydrates manifests using both kustomization and kpt functions. func (k *Deployer) Render(ctx context.Context, out io.Writer, builds []build.Artifact, _ bool, filepath string) error { + if err := sanityCheck(k.Dir); err != nil { + return err + } flags, err := k.getKptFnRunArgs() if err != nil { return err diff --git a/pkg/skaffold/deploy/kpt/kpt_test.go b/pkg/skaffold/deploy/kpt/kpt_test.go index 076c2fd889e..efb231c71ad 100644 --- a/pkg/skaffold/deploy/kpt/kpt_test.go +++ b/pkg/skaffold/deploy/kpt/kpt_test.go @@ -212,6 +212,7 @@ func TestKpt_Deploy(t *testing.T) { }, } for _, test := range tests { + sanityCheck = func(dir string) error { return nil } testutil.Run(t, test.description, func(t *testutil.T) { t.Override(&util.DefaultExecCommand, test.commands) tmpDir := t.NewTempDir().Chdir() @@ -229,7 +230,6 @@ func TestKpt_Deploy(t *testing.T) { } _, err := k.Deploy(context.Background(), ioutil.Discard, test.builds) - t.CheckError(test.shouldErr, err) }) } @@ -1015,6 +1015,108 @@ spec: } } +func TestVersionCheck(t *testing.T) { + tests := []struct { + description string + commands util.Command + kustomizations map[string]string + shouldErr bool + error error + }{ + { + description: "Both kpt and kustomize versions are good", + commands: testutil. + CmdRunOut("kpt version", `0.34.0`). + AndRunOut("kustomize version", `{Version:v3.6.1 GitCommit:a0072a2cf92bf5399565e84c621e1e7c5c1f1094 BuildDate:2020-06-15T20:19:07Z GoOs:darwin GoArch:amd64}`), + kustomizations: map[string]string{"Kustomization": `resources: + - foo.yaml`}, + shouldErr: false, + error: nil, + }, + { + description: "kpt is not installed", + commands: testutil.CmdRunOutErr("kpt version", "", errors.New("BUG")), + shouldErr: true, + error: fmt.Errorf("kpt is not installed yet\nSee kpt installation: %v", + kptDownloadLink), + }, + { + description: "kustomize is not used, kpt version is good", + commands: testutil. + CmdRunOut("kpt version", `0.34.0`), + shouldErr: false, + error: nil, + }, + { + description: "kustomize is used but not installed", + commands: testutil. + CmdRunOut("kpt version", `0.34.0`). + AndRunOutErr("kustomize version", "", errors.New("BUG")), + kustomizations: map[string]string{"Kustomization": `resources: + - foo.yaml`}, + shouldErr: true, + error: fmt.Errorf("kustomize is not installed yet\nSee kpt installation: %v", + kustomizeDownloadLink), + }, + { + description: "kpt version is too old (<0.34.0)", + commands: testutil. + CmdRunOut("kpt version", `0.1.0`), + kustomizations: map[string]string{"Kustomization": `resources: + - foo.yaml`}, + shouldErr: true, + error: fmt.Errorf("you are using kpt \"0.1.0\"\n"+ + "Please update your kpt version to >= %v\nSee kpt installation: %v", + kptMinVersion, kptDownloadLink), + }, + { + description: "kpt version is unknown", + commands: testutil. + CmdRunOut("kpt version", `unknown`), + kustomizations: map[string]string{"Kustomization": `resources: + - foo.yaml`}, + shouldErr: true, + error: fmt.Errorf("unknown kpt version unknown\nPlease upgrade your "+ + "local kpt CLI to a version >= %v\nSee kpt installation: %v", + kptMinVersion, kptDownloadLink), + }, + { + description: "kustomize versions is too old (< v3.2.3)", + commands: testutil. + CmdRunOut("kpt version", `0.34.0`). + AndRunOut("kustomize version", `{Version:v0.0.1 GitCommit:a0072a2cf92bf5399565e84c621e1e7c5c1f1094 BuildDate:2020-06-15T20:19:07Z GoOs:darwin GoArch:amd64}`), + kustomizations: map[string]string{"Kustomization": `resources: + - foo.yaml`}, + shouldErr: true, + error: fmt.Errorf("you are using kustomize \"v0.0.1\"\n"+ + "Please update your kustomize version to >= %v\n"+ + "See kustomize installation: %v", kustomizeMinVersion, kustomizeDownloadLink), + }, + { + description: "kustomize versions is unknown", + commands: testutil. + CmdRunOut("kpt version", `0.34.0`). + AndRunOut("kustomize version", `{Version:unknown GitCommit:a0072a2cf92bf5399565e84c621e1e7c5c1f1094 BuildDate:2020-06-15T20:19:07Z GoOs:darwin GoArch:amd64}`), + kustomizations: map[string]string{"Kustomization": `resources: + - foo.yaml`}, + shouldErr: true, + error: fmt.Errorf("unknown kustomize version unknown\nPlease upgrade your "+ + "local kustomize CLI to a version >= %v\nSee kustomize installation: %v", + kustomizeMinVersion, kustomizeDownloadLink), + }, + } + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + t.Override(&util.DefaultExecCommand, test.commands) + tmpDir := t.NewTempDir().Chdir() + tmpDir.WriteFiles(test.kustomizations) + err := versionCheck("") + t.CheckError(test.shouldErr, err) + }) + testutil.CheckError(t, test.shouldErr, test.error) + } +} + type kptConfig struct { runcontext.RunContext // Embedded to provide the default values. workingDir string diff --git a/vendor/modules.txt b/vendor/modules.txt index 43c3d7de483..47125fd5c37 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -544,6 +544,7 @@ golang.org/x/crypto/ssh/terminal golang.org/x/lint golang.org/x/lint/golint # golang.org/x/mod v0.3.0 +## explicit golang.org/x/mod/module golang.org/x/mod/semver # golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2