diff --git a/docs/content/en/docs/environment/templating.md b/docs/content/en/docs/environment/templating.md index 6e43de8fceb..a8525824651 100644 --- a/docs/content/en/docs/environment/templating.md +++ b/docs/content/en/docs/environment/templating.md @@ -19,6 +19,9 @@ List of fields that support templating: * `build.tagPolicy.envTemplate.template` (see [envTemplate tagger]({{< relref "/docs/pipeline-stages/taggers#envtemplate-using-values-of-environment-variables-as-tags)" >}})) * `deploy.helm.releases.setValueTemplates` (see [Deploying with helm]({{< relref "/docs/pipeline-stages/deployers#deploying-with-helm)" >}})) * `deploy.helm.releases.name` (see [Deploying with helm]({{< relref "/docs/pipeline-stages/deployers#deploying-with-helm)" >}})) +* `deploy.helm.releases.namespace` (see [Deploying with helm]({{< relref "/docs/pipeline-stages/deployers#deploying-with-helm)" >}})) +* `deploy.kubectl.defaultNamespace` +* `deploy.kustomize.defaultNamespace` _Please note, this list is not exhaustive._ diff --git a/docs/content/en/schemas/v2beta7.json b/docs/content/en/schemas/v2beta7.json index 30c4d0bc965..2f068d33328 100755 --- a/docs/content/en/schemas/v2beta7.json +++ b/docs/content/en/schemas/v2beta7.json @@ -1745,6 +1745,11 @@ }, "KubectlDeploy": { "properties": { + "defaultNamespace": { + "type": "string", + "description": "default namespace passed to kubectl on deployment if no other override is given.", + "x-intellij-html-description": "default namespace passed to kubectl on deployment if no other override is given." + }, "flags": { "$ref": "#/definitions/KubectlFlags", "description": "additional flags passed to `kubectl`.", @@ -1772,7 +1777,8 @@ "preferredOrder": [ "manifests", "remoteManifests", - "flags" + "flags", + "defaultNamespace" ], "additionalProperties": false, "description": "*beta* uses a client side `kubectl apply` to deploy manifests. You'll need a `kubectl` CLI version installed that's compatible with your cluster.", @@ -1835,6 +1841,11 @@ "x-intellij-html-description": "additional args passed to kustomize build.", "default": "[]" }, + "defaultNamespace": { + "type": "string", + "description": "default namespace passed to kubectl on deployment if no other override is given.", + "x-intellij-html-description": "default namespace passed to kubectl on deployment if no other override is given." + }, "flags": { "$ref": "#/definitions/KubectlFlags", "description": "additional flags passed to `kubectl`.", @@ -1853,7 +1864,8 @@ "preferredOrder": [ "paths", "flags", - "buildArgs" + "buildArgs", + "defaultNamespace" ], "additionalProperties": false, "description": "*beta* uses the `kustomize` CLI to \"patch\" a deployment for a target environment.", diff --git a/integration/render_test.go b/integration/render_test.go index 29a2767d29f..3d127472d32 100644 --- a/integration/render_test.go +++ b/integration/render_test.go @@ -229,7 +229,7 @@ spec: Write("deployment.yaml", test.input). Chdir() - deployer := deploy.NewKubectlDeployer(&runcontext.RunContext{ + deployer, err := deploy.NewKubectlDeployer(&runcontext.RunContext{ WorkingDir: ".", Cfg: latest.Pipeline{ Deploy: latest.DeployConfig{ @@ -240,9 +240,13 @@ spec: }, }, }, + Opts: config.SkaffoldOptions{ + AddSkaffoldLabels: true, + }, }, nil) + t.RequireNoError(err) var b bytes.Buffer - err := deployer.Render(context.Background(), &b, test.builds, false, "") + err = deployer.Render(context.Background(), &b, test.builds, false, "") t.CheckNoError(err) t.CheckDeepEqual(test.expectedOut, b.String()) diff --git a/pkg/skaffold/build/cluster/types.go b/pkg/skaffold/build/cluster/types.go index 149c89c6278..fcc3f1f212e 100644 --- a/pkg/skaffold/build/cluster/types.go +++ b/pkg/skaffold/build/cluster/types.go @@ -58,7 +58,7 @@ func NewBuilder(cfg Config) (*Builder, error) { return &Builder{ ClusterDetails: cfg.Pipeline().Build.Cluster, - kubectlcli: kubectl.NewCLI(cfg), + kubectlcli: kubectl.NewCLI(cfg, ""), timeout: timeout, kubeContext: cfg.GetKubeContext(), insecureRegistries: cfg.GetInsecureRegistries(), diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index fd8eb5d25b5..1d777e3c916 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -113,7 +113,13 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build // collect namespaces for _, r := range results { - if trimmed := strings.TrimSpace(r.Namespace); trimmed != "" { + var namespace string + namespace, err = util.ExpandEnvTemplate(r.Namespace, nil) + if err != nil { + return nil, fmt.Errorf("cannot parse the release namespace template: %w", err) + } + + if trimmed := strings.TrimSpace(namespace); trimmed != "" { nsMap[trimmed] = struct{}{} } } @@ -221,7 +227,10 @@ func (h *HelmDeployer) Cleanup(ctx context.Context, out io.Writer) error { if h.namespace != "" { namespace = h.namespace } else if r.Namespace != "" { - namespace = r.Namespace + namespace, err = util.ExpandEnvTemplate(r.Namespace, nil) + if err != nil { + return fmt.Errorf("cannot parse the release namespace template: %w", err) + } } args := []string{"delete", releaseName} @@ -284,7 +293,13 @@ func (h *HelmDeployer) Render(ctx context.Context, out io.Writer, builds []build } if r.Namespace != "" { - args = append(args, "--namespace", r.Namespace) + var namespace string + namespace, err = util.ExpandEnvTemplate(r.Namespace, nil) + if err != nil { + return fmt.Errorf("cannot parse the release namespace template: %w", err) + } + + args = append(args, "--namespace", namespace) } if err := h.exec(ctx, renderedManifests, false, args...); err != nil { @@ -336,7 +351,10 @@ func (h *HelmDeployer) deployRelease(ctx context.Context, out io.Writer, r lates if h.namespace != "" { opts.namespace = h.namespace } else if r.Namespace != "" { - opts.namespace = r.Namespace + opts.namespace, err = util.ExpandEnvTemplate(r.Namespace, nil) + if err != nil { + return nil, fmt.Errorf("cannot parse the release namespace template: %w", err) + } } if err := h.exec(ctx, ioutil.Discard, false, getArgs(helmVersion, releaseName, opts.namespace)...); err != nil { diff --git a/pkg/skaffold/deploy/helm_test.go b/pkg/skaffold/deploy/helm_test.go index b98130a9e92..1fc5b3ad9e7 100644 --- a/pkg/skaffold/deploy/helm_test.go +++ b/pkg/skaffold/deploy/helm_test.go @@ -71,6 +71,21 @@ var testDeployNamespacedConfig = latest.HelmDeploy{ }}, } +var testDeployEnvTemplateNamespacedConfig = latest.HelmDeploy{ + Releases: []latest.HelmRelease{{ + Name: "skaffold-helm", + ChartPath: "examples/test", + ArtifactOverrides: map[string]string{ + "image": "skaffold-helm", + }, + Overrides: schemautil.HelmOverrides{Values: map[string]interface{}{"foo": "bar"}}, + SetValues: map[string]string{ + "some.key": "somevalue", + }, + Namespace: "testRelease{{.FOO}}Namespace", + }}, +} + var testDeployConfigTemplated = latest.HelmDeploy{ Releases: []latest.HelmRelease{{ Name: "skaffold-helm", @@ -273,6 +288,7 @@ var testTwoReleases = latest.HelmDeploy{ } var testNamespace = "testNamespace" +var testNamespace2 = "testNamespace2" var validDeployYaml = ` # Source: skaffold-helm/templates/deployment.yaml @@ -410,6 +426,7 @@ func TestHelmDeploy(t *testing.T) { force bool shouldErr bool expectedWarnings []string + envs map[string]string }{ { description: "deploy success", @@ -467,6 +484,17 @@ func TestHelmDeploy(t *testing.T) { helm: testDeployNamespacedConfig, builds: testBuilds, }, + { + description: "helm3.0 namespaced (with env template) deploy success", + commands: testutil. + CmdRunWithOutput("helm version --client", version30). + AndRun("helm --kube-context kubecontext get all --namespace testReleaseFOOBARNamespace skaffold-helm --kubeconfig kubeconfig"). + AndRun("helm --kube-context kubecontext dep build examples/test --kubeconfig kubeconfig"). + AndRun("helm --kube-context kubecontext upgrade skaffold-helm examples/test --namespace testReleaseFOOBARNamespace -f skaffold-overrides.yaml --set-string image=docker.io:5000/skaffold-helm:3605e7bc17cf46e53f4d81c4cbc24e5b4c495184 --set some.key=somevalue --kubeconfig kubeconfig"). + AndRun("helm --kube-context kubecontext get all --namespace testReleaseFOOBARNamespace skaffold-helm --kubeconfig kubeconfig"), + helm: testDeployEnvTemplateNamespacedConfig, + builds: testBuilds, + }, { description: "helm3.0 namespaced context deploy success", commands: testutil. @@ -513,6 +541,17 @@ func TestHelmDeploy(t *testing.T) { helm: testDeployNamespacedConfig, builds: testBuilds, }, + { + description: "helm3.1 namespaced deploy (with env template) success", + commands: testutil. + CmdRunWithOutput("helm version --client", version31). + AndRun("helm --kube-context kubecontext get all --namespace testReleaseFOOBARNamespace skaffold-helm --kubeconfig kubeconfig"). + AndRun("helm --kube-context kubecontext dep build examples/test --kubeconfig kubeconfig"). + AndRun("helm --kube-context kubecontext upgrade skaffold-helm examples/test --namespace testReleaseFOOBARNamespace -f skaffold-overrides.yaml --set-string image=docker.io:5000/skaffold-helm:3605e7bc17cf46e53f4d81c4cbc24e5b4c495184 --set some.key=somevalue --kubeconfig kubeconfig"). + AndRun("helm --kube-context kubecontext get all --namespace testReleaseFOOBARNamespace skaffold-helm --kubeconfig kubeconfig"), + helm: testDeployEnvTemplateNamespacedConfig, + builds: testBuilds, + }, { description: "helm3.1 namespaced context deploy success", commands: testutil. @@ -851,6 +890,7 @@ func TestHelmCleanup(t *testing.T) { builds []build.Artifact shouldErr bool expectedWarnings []string + envs map[string]string }{ { description: "cleanup success", @@ -876,6 +916,14 @@ func TestHelmCleanup(t *testing.T) { helm: testDeployNamespacedConfig, builds: testBuilds, }, + { + description: "helm3 namespace (with env template) cleanup success", + commands: testutil. + CmdRunWithOutput("helm version --client", version31). + AndRun("helm --kube-context kubecontext delete skaffold-helm --namespace testReleaseFOOBARNamespace --kubeconfig kubeconfig"), + helm: testDeployEnvTemplateNamespacedConfig, + builds: testBuilds, + }, { description: "helm3 namespaced context cleanup success", commands: testutil. @@ -1106,6 +1154,7 @@ func TestHelmRender(t *testing.T) { outputFile string expected string builds []build.Artifact + envs map[string]string }{ { description: "error if version can't be retrieved", @@ -1160,7 +1209,7 @@ func TestHelmRender(t *testing.T) { shouldErr: false, commands: testutil. CmdRunWithOutput("helm version --client", version31). - AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --set-string image=skaffold-helm:tag1 --set image.name=skaffold-helm --set image.tag=skaffold-helm:tag1 --set missing.key= --set other.key= --set some.key=somevalue --kubeconfig kubeconfig"), + AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --set-string image=skaffold-helm:tag1 --set image.name=skaffold-helm --set image.tag=skaffold-helm:tag1 --set missing.key= --set other.key=FOOBAR --set some.key=somevalue --kubeconfig kubeconfig"), helm: testDeployConfigTemplated, builds: []build.Artifact{ { @@ -1180,6 +1229,18 @@ func TestHelmRender(t *testing.T) { Tag: "skaffold-helm:tag1", }}, }, + { + description: "render with namespace", + shouldErr: false, + commands: testutil.CmdRunWithOutput("helm version --client", version31). + AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --set-string image=skaffold-helm:tag1 --set some.key=somevalue --namespace testReleaseFOOBARNamespace --kubeconfig kubeconfig"), + helm: testDeployEnvTemplateNamespacedConfig, + builds: []build.Artifact{ + { + ImageName: "skaffold-helm", + Tag: "skaffold-helm:tag1", + }}, + }, } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { @@ -1188,6 +1249,8 @@ func TestHelmRender(t *testing.T) { file = t.NewTempDir().Path(test.outputFile) } + t.Override(&util.OSEnviron, func() []string { return []string{"FOO=FOOBAR"} }) + deployer := NewHelmDeployer(&helmConfig{ helm: test.helm, }, nil) diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index 255a7ea47fb..8da56963b4e 100644 --- a/pkg/skaffold/deploy/kubectl.go +++ b/pkg/skaffold/deploy/kubectl.go @@ -67,17 +67,26 @@ type Config interface { // NewKubectlDeployer returns a new KubectlDeployer for a DeployConfig filled // with the needed configuration for `kubectl apply` -func NewKubectlDeployer(cfg Config, labels map[string]string) *KubectlDeployer { +func NewKubectlDeployer(cfg Config, labels map[string]string) (*KubectlDeployer, error) { + defaultNamespace := "" + if cfg.Pipeline().Deploy.KubectlDeploy.DefaultNamespace != nil { + var err error + defaultNamespace, err = util.ExpandEnvTemplate(*cfg.Pipeline().Deploy.KubectlDeploy.DefaultNamespace, nil) + if err != nil { + return nil, err + } + } + return &KubectlDeployer{ KubectlDeploy: cfg.Pipeline().Deploy.KubectlDeploy, workingDir: cfg.GetWorkingDir(), globalConfig: cfg.GlobalConfig(), defaultRepo: cfg.DefaultRepo(), - kubectl: deploy.NewCLI(cfg, cfg.Pipeline().Deploy.KubectlDeploy.Flags), + kubectl: deploy.NewCLI(cfg, cfg.Pipeline().Deploy.KubectlDeploy.Flags, defaultNamespace), insecureRegistries: cfg.GetInsecureRegistries(), skipRender: cfg.SkipRender(), labels: labels, - } + }, nil } // Deploy templates the provided manifests with a simple `find and replace` and diff --git a/pkg/skaffold/deploy/kubectl/cli.go b/pkg/skaffold/deploy/kubectl/cli.go index ef394097b79..b326da9d75f 100644 --- a/pkg/skaffold/deploy/kubectl/cli.go +++ b/pkg/skaffold/deploy/kubectl/cli.go @@ -47,9 +47,9 @@ type Config interface { WaitForDeletions() config.WaitForDeletions } -func NewCLI(cfg Config, flags latest.KubectlFlags) CLI { +func NewCLI(cfg Config, flags latest.KubectlFlags, defaultNameSpace string) CLI { return CLI{ - CLI: pkgkubectl.NewCLI(cfg), + CLI: pkgkubectl.NewCLI(cfg, defaultNameSpace), Flags: flags, forceDeploy: cfg.ForceDeploy(), waitForDeletions: cfg.WaitForDeletions(), diff --git a/pkg/skaffold/deploy/kubectl_test.go b/pkg/skaffold/deploy/kubectl_test.go index 393c57f55f7..2b9e762676e 100644 --- a/pkg/skaffold/deploy/kubectl_test.go +++ b/pkg/skaffold/deploy/kubectl_test.go @@ -42,6 +42,8 @@ const ( kubectlVersion118 = `{"clientVersion":{"major":"1","minor":"18"}}` ) +var testNamespace2FromEnvTemplate = "test{{.MYENV}}ace2" // needs `MYENV=Namesp` environment variable + const deploymentWebYAML = `apiVersion: v1 kind: Pod metadata: @@ -89,13 +91,15 @@ spec: func TestKubectlDeploy(t *testing.T) { tests := []struct { - description string - kubectl latest.KubectlDeploy - builds []build.Artifact - commands util.Command - shouldErr bool - forceDeploy bool - waitForDeletions bool + description string + kubectl latest.KubectlDeploy + builds []build.Artifact + commands util.Command + shouldErr bool + forceDeploy bool + waitForDeletions bool + skipSkaffoldNamespaceOption bool + envs map[string]string }{ { description: "no manifest", @@ -171,6 +175,45 @@ func TestKubectlDeploy(t *testing.T) { }}, waitForDeletions: true, }, + { + description: "deploy success (default namespace)", + kubectl: latest.KubectlDeploy{ + Manifests: []string{"deployment.yaml"}, + DefaultNamespace: &testNamespace2, + }, + commands: testutil. + CmdRunOut("kubectl version --client -ojson", kubectlVersion118). + AndRunOut("kubectl --context kubecontext --namespace testNamespace2 create --dry-run=client -oyaml -f deployment.yaml", deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext --namespace testNamespace2 get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). + AndRun("kubectl --context kubecontext --namespace testNamespace2 apply -f -"), + builds: []build.Artifact{{ + ImageName: "leeroy-web", + Tag: "leeroy-web:v1", + }}, + waitForDeletions: true, + skipSkaffoldNamespaceOption: true, + }, + { + description: "deploy success (default namespace with env template)", + kubectl: latest.KubectlDeploy{ + Manifests: []string{"deployment.yaml"}, + DefaultNamespace: &testNamespace2FromEnvTemplate, + }, + commands: testutil. + CmdRunOut("kubectl version --client -ojson", kubectlVersion118). + AndRunOut("kubectl --context kubecontext --namespace testNamespace2 create --dry-run=client -oyaml -f deployment.yaml", deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext --namespace testNamespace2 get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). + AndRun("kubectl --context kubecontext --namespace testNamespace2 apply -f -"), + builds: []build.Artifact{{ + ImageName: "leeroy-web", + Tag: "leeroy-web:v1", + }}, + waitForDeletions: true, + skipSkaffoldNamespaceOption: true, + envs: map[string]string{ + "MYENV": "Namesp", + }, + }, { description: "http manifest", kubectl: latest.KubectlDeploy{ @@ -179,11 +222,13 @@ func TestKubectlDeploy(t *testing.T) { commands: testutil. CmdRunOut("kubectl version --client -ojson", kubectlVersion112). AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml -f http://remote.yaml", deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"), builds: []build.Artifact{{ ImageName: "leeroy-web", Tag: "leeroy-web:v1", }}, + waitForDeletions: true, }, { description: "deploy command error", @@ -193,12 +238,14 @@ func TestKubectlDeploy(t *testing.T) { commands: testutil. CmdRunOut("kubectl version --client -ojson", kubectlVersion112). AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). AndRunErr("kubectl --context kubecontext --namespace testNamespace apply -f -", fmt.Errorf("")), builds: []build.Artifact{{ ImageName: "leeroy-web", Tag: "leeroy-web:v1", }}, - shouldErr: true, + shouldErr: true, + waitForDeletions: true, }, { description: "additional flags", @@ -219,19 +266,25 @@ func TestKubectlDeploy(t *testing.T) { ImageName: "leeroy-web", Tag: "leeroy-web:v1", }}, - waitForDeletions: true, shouldErr: true, + waitForDeletions: true, }, } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { + t.SetEnvs(test.envs) t.Override(&util.DefaultExecCommand, test.commands) t.NewTempDir(). Write("deployment.yaml", deploymentWebYAML). Touch("empty.ignored"). Chdir() - k := NewKubectlDeployer(&kubectlConfig{ + skaffoldNamespaceOption := "" + if !test.skipSkaffoldNamespaceOption { + skaffoldNamespaceOption = testNamespace + } + + k, err := NewKubectlDeployer(&kubectlConfig{ workingDir: ".", kubectl: test.kubectl, force: test.forceDeploy, @@ -240,9 +293,12 @@ func TestKubectlDeploy(t *testing.T) { Delay: 0 * time.Second, Max: 10 * time.Second, }, + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{ + Namespace: skaffoldNamespaceOption}}, }, nil) + t.RequireNoError(err) - _, err := k.Deploy(context.Background(), ioutil.Discard, test.builds) + _, err = k.Deploy(context.Background(), ioutil.Discard, test.builds) t.CheckError(test.shouldErr, err) }) @@ -310,11 +366,14 @@ func TestKubectlCleanup(t *testing.T) { Write("deployment.yaml", deploymentWebYAML). Chdir() - k := NewKubectlDeployer(&kubectlConfig{ + k, err := NewKubectlDeployer(&kubectlConfig{ workingDir: ".", kubectl: test.kubectl, + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: testNamespace}}, }, nil) - err := k.Cleanup(context.Background(), ioutil.Discard) + t.RequireNoError(err) + + err = k.Cleanup(context.Background(), ioutil.Discard) t.CheckError(test.shouldErr, err) }) @@ -355,11 +414,14 @@ func TestKubectlDeployerRemoteCleanup(t *testing.T) { Write("deployment.yaml", deploymentWebYAML). Chdir() - k := NewKubectlDeployer(&kubectlConfig{ + k, err := NewKubectlDeployer(&kubectlConfig{ workingDir: ".", kubectl: test.kubectl, + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: testNamespace}}, }, nil) - err := k.Cleanup(context.Background(), ioutil.Discard) + t.RequireNoError(err) + + err = k.Cleanup(context.Background(), ioutil.Discard) t.CheckNoError(err) }) @@ -374,30 +436,29 @@ func TestKubectlRedeploy(t *testing.T) { t.Override(&util.DefaultExecCommand, testutil. CmdRunOut("kubectl version --client -ojson", kubectlVersion112). - AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentAppYAMLv1+"\n---\n"+deploymentWebYAMLv1, ""). - AndRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", deploymentAppYAMLv1+"\n---\n"+deploymentWebYAMLv1). - AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentAppYAMLv2+"\n---\n"+deploymentWebYAMLv1, ""). - AndRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", deploymentAppYAMLv2). - AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentAppYAMLv2+"\n---\n"+deploymentWebYAMLv1, ""), + AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentAppYAMLv1+"\n---\n"+deploymentWebYAMLv1, ""). + AndRunInput("kubectl --context kubecontext apply -f -", deploymentAppYAMLv1+"\n---\n"+deploymentWebYAMLv1). + AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentAppYAMLv2+"\n---\n"+deploymentWebYAMLv1, ""). + AndRunInput("kubectl --context kubecontext apply -f -", deploymentAppYAMLv2). + AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentAppYAMLv2+"\n---\n"+deploymentWebYAMLv1, ""), ) - deployer := NewKubectlDeployer(&kubectlConfig{ - workingDir: tmpDir.Root(), + deployer, err := NewKubectlDeployer(&kubectlConfig{ + workingDir: ".", kubectl: latest.KubectlDeploy{ - Manifests: []string{tmpDir.Path("deployment-app.yaml"), tmpDir.Path("deployment-web.yaml")}, - }, + Manifests: []string{tmpDir.Path("deployment-app.yaml"), tmpDir.Path("deployment-web.yaml")}}, waitForDeletions: config.WaitForDeletions{ Enabled: true, Delay: 0 * time.Millisecond, - Max: 10 * time.Second, - }, + Max: 10 * time.Second}, }, nil) + t.RequireNoError(err) // Deploy one manifest - _, err := deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ + _, err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, {ImageName: "leeroy-app", Tag: "leeroy-app:v1"}, }) @@ -425,32 +486,32 @@ func TestKubectlWaitForDeletions(t *testing.T) { t.Override(&util.DefaultExecCommand, testutil. CmdRunOut("kubectl version --client -ojson", kubectlVersion112). - AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-web.yaml"), deploymentWebYAML). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ + AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-web.yaml"), deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ "items":[ {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-app"}}, {"metadata":{"name":"leeroy-front"}} ] }`). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ "items":[ {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-app"}}, {"metadata":{"name":"leeroy-front"}} ] }`). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ "items":[ {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, {"metadata":{"name":"leeroy-front"}} ] }`). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). - AndRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", deploymentWebYAMLv1), + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). + AndRunInput("kubectl --context kubecontext apply -f -", deploymentWebYAMLv1), ) - deployer := NewKubectlDeployer(&kubectlConfig{ + deployer, err := NewKubectlDeployer(&kubectlConfig{ workingDir: tmpDir.Root(), kubectl: latest.KubectlDeploy{ Manifests: []string{tmpDir.Path("deployment-web.yaml")}, @@ -461,9 +522,10 @@ func TestKubectlWaitForDeletions(t *testing.T) { Max: 10 * time.Second, }, }, nil) + t.RequireNoError(err) var out bytes.Buffer - _, err := deployer.Deploy(context.Background(), &out, []build.Artifact{ + _, err = deployer.Deploy(context.Background(), &out, []build.Artifact{ {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, }) @@ -480,8 +542,8 @@ func TestKubectlWaitForDeletionsFails(t *testing.T) { t.Override(&util.DefaultExecCommand, testutil. CmdRunOut("kubectl version --client -ojson", kubectlVersion112). - AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-web.yaml"), deploymentWebYAML). - AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ + AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-web.yaml"), deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, `{ "items":[ {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-app"}} @@ -489,7 +551,7 @@ func TestKubectlWaitForDeletionsFails(t *testing.T) { }`), ) - deployer := NewKubectlDeployer(&kubectlConfig{ + deployer, err := NewKubectlDeployer(&kubectlConfig{ workingDir: tmpDir.Root(), kubectl: latest.KubectlDeploy{ Manifests: []string{tmpDir.Path("deployment-web.yaml")}, @@ -500,8 +562,9 @@ func TestKubectlWaitForDeletionsFails(t *testing.T) { Max: 100 * time.Millisecond, }, }, nil) + t.RequireNoError(err) - _, err := deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ + _, err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, }) @@ -559,11 +622,13 @@ func TestDependencies(t *testing.T) { Touch("00/b.yaml", "00/a.yaml"). Chdir() - k := NewKubectlDeployer(&kubectlConfig{ + k, err := NewKubectlDeployer(&kubectlConfig{ kubectl: latest.KubectlDeploy{ Manifests: test.manifests, }, }, nil) + t.RequireNoError(err) + dependencies, err := k.Dependencies() t.CheckNoError(err) @@ -674,17 +739,16 @@ spec: t.Override(&util.DefaultExecCommand, testutil. CmdRunOut("kubectl version --client -ojson", kubectlVersion112). AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment.yaml"), test.input)) - - deployer := NewKubectlDeployer(&kubectlConfig{ + deployer, err := NewKubectlDeployer(&kubectlConfig{ workingDir: ".", defaultRepo: "gcr.io/project", kubectl: latest.KubectlDeploy{ Manifests: []string{tmpDir.Path("deployment.yaml")}, }, }, nil) + t.RequireNoError(err) var b bytes.Buffer - err := deployer.Render(context.Background(), &b, test.builds, true, "") - + err = deployer.Render(context.Background(), &b, test.builds, true, "") t.CheckNoError(err) t.CheckDeepEqual(test.expected, b.String()) }) @@ -720,14 +784,15 @@ func TestGCSManifests(t *testing.T) { if err := ioutil.WriteFile(manifestTmpDir+"/deployment.yaml", []byte(deploymentWebYAML), os.ModePerm); err != nil { t.Fatal(err) } - - k := NewKubectlDeployer(&kubectlConfig{ + k, err := NewKubectlDeployer(&kubectlConfig{ workingDir: ".", kubectl: test.kubectl, skipRender: test.skipRender, + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: testNamespace}}, }, nil) + t.RequireNoError(err) - _, err := k.Deploy(context.Background(), ioutil.Discard, nil) + _, err = k.Deploy(context.Background(), ioutil.Discard, nil) t.CheckError(test.shouldErr, err) }) @@ -745,7 +810,7 @@ type kubectlConfig struct { } func (c *kubectlConfig) GetKubeContext() string { return testKubeContext } -func (c *kubectlConfig) GetKubeNamespace() string { return testNamespace } +func (c *kubectlConfig) GetKubeNamespace() string { return c.Opts.Namespace } func (c *kubectlConfig) WorkingDir() string { return c.workingDir } func (c *kubectlConfig) SkipRender() bool { return c.skipRender } func (c *kubectlConfig) ForceDeploy() bool { return c.force } diff --git a/pkg/skaffold/deploy/kustomize.go b/pkg/skaffold/deploy/kustomize.go index 4ffe8f233e3..94c4f08e5fc 100644 --- a/pkg/skaffold/deploy/kustomize.go +++ b/pkg/skaffold/deploy/kustomize.go @@ -99,14 +99,23 @@ type KustomizeDeployer struct { globalConfig string } -func NewKustomizeDeployer(cfg Config, labels map[string]string) *KustomizeDeployer { +func NewKustomizeDeployer(cfg Config, labels map[string]string) (*KustomizeDeployer, error) { + defaultNamespace := "" + if cfg.Pipeline().Deploy.KustomizeDeploy.DefaultNamespace != nil { + var err error + defaultNamespace, err = util.ExpandEnvTemplate(*cfg.Pipeline().Deploy.KustomizeDeploy.DefaultNamespace, nil) + if err != nil { + return nil, err + } + } + return &KustomizeDeployer{ KustomizeDeploy: cfg.Pipeline().Deploy.KustomizeDeploy, - kubectl: deploy.NewCLI(cfg, cfg.Pipeline().Deploy.KustomizeDeploy.Flags), + kubectl: deploy.NewCLI(cfg, cfg.Pipeline().Deploy.KustomizeDeploy.Flags, defaultNamespace), insecureRegistries: cfg.GetInsecureRegistries(), globalConfig: cfg.GlobalConfig(), labels: labels, - } + }, nil } // Deploy runs `kubectl apply` on the manifest generated by kustomize. diff --git a/pkg/skaffold/deploy/kustomize_test.go b/pkg/skaffold/deploy/kustomize_test.go index b40d8aa9f73..b083a4e93ec 100644 --- a/pkg/skaffold/deploy/kustomize_test.go +++ b/pkg/skaffold/deploy/kustomize_test.go @@ -35,12 +35,14 @@ import ( func TestKustomizeDeploy(t *testing.T) { tests := []struct { - description string - kustomize latest.KustomizeDeploy - builds []build.Artifact - commands util.Command - shouldErr bool - forceDeploy bool + description string + kustomize latest.KustomizeDeploy + builds []build.Artifact + commands util.Command + shouldErr bool + forceDeploy bool + skipSkaffoldNamespaceOption bool + envs map[string]string }{ { description: "no manifest", @@ -67,6 +69,45 @@ func TestKustomizeDeploy(t *testing.T) { }}, forceDeploy: true, }, + { + description: "deploy success (default namespace)", + kustomize: latest.KustomizeDeploy{ + KustomizePaths: []string{"."}, + DefaultNamespace: &testNamespace2, + }, + commands: testutil. + CmdRunOut("kubectl version --client -ojson", kubectlVersion112). + AndRunOut("kustomize build .", deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext --namespace testNamespace2 get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). + AndRun("kubectl --context kubecontext --namespace testNamespace2 apply -f - --force --grace-period=0"), + builds: []build.Artifact{{ + ImageName: "leeroy-web", + Tag: "leeroy-web:v1", + }}, + forceDeploy: true, + skipSkaffoldNamespaceOption: true, + }, + { + description: "deploy success (default namespace with env template)", + kustomize: latest.KustomizeDeploy{ + KustomizePaths: []string{"."}, + DefaultNamespace: &testNamespace2FromEnvTemplate, + }, + commands: testutil. + CmdRunOut("kubectl version --client -ojson", kubectlVersion112). + AndRunOut("kustomize build .", deploymentWebYAML). + AndRunInputOut("kubectl --context kubecontext --namespace testNamespace2 get -f - --ignore-not-found -ojson", deploymentWebYAMLv1, ""). + AndRun("kubectl --context kubecontext --namespace testNamespace2 apply -f - --force --grace-period=0"), + builds: []build.Artifact{{ + ImageName: "leeroy-web", + Tag: "leeroy-web:v1", + }}, + forceDeploy: true, + skipSkaffoldNamespaceOption: true, + envs: map[string]string{ + "MYENV": "Namesp", + }, + }, { description: "deploy success with multiple kustomizations", kustomize: latest.KustomizeDeploy{ @@ -93,11 +134,17 @@ func TestKustomizeDeploy(t *testing.T) { } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { + t.SetEnvs(test.envs) t.Override(&util.DefaultExecCommand, test.commands) t.NewTempDir(). Chdir() - k := NewKustomizeDeployer(&kustomizeConfig{ + skaffoldNamespaceOption := "" + if !test.skipSkaffoldNamespaceOption { + skaffoldNamespaceOption = testNamespace + } + + k, err := NewKustomizeDeployer(&kustomizeConfig{ workingDir: ".", force: test.forceDeploy, waitForDeletions: config.WaitForDeletions{ @@ -106,8 +153,11 @@ func TestKustomizeDeploy(t *testing.T) { Max: 10 * time.Second, }, kustomize: test.kustomize, - }, nil) - _, err := k.Deploy(context.Background(), ioutil.Discard, test.builds) + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{ + Namespace: skaffoldNamespaceOption, + }}}, nil) + t.RequireNoError(err) + _, err = k.Deploy(context.Background(), ioutil.Discard, test.builds) t.CheckError(test.shouldErr, err) }) @@ -169,11 +219,14 @@ func TestKustomizeCleanup(t *testing.T) { testutil.Run(t, test.description, func(t *testutil.T) { t.Override(&util.DefaultExecCommand, test.commands) - k := NewKustomizeDeployer(&kustomizeConfig{ + k, err := NewKustomizeDeployer(&kustomizeConfig{ workingDir: tmpDir.Root(), kustomize: test.kustomize, + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{ + Namespace: testNamespace}}, }, nil) - err := k.Cleanup(context.Background(), ioutil.Discard) + t.RequireNoError(err) + err = k.Cleanup(context.Background(), ioutil.Discard) t.CheckError(test.shouldErr, err) }) @@ -373,11 +426,13 @@ func TestDependenciesForKustomization(t *testing.T) { tmpDir.Write(path, contents) } - k := NewKustomizeDeployer(&kustomizeConfig{ + k, err := NewKustomizeDeployer(&kustomizeConfig{ kustomize: latest.KustomizeDeploy{ KustomizePaths: kustomizePaths, }, }, nil) + t.RequireNoError(err) + deps, err := k.Dependencies() t.CheckErrorAndDeepEqual(test.shouldErr, err, tmpDir.Paths(test.expected...), deps) @@ -616,15 +671,17 @@ spec: t.Override(&util.DefaultExecCommand, fakeCmd) t.NewTempDir().Chdir() - k := NewKustomizeDeployer(&kustomizeConfig{ + k, err := NewKustomizeDeployer(&kustomizeConfig{ workingDir: ".", kustomize: latest.KustomizeDeploy{ KustomizePaths: kustomizationPaths, }, + RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: testNamespace}}, }, test.labels) - var b bytes.Buffer - err := k.Render(context.Background(), &b, test.builds, true, "") + t.RequireNoError(err) + var b bytes.Buffer + err = k.Render(context.Background(), &b, test.builds, true, "") t.CheckError(test.shouldErr, err) t.CheckDeepEqual(test.expected, b.String()) }) @@ -643,7 +700,7 @@ func (c *kustomizeConfig) ForceDeploy() bool { return c. func (c *kustomizeConfig) WaitForDeletions() config.WaitForDeletions { return c.waitForDeletions } func (c *kustomizeConfig) WorkingDir() string { return c.workingDir } func (c *kustomizeConfig) GetKubeContext() string { return testKubeContext } -func (c *kustomizeConfig) GetKubeNamespace() string { return testNamespace } +func (c *kustomizeConfig) GetKubeNamespace() string { return c.Opts.Namespace } func (c *kustomizeConfig) Pipeline() latest.Pipeline { var pipeline latest.Pipeline pipeline.Deploy.DeployType.KustomizeDeploy = &c.kustomize diff --git a/pkg/skaffold/deploy/resource/deployment.go b/pkg/skaffold/deploy/resource/deployment.go index c648091e04a..7890e59dd40 100644 --- a/pkg/skaffold/deploy/resource/deployment.go +++ b/pkg/skaffold/deploy/resource/deployment.go @@ -101,7 +101,7 @@ func (d *Deployment) WithValidator(pd diag.Diagnose) *Deployment { } func (d *Deployment) CheckStatus(ctx context.Context, cfg kubectl.Config) { - kubeCtl := kubectl.NewCLI(cfg) + kubeCtl := kubectl.NewCLI(cfg, "") b, err := kubeCtl.RunOut(ctx, "rollout", "status", "deployment", d.name, "--namespace", d.namespace, "--watch=false") if ctx.Err() != nil { diff --git a/pkg/skaffold/kubectl/cli.go b/pkg/skaffold/kubectl/cli.go index 8a5bd621735..8ae29360bb4 100644 --- a/pkg/skaffold/kubectl/cli.go +++ b/pkg/skaffold/kubectl/cli.go @@ -41,11 +41,18 @@ type Config interface { GetKubeNamespace() string } -func NewCLI(cfg Config) *CLI { +// NewCLI creates a new kubectl CLI whereby the namespace from command +// line / environment variable takes precedence over "default namespace" +// defined in deployer configuration +func NewCLI(cfg Config, defaultNamespace string) *CLI { + ns := defaultNamespace + if nsFromOpts := cfg.GetKubeNamespace(); nsFromOpts != "" { + ns = nsFromOpts + } return &CLI{ KubeContext: cfg.GetKubeContext(), KubeConfig: cfg.GetKubeConfig(), - Namespace: cfg.GetKubeNamespace(), + Namespace: ns, } } diff --git a/pkg/skaffold/kubectl/cli_test.go b/pkg/skaffold/kubectl/cli_test.go index 26096c6844a..f3377299b85 100644 --- a/pkg/skaffold/kubectl/cli_test.go +++ b/pkg/skaffold/kubectl/cli_test.go @@ -69,7 +69,7 @@ func TestCLI(t *testing.T) { kubeContext: kubeContext, kubeConfig: test.kubeconfig, namespace: test.namespace, - }) + }, "") err := cli.Run(context.Background(), nil, nil, "exec", "arg1", "arg2") t.CheckNoError(err) @@ -88,7 +88,7 @@ func TestCLI(t *testing.T) { kubeContext: kubeContext, kubeConfig: test.kubeconfig, namespace: test.namespace, - }) + }, "") out, err := cli.RunOut(context.Background(), "exec", "arg1", "arg2") t.CheckNoError(err) @@ -108,7 +108,7 @@ func TestCLI(t *testing.T) { kubeContext: kubeContext, kubeConfig: test.kubeconfig, namespace: test.namespace, - }) + }, "") cmd := cli.CommandWithStrictCancellation(context.Background(), "exec", "arg1", "arg2") out, err := util.RunCmdOut(cmd.Cmd) diff --git a/pkg/skaffold/runner/deploy_test.go b/pkg/skaffold/runner/deploy_test.go index 7307230de50..fadbbe39088 100644 --- a/pkg/skaffold/runner/deploy_test.go +++ b/pkg/skaffold/runner/deploy_test.go @@ -144,14 +144,16 @@ func TestSkaffoldDeployRenderOnly(t *testing.T) { KubeContext: "does-not-exist", } + deployer, err := getDeployer(runCtx, nil) + t.RequireNoError(err) r := SkaffoldRunner{ runCtx: runCtx, - kubectlCLI: kubectl.NewCLI(runCtx), - deployer: getDeployer(runCtx, nil), + kubectlCLI: kubectl.NewCLI(runCtx, ""), + deployer: deployer, } var builds []build.Artifact - err := r.Deploy(context.Background(), ioutil.Discard, builds) + err = r.Deploy(context.Background(), ioutil.Discard, builds) t.CheckNoError(err) }) diff --git a/pkg/skaffold/runner/load_images_test.go b/pkg/skaffold/runner/load_images_test.go index fe10cac6eba..063fb67dee9 100644 --- a/pkg/skaffold/runner/load_images_test.go +++ b/pkg/skaffold/runner/load_images_test.go @@ -184,7 +184,7 @@ func runImageLoadingTests(t *testing.T, tests []ImageLoadingTest, loadingFunc fu r := &SkaffoldRunner{ runCtx: runCtx, - kubectlCLI: kubectl.NewCLI(runCtx), + kubectlCLI: kubectl.NewCLI(runCtx, ""), builds: test.built, } err := loadingFunc(r, test) diff --git a/pkg/skaffold/runner/new.go b/pkg/skaffold/runner/new.go index 4bf23c13cca..970d6328899 100644 --- a/pkg/skaffold/runner/new.go +++ b/pkg/skaffold/runner/new.go @@ -43,7 +43,7 @@ import ( // NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig func NewForConfig(runCtx *runcontext.RunContext) (*SkaffoldRunner, error) { - kubectlCLI := kubectl.NewCLI(runCtx) + kubectlCLI := kubectl.NewCLI(runCtx, "") tagger, err := getTagger(runCtx) if err != nil { @@ -58,7 +58,11 @@ func NewForConfig(runCtx *runcontext.RunContext) (*SkaffoldRunner, error) { labeller := deploy.NewLabeller(runCtx.AddSkaffoldLabels(), runCtx.CustomLabels()) tester := getTester(runCtx, imagesAreLocal) syncer := getSyncer(runCtx) - deployer := getDeployer(runCtx, labeller.Labels()) + var deployer deploy.Deployer + deployer, err = getDeployer(runCtx, labeller.Labels()) + if err != nil { + return nil, fmt.Errorf("creating deployer: %w", err) + } depLister := func(ctx context.Context, artifact *latest.Artifact) ([]string, error) { buildDependencies, err := build.DependenciesForArtifact(ctx, artifact, runCtx.GetInsecureRegistries()) @@ -187,7 +191,7 @@ func getSyncer(cfg sync.Config) sync.Syncer { return sync.NewSyncer(cfg) } -func getDeployer(cfg deploy.Config, labels map[string]string) deploy.Deployer { +func getDeployer(cfg deploy.Config, labels map[string]string) (deploy.Deployer, error) { d := cfg.Pipeline().Deploy var deployers deploy.DeployerMux @@ -201,19 +205,27 @@ func getDeployer(cfg deploy.Config, labels map[string]string) deploy.Deployer { } if d.KubectlDeploy != nil { - deployers = append(deployers, deploy.NewKubectlDeployer(cfg, labels)) + deployer, err := deploy.NewKubectlDeployer(cfg, labels) + if err != nil { + return nil, err + } + deployers = append(deployers, deployer) } if d.KustomizeDeploy != nil { - deployers = append(deployers, deploy.NewKustomizeDeployer(cfg, labels)) + deployer, err := deploy.NewKustomizeDeployer(cfg, labels) + if err != nil { + return nil, err + } + deployers = append(deployers, deployer) } // avoid muxing overhead when only a single deployer is configured if len(deployers) == 1 { - return deployers[0] + return deployers[0], nil } - return deployers + return deployers, nil } func getTagger(runCtx *runcontext.RunContext) (tag.Tagger, error) { diff --git a/pkg/skaffold/runner/new_test.go b/pkg/skaffold/runner/new_test.go index aaeea6b2efc..b47e464dac7 100644 --- a/pkg/skaffold/runner/new_test.go +++ b/pkg/skaffold/runner/new_test.go @@ -27,90 +27,93 @@ import ( "github.com/GoogleContainerTools/skaffold/testutil" ) -func TestGetDeployer(t *testing.T) { - tests := []struct { - description string - cfg latest.DeployType - expected deploy.Deployer - }{ - { - description: "no deployer", - expected: deploy.DeployerMux{}, - }, - { - description: "helm deployer", - cfg: latest.DeployType{HelmDeploy: &latest.HelmDeploy{}}, - expected: deploy.NewHelmDeployer(&runcontext.RunContext{}, nil), - }, - { - description: "kubectl deployer", - cfg: latest.DeployType{KubectlDeploy: &latest.KubectlDeploy{}}, - expected: deploy.NewKubectlDeployer(&runcontext.RunContext{ - Cfg: latest.Pipeline{ - Deploy: latest.DeployConfig{ - DeployType: latest.DeployType{ - KubectlDeploy: &latest.KubectlDeploy{ - Flags: latest.KubectlFlags{}, +func TestGetDeployer(tOuter *testing.T) { + testutil.Run(tOuter, "TestGetDeployer", func(t *testutil.T) { + tests := []struct { + description string + cfg latest.DeployType + expected deploy.Deployer + }{ + { + description: "no deployer", + expected: deploy.DeployerMux{}, + }, + { + description: "helm deployer", + cfg: latest.DeployType{HelmDeploy: &latest.HelmDeploy{}}, + expected: deploy.NewHelmDeployer(&runcontext.RunContext{}, nil), + }, + { + description: "kubectl deployer", + cfg: latest.DeployType{KubectlDeploy: &latest.KubectlDeploy{}}, + expected: t.RequireNonNilResult(deploy.NewKubectlDeployer(&runcontext.RunContext{ + Cfg: latest.Pipeline{ + Deploy: latest.DeployConfig{ + DeployType: latest.DeployType{ + KubectlDeploy: &latest.KubectlDeploy{ + Flags: latest.KubectlFlags{}, + }, }, }, }, - }, - }, nil), - }, - { - description: "kustomize deployer", - cfg: latest.DeployType{KustomizeDeploy: &latest.KustomizeDeploy{}}, - expected: deploy.NewKustomizeDeployer(&runcontext.RunContext{ - Cfg: latest.Pipeline{ - Deploy: latest.DeployConfig{ - DeployType: latest.DeployType{ - KustomizeDeploy: &latest.KustomizeDeploy{ - Flags: latest.KubectlFlags{}, + }, nil)).(deploy.Deployer), + }, + { + description: "kustomize deployer", + cfg: latest.DeployType{KustomizeDeploy: &latest.KustomizeDeploy{}}, + expected: t.RequireNonNilResult(deploy.NewKustomizeDeployer(&runcontext.RunContext{ + Cfg: latest.Pipeline{ + Deploy: latest.DeployConfig{ + DeployType: latest.DeployType{ + KustomizeDeploy: &latest.KustomizeDeploy{ + Flags: latest.KubectlFlags{}, + }, }, }, }, - }, - }, nil), - }, - { - description: "kpt deployer", - cfg: latest.DeployType{KptDeploy: &latest.KptDeploy{}}, - expected: deploy.NewKptDeployer(&runcontext.RunContext{}, nil), - }, - { - description: "multiple deployers", - cfg: latest.DeployType{ - HelmDeploy: &latest.HelmDeploy{}, - KptDeploy: &latest.KptDeploy{}, + }, nil)).(deploy.Deployer), }, - expected: deploy.DeployerMux{ - deploy.NewHelmDeployer(&runcontext.RunContext{}, nil), - deploy.NewKptDeployer(&runcontext.RunContext{}, nil), + { + description: "kpt deployer", + cfg: latest.DeployType{KptDeploy: &latest.KptDeploy{}}, + expected: deploy.NewKptDeployer(&runcontext.RunContext{}, nil), }, - }, - } - for _, test := range tests { - testutil.Run(t, test.description, func(t *testutil.T) { - deployer := getDeployer(&runcontext.RunContext{ - Cfg: latest.Pipeline{ - Deploy: latest.DeployConfig{ - DeployType: test.cfg, - }, + { + description: "multiple deployers", + cfg: latest.DeployType{ + HelmDeploy: &latest.HelmDeploy{}, + KptDeploy: &latest.KptDeploy{}, }, - }, nil) + expected: deploy.DeployerMux{ + deploy.NewHelmDeployer(&runcontext.RunContext{}, nil), + deploy.NewKptDeployer(&runcontext.RunContext{}, nil), + }, + }, + } + for _, test := range tests { + testutil.Run(tOuter, test.description, func(t *testutil.T) { + deployer, err := getDeployer(&runcontext.RunContext{ + Cfg: latest.Pipeline{ + Deploy: latest.DeployConfig{ + DeployType: test.cfg, + }, + }, + }, nil) - t.CheckTypeEquality(test.expected, deployer) + t.RequireNoError(err) + t.CheckTypeEquality(test.expected, deployer) - if reflect.TypeOf(test.expected) == reflect.TypeOf(deploy.DeployerMux{}) { - expected := test.expected.(deploy.DeployerMux) - deployers := deployer.(deploy.DeployerMux) - t.CheckDeepEqual(len(expected), len(deployers)) - for i, v := range expected { - t.CheckTypeEquality(v, deployers[i]) + if reflect.TypeOf(test.expected) == reflect.TypeOf(deploy.DeployerMux{}) { + expected := test.expected.(deploy.DeployerMux) + deployers := deployer.(deploy.DeployerMux) + t.CheckDeepEqual(len(expected), len(deployers)) + for i, v := range expected { + t.CheckTypeEquality(v, deployers[i]) + } } - } - }) - } + }) + } + }) } func TestCreateComponents(t *testing.T) { diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 5462fd288bb..6346e4bb643 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -470,6 +470,9 @@ type KubectlDeploy struct { // Flags are additional flags passed to `kubectl`. Flags KubectlFlags `yaml:"flags,omitempty"` + + // DefaultNamespace is the default namespace passed to kubectl on deployment if no other override is given. + DefaultNamespace *string `yaml:"defaultNamespace,omitempty"` } // KubectlFlags are additional flags passed on the command @@ -524,6 +527,9 @@ type KustomizeDeploy struct { // BuildArgs are additional args passed to `kustomize build`. BuildArgs []string `yaml:"buildArgs,omitempty"` + + // DefaultNamespace is the default namespace passed to kubectl on deployment if no other override is given. + DefaultNamespace *string `yaml:"defaultNamespace,omitempty"` } // KptDeploy *alpha* uses the `kpt` CLI to manage and deploy manifests. diff --git a/pkg/skaffold/sync/types.go b/pkg/skaffold/sync/types.go index c0eeb65b6a0..d6904e55f11 100644 --- a/pkg/skaffold/sync/types.go +++ b/pkg/skaffold/sync/types.go @@ -48,7 +48,7 @@ type Config interface { func NewSyncer(cfg Config) Syncer { return &podSyncer{ - kubectl: pkgkubectl.NewCLI(cfg), + kubectl: pkgkubectl.NewCLI(cfg, ""), namespaces: cfg.GetNamespaces(), } } diff --git a/testutil/checks.go b/testutil/checks.go index 5e12c5c4e33..cc3e7d10d27 100644 --- a/testutil/checks.go +++ b/testutil/checks.go @@ -48,3 +48,17 @@ func (t *T) RequireNoError(err error) { t.FailNow() } } + +func (t *T) RequireNonNilResult(x interface{}, err error) interface{} { + t.Helper() + + if err != nil { + t.Errorf("unexpected error (failing test now): %s", err) + t.FailNow() + } + if x == nil { + t.Errorf("unexpected nil value (failing test now)") + t.FailNow() + } + return x +}