diff --git a/cmd/skaffold/app/cmd/util/util.go b/cmd/skaffold/app/cmd/util/util.go index 7fd83cea498..1746f9756a4 100644 --- a/cmd/skaffold/app/cmd/util/util.go +++ b/cmd/skaffold/app/cmd/util/util.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/apiversion" yaml "gopkg.in/yaml.v2" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" @@ -35,10 +36,19 @@ func ParseConfig(filename string) (*config.SkaffoldConfig, error) { return nil, errors.Wrap(err, "parsing api version") } - if apiVersion.Version != config.LatestVersion { + parsedVersion, err := apiversion.ParseVersion(apiVersion.Version) + if err != nil { + return nil, errors.Wrap(err, "parsing api version") + } + + if parsedVersion.LT(config.LatestAPIVersion) { return nil, errors.New("Config version out of date: run `skaffold fix`") } + if parsedVersion.GT(config.LatestAPIVersion) { + return nil, errors.New("Config version is too new for this version of skaffold: upgrade skaffold") + } + cfg, err := config.GetConfig(buf, true) if err != nil { return nil, errors.Wrap(err, "parsing skaffold config") diff --git a/cmd/skaffold/app/cmd/util/util_test.go b/cmd/skaffold/app/cmd/util/util_test.go new file mode 100644 index 00000000000..5d87b936234 --- /dev/null +++ b/cmd/skaffold/app/cmd/util/util_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2018 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 util + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" + + yaml "gopkg.in/yaml.v2" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" +) + +func TestParseConfig(t *testing.T) { + type args struct { + apiVersion string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "good api version", + args: args{ + apiVersion: config.LatestVersion, + }, + wantErr: false, + }, + { + name: "old api version", + args: args{ + apiVersion: v1alpha1.Version, + }, + wantErr: true, + }, + { + name: "new api version", + args: args{ + apiVersion: "skaffold/v9", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg, err := config.NewConfig() + if err != nil { + t.Fatalf("error generating config: %s", err) + } + cfg.APIVersion = tt.args.apiVersion + cfgStr, err := yaml.Marshal(cfg) + if err != nil { + t.Fatalf("error marshalling config: %s", err) + } + + p := writeTestConfig(t, cfgStr) + defer os.Remove(p) + + _, err = ParseConfig(p) + if (err != nil) != tt.wantErr { + t.Errorf("ParseConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func writeTestConfig(t *testing.T, cfg []byte) string { + t.Helper() + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("error getting temp file: %s", err) + } + defer f.Close() + if _, err := f.Write(cfg); err != nil { + t.Fatalf("error writing config: %s", err) + } + return f.Name() +} diff --git a/pkg/skaffold/apiversion/apiversion.go b/pkg/skaffold/apiversion/apiversion.go new file mode 100644 index 00000000000..34fa45e2991 --- /dev/null +++ b/pkg/skaffold/apiversion/apiversion.go @@ -0,0 +1,119 @@ +/* +Copyright 2018 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 apiversion + +import ( + "fmt" + "regexp" + "strconv" +) + +type ReleaseTrack int + +const ( + alpha ReleaseTrack = 0 + beta ReleaseTrack = 1 + ga ReleaseTrack = 2 +) + +type Version struct { + Major int + Minor int + Release ReleaseTrack +} + +var re = regexp.MustCompile(`^skaffold/v(\d)(?:(alpha|beta)(\d))?$`) + +// ParseAPIVersion parses a string into a Version. +func ParseVersion(v string) (*Version, error) { + res := re.FindStringSubmatch(v) + if len(res) == 0 { + return nil, fmt.Errorf("%s is an invalid api version", v) + } + + major, err := strconv.Atoi(res[1]) + if err != nil { + return nil, fmt.Errorf("%s is an invalid major version number", res[1]) + } + + track := ga + switch res[2] { + case "alpha": + track = alpha + case "beta": + track = beta + } + + av := Version{ + Major: major, + Release: track, + } + + if track != ga { + minor, err := strconv.Atoi(res[3]) + if err != nil { + return nil, fmt.Errorf("%s is an invalid major version number", res[1]) + } + av.Minor = minor + } + return &av, nil +} + +// MustParseVersion parses the version and panics if there is an error. +func MustParseVersion(v string) *Version { + av, err := ParseVersion(v) + if err != nil { + panic(err) + } + return av +} + +// Compare compares the Version to another Version, and returns -1 if v is less than ov, 0 if they are equal and 1 if v is greater than ov. +func (v *Version) Compare(ov *Version) int { + // GA is always higher than beta, beta is always higher than alpha + if v.Release != ov.Release { + if v.Release > ov.Release { + return 1 + } + return -1 + } + + // v2alpha > v1beta + if v.Major != ov.Major { + if v.Major > ov.Major { + return 1 + } + return -1 + } + + if v.Minor > ov.Minor { + return 1 + } + if v.Minor < ov.Minor { + return -1 + } + return 0 +} + +// LT returns true if v is less than ov +func (v *Version) LT(ov *Version) bool { + return v.Compare(ov) == -1 +} + +// GT returns true if v is greather than ov +func (v *Version) GT(ov *Version) bool { + return v.Compare(ov) == 1 +} diff --git a/pkg/skaffold/apiversion/apiversion_test.go b/pkg/skaffold/apiversion/apiversion_test.go new file mode 100644 index 00000000000..13552ecde0b --- /dev/null +++ b/pkg/skaffold/apiversion/apiversion_test.go @@ -0,0 +1,239 @@ +/* +Copyright 2018 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 apiversion + +import ( + "reflect" + "testing" +) + +func TestParseVersion(t *testing.T) { + type args struct { + v string + } + tests := []struct { + name string + args args + want *Version + wantErr bool + }{ + { + name: "full", + args: args{ + v: "skaffold/v7alpha3", + }, + want: &Version{ + Major: 7, + Release: alpha, + Minor: 3, + }, + }, + { + name: "ga", + args: args{ + v: "skaffold/v3", + }, + want: &Version{ + Major: 3, + Release: ga, + }, + }, + { + name: "beta", + args: args{ + v: "skaffold/v2beta1", + }, + want: &Version{ + Major: 2, + Release: beta, + Minor: 1, + }, + }, + { + name: "bad track", + args: args{ + v: "skaffold/v7notalpha3", + }, + wantErr: true, + }, + { + name: "no minor", + args: args{ + v: "skaffold/v7alpha", + }, + wantErr: true, + }, + { + name: "bad track", + args: args{ + v: "skaffold/v7notalpha3", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseVersion(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("ParseVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseVersion() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVersion_Compare(t *testing.T) { + type fields struct { + Major int + Minor int + Release ReleaseTrack + } + type args struct { + ov *Version + } + tests := []struct { + name string + fields fields + args args + want int + }{ + { + name: "equal alpha", + fields: fields{ + Major: 3, + Minor: 4, + Release: 0, + }, + args: args{ + ov: &Version{ + Major: 3, + Minor: 4, + Release: 0, + }, + }, + want: 0, + }, + { + name: "equal ga", + fields: fields{ + Major: 3, + Release: 2, + }, + args: args{ + ov: &Version{ + Major: 3, + Release: 2, + }, + }, + want: 0, + }, + { + name: "alpha < beta", + fields: fields{ + Major: 3, + Minor: 4, + Release: 1, + }, + args: args{ + ov: &Version{ + Major: 3, + Minor: 5, + Release: 0, + }, + }, + want: 1, + }, + { + name: "beta < ga", + fields: fields{ + Major: 3, + Minor: 4, + Release: 2, + }, + args: args{ + ov: &Version{ + Major: 3, + Minor: 7, + Release: 1, + }, + }, + want: 1, + }, + { + name: "ga > beta", + fields: fields{ + Major: 3, + Minor: 5, + Release: 1, + }, + args: args{ + ov: &Version{ + Major: 3, + Minor: 4, + Release: 2, + }, + }, + want: -1, + }, + { + name: "v2 > v1", + fields: fields{ + Major: 2, + Minor: 1, + Release: 1, + }, + args: args{ + ov: &Version{ + Major: 1, + Minor: 4, + Release: 0, + }, + }, + want: 1, + }, + { + name: "minor versions", + fields: fields{ + Major: 3, + Minor: 3, + Release: 0, + }, + args: args{ + ov: &Version{ + Major: 3, + Minor: 4, + Release: 0, + }, + }, + want: -1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Version{ + Major: tt.fields.Major, + Minor: tt.fields.Minor, + Release: tt.fields.Release, + } + if got := v.Compare(tt.args.ov); got != tt.want { + t.Errorf("Version.Compare() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/skaffold/config/config.go b/pkg/skaffold/config/config.go index b056662362c..d7e34b7df44 100644 --- a/pkg/skaffold/config/config.go +++ b/pkg/skaffold/config/config.go @@ -17,6 +17,7 @@ limitations under the License. package config import ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/apiversion" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" ) @@ -25,6 +26,8 @@ type SkaffoldConfig = v1alpha3.SkaffoldConfig const LatestVersion string = v1alpha3.Version +var LatestAPIVersion *apiversion.Version = apiversion.MustParseVersion(LatestVersion) + func NewConfig() (*SkaffoldConfig, error) { return v1alpha3.NewConfig() }