diff --git a/CHANGELOG.md b/CHANGELOG.md index 9350d9612e1..3924e8076b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v0.15.1 Release - 10/02/2018 + +This is a minor release to address an inconsistency in the `skaffold fix` upgrade: + +* Transform values files in profiles to v1alpha3 [#1070](https://github.com/GoogleContainerTools/skaffold/pull/1070) + + # v0.15.0 Release - 9/27/2018 New Features: diff --git a/Gopkg.lock b/Gopkg.lock index df998700f10..4d34a23d829 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -292,7 +292,7 @@ [[projects]] branch = "master" - digest = "1:d2dec7de02135a17aa30b1a2d033403770f602d43e8f30ef3f9641383ca09eb8" + digest = "1:93e99be14151cf268fff8f59ad41a99887d6ee7f1f02f36775945856a8df64f1" name = "github.com/google/go-containerregistry" packages = [ "pkg/authn", @@ -305,7 +305,7 @@ "pkg/v1/v1util", ] pruneopts = "NUT" - revision = "2f3e3e1a55fb70c98801f944d7b6249f8e8a3e20" + revision = "03167950e20ac82689f50828811e69cdd9e02af2" [[projects]] digest = "1:51bee9f1987dcdb9f9a1b4c20745d78f6bf6f5f14ad4e64ca883eb64df4c0045" @@ -1146,6 +1146,7 @@ "cloud.google.com/go/storage", "github.com/blang/semver", "github.com/docker/cli/cli/config", + "github.com/docker/cli/cli/config/configfile", "github.com/docker/distribution/reference", "github.com/docker/docker/api", "github.com/docker/docker/api/types", diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index 12feeaf50b6..68c9e200681 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -23,7 +23,6 @@ import ( "os" "strings" - cmdutil "github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/update" @@ -164,15 +163,3 @@ func SetUpLogs(out io.Writer, level string) error { logrus.SetLevel(lvl) return nil } - -func readConfiguration(opts *config.SkaffoldOptions) (*config.SkaffoldConfig, error) { - config, err := cmdutil.ParseConfig(opts.ConfigurationFile) - if err != nil { - return nil, errors.Wrap(err, "parsing skaffold config") - } - err = config.ApplyProfiles(opts.Profiles) - if err != nil { - return nil, errors.Wrap(err, "applying profiles") - } - return config, nil -} diff --git a/cmd/skaffold/app/cmd/fix.go b/cmd/skaffold/app/cmd/fix.go index 78083a7221e..1a6d7f1335c 100644 --- a/cmd/skaffold/app/cmd/fix.go +++ b/cmd/skaffold/app/cmd/fix.go @@ -21,14 +21,13 @@ import ( "io/ioutil" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema" schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" yaml "gopkg.in/yaml.v2" + "k8s.io/client-go/tools/clientcmd/api/latest" ) func NewCmdFix(out io.Writer) *cobra.Command { @@ -36,19 +35,17 @@ func NewCmdFix(out io.Writer) *cobra.Command { Use: "fix", Short: "Converts old skaffold.yaml to newest schema version", Run: func(cmd *cobra.Command, args []string) { - contents, err := util.ReadConfiguration(opts.ConfigurationFile) - if err != nil { - logrus.Errorf("fix: %s", err) - } - cfg, err := config.GetConfig(contents, false) + cfg, err := schema.ParseConfig(opts.ConfigurationFile, false) if err != nil { logrus.Error(err) return } - if cfg.GetVersion() == config.LatestVersion { + + if cfg.GetVersion() == latest.Version { color.Default.Fprintln(out, "config is already latest version") return } + if err := runFix(out, cfg); err != nil { logrus.Errorf("fix: %s", err) } @@ -60,14 +57,16 @@ func NewCmdFix(out io.Writer) *cobra.Command { } func runFix(out io.Writer, cfg schemautil.VersionedConfig) error { - cfg, err := schema.RunTransform(cfg) + cfg, err := schema.UpgradeToLatest(cfg) if err != nil { return err } + newCfg, err := yaml.Marshal(cfg) if err != nil { return errors.Wrap(err, "marshaling new config") } + if overwrite { if err := ioutil.WriteFile(opts.ConfigurationFile, newCfg, 0644); err != nil { return errors.Wrap(err, "writing config file") @@ -76,5 +75,6 @@ func runFix(out io.Writer, cfg schemautil.VersionedConfig) error { } else { out.Write(newCfg) } + return nil } diff --git a/cmd/skaffold/app/cmd/init.go b/cmd/skaffold/app/cmd/init.go index f933aee9bb8..9aa35b6657d 100644 --- a/cmd/skaffold/app/cmd/init.go +++ b/cmd/skaffold/app/cmd/init.go @@ -25,21 +25,17 @@ import ( "path/filepath" "strings" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "gopkg.in/AlecAivazis/survey.v1" yaml "gopkg.in/yaml.v2" - - cmdutil "github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd/util" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" - "k8s.io/apimachinery/pkg/runtime" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/kubernetes/scheme" ) @@ -95,11 +91,12 @@ func doInit(out io.Writer) error { return err } for _, file := range potentialConfigs { - config, err := cmdutil.ParseConfig(file) + config, err := schema.ParseConfig(file, true) if err == nil && config != nil { out.Write([]byte(fmt.Sprintf("pre-existing skaffold yaml %s found: exiting\n", file))) return nil } + logrus.Debugf("%s is not a valid skaffold configuration: continuing", file) imgs, err := parseKubernetesYaml(file) if err == nil { @@ -206,23 +203,23 @@ func promptUserForDockerfile(image string, dockerfiles []string) dockerfilePair } } -func processBuildArtifacts(pairs []dockerfilePair) v1alpha3.BuildConfig { - var config v1alpha3.BuildConfig +func processBuildArtifacts(pairs []dockerfilePair) latest.BuildConfig { + var config latest.BuildConfig if len(pairs) > 0 { - var artifacts []*v1alpha3.Artifact + var artifacts []*latest.Artifact for _, pair := range pairs { workspace := filepath.Dir(pair.Dockerfile) dockerfilePath := filepath.Base(pair.Dockerfile) - a := &v1alpha3.Artifact{ + a := &latest.Artifact{ ImageName: pair.ImageName, } if workspace != "." { a.Workspace = workspace } if dockerfilePath != constants.DefaultDockerfilePath { - a.ArtifactType = v1alpha3.ArtifactType{ - DockerArtifact: &v1alpha3.DockerArtifact{ + a.ArtifactType = latest.ArtifactType{ + DockerArtifact: &latest.DockerArtifact{ DockerfilePath: dockerfilePath, }, } @@ -239,16 +236,18 @@ func generateSkaffoldConfig(k8sConfigs []string, dockerfilePairs []dockerfilePai // if the user doesn't have any k8s yamls, generate one for each dockerfile logrus.Info("generating skaffold config") - var err error - config, err := config.NewConfig() - if err != nil { + config := &latest.SkaffoldConfig{ + APIVersion: latest.Version, + Kind: "Config", + } + if err := config.SetDefaultValues(); err != nil { return nil, errors.Wrap(err, "generating default config") } - config.Build = processBuildArtifacts(dockerfilePairs) - config.Deploy = v1alpha3.DeployConfig{ - DeployType: v1alpha3.DeployType{ - KubectlDeploy: &v1alpha3.KubectlDeploy{ + config.Build = processBuildArtifacts(dockerfilePairs) + config.Deploy = latest.DeployConfig{ + DeployType: latest.DeployType{ + KubectlDeploy: &latest.KubectlDeploy{ Manifests: k8sConfigs, }, }, diff --git a/cmd/skaffold/app/cmd/runner.go b/cmd/skaffold/app/cmd/runner.go index a27fe964ea1..ac3e248e892 100644 --- a/cmd/skaffold/app/cmd/runner.go +++ b/cmd/skaffold/app/cmd/runner.go @@ -19,14 +19,26 @@ package cmd import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) // newRunner creates a SkaffoldRunner and returns the SkaffoldConfig associated with it. -func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *config.SkaffoldConfig, error) { - config, err := readConfiguration(opts) +func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.SkaffoldConfig, error) { + parsed, err := schema.ParseConfig(opts.ConfigurationFile, true) if err != nil { - return nil, nil, errors.Wrap(err, "reading configuration") + return nil, nil, errors.Wrap(err, "parsing skaffold config") + } + + if err := schema.CheckVersionIsLatest(parsed.GetVersion()); err != nil { + return nil, nil, errors.Wrap(err, "invalid config") + } + + config := parsed.(*latest.SkaffoldConfig) + err = schema.ApplyProfiles(config, opts.Profiles) + if err != nil { + return nil, nil, errors.Wrap(err, "applying profiles") } runner, err := runner.NewForConfig(opts, config) diff --git a/cmd/skaffold/app/cmd/util/util.go b/cmd/skaffold/app/cmd/util/util.go deleted file mode 100644 index 7fd83cea498..00000000000 --- a/cmd/skaffold/app/cmd/util/util.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -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 ( - yaml "gopkg.in/yaml.v2" - - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" - "github.com/pkg/errors" -) - -func ParseConfig(filename string) (*config.SkaffoldConfig, error) { - buf, err := util.ReadConfiguration(filename) - if err != nil { - return nil, errors.Wrap(err, "read skaffold config") - } - - apiVersion := &config.APIVersion{} - if err := yaml.Unmarshal(buf, apiVersion); err != nil { - return nil, errors.Wrap(err, "parsing api version") - } - - if apiVersion.Version != config.LatestVersion { - return nil, errors.New("Config version out of date: run `skaffold fix`") - } - - cfg, err := config.GetConfig(buf, true) - if err != nil { - return nil, errors.Wrap(err, "parsing skaffold config") - } - - // we already ensured that the versions match in the previous block, - // so this type assertion is safe. - return cfg.(*config.SkaffoldConfig), nil -} diff --git a/deploy/skaffold/Dockerfile b/deploy/skaffold/Dockerfile index f9921e50fdb..bcf4e691eb4 100644 --- a/deploy/skaffold/Dockerfile +++ b/deploy/skaffold/Dockerfile @@ -47,6 +47,11 @@ RUN echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8 RUN apt-get update \ && apt-get install -y bazel +ENV CONTAINER_STRUCTURE_TEST_VERSION=1.5.0 +RUN curl -LO https://storage.googleapis.com/container-structure-test/v${CONTAINER_STRUCTURE_TEST_VERSION}/container-structure-test-linux-amd64 \ + && chmod +x container-structure-test-linux-amd64 \ + && mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test + ENV PATH /usr/local/go/bin:/go/bin:/google-cloud-sdk/bin:$PATH FROM runtime_deps as builder @@ -94,4 +99,3 @@ CMD ["make", "integration"] FROM runtime_deps as distribution COPY --from=integration /usr/bin/skaffold /usr/bin/skaffold - diff --git a/examples/annotated-skaffold.yaml b/examples/annotated-skaffold.yaml index da9e708a451..92cbc412e76 100644 --- a/examples/annotated-skaffold.yaml +++ b/examples/annotated-skaffold.yaml @@ -133,7 +133,7 @@ deploy: # - namespace:deployment/web-app2 # kustomize: - # path: . + # kustomizePath: . # kustomize deploys manifests with kubectl. # kubectl can be passed additional option flags either on every command (Global), # on creations (Apply) or deletions (Delete). diff --git a/integration/examples/annotated-skaffold.yaml b/integration/examples/annotated-skaffold.yaml index da9e708a451..f7c3e550271 100644 --- a/integration/examples/annotated-skaffold.yaml +++ b/integration/examples/annotated-skaffold.yaml @@ -1,4 +1,4 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: # tagPolicy determines how skaffold is going to tag your images. @@ -36,15 +36,15 @@ build: # you can include as many as you want here. artifacts: # The name of the image to be built. - - imageName: gcr.io/k8s-skaffold/skaffold-example + - image: gcr.io/k8s-skaffold/skaffold-example # The path to your dockerfile context. Defaults to ".". - workspace: ../examples/getting-started + context: ../examples/getting-started # Each artifact is of a given type among: `docker` and `bazel`. # If not specified, it defaults to `docker: {}`. docker: # Dockerfile's location relative to workspace. Defaults to "Dockerfile" - dockerfilePath: Dockerfile + dockerfile: Dockerfile # Key/value arguements passed to the docker build. buildArgs: key1: "value1" diff --git a/integration/examples/bazel/README.adoc b/integration/examples/bazel/README.adoc index 20e8f2e2ac2..07de30dca51 100644 --- a/integration/examples/bazel/README.adoc +++ b/integration/examples/bazel/README.adoc @@ -8,8 +8,8 @@ The way you configure it in `skaffold.yaml` is the following build stanza: ---- build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-example - workspace: . <1> + - image: gcr.io/k8s-skaffold/skaffold-example + context: . <1> bazel: # <2> target: //:skaffold_example.tar # <3> ---- diff --git a/integration/examples/bazel/skaffold.yaml b/integration/examples/bazel/skaffold.yaml index 6424b8091a3..b85d2066fb5 100644 --- a/integration/examples/bazel/skaffold.yaml +++ b/integration/examples/bazel/skaffold.yaml @@ -1,12 +1,8 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-bazel - workspace: . + - image: gcr.io/k8s-skaffold/skaffold-bazel + context: . bazel: target: //:skaffold_example.tar - local: {} -deploy: - kubectl: - manifests: diff --git a/integration/examples/getting-started/skaffold.yaml b/integration/examples/getting-started/skaffold.yaml index fb4d4adbc4c..7ace8c5b6f8 100644 --- a/integration/examples/getting-started/skaffold.yaml +++ b/integration/examples/getting-started/skaffold.yaml @@ -1,8 +1,12 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-example + - image: gcr.io/k8s-skaffold/skaffold-example +test: + - image: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - ./test/* deploy: kubectl: manifests: @@ -12,3 +16,8 @@ profiles: build: googleCloudBuild: projectId: k8s-skaffold + - name: test + test: + - image: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - ./test/profile_structure_test.yaml diff --git a/integration/examples/getting-started/test/profile_structure_test.yaml b/integration/examples/getting-started/test/profile_structure_test.yaml new file mode 100644 index 00000000000..6966d26930a --- /dev/null +++ b/integration/examples/getting-started/test/profile_structure_test.yaml @@ -0,0 +1,6 @@ +schemaVersion: 2.0.0 + +fileExistenceTests: + - name: 'no go binary' + path: '/usr/bin/go' + shouldExist: false diff --git a/integration/examples/getting-started/test/structure_test.yaml b/integration/examples/getting-started/test/structure_test.yaml new file mode 100644 index 00000000000..6b1223eae90 --- /dev/null +++ b/integration/examples/getting-started/test/structure_test.yaml @@ -0,0 +1,6 @@ +schemaVersion: 2.0.0 + +fileExistenceTests: + - name: 'no local go binary' + path: /usr/local/bin/go' + shouldExist: false diff --git a/integration/examples/helm-deployment/README.adoc b/integration/examples/helm-deployment/README.adoc index 1fd3e17f3d8..ac728ada731 100644 --- a/integration/examples/helm-deployment/README.adoc +++ b/integration/examples/helm-deployment/README.adoc @@ -16,7 +16,7 @@ We'll be building an image called `skaffold-helm`, and its a dockerfile, so we'l ``` build: artifacts: - - imageName: skaffold-helm + - image: skaffold-helm ``` Now, we want to deploy this image with helm. diff --git a/integration/examples/helm-deployment/skaffold.yaml b/integration/examples/helm-deployment/skaffold.yaml index 6c5ae9febdc..9e21a0d59da 100644 --- a/integration/examples/helm-deployment/skaffold.yaml +++ b/integration/examples/helm-deployment/skaffold.yaml @@ -1,10 +1,10 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: tagPolicy: sha256: {} artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-helm + - image: gcr.io/k8s-skaffold/skaffold-helm deploy: helm: releases: diff --git a/integration/examples/kaniko/skaffold.yaml b/integration/examples/kaniko/skaffold.yaml index 09271f92719..4bf0641b32f 100644 --- a/integration/examples/kaniko/skaffold.yaml +++ b/integration/examples/kaniko/skaffold.yaml @@ -1,8 +1,8 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-example + - image: gcr.io/k8s-skaffold/skaffold-example kaniko: buildContext: gcsBucket: skaffold-kaniko diff --git a/integration/examples/kustomize/skaffold.yaml b/integration/examples/kustomize/skaffold.yaml index 0ad12da902c..95aa61e789c 100644 --- a/integration/examples/kustomize/skaffold.yaml +++ b/integration/examples/kustomize/skaffold.yaml @@ -1,4 +1,4 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config deploy: kustomize: {} diff --git a/integration/examples/microservices/README.adoc b/integration/examples/microservices/README.adoc index 7a32dea63f7..dafa3662f4d 100644 --- a/integration/examples/microservices/README.adoc +++ b/integration/examples/microservices/README.adoc @@ -60,10 +60,10 @@ Let's walk through the first part of the skaffold.yaml ```yaml artifacts: - - imageName: gcr.io/k8s-skaffold/leeroy-web - workspace: ./leeroy-web/ - - imageName: gcr.io/k8s-skaffold/leeroy-app - workspace: ./leeroy-app/ + - image: gcr.io/k8s-skaffold/leeroy-web + context: ./leeroy-web/ + - image: gcr.io/k8s-skaffold/leeroy-app + context: ./leeroy-app/ ``` We're deploying a `leeroy-web` image, which we build in the context of its subdirectory and a `leeroy-app` image built in a similar manner. diff --git a/integration/examples/microservices/skaffold.yaml b/integration/examples/microservices/skaffold.yaml index 09625d7e8a1..e1fa83ab55a 100644 --- a/integration/examples/microservices/skaffold.yaml +++ b/integration/examples/microservices/skaffold.yaml @@ -1,11 +1,11 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/leeroy-web - workspace: ./leeroy-web/ - - imageName: gcr.io/k8s-skaffold/leeroy-app - workspace: ./leeroy-app/ + - image: gcr.io/k8s-skaffold/leeroy-web + context: ./leeroy-web/ + - image: gcr.io/k8s-skaffold/leeroy-app + context: ./leeroy-app/ deploy: kubectl: manifests: diff --git a/integration/examples/tagging-with-environment-variables/README.adoc b/integration/examples/tagging-with-environment-variables/README.adoc index 15d4d0abebf..30703f3071d 100644 --- a/integration/examples/tagging-with-environment-variables/README.adoc +++ b/integration/examples/tagging-with-environment-variables/README.adoc @@ -8,7 +8,7 @@ The way you configure it in `skaffold.yaml` is the following build stanza: ---- build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-example + - image: gcr.io/k8s-skaffold/skaffold-example tagPolicy: # <1> envTemplate: # <1> template: "{{.IMAGE_NAME}}:{{.FOO}}" # <2> @@ -16,7 +16,7 @@ build: <1> define tagPolicy to be envTemplate <2> use https://golang.org/pkg/text/template[go templates] syntax -The `IMAGE_NAME` variable is built-in and reuses the value defined in the artifacts' `imageName`. +The `IMAGE_NAME` variable is built-in and reuses the value defined in the artifacts' `image`. ifndef::env-github[] diff --git a/integration/examples/tagging-with-environment-variables/skaffold.yaml b/integration/examples/tagging-with-environment-variables/skaffold.yaml index 4cb86eb796b..f4e2a3769f0 100644 --- a/integration/examples/tagging-with-environment-variables/skaffold.yaml +++ b/integration/examples/tagging-with-environment-variables/skaffold.yaml @@ -1,8 +1,8 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-example + - image: gcr.io/k8s-skaffold/skaffold-example tagPolicy: envTemplate: template: "{{.IMAGE_NAME}}:{{.FOO}}" diff --git a/integration/run_test.go b/integration/run_test.go index f729f788bc2..e380005180b 100644 --- a/integration/run_test.go +++ b/integration/run_test.go @@ -220,45 +220,24 @@ func setupNamespace(t *testing.T) (*v1.Namespace, func()) { client.CoreV1().Namespaces().Delete(ns.Name, &meta_v1.DeleteOptions{}) } } + func TestFix(t *testing.T) { - tests := []struct { - name string - directory string - remoteOnly bool - }{ - { - name: "test v1alpha1 to v1alpha2 fix", - directory: "testdata/v1alpha1", - }, - { - name: "test v1alpha2 to v1alpha3 fix", - directory: "testdata/v1alpha2", - remoteOnly: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if !*remote && test.remoteOnly { - t.Skip("skipping remote only test") - } - ns, deleteNs := setupNamespace(t) - defer deleteNs() + ns, deleteNs := setupNamespace(t) + defer deleteNs() - fixCmd := exec.Command("skaffold", "fix", "-f", "skaffold.yaml") - fixCmd.Dir = test.directory - out, err := util.RunCmdOut(fixCmd) - if err != nil { - t.Fatalf("testing error: %v", err) - } + fixCmd := exec.Command("skaffold", "fix", "-f", "skaffold.yaml") + fixCmd.Dir = "testdata" + out, err := util.RunCmdOut(fixCmd) + if err != nil { + t.Fatalf("testing error: %v", err) + } - runCmd := exec.Command("skaffold", "run", "--namespace", ns.Name, "-f", "-") - runCmd.Dir = test.directory - runCmd.Stdin = bytes.NewReader(out) - err = util.RunCmd(runCmd) - if err != nil { - t.Fatalf("testing error: %v", err) - } - }) + runCmd := exec.Command("skaffold", "run", "--namespace", ns.Name, "-f", "-") + runCmd.Dir = "testdata" + runCmd.Stdin = bytes.NewReader(out) + err = util.RunCmd(runCmd) + if err != nil { + t.Fatalf("testing error: %v", err) } } diff --git a/integration/testdata/v1alpha1/Dockerfile b/integration/testdata/Dockerfile similarity index 100% rename from integration/testdata/v1alpha1/Dockerfile rename to integration/testdata/Dockerfile diff --git a/integration/testdata/v1alpha1/k8s-pod.yaml b/integration/testdata/k8s-pod.yaml similarity index 100% rename from integration/testdata/v1alpha1/k8s-pod.yaml rename to integration/testdata/k8s-pod.yaml diff --git a/integration/testdata/v1alpha1/main.go b/integration/testdata/main.go similarity index 100% rename from integration/testdata/v1alpha1/main.go rename to integration/testdata/main.go diff --git a/integration/testdata/v1alpha1/skaffold.yaml b/integration/testdata/skaffold.yaml similarity index 100% rename from integration/testdata/v1alpha1/skaffold.yaml rename to integration/testdata/skaffold.yaml diff --git a/integration/testdata/v1alpha2/Dockerfile b/integration/testdata/v1alpha2/Dockerfile deleted file mode 100644 index 2d107918ad0..00000000000 --- a/integration/testdata/v1alpha2/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM gcr.io/google-appengine/golang - -WORKDIR /go/src/github.com/GoogleCloudPlatform/skaffold -CMD ["./app"] -COPY main.go . -RUN go build -o app main.go diff --git a/integration/testdata/v1alpha2/k8s-pod.yaml b/integration/testdata/v1alpha2/k8s-pod.yaml deleted file mode 100644 index df436eb186b..00000000000 --- a/integration/testdata/v1alpha2/k8s-pod.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: getting-started-kaniko -spec: - containers: - - name: getting-started - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/integration/testdata/v1alpha2/main.go b/integration/testdata/v1alpha2/main.go deleted file mode 100644 index 64b7bdfc4a1..00000000000 --- a/integration/testdata/v1alpha2/main.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "fmt" - "time" -) - -func main() { - for { - fmt.Println("Hello world!") - time.Sleep(time.Second * 1) - } -} diff --git a/integration/testdata/v1alpha2/skaffold.yaml b/integration/testdata/v1alpha2/skaffold.yaml deleted file mode 100644 index 1b81d4a523d..00000000000 --- a/integration/testdata/v1alpha2/skaffold.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: skaffold/v1alpha2 -kind: Config -build: - artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold-example - kaniko: - gcsBucket: skaffold-kaniko - pullSecretName: e2esecret - namespace: default -deploy: - kubectl: - manifests: - - k8s-* diff --git a/pkg/skaffold/apiversion/apiversion.go b/pkg/skaffold/apiversion/apiversion.go new file mode 100644 index 00000000000..ada0c7626bc --- /dev/null +++ b/pkg/skaffold/apiversion/apiversion.go @@ -0,0 +1,46 @@ +/* +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" + + "github.com/blang/semver" +) + +var re = regexp.MustCompile(`^skaffold/v(\d)(?:(alpha|beta)(\d))?$`) + +// Parse parses a string into a Version. +func Parse(v string) (semver.Version, error) { + res := re.FindStringSubmatch(v) + if res == nil { + return semver.Version{}, fmt.Errorf("%s is an invalid api version", v) + } + if res[2] == "" || res[3] == "" { + return semver.Parse(fmt.Sprintf("%s.0.0", res[1])) + } + return semver.Parse(fmt.Sprintf("%s.0.0-%s.%s", res[1], res[2], res[3])) +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) semver.Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} diff --git a/pkg/skaffold/apiversion/apiversion_test.go b/pkg/skaffold/apiversion/apiversion_test.go new file mode 100644 index 00000000000..4d7cb632a65 --- /dev/null +++ b/pkg/skaffold/apiversion/apiversion_test.go @@ -0,0 +1,95 @@ +/* +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" + + "github.com/blang/semver" +) + +func TestMustParse(t *testing.T) { + _ = MustParse("skaffold/v1alpha4") +} + +func TestMustParse_panic(t *testing.T) { + defer func() { + if recover() == nil { + t.Errorf("Should have panicked") + } + }() + _ = MustParse("invalid version") +} + +func TestParseVersion(t *testing.T) { + type args struct { + v string + } + tests := []struct { + name string + args args + want semver.Version + wantErr bool + }{ + { + name: "full", + args: args{ + v: "skaffold/v7alpha3", + }, + want: semver.Version{ + Major: 7, + Pre: []semver.PRVersion{ + { + VersionStr: "alpha", + }, + { + VersionNum: 3, + IsNum: true, + }, + }, + }, + }, + { + name: "ga", + args: args{ + v: "skaffold/v4", + }, + want: semver.Version{ + Major: 4, + }, + }, + { + name: "incorrect", + args: args{ + v: "apps/v1", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Parse(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parse() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/skaffold/bazel/bazel.go b/pkg/skaffold/bazel/bazel.go index 28829104d38..a0e9c4649a6 100644 --- a/pkg/skaffold/bazel/bazel.go +++ b/pkg/skaffold/bazel/bazel.go @@ -23,7 +23,7 @@ import ( "path/filepath" "strings" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" ) @@ -36,7 +36,7 @@ func query(target string) string { // GetDependencies finds the sources dependencies for the given bazel artifact. // All paths are relative to the workspace. -func GetDependencies(workspace string, a *v1alpha3.BazelArtifact) ([]string, error) { +func GetDependencies(workspace string, a *latest.BazelArtifact) ([]string, error) { cmd := exec.Command("bazel", "query", query(a.BuildTarget), "--noimplicit_deps", "--order_output=no") cmd.Dir = workspace stdout, err := util.RunCmdOut(cmd) diff --git a/pkg/skaffold/bazel/bazel_test.go b/pkg/skaffold/bazel/bazel_test.go index b76abcd0e5c..0210fcbda6c 100644 --- a/pkg/skaffold/bazel/bazel_test.go +++ b/pkg/skaffold/bazel/bazel_test.go @@ -19,7 +19,7 @@ package bazel import ( "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" ) @@ -36,7 +36,7 @@ func TestGetDependenciesWithWorkspace(t *testing.T) { defer cleanup() tmpDir.Write("WORKSPACE", "") - deps, err := GetDependencies(tmpDir.Root(), &v1alpha3.BazelArtifact{ + deps, err := GetDependencies(tmpDir.Root(), &latest.BazelArtifact{ BuildTarget: "target", }) @@ -51,7 +51,7 @@ func TestGetDependenciesWithoutWorkspace(t *testing.T) { nil, ) - deps, err := GetDependencies(".", &v1alpha3.BazelArtifact{ + deps, err := GetDependencies(".", &latest.BazelArtifact{ BuildTarget: "target2", }) diff --git a/pkg/skaffold/build/build.go b/pkg/skaffold/build/build.go index 94ff2223274..e9e2d9473f3 100644 --- a/pkg/skaffold/build/build.go +++ b/pkg/skaffold/build/build.go @@ -21,7 +21,7 @@ import ( "io" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" ) // Artifact is the result corresponding to each successful build. @@ -37,5 +37,5 @@ type Artifact struct { type Builder interface { Labels() map[string]string - Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact) ([]Artifact, error) + Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]Artifact, error) } diff --git a/pkg/skaffold/build/gcb/cloud_build.go b/pkg/skaffold/build/gcb/cloud_build.go index 4f0eed793af..5b82f804111 100644 --- a/pkg/skaffold/build/gcb/cloud_build.go +++ b/pkg/skaffold/build/gcb/cloud_build.go @@ -31,7 +31,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/gcp" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/version" "github.com/pkg/errors" @@ -43,11 +43,11 @@ import ( ) // Build builds a list of artifacts with Google Cloud Build. -func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact) ([]build.Artifact, error) { +func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { return build.InParallel(ctx, out, tagger, artifacts, b.buildArtifact) } -func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *v1alpha3.Artifact) (string, error) { +func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) { client, err := google.DefaultClient(ctx, cloudbuild.CloudPlatformScope) if err != nil { return "", errors.Wrap(err, "getting google client") diff --git a/pkg/skaffold/build/gcb/desc.go b/pkg/skaffold/build/gcb/desc.go index 8f2cf585945..4c62fbd708d 100644 --- a/pkg/skaffold/build/gcb/desc.go +++ b/pkg/skaffold/build/gcb/desc.go @@ -18,11 +18,11 @@ package gcb import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" cloudbuild "google.golang.org/api/cloudbuild/v1" ) -func (b *Builder) buildDescription(artifact *v1alpha3.Artifact, bucket, object string) *cloudbuild.Build { +func (b *Builder) buildDescription(artifact *latest.Artifact, bucket, object string) *cloudbuild.Build { var steps []*cloudbuild.BuildStep for _, cacheFrom := range artifact.DockerArtifact.CacheFrom { diff --git a/pkg/skaffold/build/gcb/desc_test.go b/pkg/skaffold/build/gcb/desc_test.go index 433373b5f8a..6f5a97f8491 100644 --- a/pkg/skaffold/build/gcb/desc_test.go +++ b/pkg/skaffold/build/gcb/desc_test.go @@ -19,17 +19,17 @@ package gcb import ( "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" cloudbuild "google.golang.org/api/cloudbuild/v1" ) func TestBuildDescription(t *testing.T) { - artifact := &v1alpha3.Artifact{ + artifact := &latest.Artifact{ ImageName: "nginx", - ArtifactType: v1alpha3.ArtifactType{ - DockerArtifact: &v1alpha3.DockerArtifact{ + ArtifactType: latest.ArtifactType{ + DockerArtifact: &latest.DockerArtifact{ DockerfilePath: "Dockerfile", BuildArgs: map[string]*string{ "arg1": util.StringPtr("value1"), @@ -40,7 +40,7 @@ func TestBuildDescription(t *testing.T) { } builder := Builder{ - GoogleCloudBuild: &v1alpha3.GoogleCloudBuild{ + GoogleCloudBuild: &latest.GoogleCloudBuild{ DockerImage: "docker/docker", DiskSizeGb: 100, MachineType: "n1-standard-1", @@ -73,10 +73,10 @@ func TestBuildDescription(t *testing.T) { } func TestPullCacheFrom(t *testing.T) { - artifact := &v1alpha3.Artifact{ + artifact := &latest.Artifact{ ImageName: "nginx", - ArtifactType: v1alpha3.ArtifactType{ - DockerArtifact: &v1alpha3.DockerArtifact{ + ArtifactType: latest.ArtifactType{ + DockerArtifact: &latest.DockerArtifact{ DockerfilePath: "Dockerfile", CacheFrom: []string{"from/image1", "from/image2"}, }, @@ -84,7 +84,7 @@ func TestPullCacheFrom(t *testing.T) { } builder := Builder{ - GoogleCloudBuild: &v1alpha3.GoogleCloudBuild{ + GoogleCloudBuild: &latest.GoogleCloudBuild{ DockerImage: "docker/docker", }, } diff --git a/pkg/skaffold/build/gcb/types.go b/pkg/skaffold/build/gcb/types.go index 8bd24ddaea0..dd45be8f490 100644 --- a/pkg/skaffold/build/gcb/types.go +++ b/pkg/skaffold/build/gcb/types.go @@ -20,7 +20,7 @@ import ( "time" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" ) const ( @@ -54,11 +54,11 @@ const ( // Builder builds artifacts with Google Cloud Build. type Builder struct { - *v1alpha3.GoogleCloudBuild + *latest.GoogleCloudBuild } // NewBuilder creates a new Builder that builds artifacts with Google Cloud Build. -func NewBuilder(cfg *v1alpha3.GoogleCloudBuild) *Builder { +func NewBuilder(cfg *latest.GoogleCloudBuild) *Builder { return &Builder{ GoogleCloudBuild: cfg, } diff --git a/pkg/skaffold/build/kaniko/kaniko.go b/pkg/skaffold/build/kaniko/kaniko.go index 4eaefe89f5f..d04deceebc2 100644 --- a/pkg/skaffold/build/kaniko/kaniko.go +++ b/pkg/skaffold/build/kaniko/kaniko.go @@ -23,12 +23,12 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) // Build builds a list of artifacts with Kaniko. -func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact) ([]build.Artifact, error) { +func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { teardown, err := b.setupSecret() if err != nil { return nil, errors.Wrap(err, "setting up secret") @@ -38,7 +38,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, a return build.InParallel(ctx, out, tagger, artifacts, b.buildArtifact) } -func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *v1alpha3.Artifact) (string, error) { +func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) { initialTag, err := runKaniko(ctx, out, artifact, b.KanikoBuild) if err != nil { return "", errors.Wrapf(err, "kaniko build for [%s]", artifact.ImageName) diff --git a/pkg/skaffold/build/kaniko/run.go b/pkg/skaffold/build/kaniko/run.go index b339309c3c4..200f4be63a9 100644 --- a/pkg/skaffold/build/kaniko/run.go +++ b/pkg/skaffold/build/kaniko/run.go @@ -28,7 +28,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -39,7 +39,7 @@ import ( const kanikoContainerName = "kaniko" -func runKaniko(ctx context.Context, out io.Writer, artifact *v1alpha3.Artifact, cfg *v1alpha3.KanikoBuild) (string, error) { +func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cfg *latest.KanikoBuild) (string, error) { dockerfilePath := artifact.DockerArtifact.DockerfilePath initialTag := util.RandomID() diff --git a/pkg/skaffold/build/kaniko/types.go b/pkg/skaffold/build/kaniko/types.go index 1834ed25354..d8e1aa0c10a 100644 --- a/pkg/skaffold/build/kaniko/types.go +++ b/pkg/skaffold/build/kaniko/types.go @@ -18,16 +18,16 @@ package kaniko import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" ) // Builder builds docker artifacts on Kubernetes, using Kaniko. type Builder struct { - *v1alpha3.KanikoBuild + *latest.KanikoBuild } // NewBuilder creates a new Builder that builds artifacts with Kaniko. -func NewBuilder(cfg *v1alpha3.KanikoBuild) *Builder { +func NewBuilder(cfg *latest.KanikoBuild) *Builder { return &Builder{ KanikoBuild: cfg, } diff --git a/pkg/skaffold/build/local/bazel.go b/pkg/skaffold/build/local/bazel.go index 9db6b92649e..354b1e0b9ba 100644 --- a/pkg/skaffold/build/local/bazel.go +++ b/pkg/skaffold/build/local/bazel.go @@ -26,11 +26,11 @@ import ( "strings" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) -func (b *Builder) buildBazel(ctx context.Context, out io.Writer, workspace string, a *v1alpha3.BazelArtifact) (string, error) { +func (b *Builder) buildBazel(ctx context.Context, out io.Writer, workspace string, a *latest.BazelArtifact) (string, error) { cmd := exec.CommandContext(ctx, "bazel", "build", a.BuildTarget) cmd.Dir = workspace cmd.Stdout = out diff --git a/pkg/skaffold/build/local/docker.go b/pkg/skaffold/build/local/docker.go index 34dbc777e88..de29610714a 100644 --- a/pkg/skaffold/build/local/docker.go +++ b/pkg/skaffold/build/local/docker.go @@ -24,12 +24,12 @@ import ( "os/exec" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" ) -func (b *Builder) buildDocker(ctx context.Context, out io.Writer, workspace string, a *v1alpha3.DockerArtifact) (string, error) { +func (b *Builder) buildDocker(ctx context.Context, out io.Writer, workspace string, a *latest.DockerArtifact) (string, error) { initialTag := util.RandomID() if b.cfg.UseDockerCLI || b.cfg.UseBuildkit { diff --git a/pkg/skaffold/build/local/jib.go b/pkg/skaffold/build/local/jib.go index cb34a51e6b9..afe6dd91bf1 100644 --- a/pkg/skaffold/build/local/jib.go +++ b/pkg/skaffold/build/local/jib.go @@ -20,14 +20,14 @@ import ( "context" "io" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) -func (b *Builder) buildJibMaven(_ /*ctx*/ context.Context, _ /*out*/ io.Writer, _ /*workspace*/ string, _ /*a*/ *v1alpha3.JibMavenArtifact) (string, error) { +func (b *Builder) buildJibMaven(_ /*ctx*/ context.Context, _ /*out*/ io.Writer, _ /*workspace*/ string, _ /*a*/ *latest.JibMavenArtifact) (string, error) { return "", errors.New("buildJibMaven is unimplemented") } -func (b *Builder) buildJibGradle(_ /*ctx*/ context.Context, _ /*out*/ io.Writer, _ /*workspace*/ string, _ /*a*/ *v1alpha3.JibGradleArtifact) (string, error) { +func (b *Builder) buildJibGradle(_ /*ctx*/ context.Context, _ /*out*/ io.Writer, _ /*workspace*/ string, _ /*a*/ *latest.JibGradleArtifact) (string, error) { return "", errors.New("buildJibGradle is unimplemented") } diff --git a/pkg/skaffold/build/local/local.go b/pkg/skaffold/build/local/local.go index 380053b3088..12d273cca2c 100644 --- a/pkg/skaffold/build/local/local.go +++ b/pkg/skaffold/build/local/local.go @@ -25,13 +25,13 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) // Build runs a docker build on the host and tags the resulting image with // its checksum. It streams build progress to the writer argument. -func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact) ([]build.Artifact, error) { +func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { if b.localCluster { if _, err := color.Default.Fprintf(out, "Found [%s] context, using local docker daemon.\n", b.kubeContext); err != nil { return nil, errors.Wrap(err, "writing status") @@ -43,7 +43,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, a return build.InSequence(ctx, out, tagger, artifacts, b.buildArtifact) } -func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *v1alpha3.Artifact) (string, error) { +func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) { initialTag, err := b.runBuildForArtifact(ctx, out, artifact) if err != nil { return "", errors.Wrap(err, "build artifact") @@ -87,7 +87,7 @@ func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.T return tag, nil } -func (b *Builder) runBuildForArtifact(ctx context.Context, out io.Writer, artifact *v1alpha3.Artifact) (string, error) { +func (b *Builder) runBuildForArtifact(ctx context.Context, out io.Writer, artifact *latest.Artifact) (string, error) { switch { case artifact.DockerArtifact != nil: return b.buildDocker(ctx, out, artifact.Workspace, artifact.DockerArtifact) diff --git a/pkg/skaffold/build/local/local_test.go b/pkg/skaffold/build/local/local_test.go index 9e5eb7f9537..04d8cbea98f 100644 --- a/pkg/skaffold/build/local/local_test.go +++ b/pkg/skaffold/build/local/local_test.go @@ -26,7 +26,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/docker/docker/api/types" @@ -67,11 +67,11 @@ func TestLocalRun(t *testing.T) { var tests = []struct { description string - config *v1alpha3.LocalBuild + config *latest.LocalBuild out io.Writer api docker.APIClient tagger tag.Tagger - artifacts []*v1alpha3.Artifact + artifacts []*latest.Artifact expected []build.Artifact localCluster bool shouldErr bool @@ -79,15 +79,15 @@ func TestLocalRun(t *testing.T) { { description: "single build", out: ioutil.Discard, - config: &v1alpha3.LocalBuild{ + config: &latest.LocalBuild{ SkipPush: util.BoolPtr(false), }, - artifacts: []*v1alpha3.Artifact{ + artifacts: []*latest.Artifact{ { ImageName: "gcr.io/test/image", Workspace: tmpDir.Root(), - ArtifactType: v1alpha3.ArtifactType{ - DockerArtifact: &v1alpha3.DockerArtifact{}, + ArtifactType: latest.ArtifactType{ + DockerArtifact: &latest.DockerArtifact{}, }, }, }, @@ -103,16 +103,16 @@ func TestLocalRun(t *testing.T) { { description: "subset build", out: ioutil.Discard, - config: &v1alpha3.LocalBuild{ + config: &latest.LocalBuild{ SkipPush: util.BoolPtr(true), }, tagger: &tag.ChecksumTagger{}, - artifacts: []*v1alpha3.Artifact{ + artifacts: []*latest.Artifact{ { ImageName: "gcr.io/test/image", Workspace: tmpDir.Root(), - ArtifactType: v1alpha3.ArtifactType{ - DockerArtifact: &v1alpha3.DockerArtifact{}, + ArtifactType: latest.ArtifactType{ + DockerArtifact: &latest.DockerArtifact{}, }, }, }, @@ -127,14 +127,14 @@ func TestLocalRun(t *testing.T) { { description: "local cluster bad writer", out: &testutil.BadWriter{}, - config: &v1alpha3.LocalBuild{}, + config: &latest.LocalBuild{}, shouldErr: true, localCluster: true, }, { description: "error image build", out: ioutil.Discard, - artifacts: []*v1alpha3.Artifact{{}}, + artifacts: []*latest.Artifact{{}}, tagger: &tag.ChecksumTagger{}, api: testutil.NewFakeImageAPIClient(map[string]string{}, &testutil.FakeImageAPIOptions{ ErrImageBuild: true, @@ -144,7 +144,7 @@ func TestLocalRun(t *testing.T) { { description: "error image tag", out: ioutil.Discard, - artifacts: []*v1alpha3.Artifact{{}}, + artifacts: []*latest.Artifact{{}}, tagger: &tag.ChecksumTagger{}, api: testutil.NewFakeImageAPIClient(map[string]string{}, &testutil.FakeImageAPIOptions{ ErrImageTag: true, @@ -154,7 +154,7 @@ func TestLocalRun(t *testing.T) { { description: "bad writer", out: &testutil.BadWriter{}, - artifacts: []*v1alpha3.Artifact{{}}, + artifacts: []*latest.Artifact{{}}, tagger: &tag.ChecksumTagger{}, api: testutil.NewFakeImageAPIClient(map[string]string{}, &testutil.FakeImageAPIOptions{}), shouldErr: true, @@ -162,7 +162,7 @@ func TestLocalRun(t *testing.T) { { description: "error image inspect", out: &testutil.BadWriter{}, - artifacts: []*v1alpha3.Artifact{{}}, + artifacts: []*latest.Artifact{{}}, tagger: &tag.ChecksumTagger{}, api: testutil.NewFakeImageAPIClient(map[string]string{}, &testutil.FakeImageAPIOptions{ ErrImageInspect: true, @@ -172,7 +172,7 @@ func TestLocalRun(t *testing.T) { { description: "error tagger", out: ioutil.Discard, - artifacts: []*v1alpha3.Artifact{{}}, + artifacts: []*latest.Artifact{{}}, tagger: &FakeTagger{Err: fmt.Errorf("")}, api: testutil.NewFakeImageAPIClient(map[string]string{}, &testutil.FakeImageAPIOptions{}), shouldErr: true, diff --git a/pkg/skaffold/build/local/types.go b/pkg/skaffold/build/local/types.go index 550b32d6ddb..d693c849bcc 100644 --- a/pkg/skaffold/build/local/types.go +++ b/pkg/skaffold/build/local/types.go @@ -22,14 +22,14 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // Builder uses the host docker daemon to build and tag the image. type Builder struct { - cfg *v1alpha3.LocalBuild + cfg *latest.LocalBuild api docker.APIClient localCluster bool @@ -40,7 +40,7 @@ type Builder struct { } // NewBuilder returns an new instance of a local Builder. -func NewBuilder(cfg *v1alpha3.LocalBuild, kubeContext string) (*Builder, error) { +func NewBuilder(cfg *latest.LocalBuild, kubeContext string) (*Builder, error) { api, err := docker.NewAPIClient() if err != nil { return nil, errors.Wrap(err, "getting docker client") diff --git a/pkg/skaffold/build/parallel.go b/pkg/skaffold/build/parallel.go index 79707544451..cd01b97ee92 100644 --- a/pkg/skaffold/build/parallel.go +++ b/pkg/skaffold/build/parallel.go @@ -24,16 +24,16 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) const bufferedLinesPerArtifact = 10000 -type artifactBuilder func(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *v1alpha3.Artifact) (string, error) +type artifactBuilder func(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) // InParallel builds a list of artifacts in parallel but prints the logs in sequential order. -func InParallel(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact, buildArtifact artifactBuilder) ([]Artifact, error) { +func InParallel(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact, buildArtifact artifactBuilder) ([]Artifact, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() diff --git a/pkg/skaffold/build/sequence.go b/pkg/skaffold/build/sequence.go index be43b4c0e0b..4eddb688e65 100644 --- a/pkg/skaffold/build/sequence.go +++ b/pkg/skaffold/build/sequence.go @@ -22,12 +22,12 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) // InSequence builds a list of artifacts in sequence. -func InSequence(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact, buildArtifact artifactBuilder) ([]Artifact, error) { +func InSequence(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact, buildArtifact artifactBuilder) ([]Artifact, error) { var builds []Artifact for _, artifact := range artifacts { diff --git a/pkg/skaffold/config/transform/transforms.go b/pkg/skaffold/config/transform/transforms.go deleted file mode 100644 index 1f97708340a..00000000000 --- a/pkg/skaffold/config/transform/transforms.go +++ /dev/null @@ -1,199 +0,0 @@ -/* -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 transform - -import ( - "encoding/json" - "fmt" - - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// ToV1Alpha2 transforms v1alpha1 configs to v1alpha2 -func ToV1Alpha2(vc util.VersionedConfig) (util.VersionedConfig, error) { - if vc.GetVersion() != v1alpha1.Version { - return nil, fmt.Errorf("Incompatible version: %s", vc.GetVersion()) - } - oldConfig := vc.(*v1alpha1.SkaffoldConfig) - - var tagPolicy v1alpha2.TagPolicy - if oldConfig.Build.TagPolicy == constants.TagStrategySha256 { - tagPolicy = v1alpha2.TagPolicy{ - ShaTagger: &v1alpha2.ShaTagger{}, - } - } else if oldConfig.Build.TagPolicy == constants.TagStrategyGitCommit { - tagPolicy = v1alpha2.TagPolicy{ - GitTagger: &v1alpha2.GitTagger{}, - } - } - - var newHelmDeploy *v1alpha2.HelmDeploy - if oldConfig.Deploy.DeployType.HelmDeploy != nil { - newReleases := make([]v1alpha2.HelmRelease, 0) - for _, release := range oldConfig.Deploy.DeployType.HelmDeploy.Releases { - newReleases = append(newReleases, v1alpha2.HelmRelease{ - Name: release.Name, - ChartPath: release.ChartPath, - ValuesFilePath: release.ValuesFilePath, - Values: release.Values, - Namespace: release.Namespace, - Version: release.Version, - }) - } - newHelmDeploy = &v1alpha2.HelmDeploy{ - Releases: newReleases, - } - } - var newKubectlDeploy *v1alpha2.KubectlDeploy - if oldConfig.Deploy.DeployType.KubectlDeploy != nil { - newManifests := make([]string, 0) - logrus.Warn("Ignoring manifest parameters when transforming v1alpha1 config; check kubernetes yaml before running skaffold") - for _, manifest := range oldConfig.Deploy.DeployType.KubectlDeploy.Manifests { - newManifests = append(newManifests, manifest.Paths...) - } - newKubectlDeploy = &v1alpha2.KubectlDeploy{ - Manifests: newManifests, - } - } - - var newArtifacts = make([]*v1alpha2.Artifact, 0) - for _, artifact := range oldConfig.Build.Artifacts { - newArtifacts = append(newArtifacts, &v1alpha2.Artifact{ - ImageName: artifact.ImageName, - Workspace: artifact.Workspace, - ArtifactType: v1alpha2.ArtifactType{ - DockerArtifact: &v1alpha2.DockerArtifact{ - DockerfilePath: artifact.DockerfilePath, - BuildArgs: artifact.BuildArgs, - }, - }, - }) - } - - var newBuildType = v1alpha2.BuildType{} - if oldConfig.Build.GoogleCloudBuild != nil { - newBuildType.GoogleCloudBuild = &v1alpha2.GoogleCloudBuild{ - ProjectID: oldConfig.Build.GoogleCloudBuild.ProjectID, - } - } - if oldConfig.Build.LocalBuild != nil { - newBuildType.LocalBuild = &v1alpha2.LocalBuild{ - SkipPush: oldConfig.Build.LocalBuild.SkipPush, - } - } - - newConfig := &v1alpha2.SkaffoldConfig{ - APIVersion: v1alpha2.Version, - Kind: oldConfig.Kind, - Deploy: v1alpha2.DeployConfig{ - DeployType: v1alpha2.DeployType{ - HelmDeploy: newHelmDeploy, - KubectlDeploy: newKubectlDeploy, - }, - }, - Build: v1alpha2.BuildConfig{ - Artifacts: newArtifacts, - BuildType: newBuildType, - TagPolicy: tagPolicy, - }, - } - return newConfig, nil -} - -// ToV1Alpha3 transforms configs from v1alpha2 to v1alpha3 -func ToV1Alpha3(vc util.VersionedConfig) (util.VersionedConfig, error) { - if vc.GetVersion() != v1alpha2.Version { - return nil, fmt.Errorf("Incompatible version: %s", vc.GetVersion()) - } - oldConfig := vc.(*v1alpha2.SkaffoldConfig) - - // convert v1alpha2.Deploy to v1alpha3.Deploy (should be the same) - var newDeploy v1alpha3.DeployConfig - if err := convert(oldConfig.Deploy, &newDeploy); err != nil { - return nil, errors.Wrap(err, "converting deploy config") - } - - // if the helm deploy config was set, then convert ValueFilePath to ValuesFiles - if oldHelmDeploy := oldConfig.Deploy.DeployType.HelmDeploy; oldHelmDeploy != nil { - for i, oldHelmRelease := range oldHelmDeploy.Releases { - if oldHelmRelease.ValuesFilePath != "" { - newDeploy.DeployType.HelmDeploy.Releases[i].ValuesFiles = []string{oldHelmRelease.ValuesFilePath} - } - } - } - - // the kustomize path parameter was renamed - kustomize := oldConfig.Deploy.KustomizeDeploy - if kustomize != nil { - newDeploy.KustomizeDeploy.Path = kustomize.KustomizePath - } - - // convert v1alpha2.Profiles to v1alpha3.Profiles (should be the same) - var newProfiles []v1alpha3.Profile - if oldConfig.Profiles != nil { - if err := convert(oldConfig.Profiles, &newProfiles); err != nil { - return nil, errors.Wrap(err, "converting new profile") - } - } - - // convert v1alpha2.Build to v1alpha3.Build (different only for kaniko) - oldKanikoBuilder := oldConfig.Build.KanikoBuild - oldConfig.Build.KanikoBuild = nil - - // copy over old build config to new build config - var newBuild v1alpha3.BuildConfig - if err := convert(oldConfig.Build, &newBuild); err != nil { - return nil, errors.Wrap(err, "converting new build") - } - // if the kaniko build was set, then convert it - if oldKanikoBuilder != nil { - newBuild.BuildType.KanikoBuild = &v1alpha3.KanikoBuild{ - BuildContext: v1alpha3.KanikoBuildContext{ - GCSBucket: oldKanikoBuilder.GCSBucket, - }, - Namespace: oldKanikoBuilder.Namespace, - PullSecret: oldKanikoBuilder.PullSecret, - PullSecretName: oldKanikoBuilder.PullSecretName, - Timeout: oldKanikoBuilder.Timeout, - } - } - newConfig := &v1alpha3.SkaffoldConfig{ - APIVersion: v1alpha3.Version, - Kind: oldConfig.Kind, - Deploy: newDeploy, - Build: newBuild, - Profiles: newProfiles, - } - return newConfig, nil -} - -func convert(old interface{}, new interface{}) error { - o, err := json.Marshal(old) - if err != nil { - return errors.Wrap(err, "marshalling old") - } - if err := json.Unmarshal(o, &new); err != nil { - return errors.Wrap(err, "unmarshalling new") - } - return nil -} diff --git a/pkg/skaffold/config/util.go b/pkg/skaffold/config/util.go deleted file mode 100644 index aa545f62c70..00000000000 --- a/pkg/skaffold/config/util.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -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 config - -import ( - "errors" - - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/yamltags" -) - -// Versions is an ordered list of all schema versions. -var Versions = []string{ - v1alpha1.Version, - v1alpha2.Version, - v1alpha3.Version, -} - -var schemaVersions = map[string]func() util.VersionedConfig{ - v1alpha1.Version: func() util.VersionedConfig { - return new(v1alpha1.SkaffoldConfig) - }, - v1alpha2.Version: func() util.VersionedConfig { - return new(v1alpha2.SkaffoldConfig) - }, - v1alpha3.Version: func() util.VersionedConfig { - return new(v1alpha3.SkaffoldConfig) - }, -} - -func GetConfig(contents []byte, useDefault bool) (util.VersionedConfig, error) { - for _, version := range Versions { - cfg := schemaVersions[version]() - err := cfg.Parse(contents, useDefault) - if cfg.GetVersion() == version { - // Versions are same hence propagate the parse error. - if err := yamltags.ProcessStruct(cfg); err != nil { - return nil, err - } - return cfg, err - } - } - - return nil, errors.New("Unable to parse config") -} - -type APIVersion struct { - Version string `yaml:"apiVersion"` -} diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index 3b9eaa2e7c9..b714b13ddae 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -34,7 +34,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -42,7 +42,7 @@ import ( ) type HelmDeployer struct { - *v1alpha3.HelmDeploy + *latest.HelmDeploy kubeContext string namespace string @@ -50,7 +50,7 @@ type HelmDeployer struct { // NewHelmDeployer returns a new HelmDeployer for a DeployConfig filled // with the needed configuration for `helm` -func NewHelmDeployer(cfg *v1alpha3.HelmDeploy, kubeContext string, namespace string) *HelmDeployer { +func NewHelmDeployer(cfg *latest.HelmDeploy, kubeContext string, namespace string) *HelmDeployer { return &HelmDeployer{ HelmDeploy: cfg, kubeContext: kubeContext, @@ -114,7 +114,7 @@ func (h *HelmDeployer) helm(ctx context.Context, out io.Writer, arg ...string) e return util.RunCmd(cmd) } -func (h *HelmDeployer) deployRelease(ctx context.Context, out io.Writer, r v1alpha3.HelmRelease, builds []build.Artifact) ([]Artifact, error) { +func (h *HelmDeployer) deployRelease(ctx context.Context, out io.Writer, r latest.HelmRelease, builds []build.Artifact) ([]Artifact, error) { isInstalled := true releaseName, err := evaluateReleaseName(r.Name) @@ -270,7 +270,7 @@ func extractTag(imageName string) string { // packageChart packages the chart and returns path to the chart archive file. // If this function returns an error, it will always be wrapped. -func (h *HelmDeployer) packageChart(ctx context.Context, r v1alpha3.HelmRelease) (string, error) { +func (h *HelmDeployer) packageChart(ctx context.Context, r latest.HelmRelease) (string, error) { tmp := os.TempDir() packageArgs := []string{"package", r.ChartPath, "--destination", tmp} if r.Packaged.Version != "" { @@ -323,7 +323,7 @@ func (h *HelmDeployer) getDeployResults(ctx context.Context, namespace string, r return parseReleaseInfo(namespace, b) } -func (h *HelmDeployer) deleteRelease(ctx context.Context, out io.Writer, r v1alpha3.HelmRelease) error { +func (h *HelmDeployer) deleteRelease(ctx context.Context, out io.Writer, r latest.HelmRelease) error { releaseName, err := evaluateReleaseName(r.Name) if err != nil { return errors.Wrap(err, "cannot parse the release name template") diff --git a/pkg/skaffold/deploy/helm_test.go b/pkg/skaffold/deploy/helm_test.go index 6e43b6aba8b..2fa72877aaf 100644 --- a/pkg/skaffold/deploy/helm_test.go +++ b/pkg/skaffold/deploy/helm_test.go @@ -29,7 +29,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/sirupsen/logrus" @@ -49,8 +49,8 @@ var testBuildsFoo = []build.Artifact{ }, } -var testDeployConfig = &v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ +var testDeployConfig = &latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "skaffold-helm", ChartPath: "examples/test", @@ -67,8 +67,8 @@ var testDeployConfig = &v1alpha3.HelmDeploy{ }, } -var testDeployRecreatePodsConfig = &v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ +var testDeployRecreatePodsConfig = &latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "skaffold-helm", ChartPath: "examples/test", @@ -86,8 +86,8 @@ var testDeployRecreatePodsConfig = &v1alpha3.HelmDeploy{ }, } -var testDeployHelmStyleConfig = &v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ +var testDeployHelmStyleConfig = &latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "skaffold-helm", ChartPath: "examples/test", @@ -100,17 +100,17 @@ var testDeployHelmStyleConfig = &v1alpha3.HelmDeploy{ SetValues: map[string]string{ "some.key": "somevalue", }, - ImageStrategy: v1alpha3.HelmImageStrategy{ - HelmImageConfig: v1alpha3.HelmImageConfig{ - HelmConventionConfig: &v1alpha3.HelmConventionConfig{}, + ImageStrategy: latest.HelmImageStrategy{ + HelmImageConfig: latest.HelmImageConfig{ + HelmConventionConfig: &latest.HelmConventionConfig{}, }, }, }, }, } -var testDeployConfigParameterUnmatched = &v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ +var testDeployConfigParameterUnmatched = &latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "skaffold-helm", ChartPath: "examples/test", @@ -121,15 +121,15 @@ var testDeployConfigParameterUnmatched = &v1alpha3.HelmDeploy{ }, } -var testDeployFooWithPackaged = &v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ +var testDeployFooWithPackaged = &latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "foo", ChartPath: "testdata/foo", Values: map[string]string{ "image": "foo", }, - Packaged: &v1alpha3.HelmPackaged{ + Packaged: &latest.HelmPackaged{ Version: "0.1.2", AppVersion: "1.2.3", }, @@ -137,8 +137,8 @@ var testDeployFooWithPackaged = &v1alpha3.HelmDeploy{ }, } -var testDeployWithTemplatedName = &v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ +var testDeployWithTemplatedName = &latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "{{.USER}}-skaffold-helm", ChartPath: "examples/test", @@ -526,8 +526,8 @@ func TestHelmDependencies(t *testing.T) { folder.Write(file, "") } - deployer := NewHelmDeployer(&v1alpha3.HelmDeploy{ - Releases: []v1alpha3.HelmRelease{ + deployer := NewHelmDeployer(&latest.HelmDeploy{ + Releases: []latest.HelmRelease{ { Name: "skaffold-helm", ChartPath: folder.Root(), diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index e6c61dd67cd..3eef482db37 100644 --- a/pkg/skaffold/deploy/kubectl.go +++ b/pkg/skaffold/deploy/kubectl.go @@ -28,7 +28,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -36,7 +36,7 @@ import ( // KubectlDeployer deploys workflows using kubectl CLI. type KubectlDeployer struct { - *v1alpha3.KubectlDeploy + *latest.KubectlDeploy workingDir string kubectl kubectl.CLI @@ -44,7 +44,7 @@ type KubectlDeployer struct { // NewKubectlDeployer returns a new KubectlDeployer for a DeployConfig filled // with the needed configuration for `kubectl apply` -func NewKubectlDeployer(workingDir string, cfg *v1alpha3.KubectlDeploy, kubeContext string, namespace string) *KubectlDeployer { +func NewKubectlDeployer(workingDir string, cfg *latest.KubectlDeploy, kubeContext string, namespace string) *KubectlDeployer { return &KubectlDeployer{ KubectlDeploy: cfg, workingDir: workingDir, diff --git a/pkg/skaffold/deploy/kubectl/cli.go b/pkg/skaffold/deploy/kubectl/cli.go index 36e44fd0e91..d2a1b7d3c50 100644 --- a/pkg/skaffold/deploy/kubectl/cli.go +++ b/pkg/skaffold/deploy/kubectl/cli.go @@ -22,7 +22,7 @@ import ( "os/exec" "sync" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -32,7 +32,7 @@ import ( type CLI struct { Namespace string KubeContext string - Flags v1alpha3.KubectlFlags + Flags latest.KubectlFlags version ClientVersion versionOnce sync.Once diff --git a/pkg/skaffold/deploy/kubectl_test.go b/pkg/skaffold/deploy/kubectl_test.go index 0598a27f997..1e6edfcf10b 100644 --- a/pkg/skaffold/deploy/kubectl_test.go +++ b/pkg/skaffold/deploy/kubectl_test.go @@ -23,7 +23,7 @@ import ( "testing" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/pkg/errors" @@ -52,7 +52,7 @@ spec: func TestKubectlDeploy(t *testing.T) { var tests = []struct { description string - cfg *v1alpha3.KubectlDeploy + cfg *latest.KubectlDeploy builds []build.Artifact command util.Command shouldErr bool @@ -60,7 +60,7 @@ func TestKubectlDeploy(t *testing.T) { { description: "parameter mismatch", shouldErr: true, - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, }, builds: []build.Artifact{ @@ -73,7 +73,7 @@ func TestKubectlDeploy(t *testing.T) { { description: "missing manifest file", shouldErr: true, - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, }, builds: []build.Artifact{ @@ -85,7 +85,7 @@ func TestKubectlDeploy(t *testing.T) { }, { description: "deploy success", - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, }, command: testutil.NewFakeCmd("kubectl --context kubecontext --namespace testNamespace apply -f -", nil), @@ -99,7 +99,7 @@ func TestKubectlDeploy(t *testing.T) { { description: "deploy command error", shouldErr: true, - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, }, command: testutil.NewFakeCmd("kubectl --context kubecontext --namespace testNamespace apply -f -", fmt.Errorf("")), @@ -113,9 +113,9 @@ func TestKubectlDeploy(t *testing.T) { { description: "additional flags", shouldErr: true, - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, - Flags: v1alpha3.KubectlFlags{ + Flags: latest.KubectlFlags{ Global: []string{"-v=0"}, Apply: []string{"--overwrite=true"}, Delete: []string{"ignored"}, @@ -154,20 +154,20 @@ func TestKubectlDeploy(t *testing.T) { func TestKubectlCleanup(t *testing.T) { var tests = []struct { description string - cfg *v1alpha3.KubectlDeploy + cfg *latest.KubectlDeploy command util.Command shouldErr bool }{ { description: "cleanup success", - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, }, command: testutil.NewFakeCmd("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -", nil), }, { description: "cleanup error", - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, }, command: testutil.NewFakeCmd("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -", errors.New("BUG")), @@ -175,9 +175,9 @@ func TestKubectlCleanup(t *testing.T) { }, { description: "additional flags", - cfg: &v1alpha3.KubectlDeploy{ + cfg: &latest.KubectlDeploy{ Manifests: []string{"deployment.yaml"}, - Flags: v1alpha3.KubectlFlags{ + Flags: latest.KubectlFlags{ Global: []string{"-v=0"}, Apply: []string{"ignored"}, Delete: []string{"--grace-period=1"}, @@ -216,7 +216,7 @@ func TestKubectlRedeploy(t *testing.T) { tmpDir.Write("deployment-web.yaml", deploymentWebYAML) tmpDir.Write("deployment-app.yaml", deploymentAppYaml) - cfg := &v1alpha3.KubectlDeploy{ + cfg := &latest.KubectlDeploy{ Manifests: []string{"deployment-web.yaml", "deployment-app.yaml"}, } deployer := NewKubectlDeployer(tmpDir.Root(), cfg, testKubeContext, testNamespace) diff --git a/pkg/skaffold/deploy/kustomize.go b/pkg/skaffold/deploy/kustomize.go index 96271433e3f..e34ad5217a0 100644 --- a/pkg/skaffold/deploy/kustomize.go +++ b/pkg/skaffold/deploy/kustomize.go @@ -28,7 +28,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" ) @@ -53,13 +53,13 @@ type configMapGenerator struct { // KustomizeDeployer deploys workflows using kustomize CLI. type KustomizeDeployer struct { - *v1alpha3.KustomizeDeploy + *latest.KustomizeDeploy kubectl kubectl.CLI } // NewKustomizeDeployer returns a new KustomizeDeployer. -func NewKustomizeDeployer(cfg *v1alpha3.KustomizeDeploy, kubeContext string, namespace string) *KustomizeDeployer { +func NewKustomizeDeployer(cfg *latest.KustomizeDeploy, kubeContext string, namespace string) *KustomizeDeployer { return &KustomizeDeployer{ KustomizeDeploy: cfg, kubectl: kubectl.CLI{ @@ -164,11 +164,11 @@ func joinPaths(root string, paths []string) []string { // Dependencies lists all the files that can change what needs to be deployed. func (k *KustomizeDeployer) Dependencies() ([]string, error) { - return dependenciesForKustomization(k.Path) + return dependenciesForKustomization(k.KustomizePath) } func (k *KustomizeDeployer) readManifests(ctx context.Context) (kubectl.ManifestList, error) { - cmd := exec.CommandContext(ctx, "kustomize", "build", k.Path) + cmd := exec.CommandContext(ctx, "kustomize", "build", k.KustomizePath) out, err := util.RunCmdOut(cmd) if err != nil { return nil, errors.Wrap(err, "kustomize build") diff --git a/pkg/skaffold/docker/context.go b/pkg/skaffold/docker/context.go index fea931e161c..27e834fb472 100644 --- a/pkg/skaffold/docker/context.go +++ b/pkg/skaffold/docker/context.go @@ -23,7 +23,7 @@ import ( "strings" cstorage "cloud.google.com/go/storage" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" ) @@ -40,7 +40,7 @@ func NormalizeDockerfilePath(context, dockerfile string) (string, error) { return filepath.Abs(dockerfile) } -func CreateDockerTarContext(w io.Writer, workspace string, a *v1alpha3.DockerArtifact) error { +func CreateDockerTarContext(w io.Writer, workspace string, a *latest.DockerArtifact) error { paths, err := GetDependencies(workspace, a) if err != nil { return errors.Wrap(err, "getting relative tar paths") @@ -53,7 +53,7 @@ func CreateDockerTarContext(w io.Writer, workspace string, a *v1alpha3.DockerArt return nil } -func CreateDockerTarGzContext(w io.Writer, workspace string, a *v1alpha3.DockerArtifact) error { +func CreateDockerTarGzContext(w io.Writer, workspace string, a *latest.DockerArtifact) error { paths, err := GetDependencies(workspace, a) if err != nil { return errors.Wrap(err, "getting relative tar paths") @@ -66,7 +66,7 @@ func CreateDockerTarGzContext(w io.Writer, workspace string, a *v1alpha3.DockerA return nil } -func UploadContextToGCS(ctx context.Context, workspace string, a *v1alpha3.DockerArtifact, bucket, objectName string) error { +func UploadContextToGCS(ctx context.Context, workspace string, a *latest.DockerArtifact, bucket, objectName string) error { c, err := cstorage.NewClient(ctx) if err != nil { return err diff --git a/pkg/skaffold/docker/context_test.go b/pkg/skaffold/docker/context_test.go index 375a7fe94dd..dd7c5a7345b 100644 --- a/pkg/skaffold/docker/context_test.go +++ b/pkg/skaffold/docker/context_test.go @@ -21,7 +21,7 @@ import ( "io" "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/testutil" ) @@ -33,7 +33,7 @@ func TestDockerContext(t *testing.T) { RetrieveImage = imageFetcher.fetch defer func() { RetrieveImage = retrieveImage }() - artifact := &v1alpha3.DockerArtifact{ + artifact := &latest.DockerArtifact{ DockerfilePath: "Dockerfile", BuildArgs: map[string]*string{}, } diff --git a/pkg/skaffold/docker/image.go b/pkg/skaffold/docker/image.go index 0849c6ebfed..207a149d731 100644 --- a/pkg/skaffold/docker/image.go +++ b/pkg/skaffold/docker/image.go @@ -23,7 +23,7 @@ import ( "net/http" "sort" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/docker/docker/pkg/jsonmessage" @@ -40,7 +40,7 @@ import ( ) // BuildArtifact performs a docker build and returns nothing -func BuildArtifact(ctx context.Context, out io.Writer, cli APIClient, workspace string, a *v1alpha3.DockerArtifact, initialTag string) error { +func BuildArtifact(ctx context.Context, out io.Writer, cli APIClient, workspace string, a *latest.DockerArtifact, initialTag string) error { logrus.Debugf("Running docker build: context: %s, dockerfile: %s", workspace, a.DockerfilePath) // Like `docker build`, we ignore the errors @@ -128,7 +128,7 @@ func addTag(ref name.Reference, targetRef name.Reference, auth authn.Authenticat return err } - return remote.Write(targetRef, img, auth, t, remote.WriteOptions{}) + return remote.Write(targetRef, img, auth, t) } // Digest returns the image digest for a corresponding reference. @@ -175,7 +175,7 @@ func RemoteDigest(identifier string) (string, error) { } // GetBuildArgs gives the build args flags for docker build. -func GetBuildArgs(a *v1alpha3.DockerArtifact) []string { +func GetBuildArgs(a *latest.DockerArtifact) []string { var args []string var keys []string diff --git a/pkg/skaffold/docker/image_test.go b/pkg/skaffold/docker/image_test.go index 3fbb94bc4eb..d0880fac0fa 100644 --- a/pkg/skaffold/docker/image_test.go +++ b/pkg/skaffold/docker/image_test.go @@ -23,7 +23,7 @@ import ( "os" "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/google/go-cmp/cmp" @@ -121,7 +121,7 @@ func TestRunBuildArtifact(t *testing.T) { t.Run(test.description, func(t *testing.T) { api := testutil.NewFakeImageAPIClient(test.tagToImageID, test.testOpts) - err := BuildArtifact(context.Background(), ioutil.Discard, api, ".", &v1alpha3.DockerArtifact{}, "finalimage") + err := BuildArtifact(context.Background(), ioutil.Discard, api, ".", &latest.DockerArtifact{}, "finalimage") testutil.CheckError(t, test.shouldErr, err) }) @@ -172,12 +172,12 @@ func TestDigest(t *testing.T) { func TestGetBuildArgs(t *testing.T) { tests := []struct { description string - artifact *v1alpha3.DockerArtifact + artifact *latest.DockerArtifact want []string }{ { description: "build args", - artifact: &v1alpha3.DockerArtifact{ + artifact: &latest.DockerArtifact{ BuildArgs: map[string]*string{ "key1": util.StringPtr("value1"), "key2": nil, @@ -187,21 +187,21 @@ func TestGetBuildArgs(t *testing.T) { }, { description: "cache from", - artifact: &v1alpha3.DockerArtifact{ + artifact: &latest.DockerArtifact{ CacheFrom: []string{"gcr.io/foo/bar", "baz:latest"}, }, want: []string{"--cache-from", "gcr.io/foo/bar", "--cache-from", "baz:latest"}, }, { description: "target", - artifact: &v1alpha3.DockerArtifact{ + artifact: &latest.DockerArtifact{ Target: "stage1", }, want: []string{"--target", "stage1"}, }, { description: "all", - artifact: &v1alpha3.DockerArtifact{ + artifact: &latest.DockerArtifact{ BuildArgs: map[string]*string{ "key1": util.StringPtr("value1"), }, diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go index 26d49027e66..80d9b7a320b 100644 --- a/pkg/skaffold/docker/parse.go +++ b/pkg/skaffold/docker/parse.go @@ -26,7 +26,7 @@ import ( "strings" "sync" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/docker/docker/builder/dockerignore" "github.com/docker/docker/pkg/fileutils" @@ -39,6 +39,11 @@ import ( "github.com/sirupsen/logrus" ) +type from struct { + image string + as string +} + // RetrieveImage is overridden for unit testing var RetrieveImage = retrieveImage @@ -64,30 +69,116 @@ func ValidateDockerfile(path string) bool { } func expandBuildArgs(nodes []*parser.Node, buildArgs map[string]*string) { - var key, value string + for i, node := range nodes { + if node.Value != command.Arg { + continue + } + + // build arg's key + keyValue := strings.Split(node.Next.Value, "=") + key := keyValue[0] + + // build arg's value + var value string + if buildArgs[key] != nil { + value = *buildArgs[key] + } else if len(keyValue) > 1 { + value = keyValue[1] + } + + for _, node := range nodes[i+1:] { + // Stop replacements if an arg is redefined with the same key + if node.Value == command.Arg && strings.Split(node.Next.Value, "=")[0] == key { + break + } + + // replace $key with value + for curr := node; curr != nil; curr = curr.Next { + curr.Value = util.Expand(curr.Value, key, value) + } + } + } +} + +func fromInstructions(nodes []*parser.Node) []from { + var list []from + for _, node := range nodes { + if node.Value == command.From { + list = append(list, fromInstruction(node)) + } + } + + return list +} + +func fromInstruction(node *parser.Node) from { + var as string + if next := node.Next.Next; next != nil && strings.ToLower(next.Value) == "as" && next.Next != nil { + as = next.Next.Value + } + + return from{ + image: strings.ToLower(node.Next.Value), + as: strings.ToLower(as), + } +} + +func onbuildInstructions(nodes []*parser.Node) ([]*parser.Node, error) { + var instructions []string + + stages := map[string]bool{} + for _, from := range fromInstructions(nodes) { + stages[from.as] = true + + if from.image == "scratch" { + continue + } + + if _, found := stages[from.image]; found { + continue + } + + logrus.Debugf("Checking base image %s for ONBUILD triggers.", from.image) + img, err := RetrieveImage(from.image) + if err != nil { + logrus.Warnf("Error processing base image for ONBUILD triggers: %s. Dependencies may be incomplete.", err) + continue + } + + logrus.Debugf("Found ONBUILD triggers %v in image %s", img.Config.OnBuild, from.image) + instructions = append(instructions, img.Config.OnBuild...) + } + + obRes, err := parser.Parse(strings.NewReader(strings.Join(instructions, "\n"))) + if err != nil { + return nil, errors.Wrap(err, "parsing ONBUILD instructions") + } + + return obRes.AST.Children, nil +} + +func copiedFiles(nodes []*parser.Node) ([][]string, error) { + var copied [][]string + + envs := map[string]string{} for _, node := range nodes { switch node.Value { - case command.Arg: - // build arg's key - keyValue := strings.Split(node.Next.Value, "=") - key = keyValue[0] - - // build arg's value - if buildArgs[key] != nil { - value = *buildArgs[key] - } else if len(keyValue) > 1 { - value = keyValue[1] + case command.Add, command.Copy: + files, err := processCopy(node, envs) + if err != nil { + return nil, err } - default: - if key != "" { - // replace $key with value - for curr := node; curr != nil; curr = curr.Next { - curr.Value = util.Expand(curr.Value, key, value) - } + + if len(files) > 0 { + copied = append(copied, files) } + case command.Env: + envs[node.Next.Value] = node.Next.Next.Value } } + + return copied, nil } func readDockerfile(workspace, absDockerfilePath string, buildArgs map[string]*string) ([]string, error) { @@ -104,60 +195,20 @@ func readDockerfile(workspace, absDockerfilePath string, buildArgs map[string]*s expandBuildArgs(res.AST.Children, buildArgs) - // Then process onbuilds, if present. - onbuildsImages := [][]string{} - stages := map[string]bool{} - for _, value := range res.AST.Children { - switch value.Value { - case command.From: - imageName := value.Next.Value - if _, found := stages[imageName]; found { - continue - } - - next := value.Next.Next - if next != nil && strings.ToLower(next.Value) == "as" { - if next.Next != nil { - stages[next.Next.Value] = true - } - } - - onbuilds, err := processBaseImage(imageName) - if err != nil { - logrus.Warnf("Error processing base image for onbuild triggers: %s. Dependencies may be incomplete.", err) - } - onbuildsImages = append(onbuildsImages, onbuilds) - } + instructions, err := onbuildInstructions(res.AST.Children) + if err != nil { + return nil, errors.Wrap(err, "listing ONBUILD instructions") } - var copied [][]string - envs := map[string]string{} - - var dispatchInstructions = func(r *parser.Result) { - for _, value := range r.AST.Children { - switch value.Value { - case command.Add, command.Copy: - files, _ := processCopy(value, envs) - if len(files) > 0 { - copied = append(copied, files) - } - case command.Env: - envs[value.Next.Value] = value.Next.Next.Value - } - } - } - for _, image := range onbuildsImages { - for _, ob := range image { - obRes, err := parser.Parse(strings.NewReader(ob)) - if err != nil { - return nil, err - } - dispatchInstructions(obRes) - } + copied, err := copiedFiles(append(instructions, res.AST.Children...)) + if err != nil { + return nil, errors.Wrap(err, "listing copied files") } - dispatchInstructions(res) + return expandPaths(workspace, copied) +} +func expandPaths(workspace string, copied [][]string) ([]string, error) { expandedPaths := make(map[string]bool) for _, files := range copied { matchesOne := false @@ -205,7 +256,7 @@ func readDockerfile(workspace, absDockerfilePath string, buildArgs map[string]*s // GetDependencies finds the sources dependencies for the given docker artifact. // All paths are relative to the workspace. -func GetDependencies(workspace string, a *v1alpha3.DockerArtifact) ([]string, error) { +func GetDependencies(workspace string, a *latest.DockerArtifact) ([]string, error) { absDockerfilePath, err := NormalizeDockerfilePath(workspace, a.DockerfilePath) if err != nil { return nil, errors.Wrap(err, "normalizing dockerfile path") @@ -307,21 +358,6 @@ func GetDependencies(workspace string, a *v1alpha3.DockerArtifact) ([]string, er return dependencies, nil } -func processBaseImage(baseImageName string) ([]string, error) { - if strings.ToLower(baseImageName) == "scratch" { - return nil, nil - } - - logrus.Debugf("Checking base image %s for ONBUILD triggers.", baseImageName) - img, err := RetrieveImage(baseImageName) - if err != nil { - return nil, err - } - - logrus.Debugf("Found onbuild triggers %v in image %s", img.Config.OnBuild, baseImageName) - return img.Config.OnBuild, nil -} - var imageCache sync.Map func retrieveImage(image string) (*v1.ConfigFile, error) { diff --git a/pkg/skaffold/docker/parse_test.go b/pkg/skaffold/docker/parse_test.go index 2b4799b3a0c..d47a24b577d 100644 --- a/pkg/skaffold/docker/parse_test.go +++ b/pkg/skaffold/docker/parse_test.go @@ -21,7 +21,7 @@ import ( "path/filepath" "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/google/go-containerregistry/pkg/v1" @@ -116,7 +116,7 @@ ARG FOO COPY $FOO . ` -const copyServerGoBuildArgSamePrefix = ` +const copyWorkerGoBuildArgSamePrefix = ` FROM ubuntu:14.04 ARG FOO=server.go ARG FOO2 @@ -141,16 +141,31 @@ ARG FOO=server.go COPY $FOO . ` -const copyServerGoBuildArgRedefinedDefaultValue = ` +const copyWorkerGoBuildArgRedefinedDefaultValue = ` FROM ubuntu:14.04 ARG FOO=server.go ARG FOO=worker.go COPY $FOO . ` +const copyServerGoBuildArgsAtTheTop = ` +FROM ubuntu:14.04 +ARG FOO=server.go +ARG FOO2=ignored +ARG FOO3=ignored +COPY $FOO . +` + const fromStage = ` FROM ubuntu:14.04 as base FROM base as dist +FROM dist as prod +` + +const fromStageIgnoreCase = ` +FROM ubuntu:14.04 as BASE +FROM base as dist +FROM DIST as prod ` type fakeImageFetcher struct { @@ -320,7 +335,7 @@ func TestGetDependencies(t *testing.T) { }, { description: "build args with same prefix", - dockerfile: copyServerGoBuildArgSamePrefix, + dockerfile: copyWorkerGoBuildArgSamePrefix, workspace: ".", buildArgs: map[string]*string{"FOO2": util.StringPtr("worker.go")}, expected: []string{"Dockerfile", "worker.go"}, @@ -351,11 +366,18 @@ func TestGetDependencies(t *testing.T) { }, { description: "build args with redefined default value", - dockerfile: copyServerGoBuildArgRedefinedDefaultValue, + dockerfile: copyWorkerGoBuildArgRedefinedDefaultValue, workspace: ".", expected: []string{"Dockerfile", "worker.go"}, fetched: []string{"ubuntu:14.04"}, }, + { + description: "build args all defined a the top", + dockerfile: copyServerGoBuildArgsAtTheTop, + workspace: ".", + expected: []string{"Dockerfile", "server.go"}, + fetched: []string{"ubuntu:14.04"}, + }, { description: "override default build arg", dockerfile: copyServerGoBuildArgDefaultValue, @@ -377,7 +399,14 @@ func TestGetDependencies(t *testing.T) { dockerfile: fromStage, workspace: ".", expected: []string{"Dockerfile"}, - fetched: []string{"ubuntu:14.04"}, // Don't fetch `base` + fetched: []string{"ubuntu:14.04"}, + }, + { + description: "from base stage, ignoring case", + dockerfile: fromStageIgnoreCase, + workspace: ".", + expected: []string{"Dockerfile"}, + fetched: []string{"ubuntu:14.04"}, }, } @@ -403,7 +432,7 @@ func TestGetDependencies(t *testing.T) { } workspace := tmpDir.Path(test.workspace) - deps, err := GetDependencies(workspace, &v1alpha3.DockerArtifact{ + deps, err := GetDependencies(workspace, &latest.DockerArtifact{ BuildArgs: test.buildArgs, DockerfilePath: "Dockerfile", }) diff --git a/pkg/skaffold/jib/jib_gradle.go b/pkg/skaffold/jib/jib_gradle.go index 518bed6c1bb..9cd94452965 100644 --- a/pkg/skaffold/jib/jib_gradle.go +++ b/pkg/skaffold/jib/jib_gradle.go @@ -19,13 +19,13 @@ package jib import ( "os/exec" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) // GetDependenciesGradle finds the source dependencies for the given jib-gradle artifact. // All paths are absolute. -func GetDependenciesGradle(workspace string, a *v1alpha3.JibGradleArtifact) ([]string, error) { +func GetDependenciesGradle(workspace string, a *latest.JibGradleArtifact) ([]string, error) { cmd := getCommandGradle(workspace, a) deps, err := getDependencies(cmd) if err != nil { @@ -34,6 +34,6 @@ func GetDependenciesGradle(workspace string, a *v1alpha3.JibGradleArtifact) ([]s return deps, nil } -func getCommandGradle(workspace string, _ /* a */ *v1alpha3.JibGradleArtifact) *exec.Cmd { +func getCommandGradle(workspace string, _ /* a */ *latest.JibGradleArtifact) *exec.Cmd { return getCommand(workspace, "gradle", getWrapperGradle(), []string{"_jibSkaffoldFiles", "-q"}) } diff --git a/pkg/skaffold/jib/jib_gradle_test.go b/pkg/skaffold/jib/jib_gradle_test.go index 368938081fb..ff538d06936 100644 --- a/pkg/skaffold/jib/jib_gradle_test.go +++ b/pkg/skaffold/jib/jib_gradle_test.go @@ -21,7 +21,7 @@ import ( "strings" "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/pkg/errors" @@ -52,12 +52,12 @@ func TestGetDependenciesGradle(t *testing.T) { defer func(c util.Command) { util.DefaultExecCommand = c }(util.DefaultExecCommand) util.DefaultExecCommand = testutil.NewFakeCmdOut( - strings.Join(getCommandGradle(tmpDir.Root(), &v1alpha3.JibGradleArtifact{}).Args, " "), + strings.Join(getCommandGradle(tmpDir.Root(), &latest.JibGradleArtifact{}).Args, " "), test.stdout, test.err, ) - deps, err := GetDependenciesGradle(tmpDir.Root(), &v1alpha3.JibGradleArtifact{}) + deps, err := GetDependenciesGradle(tmpDir.Root(), &latest.JibGradleArtifact{}) if test.err != nil { testutil.CheckErrorAndDeepEqual(t, true, err, "getting jib-gradle dependencies: "+test.err.Error(), err.Error()) } else { @@ -70,13 +70,13 @@ func TestGetDependenciesGradle(t *testing.T) { func TestGetCommandGradle(t *testing.T) { var tests = []struct { description string - jibGradleArtifact v1alpha3.JibGradleArtifact + jibGradleArtifact latest.JibGradleArtifact filesInWorkspace []string expectedCmd func(workspace string) *exec.Cmd }{ { description: "gradle default", - jibGradleArtifact: v1alpha3.JibGradleArtifact{}, + jibGradleArtifact: latest.JibGradleArtifact{}, filesInWorkspace: []string{}, expectedCmd: func(workspace string) *exec.Cmd { return getCommand(workspace, "gradle", "ignored", []string{"_jibSkaffoldFiles", "-q"}) @@ -84,7 +84,7 @@ func TestGetCommandGradle(t *testing.T) { }, { description: "gradle with wrapper", - jibGradleArtifact: v1alpha3.JibGradleArtifact{}, + jibGradleArtifact: latest.JibGradleArtifact{}, filesInWorkspace: []string{getWrapperGradle()}, expectedCmd: func(workspace string) *exec.Cmd { return getCommand(workspace, "ignored", getWrapperGradle(), []string{"_jibSkaffoldFiles", "-q"}) diff --git a/pkg/skaffold/jib/jib_maven.go b/pkg/skaffold/jib/jib_maven.go index 975ced4d75c..4754e9894b1 100644 --- a/pkg/skaffold/jib/jib_maven.go +++ b/pkg/skaffold/jib/jib_maven.go @@ -19,14 +19,14 @@ package jib import ( "os/exec" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/pkg/errors" ) // GetDependenciesMaven finds the source dependencies for the given jib-maven artifact. // All paths are absolute. // TODO(coollog): Add support for multi-module projects. -func GetDependenciesMaven(workspace string, a *v1alpha3.JibMavenArtifact) ([]string, error) { +func GetDependenciesMaven(workspace string, a *latest.JibMavenArtifact) ([]string, error) { deps, err := getDependencies(getCommandMaven(workspace, a)) if err != nil { return nil, errors.Wrapf(err, "getting jib-maven dependencies") @@ -34,7 +34,7 @@ func GetDependenciesMaven(workspace string, a *v1alpha3.JibMavenArtifact) ([]str return deps, nil } -func getCommandMaven(workspace string, a *v1alpha3.JibMavenArtifact) *exec.Cmd { +func getCommandMaven(workspace string, a *latest.JibMavenArtifact) *exec.Cmd { args := []string{"jib:_skaffold-files", "-q"} if a.Profile != "" { args = append(args, "-P", a.Profile) diff --git a/pkg/skaffold/jib/jib_maven_test.go b/pkg/skaffold/jib/jib_maven_test.go index 18baa55c4f9..92dece4e413 100644 --- a/pkg/skaffold/jib/jib_maven_test.go +++ b/pkg/skaffold/jib/jib_maven_test.go @@ -21,7 +21,7 @@ import ( "strings" "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" "github.com/pkg/errors" @@ -52,12 +52,12 @@ func TestGetDependenciesMaven(t *testing.T) { defer func(c util.Command) { util.DefaultExecCommand = c }(util.DefaultExecCommand) util.DefaultExecCommand = testutil.NewFakeCmdOut( - strings.Join(getCommandMaven(tmpDir.Root(), &v1alpha3.JibMavenArtifact{}).Args, " "), + strings.Join(getCommandMaven(tmpDir.Root(), &latest.JibMavenArtifact{}).Args, " "), test.stdout, test.err, ) - deps, err := GetDependenciesMaven(tmpDir.Root(), &v1alpha3.JibMavenArtifact{}) + deps, err := GetDependenciesMaven(tmpDir.Root(), &latest.JibMavenArtifact{}) if test.err != nil { testutil.CheckErrorAndDeepEqual(t, true, err, "getting jib-maven dependencies: "+test.err.Error(), err.Error()) } else { @@ -70,13 +70,13 @@ func TestGetDependenciesMaven(t *testing.T) { func TestGetCommandMaven(t *testing.T) { var tests = []struct { description string - jibMavenArtifact v1alpha3.JibMavenArtifact + jibMavenArtifact latest.JibMavenArtifact filesInWorkspace []string expectedCmd func(workspace string) *exec.Cmd }{ { description: "maven no profile", - jibMavenArtifact: v1alpha3.JibMavenArtifact{}, + jibMavenArtifact: latest.JibMavenArtifact{}, filesInWorkspace: []string{}, expectedCmd: func(workspace string) *exec.Cmd { return getCommand(workspace, "mvn", "ignored", []string{"jib:_skaffold-files", "-q"}) @@ -84,7 +84,7 @@ func TestGetCommandMaven(t *testing.T) { }, { description: "maven with profile", - jibMavenArtifact: v1alpha3.JibMavenArtifact{Profile: "profile"}, + jibMavenArtifact: latest.JibMavenArtifact{Profile: "profile"}, filesInWorkspace: []string{}, expectedCmd: func(workspace string) *exec.Cmd { return getCommand(workspace, "mvn", "ignored", []string{"jib:_skaffold-files", "-q", "-P", "profile"}) @@ -92,7 +92,7 @@ func TestGetCommandMaven(t *testing.T) { }, { description: "maven with wrapper no profile", - jibMavenArtifact: v1alpha3.JibMavenArtifact{}, + jibMavenArtifact: latest.JibMavenArtifact{}, filesInWorkspace: []string{getWrapperMaven()}, expectedCmd: func(workspace string) *exec.Cmd { return getCommand(workspace, "ignored", getWrapperMaven(), []string{"jib:_skaffold-files", "-q"}) @@ -100,7 +100,7 @@ func TestGetCommandMaven(t *testing.T) { }, { description: "maven with wrapper and profile", - jibMavenArtifact: v1alpha3.JibMavenArtifact{Profile: "profile"}, + jibMavenArtifact: latest.JibMavenArtifact{Profile: "profile"}, filesInWorkspace: []string{getWrapperMaven()}, expectedCmd: func(workspace string) *exec.Cmd { return getCommand(workspace, "ignored", getWrapperMaven(), []string{"jib:_skaffold-files", "-q", "-P", "profile"}) diff --git a/pkg/skaffold/kubernetes/colorpicker.go b/pkg/skaffold/kubernetes/colorpicker.go index a212b648406..490d841d059 100644 --- a/pkg/skaffold/kubernetes/colorpicker.go +++ b/pkg/skaffold/kubernetes/colorpicker.go @@ -20,7 +20,7 @@ import ( "strings" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" v1 "k8s.io/api/core/v1" ) @@ -53,7 +53,7 @@ type colorPicker struct { // sequentially from `colorCodes`. If all colors are used, the first color will be used // again. The formatter for the associated color will then be returned by `Pick` each // time it is called for the artifact and can be used to write to out in that color. -func NewColorPicker(artifacts []*v1alpha3.Artifact) ColorPicker { +func NewColorPicker(artifacts []*latest.Artifact) ColorPicker { c := colorPicker{imageColors: map[string]color.Color{}} for i, artifact := range artifacts { c.imageColors[artifact.ImageName] = colorCodes[i%len(colorCodes)] diff --git a/pkg/skaffold/kubernetes/colorpicker_test.go b/pkg/skaffold/kubernetes/colorpicker_test.go index 264b413fdba..7df6b8265d7 100644 --- a/pkg/skaffold/kubernetes/colorpicker_test.go +++ b/pkg/skaffold/kubernetes/colorpicker_test.go @@ -20,7 +20,7 @@ import ( "testing" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" v1 "k8s.io/api/core/v1" ) @@ -70,7 +70,7 @@ func TestColorPicker(t *testing.T) { }, } - picker := NewColorPicker([]*v1alpha3.Artifact{ + picker := NewColorPicker([]*latest.Artifact{ {ImageName: "image"}, {ImageName: "second"}, }) diff --git a/pkg/skaffold/runner/changes.go b/pkg/skaffold/runner/changes.go index 4a02496bd82..e33137564fd 100644 --- a/pkg/skaffold/runner/changes.go +++ b/pkg/skaffold/runner/changes.go @@ -17,16 +17,16 @@ limitations under the License. package runner import ( - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" ) type changes struct { - dirtyArtifacts []*v1alpha3.Artifact + dirtyArtifacts []*latest.Artifact needsRedeploy bool needsReload bool } -func (c *changes) Add(a *v1alpha3.Artifact) { +func (c *changes) Add(a *latest.Artifact) { c.dirtyArtifacts = append(c.dirtyArtifacts, a) } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 69231ce96b7..0efdad981e2 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -38,7 +38,8 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/jib" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -52,6 +53,7 @@ var ErrorConfigurationChanged = errors.New("configuration changed") type SkaffoldRunner struct { build.Builder deploy.Deployer + test.Tester tag.Tagger opts *config.SkaffoldOptions @@ -60,7 +62,7 @@ type SkaffoldRunner struct { } // NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig -func NewForConfig(opts *config.SkaffoldOptions, cfg *config.SkaffoldConfig) (*SkaffoldRunner, error) { +func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*SkaffoldRunner, error) { kubeContext, err := kubectx.CurrentContext() if err != nil { return nil, errors.Wrap(err, "getting current cluster context") @@ -77,19 +79,25 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *config.SkaffoldConfig) (*Sk return nil, errors.Wrap(err, "parsing skaffold build config") } + tester, err := getTester(&cfg.Test) + if err != nil { + return nil, errors.Wrap(err, "parsing skaffold test config") + } + deployer, err := getDeployer(&cfg.Deploy, kubeContext, opts.Namespace) if err != nil { return nil, errors.Wrap(err, "parsing skaffold deploy config") } deployer = deploy.WithLabels(deployer, opts, builder, deployer, tagger) - builder, deployer = WithTimings(builder, deployer) + builder, tester, deployer = WithTimings(builder, tester, deployer) if opts.Notification { deployer = WithNotification(deployer) } return &SkaffoldRunner{ Builder: builder, + Tester: tester, Deployer: deployer, Tagger: tagger, opts: opts, @@ -97,7 +105,7 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *config.SkaffoldConfig) (*Sk }, nil } -func getBuilder(cfg *v1alpha3.BuildConfig, kubeContext string) (build.Builder, error) { +func getBuilder(cfg *latest.BuildConfig, kubeContext string) (build.Builder, error) { switch { case cfg.LocalBuild != nil: logrus.Debugf("Using builder: local") @@ -116,7 +124,11 @@ func getBuilder(cfg *v1alpha3.BuildConfig, kubeContext string) (build.Builder, e } } -func getDeployer(cfg *v1alpha3.DeployConfig, kubeContext string, namespace string) (deploy.Deployer, error) { +func getTester(cfg *[]latest.TestCase) (test.Tester, error) { + return test.NewTester(cfg) +} + +func getDeployer(cfg *latest.DeployConfig, kubeContext string, namespace string) (deploy.Deployer, error) { deployers := []deploy.Deployer{} // HelmDeploy first, in case there are resources in Kubectl that depend on these... @@ -148,7 +160,7 @@ func getDeployer(cfg *v1alpha3.DeployConfig, kubeContext string, namespace strin return deploy.NewMultiDeployer(deployers), nil } -func getTagger(t v1alpha3.TagPolicy, customTag string) (tag.Tagger, error) { +func getTagger(t latest.TagPolicy, customTag string) (tag.Tagger, error) { switch { case customTag != "": return &tag.CustomTag{ @@ -172,13 +184,17 @@ func getTagger(t v1alpha3.TagPolicy, customTag string) (tag.Tagger, error) { } } -// Run builds artifacts and then deploys them. -func (r *SkaffoldRunner) Run(ctx context.Context, out io.Writer, artifacts []*v1alpha3.Artifact) error { +// Run builds artifacts, runs tests on built artifacts, and then deploys them. +func (r *SkaffoldRunner) Run(ctx context.Context, out io.Writer, artifacts []*latest.Artifact) error { bRes, err := r.Build(ctx, out, r.Tagger, artifacts) if err != nil { return errors.Wrap(err, "build step") } + if err = r.Test(out, bRes); err != nil { + return errors.Wrap(err, "test step") + } + _, err = r.Deploy(ctx, out, bRes) if err != nil { return errors.Wrap(err, "deploy step") @@ -188,7 +204,7 @@ func (r *SkaffoldRunner) Run(ctx context.Context, out io.Writer, artifacts []*v1 } // TailLogs prints the logs for deployed artifacts. -func (r *SkaffoldRunner) TailLogs(ctx context.Context, out io.Writer, artifacts []*v1alpha3.Artifact, bRes []build.Artifact) error { +func (r *SkaffoldRunner) TailLogs(ctx context.Context, out io.Writer, artifacts []*latest.Artifact, bRes []build.Artifact) error { if !r.opts.Tail { return nil } @@ -210,7 +226,7 @@ func (r *SkaffoldRunner) TailLogs(ctx context.Context, out io.Writer, artifacts // Dev watches for changes and runs the skaffold build and deploy // pipeline until interrrupted by the user. -func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*v1alpha3.Artifact) ([]build.Artifact, error) { +func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*latest.Artifact) ([]build.Artifact, error) { imageList := kubernetes.NewImageList() colorPicker := kubernetes.NewColorPicker(artifacts) logger := kubernetes.NewLogAggregator(out, imageList, colorPicker) @@ -242,12 +258,20 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*v1 } r.updateBuiltImages(imageList, bRes) + if err := r.Test(out, bRes); err != nil { + logrus.Warnln("Skipping Deploy due to failed tests:", err) + return nil + } if _, err = r.Deploy(ctx, out, r.builds); err != nil { logrus.Warnln("Skipping Deploy due to error:", err) return nil } case changed.needsRedeploy: + if err := r.Test(out, r.builds); err != nil { + logrus.Warnln("Skipping Deploy due to failed tests:", err) + return nil + } if _, err := r.Deploy(ctx, out, r.builds); err != nil { logrus.Warnln("Skipping Deploy due to error:", err) return nil @@ -270,16 +294,24 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*v1 if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func() { changed.Add(artifact) }, + func(watch.Events) { changed.Add(artifact) }, ); err != nil { return nil, errors.Wrapf(err, "watching files for artifact %s", artifact.ImageName) } } + // Watch test configuration + if err := watcher.Register( + func() ([]string, error) { return r.TestDependencies(), nil }, + func(watch.Events) { changed.needsRedeploy = true }, + ); err != nil { + return nil, errors.Wrap(err, "watching test files") + } + // Watch deployment configuration if err := watcher.Register( func() ([]string, error) { return r.Dependencies() }, - func() { changed.needsRedeploy = true }, + func(watch.Events) { changed.needsRedeploy = true }, ); err != nil { return nil, errors.Wrap(err, "watching files for deployer") } @@ -287,7 +319,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*v1 // Watch Skaffold configuration if err := watcher.Register( func() ([]string, error) { return []string{r.opts.ConfigurationFile}, nil }, - func() { changed.needsReload = true }, + func(watch.Events) { changed.needsReload = true }, ); err != nil { return nil, errors.Wrapf(err, "watching skaffold configuration %s", r.opts.ConfigurationFile) } @@ -299,6 +331,9 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*v1 } r.updateBuiltImages(imageList, bRes) + if err := r.Test(out, bRes); err != nil { + return nil, errors.Wrap(err, "exiting dev mode because the first test run failed") + } _, err = r.Deploy(ctx, out, r.builds) if err != nil { @@ -322,7 +357,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*v1 return nil, watcher.Run(ctx, pollInterval, onChange) } -func (r *SkaffoldRunner) shouldWatch(artifact *v1alpha3.Artifact) bool { +func (r *SkaffoldRunner) shouldWatch(artifact *latest.Artifact) bool { if len(r.opts.Watch) == 0 { return true } @@ -364,7 +399,7 @@ func mergeWithPreviousBuilds(builds, previous []build.Artifact) []build.Artifact return merged } -func dependenciesForArtifact(a *v1alpha3.Artifact) ([]string, error) { +func dependenciesForArtifact(a *latest.Artifact) ([]string, error) { var ( paths []string err error diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 472546088b1..2260d97e414 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -30,7 +30,8 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" clientgo "k8s.io/client-go/kubernetes" @@ -46,7 +47,7 @@ func (t *TestBuilder) Labels() map[string]string { return map[string]string{} } -func (t *TestBuilder) Build(ctx context.Context, w io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact) ([]build.Artifact, error) { +func (t *TestBuilder) Build(ctx context.Context, w io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { if len(t.errors) > 0 { err := t.errors[0] t.errors = t.errors[1:] @@ -65,6 +66,23 @@ func (t *TestBuilder) Build(ctx context.Context, w io.Writer, tagger tag.Tagger, return builds, nil } +type TestTester struct { + errors []error +} + +func (t *TestTester) Test(out io.Writer, builds []build.Artifact) error { + if len(t.errors) > 0 { + err := t.errors[0] + t.errors = t.errors[1:] + return err + } + return nil +} + +func (t *TestTester) TestDependencies() []string { + return []string{} +} + type TestDeployer struct { deployed []build.Artifact errors []error @@ -98,28 +116,36 @@ func fakeGetClient() (clientgo.Interface, error) { return fake.NewSimpleClientse type TestWatcher struct { changedArtifacts [][]int - changeCallbacks []func() + changeCallbacks []func(watch.Events) + events []watch.Events err error } -func NewWatcherFactory(err error, changedArtifacts ...[]int) watch.Factory { +func NewWatcherFactory(err error, events []watch.Events, changedArtifacts ...[]int) watch.Factory { return func() watch.Watcher { return &TestWatcher{ changedArtifacts: changedArtifacts, + events: events, err: err, } } } -func (t *TestWatcher) Register(deps func() ([]string, error), onChange func()) error { +func (t *TestWatcher) Register(deps func() ([]string, error), onChange func(watch.Events)) error { t.changeCallbacks = append(t.changeCallbacks, onChange) return nil } func (t *TestWatcher) Run(ctx context.Context, pollInterval time.Duration, onChange func() error) error { + evts := watch.Events{} + if t.events != nil { + evts = t.events[0] + t.events = t.events[1:] + } + for _, artifactIndices := range t.changedArtifacts { for _, artifactIndex := range artifactIndices { - t.changeCallbacks[artifactIndex]() + t.changeCallbacks[artifactIndex](evts) } onChange() } @@ -129,41 +155,43 @@ func (t *TestWatcher) Run(ctx context.Context, pollInterval time.Duration, onCha func TestNewForConfig(t *testing.T) { var tests = []struct { description string - config *v1alpha3.SkaffoldConfig + config *latest.SkaffoldConfig shouldErr bool expectedBuilder build.Builder + expectedTester test.Tester expectedDeployer deploy.Deployer }{ { description: "local builder config", - config: &config.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{ - TagPolicy: v1alpha3.TagPolicy{ShaTagger: &v1alpha3.ShaTagger{}}, - BuildType: v1alpha3.BuildType{ - LocalBuild: &v1alpha3.LocalBuild{}, + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, + BuildType: latest.BuildType{ + LocalBuild: &latest.LocalBuild{}, }, }, - Deploy: v1alpha3.DeployConfig{ - DeployType: v1alpha3.DeployType{ - KubectlDeploy: &v1alpha3.KubectlDeploy{}, + Deploy: latest.DeployConfig{ + DeployType: latest.DeployType{ + KubectlDeploy: &latest.KubectlDeploy{}, }, }, }, expectedBuilder: &local.Builder{}, + expectedTester: &test.FullTester{}, expectedDeployer: &deploy.KubectlDeployer{}, }, { description: "bad tagger config", - config: &v1alpha3.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{ - TagPolicy: v1alpha3.TagPolicy{}, - BuildType: v1alpha3.BuildType{ - LocalBuild: &v1alpha3.LocalBuild{}, + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + TagPolicy: latest.TagPolicy{}, + BuildType: latest.BuildType{ + LocalBuild: &latest.LocalBuild{}, }, }, - Deploy: v1alpha3.DeployConfig{ - DeployType: v1alpha3.DeployType{ - KubectlDeploy: &v1alpha3.KubectlDeploy{}, + Deploy: latest.DeployConfig{ + DeployType: latest.DeployType{ + KubectlDeploy: &latest.KubectlDeploy{}, }, }, }, @@ -171,33 +199,35 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown builder", - config: &v1alpha3.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{}, + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{}, }, shouldErr: true, expectedBuilder: &local.Builder{}, + expectedTester: &test.FullTester{}, expectedDeployer: &deploy.KubectlDeployer{}, }, { description: "unknown tagger", - config: &config.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{ - TagPolicy: v1alpha3.TagPolicy{}, - BuildType: v1alpha3.BuildType{ - LocalBuild: &v1alpha3.LocalBuild{}, + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + TagPolicy: latest.TagPolicy{}, + BuildType: latest.BuildType{ + LocalBuild: &latest.LocalBuild{}, }, }}, shouldErr: true, expectedBuilder: &local.Builder{}, + expectedTester: &test.FullTester{}, expectedDeployer: &deploy.KubectlDeployer{}, }, { description: "unknown deployer", - config: &config.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{ - TagPolicy: v1alpha3.TagPolicy{ShaTagger: &v1alpha3.ShaTagger{}}, - BuildType: v1alpha3.BuildType{ - LocalBuild: &v1alpha3.LocalBuild{}, + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, + BuildType: latest.BuildType{ + LocalBuild: &latest.LocalBuild{}, }, }, }, @@ -210,9 +240,10 @@ func TestNewForConfig(t *testing.T) { testutil.CheckError(t, test.shouldErr, err) if cfg != nil { - b, d := WithTimings(test.expectedBuilder, test.expectedDeployer) + b, _t, d := WithTimings(test.expectedBuilder, test.expectedTester, test.expectedDeployer) testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, b, cfg.Builder) + testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, _t, cfg.Tester) testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, d, cfg.Deployer) } }) @@ -222,30 +253,33 @@ func TestNewForConfig(t *testing.T) { func TestRun(t *testing.T) { var tests = []struct { description string - config *config.SkaffoldConfig + config *latest.SkaffoldConfig builder build.Builder + tester test.Tester deployer deploy.Deployer shouldErr bool }{ { description: "run no error", - config: &v1alpha3.SkaffoldConfig{}, + config: &latest.SkaffoldConfig{}, builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{}, }, { description: "run build error", - config: &v1alpha3.SkaffoldConfig{}, + config: &latest.SkaffoldConfig{}, builder: &TestBuilder{ errors: []error{fmt.Errorf("")}, }, + tester: &TestTester{}, shouldErr: true, }, { description: "run deploy error", - config: &v1alpha3.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{ - Artifacts: []*v1alpha3.Artifact{ + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ { ImageName: "test", }, @@ -253,17 +287,42 @@ func TestRun(t *testing.T) { }, }, builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{fmt.Errorf("")}, }, shouldErr: true, }, + { + description: "run test error", + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + { + ImageName: "test", + }, + }, + }, + Test: []latest.TestCase{ + { + ImageName: "test", + StructureTests: []string{"fake_file.yaml"}, + }, + }, + }, + builder: &TestBuilder{}, + tester: &TestTester{ + errors: []error{fmt.Errorf("")}, + }, + shouldErr: true, + }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { runner := &SkaffoldRunner{ Builder: test.builder, + Tester: test.tester, Deployer: test.deployer, Tagger: &tag.ChecksumTagger{}, opts: &config.SkaffoldOptions{}, @@ -282,6 +341,7 @@ func TestDev(t *testing.T) { var tests = []struct { description string builder build.Builder + tester test.Tester deployer deploy.Deployer watcherFactory watch.Factory shouldErr bool @@ -292,16 +352,26 @@ func TestDev(t *testing.T) { errors: []error{fmt.Errorf("")}, }, deployer: &TestDeployer{}, - watcherFactory: NewWatcherFactory(nil), + watcherFactory: NewWatcherFactory(nil, nil), shouldErr: true, }, { description: "fails to deploy the first time", builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{fmt.Errorf("")}, }, - watcherFactory: NewWatcherFactory(nil), + watcherFactory: NewWatcherFactory(nil, nil), + shouldErr: true, + }, + { + description: "fails to deploy due to failed tests", + builder: &TestBuilder{}, + tester: &TestTester{ + errors: []error{fmt.Errorf("")}, + }, + watcherFactory: NewWatcherFactory(nil, nil), shouldErr: true, }, { @@ -309,22 +379,25 @@ func TestDev(t *testing.T) { builder: &TestBuilder{ errors: []error{nil, fmt.Errorf("")}, }, + tester: &TestTester{}, deployer: &TestDeployer{}, - watcherFactory: NewWatcherFactory(nil, nil), + watcherFactory: NewWatcherFactory(nil, nil, nil), }, { description: "ignore subsequent deploy errors", builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{nil, fmt.Errorf("")}, }, - watcherFactory: NewWatcherFactory(nil, nil), + watcherFactory: NewWatcherFactory(nil, nil, nil), }, { description: "fail to watch files", builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{}, - watcherFactory: NewWatcherFactory(fmt.Errorf("")), + watcherFactory: NewWatcherFactory(fmt.Errorf(""), nil), shouldErr: true, }, } @@ -333,6 +406,7 @@ func TestDev(t *testing.T) { t.Run(test.description, func(t *testing.T) { runner := &SkaffoldRunner{ Builder: test.builder, + Tester: test.tester, Deployer: test.deployer, Tagger: &tag.ChecksumTagger{}, watchFactory: test.watcherFactory, @@ -352,14 +426,16 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { defer resetClient() builder := &TestBuilder{} + tester := &TestTester{} deployer := &TestDeployer{} - artifacts := []*v1alpha3.Artifact{ + artifacts := []*latest.Artifact{ {ImageName: "image1"}, {ImageName: "image2"}, } runner := &SkaffoldRunner{ Builder: builder, + Tester: tester, Deployer: deployer, opts: &config.SkaffoldOptions{}, } @@ -367,7 +443,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { ctx := context.Background() // Both artifacts are changed - runner.watchFactory = NewWatcherFactory(nil, []int{0, 1}) + runner.watchFactory = NewWatcherFactory(nil, nil, []int{0, 1}) _, err := runner.Dev(ctx, ioutil.Discard, artifacts) if err != nil { @@ -381,7 +457,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { } // Only one is changed - runner.watchFactory = NewWatcherFactory(nil, []int{1}) + runner.watchFactory = NewWatcherFactory(nil, nil, []int{1}) _, err = runner.Dev(ctx, ioutil.Discard, artifacts) if err != nil { @@ -436,7 +512,7 @@ func TestShouldWatch(t *testing.T) { }, } - match := runner.shouldWatch(&v1alpha3.Artifact{ + match := runner.shouldWatch(&latest.Artifact{ ImageName: "domain/image", }) diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go index 51cc589f1ae..0bf13b0b000 100644 --- a/pkg/skaffold/runner/timings.go +++ b/pkg/skaffold/runner/timings.go @@ -25,25 +25,28 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" ) // WithTimings creates a deployer that logs the duration of each phase. -func WithTimings(b build.Builder, d deploy.Deployer) (build.Builder, deploy.Deployer) { +func WithTimings(b build.Builder, t test.Tester, d deploy.Deployer) (build.Builder, test.Tester, deploy.Deployer) { w := withTimings{ Builder: b, + Tester: t, Deployer: d, } - return w, w + return w, w, w } type withTimings struct { build.Builder + test.Tester deploy.Deployer } -func (w withTimings) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha3.Artifact) ([]build.Artifact, error) { +func (w withTimings) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { start := time.Now() color.Default.Fprintln(out, "Starting build...") @@ -54,6 +57,17 @@ func (w withTimings) Build(ctx context.Context, out io.Writer, tagger tag.Tagger return bRes, err } +func (w withTimings) Test(out io.Writer, builds []build.Artifact) error { + start := time.Now() + color.Default.Fprintln(out, "Starting test...") + + err := w.Tester.Test(out, builds) + if err == nil { + color.Default.Fprintln(out, "Test complete in", time.Since(start)) + } + return err +} + func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact) ([]deploy.Artifact, error) { start := time.Now() color.Default.Fprintln(out, "Starting deploy...") diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go new file mode 100644 index 00000000000..6b6f34da30c --- /dev/null +++ b/pkg/skaffold/schema/latest/config.go @@ -0,0 +1,272 @@ +/* +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 latest + +import ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" +) + +const Version string = "skaffold/v1alpha4" + +// NewSkaffoldConfig creates a SkaffoldConfig +func NewSkaffoldConfig() util.VersionedConfig { + return new(SkaffoldConfig) +} + +type SkaffoldConfig struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + + Build BuildConfig `yaml:"build,omitempty"` + Test []TestCase `yaml:"test,omitempty"` + Deploy DeployConfig `yaml:"deploy,omitempty"` + Profiles []Profile `yaml:"profiles,omitempty"` +} + +func (c *SkaffoldConfig) GetVersion() string { + return c.APIVersion +} + +// BuildConfig contains all the configuration for the build steps +type BuildConfig struct { + Artifacts []*Artifact `yaml:"artifacts,omitempty"` + TagPolicy TagPolicy `yaml:"tagPolicy,omitempty"` + BuildType `yaml:",inline"` +} + +// TagPolicy contains all the configuration for the tagging step +type TagPolicy struct { + GitTagger *GitTagger `yaml:"gitCommit,omitempty" yamltags:"oneOf=tag"` + ShaTagger *ShaTagger `yaml:"sha256,omitempty" yamltags:"oneOf=tag"` + EnvTemplateTagger *EnvTemplateTagger `yaml:"envTemplate,omitempty" yamltags:"oneOf=tag"` + DateTimeTagger *DateTimeTagger `yaml:"dateTime,omitempty" yamltags:"oneOf=tag"` +} + +// ShaTagger contains the configuration for the SHA tagger. +type ShaTagger struct{} + +// GitTagger contains the configuration for the git tagger. +type GitTagger struct{} + +// EnvTemplateTagger contains the configuration for the envTemplate tagger. +type EnvTemplateTagger struct { + Template string `yaml:"template"` +} + +// DateTimeTagger contains the configuration for the DateTime tagger. +type DateTimeTagger struct { + Format string `yaml:"format,omitempty"` + TimeZone string `yaml:"timezone,omitempty"` +} + +// BuildType contains the specific implementation and parameters needed +// for the build step. Only one field should be populated. +type BuildType struct { + LocalBuild *LocalBuild `yaml:"local,omitempty" yamltags:"oneOf=build"` + GoogleCloudBuild *GoogleCloudBuild `yaml:"googleCloudBuild,omitempty" yamltags:"oneOf=build"` + KanikoBuild *KanikoBuild `yaml:"kaniko,omitempty" yamltags:"oneOf=build"` +} + +// LocalBuild contains the fields needed to do a build on the local docker daemon +// and optionally push to a repository. +type LocalBuild struct { + SkipPush *bool `yaml:"skipPush,omitempty"` + UseDockerCLI bool `yaml:"useDockerCLI,omitempty"` + UseBuildkit bool `yaml:"useBuildkit,omitempty"` +} + +// GoogleCloudBuild contains the fields needed to do a remote build on +// Google Cloud Build. +type GoogleCloudBuild struct { + ProjectID string `yaml:"projectId"` + DiskSizeGb int64 `yaml:"diskSizeGb,omitempty"` + MachineType string `yaml:"machineType,omitempty"` + Timeout string `yaml:"timeout,omitempty"` + DockerImage string `yaml:"dockerImage,omitempty"` +} + +// KanikoBuildContext contains the different fields available to specify +// a kaniko build context +type KanikoBuildContext struct { + GCSBucket string `yaml:"gcsBucket,omitempty" yamltags:"oneOf=buildContext"` +} + +// KanikoBuild contains the fields needed to do a on-cluster build using +// the kaniko image +type KanikoBuild struct { + BuildContext KanikoBuildContext `yaml:"buildContext,omitempty"` + PullSecret string `yaml:"pullSecret,omitempty"` + PullSecretName string `yaml:"pullSecretName,omitempty"` + Namespace string `yaml:"namespace,omitempty"` + Timeout string `yaml:"timeout,omitempty"` +} + +// TestCase is a struct containing all the specified test +// configuration for an image. +type TestCase struct { + ImageName string `yaml:"image"` + StructureTests []string `yaml:"structureTests,omitempty"` +} + +// DeployConfig contains all the configuration needed by the deploy steps +type DeployConfig struct { + DeployType `yaml:",inline"` +} + +// DeployType contains the specific implementation and parameters needed +// for the deploy step. Only one field should be populated. +type DeployType struct { + HelmDeploy *HelmDeploy `yaml:"helm,omitempty" yamltags:"oneOf=deploy"` + KubectlDeploy *KubectlDeploy `yaml:"kubectl,omitempty" yamltags:"oneOf=deploy"` + KustomizeDeploy *KustomizeDeploy `yaml:"kustomize,omitempty" yamltags:"oneOf=deploy"` +} + +// KubectlDeploy contains the configuration needed for deploying with `kubectl apply` +type KubectlDeploy struct { + Manifests []string `yaml:"manifests,omitempty"` + RemoteManifests []string `yaml:"remoteManifests,omitempty"` + Flags KubectlFlags `yaml:"flags,omitempty"` +} + +// KubectlFlags describes additional options flags that are passed on the command +// line to kubectl either on every command (Global), on creations (Apply) +// or deletions (Delete). +type KubectlFlags struct { + Global []string `yaml:"global,omitempty"` + Apply []string `yaml:"apply,omitempty"` + Delete []string `yaml:"delete,omitempty"` +} + +// HelmDeploy contains the configuration needed for deploying with helm +type HelmDeploy struct { + Releases []HelmRelease `yaml:"releases,omitempty"` +} + +// KustomizeDeploy contains the configuration needed for deploying with kustomize. +type KustomizeDeploy struct { + KustomizePath string `yaml:"path,omitempty"` + Flags KubectlFlags `yaml:"flags,omitempty"` +} + +type HelmRelease struct { + Name string `yaml:"name"` + ChartPath string `yaml:"chartPath"` + ValuesFiles []string `yaml:"valuesFiles"` + Values map[string]string `yaml:"values,omitempty"` + Namespace string `yaml:"namespace"` + Version string `yaml:"version"` + SetValues map[string]string `yaml:"setValues"` + SetValueTemplates map[string]string `yaml:"setValueTemplates"` + Wait bool `yaml:"wait"` + RecreatePods bool `yaml:"recreatePods"` + Overrides map[string]interface{} `yaml:"overrides"` + Packaged *HelmPackaged `yaml:"packaged"` + ImageStrategy HelmImageStrategy `yaml:"imageStrategy"` +} + +// HelmPackaged represents parameters for packaging helm chart. +type HelmPackaged struct { + // Version sets the version on the chart to this semver version. + Version string `yaml:"version"` + + // AppVersion set the appVersion on the chart to this version + AppVersion string `yaml:"appVersion"` +} + +type HelmImageStrategy struct { + HelmImageConfig `yaml:",inline"` +} + +type HelmImageConfig struct { + HelmFQNConfig *HelmFQNConfig `yaml:"fqn"` + HelmConventionConfig *HelmConventionConfig `yaml:"helm"` +} + +// HelmFQNConfig represents image config to use the FullyQualifiedImageName as param to set +type HelmFQNConfig struct { + Property string `yaml:"property"` +} + +// HelmConventionConfig represents image config in the syntax of image.repository and image.tag +type HelmConventionConfig struct { +} + +// Artifact represents items that need to be built, along with the context in which +// they should be built. +type Artifact struct { + ImageName string `yaml:"image"` + Workspace string `yaml:"context,omitempty"` + ArtifactType `yaml:",inline"` +} + +// Profile is additional configuration that overrides default +// configuration when it is activated. +type Profile struct { + Name string `yaml:"name"` + Build BuildConfig `yaml:"build,omitempty"` + Test []TestCase `yaml:"test,omitempty"` + Deploy DeployConfig `yaml:"deploy,omitempty"` +} + +type ArtifactType struct { + DockerArtifact *DockerArtifact `yaml:"docker,omitempty" yamltags:"oneOf=artifact"` + BazelArtifact *BazelArtifact `yaml:"bazel,omitempty" yamltags:"oneOf=artifact"` + JibMavenArtifact *JibMavenArtifact `yaml:"jibMaven,omitempty" yamltags:"oneOf=artifact"` + JibGradleArtifact *JibGradleArtifact `yaml:"jibGradle,omitempty" yamltags:"oneOf=artifact"` +} + +// DockerArtifact describes an artifact built from a Dockerfile, +// usually using `docker build`. +type DockerArtifact struct { + DockerfilePath string `yaml:"dockerfile,omitempty"` + BuildArgs map[string]*string `yaml:"buildArgs,omitempty"` + CacheFrom []string `yaml:"cacheFrom,omitempty"` + Target string `yaml:"target,omitempty"` +} + +// BazelArtifact describes an artifact built with Bazel. +type BazelArtifact struct { + BuildTarget string `yaml:"target"` +} + +type JibMavenArtifact struct { + // Only multi-module + Module string `yaml:"module"` + Profile string `yaml:"profile"` +} + +type JibGradleArtifact struct { + // Only multi-module + Project string `yaml:"project"` +} + +// Parse reads a SkaffoldConfig from yaml. +func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { + if err := yaml.UnmarshalStrict(contents, c); err != nil { + return err + } + + if useDefaults { + if err := c.SetDefaultValues(); err != nil { + return errors.Wrap(err, "applying default values") + } + } + + return nil +} diff --git a/pkg/skaffold/schema/latest/defaults.go b/pkg/skaffold/schema/latest/defaults.go new file mode 100644 index 00000000000..7c9f49f8c1a --- /dev/null +++ b/pkg/skaffold/schema/latest/defaults.go @@ -0,0 +1,195 @@ +/* +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 latest + +import ( + "fmt" + + homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" + kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" +) + +// SetDefaultValues makes sure default values are set. +func (c *SkaffoldConfig) SetDefaultValues() error { + c.defaultToLocalBuild() + c.defaultToKubectlDeploy() + c.setDefaultCloudBuildDockerImage() + c.setDefaultTagger() + c.setDefaultKustomizePath() + c.setDefaultKubectlManifests() + c.setDefaultKanikoTimeout() + if err := c.setDefaultKanikoNamespace(); err != nil { + return err + } + if err := c.setDefaultKanikoSecret(); err != nil { + return err + } + + for _, a := range c.Build.Artifacts { + c.defaultToDockerArtifact(a) + c.setDefaultDockerfile(a) + c.setDefaultWorkspace(a) + } + + return nil +} + +func (c *SkaffoldConfig) defaultToLocalBuild() { + if c.Build.BuildType != (BuildType{}) { + return + } + + logrus.Debugf("Defaulting build type to local build") + c.Build.BuildType.LocalBuild = &LocalBuild{} +} + +func (c *SkaffoldConfig) defaultToKubectlDeploy() { + if c.Deploy.DeployType != (DeployType{}) { + return + } + + logrus.Debugf("Defaulting deploy type to kubectl") + c.Deploy.DeployType.KubectlDeploy = &KubectlDeploy{} +} + +func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { + cloudBuild := c.Build.BuildType.GoogleCloudBuild + if cloudBuild == nil { + return + } + + if cloudBuild.DockerImage == "" { + cloudBuild.DockerImage = constants.DefaultCloudBuildDockerImage + } +} + +func (c *SkaffoldConfig) setDefaultTagger() { + if c.Build.TagPolicy != (TagPolicy{}) { + return + } + + c.Build.TagPolicy = TagPolicy{GitTagger: &GitTagger{}} +} + +func (c *SkaffoldConfig) setDefaultKustomizePath() { + kustomize := c.Deploy.KustomizeDeploy + if kustomize == nil { + return + } + + if kustomize.KustomizePath == "" { + kustomize.KustomizePath = constants.DefaultKustomizationPath + } +} + +func (c *SkaffoldConfig) setDefaultKubectlManifests() { + if c.Deploy.KubectlDeploy != nil && len(c.Deploy.KubectlDeploy.Manifests) == 0 { + c.Deploy.KubectlDeploy.Manifests = constants.DefaultKubectlManifests + } +} + +func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { + if a.ArtifactType == (ArtifactType{}) { + a.ArtifactType = ArtifactType{ + DockerArtifact: &DockerArtifact{}, + } + } +} + +func (c *SkaffoldConfig) setDefaultDockerfile(a *Artifact) { + if a.DockerArtifact != nil && a.DockerArtifact.DockerfilePath == "" { + a.DockerArtifact.DockerfilePath = constants.DefaultDockerfilePath + } +} + +func (c *SkaffoldConfig) setDefaultWorkspace(a *Artifact) { + if a.Workspace == "" { + a.Workspace = "." + } +} + +func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { + kaniko := c.Build.KanikoBuild + if kaniko == nil { + return nil + } + + if kaniko.Namespace == "" { + ns, err := currentNamespace() + if err != nil { + return errors.Wrap(err, "getting current namespace") + } + + kaniko.Namespace = ns + } + + return nil +} + +func (c *SkaffoldConfig) setDefaultKanikoTimeout() { + kaniko := c.Build.KanikoBuild + if kaniko == nil { + return + } + + if kaniko.Timeout == "" { + kaniko.Timeout = constants.DefaultKanikoTimeout + } +} + +func (c *SkaffoldConfig) setDefaultKanikoSecret() error { + kaniko := c.Build.KanikoBuild + if kaniko == nil { + return nil + } + + if kaniko.PullSecretName == "" { + kaniko.PullSecretName = constants.DefaultKanikoSecretName + } + + if kaniko.PullSecret != "" { + absPath, err := homedir.Expand(kaniko.PullSecret) + if err != nil { + return fmt.Errorf("unable to expand pullSecret %s", kaniko.PullSecret) + } + + kaniko.PullSecret = absPath + return nil + } + + return nil +} + +func currentNamespace() (string, error) { + cfg, err := kubectx.CurrentConfig() + if err != nil { + return "", err + } + + current, present := cfg.Contexts[cfg.CurrentContext] + if present { + if current.Namespace != "" { + return current.Namespace, nil + } + } + + return "default", nil +} diff --git a/pkg/skaffold/config/config.go b/pkg/skaffold/schema/latest/upgrade.go similarity index 64% rename from pkg/skaffold/config/config.go rename to pkg/skaffold/schema/latest/upgrade.go index b056662362c..7c4459819d8 100644 --- a/pkg/skaffold/config/config.go +++ b/pkg/skaffold/schema/latest/upgrade.go @@ -14,17 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package config +package latest import ( - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" -) - -// SkaffoldConfig references the most recent skaffold config version -type SkaffoldConfig = v1alpha3.SkaffoldConfig + "errors" -const LatestVersion string = v1alpha3.Version + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" +) -func NewConfig() (*SkaffoldConfig, error) { - return v1alpha3.NewConfig() +// Upgrade upgrades a configuration to the next version. +func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { + return nil, errors.New("not implemented yet") } diff --git a/pkg/skaffold/config/profile_test.go b/pkg/skaffold/schema/profile_test.go similarity index 79% rename from pkg/skaffold/config/profile_test.go rename to pkg/skaffold/schema/profile_test.go index af3471337df..8690e50ecfc 100644 --- a/pkg/skaffold/config/profile_test.go +++ b/pkg/skaffold/schema/profile_test.go @@ -14,21 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package config +package schema import ( "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/testutil" ) func TestApplyProfiles(t *testing.T) { tests := []struct { description string - config *SkaffoldConfig + config *latest.SkaffoldConfig profile string - expected *SkaffoldConfig + expected *latest.SkaffoldConfig shouldErr bool }{ { @@ -47,11 +47,11 @@ func TestApplyProfiles(t *testing.T) { withDockerArtifact("image", ".", "Dockerfile"), ), withKubectlDeploy("k8s/*.yaml"), - withProfiles(v1alpha3.Profile{ + withProfiles(latest.Profile{ Name: "profile", - Build: v1alpha3.BuildConfig{ - BuildType: v1alpha3.BuildType{ - GoogleCloudBuild: &v1alpha3.GoogleCloudBuild{ + Build: latest.BuildConfig{ + BuildType: latest.BuildType{ + GoogleCloudBuild: &latest.GoogleCloudBuild{ ProjectID: "my-project", }, }, @@ -75,10 +75,10 @@ func TestApplyProfiles(t *testing.T) { withDockerArtifact("image", ".", "Dockerfile"), ), withKubectlDeploy("k8s/*.yaml"), - withProfiles(v1alpha3.Profile{ + withProfiles(latest.Profile{ Name: "dev", - Build: v1alpha3.BuildConfig{ - TagPolicy: v1alpha3.TagPolicy{ShaTagger: &v1alpha3.ShaTagger{}}, + Build: latest.BuildConfig{ + TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, }, }), ), @@ -99,10 +99,10 @@ func TestApplyProfiles(t *testing.T) { withDockerArtifact("image", ".", "Dockerfile"), ), withKubectlDeploy("k8s/*.yaml"), - withProfiles(v1alpha3.Profile{ + withProfiles(latest.Profile{ Name: "profile", - Build: v1alpha3.BuildConfig{ - Artifacts: []*v1alpha3.Artifact{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ {ImageName: "image"}, {ImageName: "imageProd"}, }, @@ -126,11 +126,11 @@ func TestApplyProfiles(t *testing.T) { withGitTagger(), ), withKubectlDeploy("k8s/*.yaml"), - withProfiles(v1alpha3.Profile{ + withProfiles(latest.Profile{ Name: "profile", - Deploy: v1alpha3.DeployConfig{ - DeployType: v1alpha3.DeployType{ - HelmDeploy: &v1alpha3.HelmDeploy{}, + Deploy: latest.DeployConfig{ + DeployType: latest.DeployType{ + HelmDeploy: &latest.HelmDeploy{}, }, }, }), @@ -146,7 +146,7 @@ func TestApplyProfiles(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - err := test.config.ApplyProfiles([]string{test.profile}) + err := ApplyProfiles(test.config, []string{test.profile}) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, test.config) }) diff --git a/pkg/skaffold/schema/v1alpha2/profiles.go b/pkg/skaffold/schema/profiles.go similarity index 83% rename from pkg/skaffold/schema/v1alpha2/profiles.go rename to pkg/skaffold/schema/profiles.go index c12167e13f0..fd191393151 100644 --- a/pkg/skaffold/schema/v1alpha2/profiles.go +++ b/pkg/skaffold/schema/profiles.go @@ -14,21 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha2 +package schema import ( "fmt" "reflect" + "strings" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" ) // ApplyProfiles returns configuration modified by the application // of a list of profiles. -func (c *SkaffoldConfig) ApplyProfiles(profiles []string) error { +func ApplyProfiles(c *latest.SkaffoldConfig, profiles []string) error { byName := profilesByName(c.Profiles) for _, name := range profiles { profile, present := byName[name] @@ -38,27 +39,28 @@ func (c *SkaffoldConfig) ApplyProfiles(profiles []string) error { applyProfile(c, profile) } - if err := c.setDefaultValues(); err != nil { + if err := c.SetDefaultValues(); err != nil { return errors.Wrap(err, "applying default values") } return nil } -func applyProfile(config *SkaffoldConfig, profile Profile) { +func applyProfile(config *latest.SkaffoldConfig, profile latest.Profile) { logrus.Infof("applying profile: %s", profile.Name) // this intentionally removes the Profiles field from the returned config - *config = SkaffoldConfig{ + *config = latest.SkaffoldConfig{ APIVersion: config.APIVersion, Kind: config.Kind, - Build: overlayProfileField(config.Build, profile.Build).(BuildConfig), - Deploy: overlayProfileField(config.Deploy, profile.Deploy).(DeployConfig), + Build: overlayProfileField(config.Build, profile.Build).(latest.BuildConfig), + Deploy: overlayProfileField(config.Deploy, profile.Deploy).(latest.DeployConfig), + Test: overlayProfileField(config.Test, profile.Test).([]latest.TestCase), } } -func profilesByName(profiles []Profile) map[string]Profile { - byName := make(map[string]Profile) +func profilesByName(profiles []latest.Profile) map[string]latest.Profile { + byName := make(map[string]latest.Profile) for _, profile := range profiles { byName[profile.Name] = profile } @@ -108,7 +110,7 @@ func overlayProfileField(config interface{}, profile interface{}) interface{} { switch v.Kind() { case reflect.Struct: // check the first field of the struct for a oneOf yamltag. - if util.IsOneOf(t.Field(0)) { + if isOneOf(t.Field(0)) { return overlayOneOfField(config, profile) } return overlayStructField(config, profile) @@ -123,3 +125,14 @@ func overlayProfileField(config interface{}, profile interface{}) interface{} { return config } } + +func isOneOf(field reflect.StructField) bool { + for _, tag := range strings.Split(field.Tag.Get("yamltags"), ",") { + tagParts := strings.Split(tag, "=") + + if tagParts[0] == "oneOf" { + return true + } + } + return false +} diff --git a/pkg/skaffold/schema/transform.go b/pkg/skaffold/schema/transform.go deleted file mode 100644 index 03c1ac74290..00000000000 --- a/pkg/skaffold/schema/transform.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -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 schema - -import ( - "fmt" - - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config/transform" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" - "github.com/pkg/errors" -) - -type Transform func(vc util.VersionedConfig) (util.VersionedConfig, error) - -// Map of schema version to transform functions -// If there are n schema versions, this should always contain (n-1) entries, -// since the last schema version should not have a transform -var transformers = map[string]Transform{ - v1alpha1.Version: transform.ToV1Alpha2, - v1alpha2.Version: transform.ToV1Alpha3, -} - -func RunTransform(vc util.VersionedConfig) (util.VersionedConfig, error) { - for i, version := range config.Versions { - if version == vc.GetVersion() { - return transformToLatest(vc, i) - } - } - return nil, fmt.Errorf("Unsupported version: %s", vc.GetVersion()) -} - -func transformToLatest(vc util.VersionedConfig, pos int) (util.VersionedConfig, error) { - if pos == len(config.Versions)-1 { - // if there are n versions, there are (n-1) transforms - return vc, nil - } - transformer := transformers[config.Versions[pos]] - newConfig, err := transformer(vc) - if err != nil { - return nil, errors.Wrapf(err, "transforming skaffold config") - } - return transformToLatest(newConfig, pos+1) -} diff --git a/pkg/skaffold/schema/util/util.go b/pkg/skaffold/schema/util/util.go index 68cc5090ffd..659607e708d 100644 --- a/pkg/skaffold/schema/util/util.go +++ b/pkg/skaffold/schema/util/util.go @@ -16,27 +16,8 @@ limitations under the License. package util -import ( - "reflect" - "strings" -) - type VersionedConfig interface { GetVersion() string Parse([]byte, bool) error -} - -type Config interface { - Parse([]byte) (VersionedConfig, error) -} - -func IsOneOf(field reflect.StructField) bool { - for _, tag := range strings.Split(field.Tag.Get("yamltags"), ",") { - tagParts := strings.Split(tag, "=") - - if tagParts[0] == "oneOf" { - return true - } - } - return false + Upgrade() (VersionedConfig, error) } diff --git a/pkg/skaffold/schema/v1alpha1/config.go b/pkg/skaffold/schema/v1alpha1/config.go index b55cc4af242..c60c87afd78 100644 --- a/pkg/skaffold/schema/v1alpha1/config.go +++ b/pkg/skaffold/schema/v1alpha1/config.go @@ -18,12 +18,18 @@ package v1alpha1 import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" yaml "gopkg.in/yaml.v2" ) const Version string = "skaffold/v1alpha1" +// NewSkaffoldConfig creates a SkaffoldConfig +func NewSkaffoldConfig() util.VersionedConfig { + return new(SkaffoldConfig) +} + // SkaffoldConfig is the top level config object // that is parsed from a skaffold.yaml type SkaffoldConfig struct { diff --git a/pkg/skaffold/schema/v1alpha1/upgrade.go b/pkg/skaffold/schema/v1alpha1/upgrade.go new file mode 100644 index 00000000000..314a797585c --- /dev/null +++ b/pkg/skaffold/schema/v1alpha1/upgrade.go @@ -0,0 +1,109 @@ +/* +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 v1alpha1 + +import ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" + "github.com/sirupsen/logrus" +) + +// Upgrade upgrades a configuration to the next version. +func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { + var tagPolicy next.TagPolicy + if config.Build.TagPolicy == constants.TagStrategySha256 { + tagPolicy = next.TagPolicy{ + ShaTagger: &next.ShaTagger{}, + } + } else if config.Build.TagPolicy == constants.TagStrategyGitCommit { + tagPolicy = next.TagPolicy{ + GitTagger: &next.GitTagger{}, + } + } + + var newHelmDeploy *next.HelmDeploy + if config.Deploy.DeployType.HelmDeploy != nil { + newReleases := make([]next.HelmRelease, 0) + for _, release := range config.Deploy.DeployType.HelmDeploy.Releases { + newReleases = append(newReleases, next.HelmRelease{ + Name: release.Name, + ChartPath: release.ChartPath, + ValuesFilePath: release.ValuesFilePath, + Values: release.Values, + Namespace: release.Namespace, + Version: release.Version, + }) + } + newHelmDeploy = &next.HelmDeploy{ + Releases: newReleases, + } + } + var newKubectlDeploy *next.KubectlDeploy + if config.Deploy.DeployType.KubectlDeploy != nil { + newManifests := make([]string, 0) + logrus.Warn("Ignoring manifest parameters when transforming v1alpha1 config; check kubernetes yaml before running skaffold") + for _, manifest := range config.Deploy.DeployType.KubectlDeploy.Manifests { + newManifests = append(newManifests, manifest.Paths...) + } + newKubectlDeploy = &next.KubectlDeploy{ + Manifests: newManifests, + } + } + + var newArtifacts = make([]*next.Artifact, 0) + for _, artifact := range config.Build.Artifacts { + newArtifacts = append(newArtifacts, &next.Artifact{ + ImageName: artifact.ImageName, + Workspace: artifact.Workspace, + ArtifactType: next.ArtifactType{ + DockerArtifact: &next.DockerArtifact{ + DockerfilePath: artifact.DockerfilePath, + BuildArgs: artifact.BuildArgs, + }, + }, + }) + } + + var newBuildType = next.BuildType{} + if config.Build.GoogleCloudBuild != nil { + newBuildType.GoogleCloudBuild = &next.GoogleCloudBuild{ + ProjectID: config.Build.GoogleCloudBuild.ProjectID, + } + } + if config.Build.LocalBuild != nil { + newBuildType.LocalBuild = &next.LocalBuild{ + SkipPush: config.Build.LocalBuild.SkipPush, + } + } + + return &next.SkaffoldConfig{ + APIVersion: next.Version, + Kind: config.Kind, + Deploy: next.DeployConfig{ + DeployType: next.DeployType{ + HelmDeploy: newHelmDeploy, + KubectlDeploy: newKubectlDeploy, + }, + }, + Build: next.BuildConfig{ + Artifacts: newArtifacts, + BuildType: newBuildType, + TagPolicy: tagPolicy, + }, + }, nil +} diff --git a/pkg/skaffold/schema/v1alpha2/config.go b/pkg/skaffold/schema/v1alpha2/config.go index 284c289a1f6..2ff49c167f8 100644 --- a/pkg/skaffold/schema/v1alpha2/config.go +++ b/pkg/skaffold/schema/v1alpha2/config.go @@ -17,12 +17,18 @@ limitations under the License. package v1alpha2 import ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" ) const Version string = "skaffold/v1alpha2" +// NewSkaffoldConfig creates a SkaffoldConfig +func NewSkaffoldConfig() util.VersionedConfig { + return new(SkaffoldConfig) +} + type SkaffoldConfig struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -225,21 +231,10 @@ func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { } if useDefaults { - if err := c.setDefaultValues(); err != nil { + if err := c.SetDefaultValues(); err != nil { return errors.Wrap(err, "applying default values") } } return nil } - -func NewConfig() (*SkaffoldConfig, error) { - cfg := &SkaffoldConfig{} - if err := cfg.setBaseDefaultValues(); err != nil { - return nil, err - } - if err := cfg.setDefaultValues(); err != nil { - return nil, err - } - return cfg, nil -} diff --git a/pkg/skaffold/schema/v1alpha2/defaults.go b/pkg/skaffold/schema/v1alpha2/defaults.go index b5019c3ba37..c5aebd5ad7e 100644 --- a/pkg/skaffold/schema/v1alpha2/defaults.go +++ b/pkg/skaffold/schema/v1alpha2/defaults.go @@ -27,13 +27,8 @@ import ( kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" ) -func (c *SkaffoldConfig) setBaseDefaultValues() error { - c.APIVersion = Version - c.Kind = "Config" - return nil -} - -func (c *SkaffoldConfig) setDefaultValues() error { +// SetDefaultValues makes sure default values are set. +func (c *SkaffoldConfig) SetDefaultValues() error { c.defaultToLocalBuild() c.defaultToKubectlDeploy() c.setDefaultCloudBuildDockerImage() diff --git a/pkg/skaffold/schema/v1alpha2/upgrade.go b/pkg/skaffold/schema/v1alpha2/upgrade.go new file mode 100644 index 00000000000..ef23a6a3f10 --- /dev/null +++ b/pkg/skaffold/schema/v1alpha2/upgrade.go @@ -0,0 +1,102 @@ +/* +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 v1alpha2 + +import ( + "encoding/json" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/pkg/errors" +) + +// Upgrade upgrades a configuration to the next version. +func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { + // convert Deploy (should be the same) + var newDeploy next.DeployConfig + if err := convert(config.Deploy, &newDeploy); err != nil { + return nil, errors.Wrap(err, "converting deploy config") + } + // if the helm deploy config was set, then convert ValueFilePath to ValuesFiles + if oldHelmDeploy := config.Deploy.DeployType.HelmDeploy; oldHelmDeploy != nil { + for i, oldHelmRelease := range oldHelmDeploy.Releases { + if oldHelmRelease.ValuesFilePath != "" { + newDeploy.DeployType.HelmDeploy.Releases[i].ValuesFiles = []string{oldHelmRelease.ValuesFilePath} + } + } + } + + // convert Profiles (should be the same) + var newProfiles []next.Profile + if config.Profiles != nil { + if err := convert(config.Profiles, &newProfiles); err != nil { + return nil, errors.Wrap(err, "converting new profile") + } + } + + // if the helm deploy config was set for a profile, then convert ValueFilePath to ValuesFiles + for p, oldProfile := range config.Profiles { + if oldProfileHelmDeploy := oldProfile.Deploy.DeployType.HelmDeploy; oldProfileHelmDeploy != nil { + for i, oldProfileHelmRelease := range oldProfileHelmDeploy.Releases { + if oldProfileHelmRelease.ValuesFilePath != "" { + newProfiles[p].Deploy.DeployType.HelmDeploy.Releases[i].ValuesFiles = []string{oldProfileHelmRelease.ValuesFilePath} + } + } + } + } + + // convert Build (different only for kaniko) + oldKanikoBuilder := config.Build.KanikoBuild + config.Build.KanikoBuild = nil + + // copy over old build config to new build config + var newBuild next.BuildConfig + if err := convert(config.Build, &newBuild); err != nil { + return nil, errors.Wrap(err, "converting new build") + } + // if the kaniko build was set, then convert it + if oldKanikoBuilder != nil { + newBuild.BuildType.KanikoBuild = &next.KanikoBuild{ + BuildContext: next.KanikoBuildContext{ + GCSBucket: oldKanikoBuilder.GCSBucket, + }, + Namespace: oldKanikoBuilder.Namespace, + PullSecret: oldKanikoBuilder.PullSecret, + PullSecretName: oldKanikoBuilder.PullSecretName, + Timeout: oldKanikoBuilder.Timeout, + } + } + + return &next.SkaffoldConfig{ + APIVersion: next.Version, + Kind: config.Kind, + Deploy: newDeploy, + Build: newBuild, + Profiles: newProfiles, + }, nil +} + +func convert(old interface{}, new interface{}) error { + o, err := json.Marshal(old) + if err != nil { + return errors.Wrap(err, "marshalling old") + } + if err := json.Unmarshal(o, &new); err != nil { + return errors.Wrap(err, "unmarshalling new") + } + return nil +} diff --git a/pkg/skaffold/schema/v1alpha3/config.go b/pkg/skaffold/schema/v1alpha3/config.go index 2f7266a3082..35cf08cff54 100644 --- a/pkg/skaffold/schema/v1alpha3/config.go +++ b/pkg/skaffold/schema/v1alpha3/config.go @@ -17,12 +17,18 @@ limitations under the License. package v1alpha3 import ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" ) const Version string = "skaffold/v1alpha3" +// NewSkaffoldConfig creates a SkaffoldConfig +func NewSkaffoldConfig() util.VersionedConfig { + return new(SkaffoldConfig) +} + type SkaffoldConfig struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -144,10 +150,9 @@ type HelmDeploy struct { Releases []HelmRelease `yaml:"releases,omitempty"` } -// KustomizeDeploy contains the configuration needed for deploying with kustomize. type KustomizeDeploy struct { - Path string `yaml:"path,omitempty"` - Flags KubectlFlags `yaml:"flags,omitempty"` + KustomizePath string `yaml:"kustomizePath,omitempty"` + Flags KubectlFlags `yaml:"flags,omitempty"` } type HelmRelease struct { @@ -196,9 +201,10 @@ type HelmConventionConfig struct { // Artifact represents items that need to be built, along with the context in which // they should be built. type Artifact struct { - ImageName string `yaml:"imageName"` - Workspace string `yaml:"workspace,omitempty"` - ArtifactType `yaml:",inline"` + ImageName string `yaml:"imageName"` + Workspace string `yaml:"workspace,omitempty"` + ArtifactType `yaml:",inline"` + StructureTestFiles []string `yaml:"structureTestFiles"` } // Profile is additional configuration that overrides default @@ -210,10 +216,8 @@ type Profile struct { } type ArtifactType struct { - DockerArtifact *DockerArtifact `yaml:"docker,omitempty" yamltags:"oneOf=artifact"` - BazelArtifact *BazelArtifact `yaml:"bazel,omitempty" yamltags:"oneOf=artifact"` - JibMavenArtifact *JibMavenArtifact `yaml:"jibMaven,omitempty" yamltags:"oneOf=artifact"` - JibGradleArtifact *JibGradleArtifact `yaml:"jibGradle,omitempty" yamltags:"oneOf=artifact"` + DockerArtifact *DockerArtifact `yaml:"docker,omitempty" yamltags:"oneOf=artifact"` + BazelArtifact *BazelArtifact `yaml:"bazel,omitempty" yamltags:"oneOf=artifact"` } type DockerArtifact struct { @@ -227,17 +231,6 @@ type BazelArtifact struct { BuildTarget string `yaml:"target"` } -type JibMavenArtifact struct { - // Only multi-module - Module string `yaml:"module"` - Profile string `yaml:"profile"` -} - -type JibGradleArtifact struct { - // Only multi-module - Project string `yaml:"project"` -} - // Parse reads a SkaffoldConfig from yaml. func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { if err := yaml.UnmarshalStrict(contents, c); err != nil { @@ -245,21 +238,10 @@ func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { } if useDefaults { - if err := c.setDefaultValues(); err != nil { + if err := c.SetDefaultValues(); err != nil { return errors.Wrap(err, "applying default values") } } return nil } - -func NewConfig() (*SkaffoldConfig, error) { - cfg := &SkaffoldConfig{} - if err := cfg.setBaseDefaultValues(); err != nil { - return nil, err - } - if err := cfg.setDefaultValues(); err != nil { - return nil, err - } - return cfg, nil -} diff --git a/pkg/skaffold/schema/v1alpha3/defaults.go b/pkg/skaffold/schema/v1alpha3/defaults.go index 36e1c1a0a88..423be3953fc 100644 --- a/pkg/skaffold/schema/v1alpha3/defaults.go +++ b/pkg/skaffold/schema/v1alpha3/defaults.go @@ -27,13 +27,8 @@ import ( kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" ) -func (c *SkaffoldConfig) setBaseDefaultValues() error { - c.APIVersion = Version - c.Kind = "Config" - return nil -} - -func (c *SkaffoldConfig) setDefaultValues() error { +// SetDefaultValues makes sure default values are set. +func (c *SkaffoldConfig) SetDefaultValues() error { c.defaultToLocalBuild() c.defaultToKubectlDeploy() c.setDefaultCloudBuildDockerImage() @@ -95,13 +90,8 @@ func (c *SkaffoldConfig) setDefaultTagger() { } func (c *SkaffoldConfig) setDefaultKustomizePath() { - kustomize := c.Deploy.KustomizeDeploy - if kustomize == nil { - return - } - - if kustomize.Path == "" { - kustomize.Path = constants.DefaultKustomizationPath + if c.Deploy.KustomizeDeploy != nil && c.Deploy.KustomizeDeploy.KustomizePath == "" { + c.Deploy.KustomizeDeploy.KustomizePath = constants.DefaultKustomizationPath } } diff --git a/pkg/skaffold/schema/v1alpha3/profiles.go b/pkg/skaffold/schema/v1alpha3/profiles.go deleted file mode 100644 index 224827e77b3..00000000000 --- a/pkg/skaffold/schema/v1alpha3/profiles.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -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 v1alpha3 - -import ( - "fmt" - "reflect" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" -) - -// ApplyProfiles returns configuration modified by the application -// of a list of profiles. -func (c *SkaffoldConfig) ApplyProfiles(profiles []string) error { - byName := profilesByName(c.Profiles) - for _, name := range profiles { - profile, present := byName[name] - if !present { - return fmt.Errorf("couldn't find profile %s", name) - } - - applyProfile(c, profile) - } - if err := c.setDefaultValues(); err != nil { - return errors.Wrap(err, "applying default values") - } - - return nil -} - -func applyProfile(config *SkaffoldConfig, profile Profile) { - logrus.Infof("applying profile: %s", profile.Name) - - // this intentionally removes the Profiles field from the returned config - *config = SkaffoldConfig{ - APIVersion: config.APIVersion, - Kind: config.Kind, - Build: overlayProfileField(config.Build, profile.Build).(BuildConfig), - Deploy: overlayProfileField(config.Deploy, profile.Deploy).(DeployConfig), - } -} - -func profilesByName(profiles []Profile) map[string]Profile { - byName := make(map[string]Profile) - for _, profile := range profiles { - byName[profile.Name] = profile - } - return byName -} - -// if we find a oneOf tag, the fields in this struct are themselves pointers to structs, -// but should be treated as values. the first non-nil one we find is what we should use. -func overlayOneOfField(config interface{}, profile interface{}) interface{} { - v := reflect.ValueOf(profile) // the profile itself - t := reflect.TypeOf(profile) // the type of the profile, used for getting struct field types - for i := 0; i < v.NumField(); i++ { - fieldType := t.Field(i) // the field type (e.g. 'LocalBuild' for BuildConfig) - fieldValue := v.Field(i).Interface() // the value of the field itself - - if fieldValue != nil && !reflect.ValueOf(fieldValue).IsNil() { - ret := reflect.New(t) // New(t) returns a Value representing pointer to new zero value for type t - ret.Elem().FieldByName(fieldType.Name).Set(reflect.ValueOf(fieldValue)) // set the value - return reflect.Indirect(ret).Interface() // since ret is a pointer, dereference it - } - } - // if we're here, we didn't find any values set in the profile config. just return the original. - logrus.Infof("no values found in profile for field %s, using original config values", t.Name()) - return config -} - -func overlayStructField(config interface{}, profile interface{}) interface{} { - // we already know the top level fields for whatever struct we have are themselves structs - // (and not one-of values), so we need to recursively overlay them - configValue := reflect.ValueOf(config) - profileValue := reflect.ValueOf(profile) - t := reflect.TypeOf(profile) - finalConfig := reflect.New(t) - - for i := 0; i < profileValue.NumField(); i++ { - fieldType := t.Field(i) - overlay := overlayProfileField(configValue.Field(i).Interface(), profileValue.Field(i).Interface()) - finalConfig.Elem().FieldByName(fieldType.Name).Set(reflect.ValueOf(overlay)) - } - return reflect.Indirect(finalConfig).Interface() // since finalConfig is a pointer, dereference it -} - -func overlayProfileField(config interface{}, profile interface{}) interface{} { - v := reflect.ValueOf(profile) // the profile itself - t := reflect.TypeOf(profile) // the type of the profile, used for getting struct field types - logrus.Debugf("overlaying profile on config for field %s", t.Name()) - switch v.Kind() { - case reflect.Struct: - // check the first field of the struct for a oneOf yamltag. - if util.IsOneOf(t.Field(0)) { - return overlayOneOfField(config, profile) - } - return overlayStructField(config, profile) - case reflect.Slice: - // either return the values provided in the profile, or the original values if none were provided. - if v.Len() == 0 { - return config - } - return v.Interface() - default: - logrus.Warnf("unknown field type in profile overlay: %s. falling back to original config values", v.Kind()) - return config - } -} diff --git a/pkg/skaffold/schema/v1alpha3/upgrade.go b/pkg/skaffold/schema/v1alpha3/upgrade.go new file mode 100644 index 00000000000..eae40b3f347 --- /dev/null +++ b/pkg/skaffold/schema/v1alpha3/upgrade.go @@ -0,0 +1,67 @@ +/* +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 v1alpha3 + +import ( + "encoding/json" + + next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + "github.com/pkg/errors" +) + +// Upgrade upgrades a configuration to the next version. +func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { + // convert Deploy (should be the same) + var newDeploy next.DeployConfig + if err := convert(config.Deploy, &newDeploy); err != nil { + return nil, errors.Wrap(err, "converting deploy config") + } + + // convert Profiles (should be the same) + var newProfiles []next.Profile + if config.Profiles != nil { + if err := convert(config.Profiles, &newProfiles); err != nil { + return nil, errors.Wrap(err, "converting new profile") + } + } + + // convert Build (should be the same) + var newBuild next.BuildConfig + if err := convert(config.Build, &newBuild); err != nil { + return nil, errors.Wrap(err, "converting new build") + } + + return &next.SkaffoldConfig{ + APIVersion: next.Version, + Kind: config.Kind, + Deploy: newDeploy, + Build: newBuild, + Profiles: newProfiles, + }, nil +} + +func convert(old interface{}, new interface{}) error { + o, err := json.Marshal(old) + if err != nil { + return errors.Wrap(err, "marshalling old") + } + if err := json.Unmarshal(o, &new); err != nil { + return errors.Wrap(err, "unmarshalling new") + } + return nil +} diff --git a/pkg/skaffold/schema/versions.go b/pkg/skaffold/schema/versions.go new file mode 100644 index 00000000000..86904a75eac --- /dev/null +++ b/pkg/skaffold/schema/versions.go @@ -0,0 +1,103 @@ +/* +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 schema + +import ( + "github.com/pkg/errors" + + version "github.com/GoogleContainerTools/skaffold/pkg/skaffold/apiversion" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + misc "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/yamltags" + yaml "gopkg.in/yaml.v2" +) + +type APIVersion struct { + Version string `yaml:"apiVersion"` +} + +var schemaVersions = map[string]func() util.VersionedConfig{ + v1alpha1.Version: v1alpha1.NewSkaffoldConfig, + v1alpha2.Version: v1alpha2.NewSkaffoldConfig, + v1alpha3.Version: v1alpha3.NewSkaffoldConfig, + latest.Version: latest.NewSkaffoldConfig, +} + +// ParseConfig reads a configuration file. +func ParseConfig(filename string, applyDefaults bool) (util.VersionedConfig, error) { + buf, err := misc.ReadConfiguration(filename) + if err != nil { + return nil, errors.Wrap(err, "read skaffold config") + } + + apiVersion := &APIVersion{} + if err := yaml.Unmarshal(buf, apiVersion); err != nil { + return nil, errors.Wrap(err, "parsing api version") + } + + factory, present := schemaVersions[apiVersion.Version] + if !present { + return nil, errors.Wrapf(err, "unknown version: %s", apiVersion.Version) + } + + cfg := factory() + if err := cfg.Parse(buf, applyDefaults); err != nil { + return nil, errors.Wrap(err, "unable to parse config") + } + + if err := yamltags.ProcessStruct(cfg); err != nil { + return nil, errors.Wrap(err, "invalid config") + } + + return cfg, nil +} + +// CheckVersionIsLatest checks that a given version is the most recent. +func CheckVersionIsLatest(apiVersion string) error { + parsedVersion, err := version.Parse(apiVersion) + if err != nil { + return errors.Wrap(err, "parsing api version") + } + + if parsedVersion.LT(version.MustParse(latest.Version)) { + return errors.New("config version out of date: run `skaffold fix`") + } + + if parsedVersion.GT(version.MustParse(latest.Version)) { + return errors.New("config version is too new for this version of skaffold: upgrade skaffold") + } + + return nil +} + +// UpgradeToLatest upgrades a configuration to the latest version. +func UpgradeToLatest(vc util.VersionedConfig) (util.VersionedConfig, error) { + var err error + + for vc.GetVersion() != latest.Version { + vc, err = vc.Upgrade() + if err != nil { + return nil, errors.Wrapf(err, "transforming skaffold config") + } + } + + return vc, nil +} diff --git a/pkg/skaffold/config/config_test.go b/pkg/skaffold/schema/versions_test.go similarity index 56% rename from pkg/skaffold/config/config_test.go rename to pkg/skaffold/schema/versions_test.go index 0193d62eca0..17bd25a6fcc 100644 --- a/pkg/skaffold/config/config_test.go +++ b/pkg/skaffold/schema/versions_test.go @@ -14,60 +14,54 @@ See the License for the specific language governing permissions and limitations under the License. */ -package config +package schema import ( + "fmt" "testing" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" "github.com/GoogleContainerTools/skaffold/testutil" "k8s.io/client-go/tools/clientcmd/api" ) const ( - minimalConfig = ` -apiVersion: skaffold/v1alpha3 -kind: Config -` + minimalConfig = `` + simpleConfig = ` -apiVersion: skaffold/v1alpha3 -kind: Config build: tagPolicy: gitCommit: {} artifacts: - - imageName: example + - image: example deploy: kubectl: {} ` // This config has two tag policies set. invalidConfig = ` -apiVersion: skaffold/v1alpha3 -kind: Config build: tagPolicy: sha256: {} gitCommit: {} artifacts: - - imageName: example + - image: example deploy: name: example ` completeConfig = ` -apiVersion: skaffold/v1alpha3 -kind: Config build: tagPolicy: sha256: {} artifacts: - - imageName: image1 - workspace: ./examples/app1 + - image: image1 + context: ./examples/app1 docker: - dockerfilePath: Dockerfile.dev - - imageName: image2 - workspace: ./examples/app2 + dockerfile: Dockerfile.dev + - image: image2 + context: ./examples/app2 bazel: target: //:example.tar googleCloudBuild: @@ -79,16 +73,12 @@ deploy: - svc.yaml ` minimalKanikoConfig = ` -apiVersion: skaffold/v1alpha3 -kind: Config build: kaniko: buildContext: gcsBucket: demo ` completeKanikoConfig = ` -apiVersion: skaffold/v1alpha3 -kind: Config build: kaniko: buildContext: @@ -179,23 +169,30 @@ func TestParseConfig(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - cfg, err := GetConfig([]byte(test.config), true) + tmp, cleanup := testutil.NewTempDir(t) + defer cleanup() + + yaml := fmt.Sprintf("apiVersion: %s\nkind: Config\n%s", latest.Version, test.config) + tmp.Write("skaffold.yaml", yaml) + + cfg, err := ParseConfig(tmp.Path("skaffold.yaml"), true) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, cfg) }) } } -func config(ops ...func(*SkaffoldConfig)) *SkaffoldConfig { - cfg := &SkaffoldConfig{APIVersion: "skaffold/v1alpha3", Kind: "Config"} +func config(ops ...func(*latest.SkaffoldConfig)) *latest.SkaffoldConfig { + cfg := &latest.SkaffoldConfig{APIVersion: latest.Version, Kind: "Config"} for _, op := range ops { op(cfg) } return cfg } -func withLocalBuild(ops ...func(*v1alpha3.BuildConfig)) func(*SkaffoldConfig) { - return func(cfg *SkaffoldConfig) { - b := v1alpha3.BuildConfig{BuildType: v1alpha3.BuildType{LocalBuild: &v1alpha3.LocalBuild{}}} +func withLocalBuild(ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfig) { + return func(cfg *latest.SkaffoldConfig) { + b := latest.BuildConfig{BuildType: latest.BuildType{LocalBuild: &latest.LocalBuild{}}} for _, op := range ops { op(&b) } @@ -203,9 +200,9 @@ func withLocalBuild(ops ...func(*v1alpha3.BuildConfig)) func(*SkaffoldConfig) { } } -func withGoogleCloudBuild(id string, ops ...func(*v1alpha3.BuildConfig)) func(*SkaffoldConfig) { - return func(cfg *SkaffoldConfig) { - b := v1alpha3.BuildConfig{BuildType: v1alpha3.BuildType{GoogleCloudBuild: &v1alpha3.GoogleCloudBuild{ +func withGoogleCloudBuild(id string, ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfig) { + return func(cfg *latest.SkaffoldConfig) { + b := latest.BuildConfig{BuildType: latest.BuildType{GoogleCloudBuild: &latest.GoogleCloudBuild{ ProjectID: id, DockerImage: "gcr.io/cloud-builders/docker", }}} @@ -216,10 +213,10 @@ func withGoogleCloudBuild(id string, ops ...func(*v1alpha3.BuildConfig)) func(*S } } -func withKanikoBuild(bucket, secretName, namespace, secret string, timeout string, ops ...func(*v1alpha3.BuildConfig)) func(*SkaffoldConfig) { - return func(cfg *SkaffoldConfig) { - b := v1alpha3.BuildConfig{BuildType: v1alpha3.BuildType{KanikoBuild: &v1alpha3.KanikoBuild{ - BuildContext: v1alpha3.KanikoBuildContext{ +func withKanikoBuild(bucket, secretName, namespace, secret string, timeout string, ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfig) { + return func(cfg *latest.SkaffoldConfig) { + b := latest.BuildConfig{BuildType: latest.BuildType{KanikoBuild: &latest.KanikoBuild{ + BuildContext: latest.KanikoBuildContext{ GCSBucket: bucket, }, PullSecretName: secretName, @@ -234,11 +231,11 @@ func withKanikoBuild(bucket, secretName, namespace, secret string, timeout strin } } -func withKubectlDeploy(manifests ...string) func(*SkaffoldConfig) { - return func(cfg *SkaffoldConfig) { - cfg.Deploy = v1alpha3.DeployConfig{ - DeployType: v1alpha3.DeployType{ - KubectlDeploy: &v1alpha3.KubectlDeploy{ +func withKubectlDeploy(manifests ...string) func(*latest.SkaffoldConfig) { + return func(cfg *latest.SkaffoldConfig) { + cfg.Deploy = latest.DeployConfig{ + DeployType: latest.DeployType{ + KubectlDeploy: &latest.KubectlDeploy{ Manifests: manifests, }, }, @@ -246,23 +243,23 @@ func withKubectlDeploy(manifests ...string) func(*SkaffoldConfig) { } } -func withHelmDeploy() func(*SkaffoldConfig) { - return func(cfg *SkaffoldConfig) { - cfg.Deploy = v1alpha3.DeployConfig{ - DeployType: v1alpha3.DeployType{ - HelmDeploy: &v1alpha3.HelmDeploy{}, +func withHelmDeploy() func(*latest.SkaffoldConfig) { + return func(cfg *latest.SkaffoldConfig) { + cfg.Deploy = latest.DeployConfig{ + DeployType: latest.DeployType{ + HelmDeploy: &latest.HelmDeploy{}, }, } } } -func withDockerArtifact(image, workspace, dockerfile string) func(*v1alpha3.BuildConfig) { - return func(cfg *v1alpha3.BuildConfig) { - cfg.Artifacts = append(cfg.Artifacts, &v1alpha3.Artifact{ +func withDockerArtifact(image, workspace, dockerfile string) func(*latest.BuildConfig) { + return func(cfg *latest.BuildConfig) { + cfg.Artifacts = append(cfg.Artifacts, &latest.Artifact{ ImageName: image, Workspace: workspace, - ArtifactType: v1alpha3.ArtifactType{ - DockerArtifact: &v1alpha3.DockerArtifact{ + ArtifactType: latest.ArtifactType{ + DockerArtifact: &latest.DockerArtifact{ DockerfilePath: dockerfile, }, }, @@ -270,13 +267,13 @@ func withDockerArtifact(image, workspace, dockerfile string) func(*v1alpha3.Buil } } -func withBazelArtifact(image, workspace, target string) func(*v1alpha3.BuildConfig) { - return func(cfg *v1alpha3.BuildConfig) { - cfg.Artifacts = append(cfg.Artifacts, &v1alpha3.Artifact{ +func withBazelArtifact(image, workspace, target string) func(*latest.BuildConfig) { + return func(cfg *latest.BuildConfig) { + cfg.Artifacts = append(cfg.Artifacts, &latest.Artifact{ ImageName: image, Workspace: workspace, - ArtifactType: v1alpha3.ArtifactType{ - BazelArtifact: &v1alpha3.BazelArtifact{ + ArtifactType: latest.ArtifactType{ + BazelArtifact: &latest.BazelArtifact{ BuildTarget: target, }, }, @@ -284,20 +281,50 @@ func withBazelArtifact(image, workspace, target string) func(*v1alpha3.BuildConf } } -func withTagPolicy(tagPolicy v1alpha3.TagPolicy) func(*v1alpha3.BuildConfig) { - return func(cfg *v1alpha3.BuildConfig) { cfg.TagPolicy = tagPolicy } +func withTagPolicy(tagPolicy latest.TagPolicy) func(*latest.BuildConfig) { + return func(cfg *latest.BuildConfig) { cfg.TagPolicy = tagPolicy } } -func withGitTagger() func(*v1alpha3.BuildConfig) { - return withTagPolicy(v1alpha3.TagPolicy{GitTagger: &v1alpha3.GitTagger{}}) +func withGitTagger() func(*latest.BuildConfig) { + return withTagPolicy(latest.TagPolicy{GitTagger: &latest.GitTagger{}}) } -func withShaTagger() func(*v1alpha3.BuildConfig) { - return withTagPolicy(v1alpha3.TagPolicy{ShaTagger: &v1alpha3.ShaTagger{}}) +func withShaTagger() func(*latest.BuildConfig) { + return withTagPolicy(latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}) } -func withProfiles(profiles ...v1alpha3.Profile) func(*SkaffoldConfig) { - return func(cfg *SkaffoldConfig) { +func withProfiles(profiles ...latest.Profile) func(*latest.SkaffoldConfig) { + return func(cfg *latest.SkaffoldConfig) { cfg.Profiles = profiles } } + +func TestCheckVersionIsLatest(t *testing.T) { + tests := []struct { + name string + version string + shouldErr bool + }{ + { + name: "latest api version", + version: latest.Version, + }, + { + name: "old api version", + version: v1alpha1.Version, + shouldErr: true, + }, + { + name: "new api version", + version: "skaffold/v9", + shouldErr: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := CheckVersionIsLatest(test.version) + + testutil.CheckError(t, test.shouldErr, err) + }) + } +} diff --git a/pkg/skaffold/test/structure/structure.go b/pkg/skaffold/test/structure/structure.go new file mode 100644 index 00000000000..fb1e49aa401 --- /dev/null +++ b/pkg/skaffold/test/structure/structure.go @@ -0,0 +1,39 @@ +/* +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 structure + +import ( + "os/exec" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + + "github.com/sirupsen/logrus" +) + +// Test is the entrypoint for running structure tests +func (tr *Runner) Test(image string) error { + logrus.Infof("running structure tests for files %v", tr.testFiles) + args := []string{"test", "--image", image} + for _, f := range tr.testFiles { + args = append(args, "--config", f) + } + args = append(args, tr.testFiles...) + cmd := exec.Command("container-structure-test", args...) + + _, err := util.RunCmdOut(cmd) + return err +} diff --git a/pkg/skaffold/test/structure/types.go b/pkg/skaffold/test/structure/types.go new file mode 100644 index 00000000000..dddf0618b5a --- /dev/null +++ b/pkg/skaffold/test/structure/types.go @@ -0,0 +1,27 @@ +/* +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 structure + +type Runner struct { + testFiles []string +} + +func NewStructureTestRunner(files []string) (*Runner, error) { + return &Runner{ + testFiles: files, + }, nil +} diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go new file mode 100644 index 00000000000..e75861411e0 --- /dev/null +++ b/pkg/skaffold/test/test.go @@ -0,0 +1,104 @@ +/* +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 test + +import ( + "io" + "os" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/structure" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + + "github.com/pkg/errors" +) + +// NewTester parses the provided test cases from the Skaffold config, +// and returns a Tester instance with all the necessary test runners +// to run all specified tests. +func NewTester(testCases *[]latest.TestCase) (Tester, error) { + testers := []*ArtifactTester{} + deps := []string{} + // TODO(nkubala): copied this from runner.getDeployer(), this should be moved somewhere else + cwd, err := os.Getwd() + if err != nil { + return nil, errors.Wrap(err, "finding current directory") + } + for _, testCase := range *testCases { + testRunner := &ArtifactTester{ + ImageName: testCase.ImageName, + } + if testCase.StructureTests != nil { + stFiles, err := util.ExpandPathsGlob(cwd, testCase.StructureTests) + if err != nil { + return FullTester{}, errors.Wrap(err, "expanding test file paths") + } + stRunner, err := structure.NewStructureTestRunner(stFiles) + if err != nil { + return FullTester{}, errors.Wrap(err, "retrieving structure test runner") + } + testRunner.TestRunners = append(testRunner.TestRunners, stRunner) + + deps = append(deps, stFiles...) + } + testers = append(testers, testRunner) + } + return FullTester{ + ArtifactTesters: testers, + Dependencies: deps, + }, nil +} + +// TestDependencies returns the watch dependencies to the runner. +func (t FullTester) TestDependencies() []string { + return t.Dependencies +} + +// Test is the top level testing execution call. It serves as the +// entrypoint to all individual tests. +func (t FullTester) Test(out io.Writer, bRes []build.Artifact) error { + t.resolveArtifactImageTags(bRes) + for _, aTester := range t.ArtifactTesters { + if err := aTester.RunTests(); err != nil { + return err + } + } + return nil +} + +// RunTests serves as the entrypoint to each group of +// artifact-specific tests. +func (a *ArtifactTester) RunTests() error { + for _, t := range a.TestRunners { + if err := t.Test(a.ImageName); err != nil { + return err + } + } + return nil +} + +// replace original test artifact images with tagged build artifact images +func (t *FullTester) resolveArtifactImageTags(bRes []build.Artifact) { + for _, aTest := range t.ArtifactTesters { + for _, res := range bRes { + if aTest.ImageName == res.ImageName { + aTest.ImageName = res.Tag + } + } + } +} diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go new file mode 100644 index 00000000000..524cdf0d1e2 --- /dev/null +++ b/pkg/skaffold/test/types.go @@ -0,0 +1,59 @@ +/* +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 test + +import ( + "io" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" +) + +// Tester is the top level test executor in Skaffold. +// A tester is really a collection of artifact-specific testers, +// each of which contains one or more TestRunners which implements +// a single test run. +type Tester interface { + Test(io.Writer, []build.Artifact) error + + TestDependencies() []string +} + +// FullTester serves as a holder for the individual artifact-specific +// testers. It exists so that the Tester interface can mimic the Builder/Deployer +// interface, so it can be called in a similar fashion from the Runner, while +// the FullTester actually handles the work. + +// FullTester should always be the ONLY implementation of the Tester interface; +// newly added testing implementations should implement the TestRunner interface. +type FullTester struct { + ArtifactTesters []*ArtifactTester + Dependencies []string +} + +// ArtifactTester is an artifact-specific test holder, which contains +// tests runners to run all specified tests on an individual artifact. +type ArtifactTester struct { + ImageName string + TestRunners []Runner +} + +// TestRunner is the lowest-level test executor in Skaffold, responsible for +// running a single test on a single artifact image and returning its result. +// Any new test type should implement this interface. +type Runner interface { + Test(image string) error +} diff --git a/pkg/skaffold/watch/changes.go b/pkg/skaffold/watch/changes.go index d73ac9a33da..34857620396 100644 --- a/pkg/skaffold/watch/changes.go +++ b/pkg/skaffold/watch/changes.go @@ -17,60 +17,100 @@ limitations under the License. package watch import ( + "fmt" "os" + "sort" + "strings" "time" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -type fileMap struct { - count int - lastModified time.Time -} +type fileMap map[string]time.Time +// TODO(mrick): cached tree extension ala git func stat(deps func() ([]string, error)) (fileMap, error) { + state := fileMap{} paths, err := deps() if err != nil { - return fileMap{}, errors.Wrap(err, "listing files") - } - - last, err := lastModified(paths) - if err != nil { - return fileMap{}, err + return state, errors.Wrap(err, "listing files") } - - return fileMap{ - count: len(paths), - lastModified: last, - }, nil -} - -func lastModified(paths []string) (time.Time, error) { - var last time.Time - for _, path := range paths { stat, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { + logrus.Debugf("could not stat dependency: %s", err) continue // Ignore files that don't exist } - - return last, errors.Wrapf(err, "unable to stat file %s", path) + return nil, errors.Wrapf(err, "unable to stat file %s", path) } + state[path] = stat.ModTime() + } + + return state, nil +} - if stat.IsDir() { - continue // Ignore time changes on directories +type Events struct { + Added []string + Modified []string + Deleted []string +} + +func (e Events) HasChanged() bool { + return len(e.Added) != 0 || len(e.Deleted) != 0 || len(e.Modified) != 0 +} + +func (e *Events) String() string { + added, deleted, modified := len(e.Added), len(e.Deleted), len(e.Modified) + + var sb strings.Builder + if added > 0 { + sb.WriteString(fmt.Sprintf("[watch event] added: %s\n", e.Added)) + } + if deleted > 0 { + sb.WriteString(fmt.Sprintf("[watch event] deleted: %s\n", e.Deleted)) + } + if modified > 0 { + sb.WriteString(fmt.Sprintf("[watch event] modified: %s\n", e.Modified)) + } + return sb.String() +} + +func events(prev, curr fileMap) Events { + e := Events{} + for f, t := range prev { + modtime, ok := curr[f] + if !ok { + // file in prev but not in curr -> file deleted + e.Deleted = append(e.Deleted, f) + continue + } + if !modtime.Equal(t) { + // file in both prev and curr + // time not equal -> file modified + e.Modified = append(e.Modified, f) + continue } + } - modTime := stat.ModTime() - if modTime.After(last) { - last = modTime + for f := range curr { + // don't need to check case where file is in both curr and prev + // covered above + _, ok := prev[f] + if !ok { + // file in curr but not in prev -> file added + e.Added = append(e.Added, f) } } - return last, nil + sortEvt(e) + logrus.Debug(e.String()) + return e } -func hasChanged(prev, curr fileMap) bool { - return prev.count != curr.count || !prev.lastModified.Equal(curr.lastModified) +func sortEvt(e Events) { + sort.Strings(e.Added) + sort.Strings(e.Modified) + sort.Strings(e.Deleted) } diff --git a/pkg/skaffold/watch/changes_test.go b/pkg/skaffold/watch/changes_test.go index 7146f5a0cc4..8c6c7641d42 100644 --- a/pkg/skaffold/watch/changes_test.go +++ b/pkg/skaffold/watch/changes_test.go @@ -13,82 +13,165 @@ 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 watch import ( + "fmt" "testing" "time" "github.com/GoogleContainerTools/skaffold/testutil" ) -func TestHasChanged(t *testing.T) { +var ( + yesterday, _ = time.Parse( + time.RFC3339, + "1991-11-26T22:08:41+00:00") + today, _ = time.Parse( + time.RFC3339, + "1991-11-27T22:08:41+00:00") +) + +func TestEvents(t *testing.T) { var tests = []struct { - description string - setup func(tmp *testutil.TempDir) - update func(tmp *testutil.TempDir) - expectedChanged bool + description string + prev, current fileMap + expected Events }{ { - description: "no file", - setup: func(*testutil.TempDir) {}, - update: func(*testutil.TempDir) {}, - }, - { - description: "added", - setup: func(*testutil.TempDir) {}, - update: func(tmp *testutil.TempDir) { tmp.Write("added", "") }, - expectedChanged: true, + description: "added, modified, and deleted files", + prev: map[string]time.Time{ + "a": yesterday, + "b": yesterday, + }, + current: map[string]time.Time{ + "a": today, + "c": today, + }, + expected: Events{ + Added: []string{"c"}, + Modified: []string{"a"}, + Deleted: []string{"b"}, + }, }, { - description: "removed", - setup: func(tmp *testutil.TempDir) { tmp.Write("removed", "") }, - update: func(tmp *testutil.TempDir) { tmp.Remove("removed") }, - expectedChanged: true, + description: "no changes", + prev: map[string]time.Time{ + "a": today, + "b": today, + }, + current: map[string]time.Time{ + "a": today, + "b": today, + }, + expected: Events{}, }, { - description: "modified", - setup: func(tmp *testutil.TempDir) { tmp.Write("file", "") }, - update: func(tmp *testutil.TempDir) { tmp.Chtimes("file", time.Now().Add(2*time.Second)) }, - expectedChanged: true, + description: "added all", + prev: map[string]time.Time{}, + current: map[string]time.Time{ + "a": today, + "b": today, + "c": today, + }, + expected: Events{Added: []string{"a", "b", "c"}}, }, { - description: "removed and added", - setup: func(tmp *testutil.TempDir) { tmp.Write("removed", "") }, - update: func(tmp *testutil.TempDir) { - tmp.Remove("removed").Write("added", "").Chtimes("added", time.Now().Add(2*time.Second)) + description: "deleted all", + prev: map[string]time.Time{ + "a": today, + "b": today, + "c": today, }, - expectedChanged: true, + current: map[string]time.Time{}, + expected: Events{Deleted: []string{"a", "b", "c"}}, }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + testutil.CheckDeepEqual(t, test.expected, events(test.prev, test.current)) + }) + } +} + +func TestStat(t *testing.T) { + var tests = []struct { + description string + setup func(folder *testutil.TempDir) + expected fileMap + shouldErr bool + }{ { - description: "ignore modified directory", - setup: func(tmp *testutil.TempDir) { tmp.Mkdir("dir") }, - update: func(tmp *testutil.TempDir) { tmp.Chtimes("dir", time.Now().Add(2*time.Second)) }, - expectedChanged: false, + description: "stat files", + setup: func(folder *testutil.TempDir) { + folder.Write("file", "content") + }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - tmpDir, tearDown := testutil.NewTempDir(t) - defer tearDown() + folder, cleanup := testutil.NewTempDir(t) + defer cleanup() - test.setup(tmpDir) - prev, err := stat(tmpDir.List) - if err != nil { - t.Fatal("Unable to setup test directory", err) - } + test.setup(folder) + list, _ := folder.List() - test.update(tmpDir) - curr, err := stat(tmpDir.List) - if err != nil { - t.Fatal("Unable to update test directory", err) - } + actual, err := stat(folder.List) + testutil.CheckError(t, test.shouldErr, err) + checkListInMap(t, list, actual) + }) + } +} - changed := hasChanged(prev, curr) +func TestStatNotExist(t *testing.T) { + var tests = []struct { + description string + setup func(folder *testutil.TempDir) + deps []string + depsErr error + expected fileMap + shouldErr bool + }{ + { + description: "no error when deps returns nonexisting file", + setup: func(folder *testutil.TempDir) { + folder.Write("file", "content") + }, + deps: []string{"file/that/doesnt/exist/anymore"}, + }, + { + description: "deps function error", + setup: func(folder *testutil.TempDir) { + folder.Write("file", "content") + }, + deps: []string{"file/that/doesnt/exist/anymore"}, + depsErr: fmt.Errorf(""), + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + folder, cleanup := testutil.NewTempDir(t) + defer cleanup() - testutil.CheckDeepEqual(t, test.expectedChanged, changed) + test.setup(folder) + + _, err := stat(func() ([]string, error) { return test.deps, test.depsErr }) + testutil.CheckError(t, test.shouldErr, err) }) } } + +func checkListInMap(t *testing.T, list []string, m fileMap) { + for _, f := range list { + if _, ok := m[f]; !ok { + t.Errorf("File %s not in map", f) + } + } + if len(list) != len(m) { + t.Errorf("List and map length differ %s, %s", list, m) + } +} diff --git a/pkg/skaffold/watch/watch.go b/pkg/skaffold/watch/watch.go index 56bc9294ca9..6b2933c8f79 100644 --- a/pkg/skaffold/watch/watch.go +++ b/pkg/skaffold/watch/watch.go @@ -28,7 +28,7 @@ type Factory func() Watcher // Watcher monitors files changes for multiples components. type Watcher interface { - Register(deps func() ([]string, error), onChange func()) error + Register(deps func() ([]string, error), onChange func(Events)) error Run(ctx context.Context, pollInterval time.Duration, onChange func() error) error } @@ -41,12 +41,13 @@ func NewWatcher() Watcher { type component struct { deps func() ([]string, error) - onChange func() + onChange func(Events) state fileMap + events Events } // Register adds a new component to the watch list. -func (w *watchList) Register(deps func() ([]string, error), onChange func()) error { +func (w *watchList) Register(deps func() ([]string, error), onChange func(Events)) error { state, err := stat(deps) if err != nil { return errors.Wrap(err, "listing files") @@ -73,16 +74,17 @@ func (w *watchList) Run(ctx context.Context, pollInterval time.Duration, onChang return nil case <-ticker.C: changed := 0 - for i, component := range *w { state, err := stat(component.deps) if err != nil { return errors.Wrap(err, "listing files") } + e := events(component.state, state) - if hasChanged(component.state, state) { + if e.HasChanged() { changedComponents[i] = true component.state = state + component.events = e changed++ } } @@ -95,7 +97,7 @@ func (w *watchList) Run(ctx context.Context, pollInterval time.Duration, onChang if changed == 0 && len(changedComponents) > 0 { for i, component := range *w { if changedComponents[i] { - component.onChange() + component.onChange(component.events) } } diff --git a/pkg/skaffold/watch/watch_test.go b/pkg/skaffold/watch/watch_test.go index 8be72a95419..64a44230508 100644 --- a/pkg/skaffold/watch/watch_test.go +++ b/pkg/skaffold/watch/watch_test.go @@ -108,7 +108,7 @@ func newCallback() *callback { } } -func (c *callback) call() { +func (c *callback) call(e Events) { c.wg.Done() } diff --git a/skaffold.yaml b/skaffold.yaml index c8fd93c6c19..2979b09396f 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -2,11 +2,9 @@ apiVersion: skaffold/v1alpha3 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/skaffold - workspace: . + - image: gcr.io/k8s-skaffold/skaffold docker: - dockerfilePath: deploy/skaffold/Dockerfile - local: {} + dockerfile: deploy/skaffold/Dockerfile deploy: kubectl: manifests: diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/delete.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/delete.go index 5108a05dea9..2032e276ea7 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/delete.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/delete.go @@ -25,15 +25,8 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote/transport" ) -// DeleteOptions are used to expose optional information to guide or -// control the image deletion. -type DeleteOptions struct { - // TODO(mattmoor): Fail on not found? - // TODO(mattmoor): Delete tag and manifest? -} - // Delete removes the specified image reference from the remote registry. -func Delete(ref name.Reference, auth authn.Authenticator, t http.RoundTripper, do DeleteOptions) error { +func Delete(ref name.Reference, auth authn.Authenticator, t http.RoundTripper) error { scopes := []string{ref.Scope(transport.DeleteScope)} tr, err := transport.New(ref.Context().Registry, auth, t, scopes) if err != nil { diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go index af61e361bec..1fd633c0d96 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go @@ -28,16 +28,8 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote/transport" ) -// WriteOptions are used to expose optional information to guide or -// control the image write. -type WriteOptions struct { - // TODO(mattmoor): Expose "threads" to limit parallelism? -} - // Write pushes the provided img to the specified image reference. -func Write(ref name.Reference, img v1.Image, auth authn.Authenticator, t http.RoundTripper, - wo WriteOptions) error { - +func Write(ref name.Reference, img v1.Image, auth authn.Authenticator, t http.RoundTripper) error { ls, err := img.Layers() if err != nil { return err @@ -52,7 +44,6 @@ func Write(ref name.Reference, img v1.Image, auth authn.Authenticator, t http.Ro ref: ref, client: &http.Client{Transport: tr}, img: img, - options: wo, } bs, err := img.BlobSet() @@ -92,7 +83,6 @@ type writer struct { ref name.Reference client *http.Client img v1.Image - options WriteOptions } // url returns a url.Url for the specified path in the context of this remote image reference.