diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index 9fd02e82ec6..1b17d6e6c73 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -142,6 +142,7 @@ func AddRunDevFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&opts.Notification, "toot", false, "Emit a terminal beep after the deploy is complete") cmd.Flags().StringArrayVarP(&opts.Profiles, "profile", "p", nil, "Activate profiles by name") cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "", "Run deployments in the specified namespace") + cmd.Flags().StringVarP(&opts.DefaultRepo, "default-repo", "d", "", "Default repository value (overrides global config)") } func SetUpLogs(out io.Writer, level string) error { diff --git a/cmd/skaffold/app/cmd/config/config_test.go b/cmd/skaffold/app/cmd/config/config_test.go index 60b2f11f998..d6889c2ea3c 100644 --- a/cmd/skaffold/app/cmd/config/config_test.go +++ b/cmd/skaffold/app/cmd/config/config_test.go @@ -67,6 +67,7 @@ func TestReadConfig(t *testing.T) { } func TestSetAndUnsetConfig(t *testing.T) { + dummyContext := "dummy_context" var tests = []struct { expectedSetCfg *Config expectedUnsetCfg *Config @@ -103,7 +104,11 @@ func TestSetAndUnsetConfig(t *testing.T) { key: "not_a_real_value", shouldErrSet: true, expectedSetCfg: &Config{ - ContextConfigs: []*ContextConfig{{}}, + ContextConfigs: []*ContextConfig{ + { + Kubecontext: dummyContext, + }, + }, }, }, { @@ -130,8 +135,18 @@ func TestSetAndUnsetConfig(t *testing.T) { c, _ := yaml.Marshal(*emptyConfig) cfg, teardown := testutil.TempFile(t, "config", c) + defer func() { + // reset all config context for next test + teardown() + kubecontext = dummyContext + configFile = "" + global = false + }() + // setup config context - kubecontext = test.kubecontext + if test.kubecontext != "" { + kubecontext = test.kubecontext + } configFile = cfg global = test.global @@ -155,12 +170,6 @@ func TestSetAndUnsetConfig(t *testing.T) { t.Error(cfgErr) } testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedUnsetCfg, newConfig) - - // reset all config context for next test - teardown() - kubecontext = "" - configFile = "" - global = false }) } } diff --git a/cmd/skaffold/app/cmd/config/list.go b/cmd/skaffold/app/cmd/config/list.go index 3b10573765b..5346ee8a8ac 100644 --- a/cmd/skaffold/app/cmd/config/list.go +++ b/cmd/skaffold/app/cmd/config/list.go @@ -31,7 +31,6 @@ func NewCmdList(out io.Writer) *cobra.Command { Short: "List all values set in the global skaffold config", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - resolveKubectlContext() return runList(out) }, } @@ -55,7 +54,7 @@ func runList(out io.Writer) error { return errors.Wrap(err, "marshaling config") } } else { - config, err := getConfigForKubectx() + config, err := GetConfigForKubectx() if err != nil { return err } diff --git a/cmd/skaffold/app/cmd/config/set.go b/cmd/skaffold/app/cmd/config/set.go index c29a36bd6b5..5595541f09b 100644 --- a/cmd/skaffold/app/cmd/config/set.go +++ b/cmd/skaffold/app/cmd/config/set.go @@ -34,7 +34,6 @@ func NewCmdSet(out io.Writer) *cobra.Command { Short: "Set a value in the global skaffold config", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - resolveKubectlContext() if err := setConfigValue(args[0], args[1]); err != nil { return err } diff --git a/cmd/skaffold/app/cmd/config/util.go b/cmd/skaffold/app/cmd/config/util.go index 235d4a7b9a6..4fb6b283853 100644 --- a/cmd/skaffold/app/cmd/config/util.go +++ b/cmd/skaffold/app/cmd/config/util.go @@ -17,6 +17,7 @@ limitations under the License. package config import ( + "fmt" "io/ioutil" "path/filepath" @@ -59,7 +60,10 @@ func resolveConfigFile() error { return util.VerifyOrCreateFile(configFile) } +// ReadConfigForFile reads the specified file and returns the contents +// parsed into a Config struct. func ReadConfigForFile(filename string) (*Config, error) { + fmt.Printf("reading config for file %s\n", filename) contents, err := ioutil.ReadFile(filename) if err != nil { return nil, errors.Wrap(err, "reading global config") @@ -78,10 +82,12 @@ func readConfig() (*Config, error) { return ReadConfigForFile(configFile) } -// return the specific config to be modified based on the provided kube context. -// either returns the config corresponding to the provided or current context, +// GetConfigForKubectx returns the specific config to be modified based on the +// provided kube context. +// Either returns the config corresponding to the provided or current context, // or the global config if that is specified (or if no current context is set). -func getConfigForKubectx() (*ContextConfig, error) { +func GetConfigForKubectx() (*ContextConfig, error) { + resolveKubectlContext() cfg, err := readConfig() if err != nil { return nil, err @@ -98,7 +104,17 @@ func getConfigForKubectx() (*ContextConfig, error) { return nil, nil } +// GetGlobalConfig returns the global config values +func GetGlobalConfig() (*ContextConfig, error) { + cfg, err := readConfig() + if err != nil { + return nil, err + } + return cfg.Global, nil +} + func getOrCreateConfigForKubectx() (*ContextConfig, error) { + resolveKubectlContext() cfg, err := readConfig() if err != nil { return nil, err @@ -129,3 +145,32 @@ func getOrCreateConfigForKubectx() (*ContextConfig, error) { return newCfg, nil } + +func GetDefaultRepo(cliValue string) (string, error) { + // CLI flag takes precedence. If no default-repo specified from a flag, + // retrieve the value from the global config. + if cliValue != "" { + return cliValue, nil + } + cfg, err := GetConfigForKubectx() + if err != nil { + return "", errors.Wrap(err, "retrieving global config") + } + var defaultRepo string + if cfg != nil { + defaultRepo = cfg.DefaultRepo + } + if defaultRepo == "" { + // if we don't have a defaultRepo value set for the current context, + // retrieve the global config and use this value as a fallback + cfg, err := GetGlobalConfig() + if err != nil { + return "", errors.Wrap(err, "retrieving global config") + } + if cfg != nil { + defaultRepo = cfg.DefaultRepo + } + } + + return defaultRepo, nil +} diff --git a/cmd/skaffold/app/cmd/runner.go b/cmd/skaffold/app/cmd/runner.go index 9da35519617..57440ec182f 100644 --- a/cmd/skaffold/app/cmd/runner.go +++ b/cmd/skaffold/app/cmd/runner.go @@ -17,10 +17,12 @@ limitations under the License. package cmd import ( + configutil "github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd/config" "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/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" ) @@ -41,6 +43,15 @@ func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.Sk return nil, nil, errors.Wrap(err, "applying profiles") } + defaultRepo, err := configutil.GetDefaultRepo(opts.DefaultRepo) + if err != nil { + return nil, nil, errors.Wrap(err, "getting default repo") + } + + if err = applyDefaultRepoSubstitution(config, defaultRepo); err != nil { + return nil, nil, errors.Wrap(err, "substituting default repos") + } + runner, err := runner.NewForConfig(opts, config) if err != nil { return nil, nil, errors.Wrap(err, "creating runner") @@ -48,3 +59,17 @@ func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.Sk return runner, config, nil } + +func applyDefaultRepoSubstitution(config *latest.SkaffoldPipeline, defaultRepo string) error { + if defaultRepo == "" { + // noop + return nil + } + for _, artifact := range config.Build.Artifacts { + artifact.ImageName = util.SubstituteDefaultRepoIntoImage(defaultRepo, artifact.ImageName) + } + for _, testCase := range config.Test { + testCase.ImageName = util.SubstituteDefaultRepoIntoImage(defaultRepo, testCase.ImageName) + } + return nil +} diff --git a/pkg/skaffold/config/options.go b/pkg/skaffold/config/options.go index 9563be8df5c..2ac0b2033fb 100644 --- a/pkg/skaffold/config/options.go +++ b/pkg/skaffold/config/options.go @@ -36,6 +36,7 @@ type SkaffoldOptions struct { Trigger string CustomLabels []string WatchPollInterval int + DefaultRepo string } // Labels returns a map of labels to be applied to all deployed diff --git a/pkg/skaffold/deploy/deploy.go b/pkg/skaffold/deploy/deploy.go index 07765944daa..3b6dc020364 100644 --- a/pkg/skaffold/deploy/deploy.go +++ b/pkg/skaffold/deploy/deploy.go @@ -111,20 +111,3 @@ func (m *multiDeployer) Cleanup(ctx context.Context, w io.Writer) error { return nil } - -func joinTagsToBuildResult(builds []build.Artifact, params map[string]string) (map[string]build.Artifact, error) { - imageToBuildResult := map[string]build.Artifact{} - for _, build := range builds { - imageToBuildResult[build.ImageName] = build - } - - paramToBuildResult := map[string]build.Artifact{} - for param, imageName := range params { - build, ok := imageToBuildResult[imageName] - if !ok { - return nil, fmt.Errorf("No build present for %s", imageName) - } - paramToBuildResult[param] = build - } - return paramToBuildResult, nil -} diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index b714b13ddae..9a57745af3e 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -46,15 +46,17 @@ type HelmDeployer struct { kubeContext string namespace string + defaultRepo string } // NewHelmDeployer returns a new HelmDeployer for a DeployConfig filled // with the needed configuration for `helm` -func NewHelmDeployer(cfg *latest.HelmDeploy, kubeContext string, namespace string) *HelmDeployer { +func NewHelmDeployer(cfg *latest.HelmDeploy, kubeContext string, namespace string, defaultRepo string) *HelmDeployer { return &HelmDeployer{ HelmDeploy: cfg, kubeContext: kubeContext, namespace: namespace, + defaultRepo: defaultRepo, } } @@ -125,7 +127,7 @@ func (h *HelmDeployer) deployRelease(ctx context.Context, out io.Writer, r lates color.Red.Fprintf(out, "Helm release %s not installed. Installing...\n", releaseName) isInstalled = false } - params, err := joinTagsToBuildResult(builds, r.Values) + params, err := h.joinTagsToBuildResult(builds, r.Values) if err != nil { return nil, errors.Wrap(err, "matching build results to chart values") } @@ -336,6 +338,24 @@ func (h *HelmDeployer) deleteRelease(ctx context.Context, out io.Writer, r lates return nil } +func (h *HelmDeployer) joinTagsToBuildResult(builds []build.Artifact, params map[string]string) (map[string]build.Artifact, error) { + imageToBuildResult := map[string]build.Artifact{} + for _, build := range builds { + imageToBuildResult[build.ImageName] = build + } + + paramToBuildResult := map[string]build.Artifact{} + for param, imageName := range params { + newImageName := util.SubstituteDefaultRepoIntoImage(h.defaultRepo, imageName) + build, ok := imageToBuildResult[newImageName] + if !ok { + return nil, fmt.Errorf("No build present for %s", imageName) + } + paramToBuildResult[param] = build + } + return paramToBuildResult, nil +} + func evaluateReleaseName(nameTemplate string) (string, error) { tmpl, err := util.ParseEnvTemplate(nameTemplate) if err != nil { diff --git a/pkg/skaffold/deploy/helm_test.go b/pkg/skaffold/deploy/helm_test.go index 2fa72877aaf..f540d386b16 100644 --- a/pkg/skaffold/deploy/helm_test.go +++ b/pkg/skaffold/deploy/helm_test.go @@ -252,19 +252,19 @@ func TestHelmDeploy(t *testing.T) { { description: "deploy success", cmd: &MockHelm{t: t}, - deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { description: "deploy success with recreatePods", cmd: &MockHelm{t: t}, - deployer: NewHelmDeployer(testDeployRecreatePodsConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployRecreatePodsConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { description: "deploy error unmatched parameter", cmd: &MockHelm{t: t}, - deployer: NewHelmDeployer(testDeployConfigParameterUnmatched, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployConfigParameterUnmatched, testKubeContext, testNamespace, ""), builds: testBuilds, shouldErr: true, }, @@ -284,7 +284,7 @@ func TestHelmDeploy(t *testing.T) { }, upgradeResult: fmt.Errorf("should not have called upgrade"), }, - deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { @@ -308,7 +308,7 @@ func TestHelmDeploy(t *testing.T) { }, upgradeResult: fmt.Errorf("should not have called upgrade"), }, - deployer: NewHelmDeployer(testDeployHelmStyleConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployHelmStyleConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { @@ -317,7 +317,7 @@ func TestHelmDeploy(t *testing.T) { t: t, installResult: fmt.Errorf("should not have called install"), }, - deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { @@ -327,7 +327,7 @@ func TestHelmDeploy(t *testing.T) { upgradeResult: fmt.Errorf("unexpected error"), }, shouldErr: true, - deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { @@ -337,7 +337,7 @@ func TestHelmDeploy(t *testing.T) { depResult: fmt.Errorf("unexpected error"), }, shouldErr: true, - deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployConfig, testKubeContext, testNamespace, ""), builds: testBuilds, }, { @@ -351,6 +351,7 @@ func TestHelmDeploy(t *testing.T) { testDeployFooWithPackaged, testKubeContext, testNamespace, + "", ), builds: testBuildsFoo, }, @@ -365,13 +366,14 @@ func TestHelmDeploy(t *testing.T) { testDeployFooWithPackaged, testKubeContext, testNamespace, + "", ), builds: testBuildsFoo, }, { description: "deploy and get templated release name", cmd: &MockHelm{t: t}, - deployer: NewHelmDeployer(testDeployWithTemplatedName, testKubeContext, testNamespace), + deployer: NewHelmDeployer(testDeployWithTemplatedName, testKubeContext, testNamespace, ""), builds: testBuilds, }, } @@ -537,7 +539,7 @@ func TestHelmDependencies(t *testing.T) { SetValues: map[string]string{"some.key": "somevalue"}, }, }, - }, testKubeContext, testNamespace) + }, testKubeContext, testNamespace, "") deps, err := deployer.Dependencies() diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index 0865bb0fa2c..9b005770a85 100644 --- a/pkg/skaffold/deploy/kubectl.go +++ b/pkg/skaffold/deploy/kubectl.go @@ -38,13 +38,14 @@ import ( type KubectlDeployer struct { *latest.KubectlDeploy - workingDir string - kubectl kubectl.CLI + workingDir string + kubectl kubectl.CLI + defaultRepo string } // NewKubectlDeployer returns a new KubectlDeployer for a DeployConfig filled // with the needed configuration for `kubectl apply` -func NewKubectlDeployer(workingDir string, cfg *latest.KubectlDeploy, kubeContext string, namespace string) *KubectlDeployer { +func NewKubectlDeployer(workingDir string, cfg *latest.KubectlDeploy, kubeContext string, namespace string, defaultRepo string) *KubectlDeployer { return &KubectlDeployer{ KubectlDeploy: cfg, workingDir: workingDir, @@ -53,6 +54,7 @@ func NewKubectlDeployer(workingDir string, cfg *latest.KubectlDeploy, kubeContex KubeContext: kubeContext, Flags: cfg.Flags, }, + defaultRepo: defaultRepo, } } @@ -79,7 +81,7 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu return nil, nil } - manifests, err = manifests.ReplaceImages(builds) + manifests, err = manifests.ReplaceImages(builds, k.defaultRepo) if err != nil { return nil, errors.Wrap(err, "replacing images in manifests") } diff --git a/pkg/skaffold/deploy/kubectl/images.go b/pkg/skaffold/deploy/kubectl/images.go index c1d75f5db5b..9b17d7eda10 100644 --- a/pkg/skaffold/deploy/kubectl/images.go +++ b/pkg/skaffold/deploy/kubectl/images.go @@ -22,14 +22,15 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" ) // for testing var warner Warner = &logrusWarner{} // ReplaceImages replaces image names in a list of manifests. -func (l *ManifestList) ReplaceImages(builds []build.Artifact) (ManifestList, error) { - replacer := newImageReplacer(builds) +func (l *ManifestList) ReplaceImages(builds []build.Artifact, defaultRepo string) (ManifestList, error) { + replacer := newImageReplacer(builds, defaultRepo) updated, err := l.Visit(replacer) if err != nil { @@ -43,17 +44,19 @@ func (l *ManifestList) ReplaceImages(builds []build.Artifact) (ManifestList, err } type imageReplacer struct { + defaultRepo string tagsByImageName map[string]string found map[string]bool } -func newImageReplacer(builds []build.Artifact) *imageReplacer { +func newImageReplacer(builds []build.Artifact, defaultRepo string) *imageReplacer { tagsByImageName := make(map[string]string) for _, build := range builds { tagsByImageName[build.ImageName] = build.Tag } return &imageReplacer{ + defaultRepo: defaultRepo, tagsByImageName: tagsByImageName, found: make(map[string]bool), } @@ -63,9 +66,21 @@ func (r *imageReplacer) Matches(key string) bool { return key == "image" } -func (r *imageReplacer) NewValue(key string, old interface{}) (bool, interface{}) { +func (r *imageReplacer) NewValue(old interface{}) (bool, interface{}) { image := old.(string) + found, tag := r.parseAndReplace(image) + if !found { + subbedImage := r.substituteRepoIntoImage(image) + if image == subbedImage { + return found, tag + } + // no match, so try substituting in defaultRepo value + found, tag = r.parseAndReplace(subbedImage) + } + return found, tag +} +func (r *imageReplacer) parseAndReplace(image string) (bool, interface{}) { parsed, err := docker.ParseReference(image) if err != nil { warner.Warnf("Couldn't parse image: %s", image) @@ -82,7 +97,6 @@ func (r *imageReplacer) NewValue(key string, old interface{}) (bool, interface{} return true, tag } } - return false, nil } @@ -93,3 +107,7 @@ func (r *imageReplacer) Check() { } } } + +func (r *imageReplacer) substituteRepoIntoImage(originalImage string) string { + return util.SubstituteDefaultRepoIntoImage(r.defaultRepo, originalImage) +} diff --git a/pkg/skaffold/deploy/kubectl/images_test.go b/pkg/skaffold/deploy/kubectl/images_test.go index 4975ba00b8a..b5cb5aedffd 100644 --- a/pkg/skaffold/deploy/kubectl/images_test.go +++ b/pkg/skaffold/deploy/kubectl/images_test.go @@ -100,7 +100,7 @@ spec: fakeWarner := &fakeWarner{} warner = fakeWarner - resultManifest, err := manifests.ReplaceImages(builds) + resultManifest, err := manifests.ReplaceImages(builds, "") testutil.CheckErrorAndDeepEqual(t, false, err, expected.String(), resultManifest.String()) testutil.CheckErrorAndDeepEqual(t, false, err, []string{ @@ -114,7 +114,7 @@ func TestReplaceEmptyManifest(t *testing.T) { manifests := ManifestList{[]byte(""), []byte(" ")} expected := ManifestList{} - resultManifest, err := manifests.ReplaceImages(nil) + resultManifest, err := manifests.ReplaceImages(nil, "") testutil.CheckErrorAndDeepEqual(t, false, err, expected.String(), resultManifest.String()) } @@ -122,7 +122,7 @@ func TestReplaceEmptyManifest(t *testing.T) { func TestReplaceInvalidManifest(t *testing.T) { manifests := ManifestList{[]byte("INVALID")} - _, err := manifests.ReplaceImages(nil) + _, err := manifests.ReplaceImages(nil, "") testutil.CheckError(t, true, err) } diff --git a/pkg/skaffold/deploy/kubectl/visitor.go b/pkg/skaffold/deploy/kubectl/visitor.go index 779515e2c09..68ca5fa8c5d 100644 --- a/pkg/skaffold/deploy/kubectl/visitor.go +++ b/pkg/skaffold/deploy/kubectl/visitor.go @@ -25,7 +25,7 @@ import ( type Replacer interface { Matches(key string) bool - NewValue(key string, old interface{}) (bool, interface{}) + NewValue(old interface{}) (bool, interface{}) } // Visit recursively visits a list of manifests and applies transformations of them. @@ -70,7 +70,7 @@ func recursiveVisit(i interface{}, replacer Replacer) { continue } - ok, newValue := replacer.NewValue(key, v) + ok, newValue := replacer.NewValue(v) if ok { t[k] = newValue } diff --git a/pkg/skaffold/deploy/kubectl_test.go b/pkg/skaffold/deploy/kubectl_test.go index c8a7c40c010..c51a2e1c71a 100644 --- a/pkg/skaffold/deploy/kubectl_test.go +++ b/pkg/skaffold/deploy/kubectl_test.go @@ -143,7 +143,7 @@ func TestKubectlDeploy(t *testing.T) { util.DefaultExecCommand = test.command } - k := NewKubectlDeployer(tmpDir.Root(), test.cfg, testKubeContext, testNamespace) + k := NewKubectlDeployer(tmpDir.Root(), test.cfg, testKubeContext, testNamespace, "") _, err := k.Deploy(context.Background(), ioutil.Discard, test.builds) testutil.CheckError(t, test.shouldErr, err) @@ -199,7 +199,7 @@ func TestKubectlCleanup(t *testing.T) { util.DefaultExecCommand = test.command } - k := NewKubectlDeployer(tmpDir.Root(), test.cfg, testKubeContext, testNamespace) + k := NewKubectlDeployer(tmpDir.Root(), test.cfg, testKubeContext, testNamespace, "") err := k.Cleanup(context.Background(), ioutil.Discard) testutil.CheckError(t, test.shouldErr, err) @@ -219,7 +219,7 @@ func TestKubectlRedeploy(t *testing.T) { cfg := &latest.KubectlDeploy{ Manifests: []string{"deployment-web.yaml", "deployment-app.yaml"}, } - deployer := NewKubectlDeployer(tmpDir.Root(), cfg, testKubeContext, testNamespace) + deployer := NewKubectlDeployer(tmpDir.Root(), cfg, testKubeContext, testNamespace, "") // Deploy one manifest deployed, err := deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ diff --git a/pkg/skaffold/deploy/kustomize.go b/pkg/skaffold/deploy/kustomize.go index 87545dcb35e..70e563ef687 100644 --- a/pkg/skaffold/deploy/kustomize.go +++ b/pkg/skaffold/deploy/kustomize.go @@ -18,6 +18,7 @@ package deploy import ( "context" + "fmt" "io" "io/ioutil" "os/exec" @@ -55,11 +56,11 @@ type configMapGenerator struct { type KustomizeDeployer struct { *latest.KustomizeDeploy - kubectl kubectl.CLI + kubectl kubectl.CLI + defaultRepo string } -// NewKustomizeDeployer returns a new KustomizeDeployer. -func NewKustomizeDeployer(cfg *latest.KustomizeDeploy, kubeContext string, namespace string) *KustomizeDeployer { +func NewKustomizeDeployer(cfg *latest.KustomizeDeploy, kubeContext string, namespace string, defaultRepo string) *KustomizeDeployer { return &KustomizeDeployer{ KustomizeDeploy: cfg, kubectl: kubectl.CLI{ @@ -67,6 +68,7 @@ func NewKustomizeDeployer(cfg *latest.KustomizeDeploy, kubeContext string, names KubeContext: kubeContext, Flags: cfg.Flags, }, + defaultRepo: defaultRepo, } } @@ -88,10 +90,13 @@ func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds [] return nil, nil } - manifests, err = manifests.ReplaceImages(builds) + manifests, err = manifests.ReplaceImages(builds, k.defaultRepo) if err != nil { return nil, errors.Wrap(err, "replacing images in manifests") } + for _, manifest := range manifests { + fmt.Printf("manifest: %s\n", string(manifest)) + } updated, err := k.kubectl.Apply(ctx, out, manifests) if err != nil { diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 8ef9f3c7066..53c54837ae4 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -24,10 +24,10 @@ import ( "path/filepath" "strings" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/acr" - + configutil "github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/bazel" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/acr" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/gcb" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" @@ -74,6 +74,11 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldPipeline) (* } logrus.Infof("Using kubectl context: %s", kubeContext) + defaultRepo, err := configutil.GetDefaultRepo(opts.DefaultRepo) + if err != nil { + return nil, errors.Wrap(err, "getting default repo") + } + tagger, err := getTagger(cfg.Build.TagPolicy, opts.CustomTag) if err != nil { return nil, errors.Wrap(err, "parsing skaffold tag config") @@ -89,7 +94,7 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldPipeline) (* return nil, errors.Wrap(err, "parsing skaffold test config") } - deployer, err := getDeployer(&cfg.Deploy, kubeContext, opts.Namespace) + deployer, err := getDeployer(&cfg.Deploy, kubeContext, opts.Namespace, defaultRepo) if err != nil { return nil, errors.Wrap(err, "parsing skaffold deploy config") } @@ -140,16 +145,16 @@ func getBuilder(cfg *latest.BuildConfig, kubeContext string) (build.Builder, err } } -func getTester(cfg *[]latest.TestCase) (test.Tester, 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) { +func getDeployer(cfg *latest.DeployConfig, kubeContext string, namespace string, defaultRepo string) (deploy.Deployer, error) { deployers := []deploy.Deployer{} // HelmDeploy first, in case there are resources in Kubectl that depend on these... if cfg.HelmDeploy != nil { - deployers = append(deployers, deploy.NewHelmDeployer(cfg.HelmDeploy, kubeContext, namespace)) + deployers = append(deployers, deploy.NewHelmDeployer(cfg.HelmDeploy, kubeContext, namespace, defaultRepo)) } if cfg.KubectlDeploy != nil { @@ -158,11 +163,11 @@ func getDeployer(cfg *latest.DeployConfig, kubeContext string, namespace string) if err != nil { return nil, errors.Wrap(err, "finding current directory") } - deployers = append(deployers, deploy.NewKubectlDeployer(cwd, cfg.KubectlDeploy, kubeContext, namespace)) + deployers = append(deployers, deploy.NewKubectlDeployer(cwd, cfg.KubectlDeploy, kubeContext, namespace, defaultRepo)) } if cfg.KustomizeDeploy != nil { - deployers = append(deployers, deploy.NewKustomizeDeployer(cfg.KustomizeDeploy, kubeContext, namespace)) + deployers = append(deployers, deploy.NewKustomizeDeployer(cfg.KustomizeDeploy, kubeContext, namespace, defaultRepo)) } if len(deployers) == 0 { diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 8e0ed10afa3..7f4da0b352e 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -330,7 +330,7 @@ func TestRun(t *testing.T) { }, }, }, - Test: []latest.TestCase{ + Test: []*latest.TestCase{ { ImageName: "test", StructureTests: []string{"fake_file.yaml"}, diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index b0746e355ac..bbbe87ed515 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -34,7 +34,7 @@ type SkaffoldPipeline struct { Kind string `yaml:"kind"` Build BuildConfig `yaml:"build,omitempty"` - Test []TestCase `yaml:"test,omitempty"` + Test []*TestCase `yaml:"test,omitempty"` Deploy DeployConfig `yaml:"deploy,omitempty"` Profiles []Profile `yaml:"profiles,omitempty"` } @@ -237,7 +237,7 @@ type Artifact struct { type Profile struct { Name string `yaml:"name,omitempty"` Build BuildConfig `yaml:"build,omitempty"` - Test []TestCase `yaml:"test,omitempty"` + Test []*TestCase `yaml:"test,omitempty"` Deploy DeployConfig `yaml:"deploy,omitempty"` } diff --git a/pkg/skaffold/schema/profiles.go b/pkg/skaffold/schema/profiles.go index 23f5fd3d76d..090dcbf18c0 100644 --- a/pkg/skaffold/schema/profiles.go +++ b/pkg/skaffold/schema/profiles.go @@ -55,7 +55,7 @@ func applyProfile(config *latest.SkaffoldPipeline, profile latest.Profile) { Kind: config.Kind, Build: overlayProfileField(config.Build, profile.Build).(latest.BuildConfig), Deploy: overlayProfileField(config.Deploy, profile.Deploy).(latest.DeployConfig), - Test: overlayProfileField(config.Test, profile.Test).([]latest.TestCase), + Test: overlayProfileField(config.Test, profile.Test).([]*latest.TestCase), } } diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go index a46b7bf499a..5264e036b70 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -32,7 +32,7 @@ import ( // 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) { +func NewTester(testCases *[]*latest.TestCase) (Tester, error) { // TODO(nkubala): copied this from runner.getDeployer(), this should be moved somewhere else cwd, err := os.Getwd() if err != nil { @@ -77,7 +77,7 @@ func (t FullTester) Test(ctx context.Context, out io.Writer, bRes []build.Artifa return nil } -func (t FullTester) runStructureTests(ctx context.Context, out io.Writer, bRes []build.Artifact, testCase latest.TestCase) error { +func (t FullTester) runStructureTests(ctx context.Context, out io.Writer, bRes []build.Artifact, testCase *latest.TestCase) error { if len(testCase.StructureTests) == 0 { return nil } diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go index e23d46a6324..c43539e00ac 100644 --- a/pkg/skaffold/test/types.go +++ b/pkg/skaffold/test/types.go @@ -42,7 +42,7 @@ type Tester interface { // FullTester should always be the ONLY implementation of the Tester interface; // newly added testing implementations should implement the Runner interface. type FullTester struct { - testCases *[]latest.TestCase + testCases *[]*latest.TestCase workingDir string } diff --git a/pkg/skaffold/util/image.go b/pkg/skaffold/util/image.go new file mode 100644 index 00000000000..b69e8066b84 --- /dev/null +++ b/pkg/skaffold/util/image.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 util + +import ( + "regexp" + "strings" +) + +const maxLength = 255 + +const gcr = "gcr.io" +const escapeChars = "[/._:@]" +const prefixRegexStr = "gcr.io/[a-zA-Z]+/" + +var escapeRegex = regexp.MustCompile(escapeChars) +var prefixRegex = regexp.MustCompile(prefixRegexStr) + +func SubstituteDefaultRepoIntoImage(defaultRepo string, originalImage string) string { + if defaultRepo == "" { + return originalImage + } + if strings.HasPrefix(defaultRepo, gcr) { + originalPrefix := prefixRegex.FindString(originalImage) + defaultRepoPrefix := prefixRegex.FindString(defaultRepo) + + if originalPrefix == defaultRepoPrefix { + // prefixes match + return defaultRepo + "/" + originalImage[len(originalPrefix):] + } else if strings.HasPrefix(originalImage, defaultRepo) { + return originalImage + } + // prefixes don't match, concatenate and truncate + return truncate(defaultRepo + "/" + originalImage) + } + // TODO: check defaultRepo is < 256 chars when setting + return truncate(defaultRepo + "/" + escapeRegex.ReplaceAllString(originalImage, "_")) +} + +func truncate(image string) string { + if len(image) > maxLength { + return image[0:maxLength] + } + return image +} diff --git a/pkg/skaffold/util/image_test.go b/pkg/skaffold/util/image_test.go new file mode 100644 index 00000000000..a503d3298ce --- /dev/null +++ b/pkg/skaffold/util/image_test.go @@ -0,0 +1,74 @@ +/* +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 ( + "testing" + + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestImageReplaceDefaultRepo(t *testing.T) { + tests := []struct { + name string + image string + defaultRepo string + expectedImage string + }{ + { + name: "basic GCR concatenation", + image: "gcr.io/some/registry", + defaultRepo: "gcr.io/default", + expectedImage: "gcr.io/default/gcr.io/some/registry", + }, + { + name: "no default repo set", + image: "gcr.io/some/registry", + expectedImage: "gcr.io/some/registry", + }, + { + name: "provided image has defaultRepo prefix", + image: "gcr.io/default/registry", + defaultRepo: "gcr.io/default", + expectedImage: "gcr.io/default/registry", + }, + { + name: "image has shared prefix with defaultRepo", + image: "gcr.io/default/example/registry", + defaultRepo: "gcr.io/default/repository", + expectedImage: "gcr.io/default/repository/example/registry", + }, + { + name: "aws", + image: "gcr.io/some/registry", + defaultRepo: "aws_account_id.dkr.ecr.region.amazonaws.com", + expectedImage: "aws_account_id.dkr.ecr.region.amazonaws.com/gcr_io_some_registry", + }, + { + name: "aws over 255 chars", + image: "gcr.io/herewehaveanincrediblylongregistryname/herewealsohaveanabnormallylongimagename/doubtyouveseenanimagethislong/butyouneverknowdoyouimeanpeopledosomecrazystuffoutthere/goodluckpushingthistoanyregistrymyfriend", + defaultRepo: "aws_account_id.dkr.ecr.region.amazonaws.com", + expectedImage: "aws_account_id.dkr.ecr.region.amazonaws.com/gcr_io_herewehaveanincrediblylongregistryname_herewealsohaveanabnormallylongimagename_doubtyouveseenanimagethislong_butyouneverknowdoyouimeanpeopledosomecrazystuffoutthere_goodluckpushingthistoanyregistrymyfrien", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testutil.CheckDeepEqual(t, test.expectedImage, SubstituteDefaultRepoIntoImage(test.defaultRepo, test.image)) + }) + } +}