From 02fbd27055d995c36e0316a1c1e694e5b0f187da Mon Sep 17 00:00:00 2001 From: Bridget McErlean Date: Tue, 17 Dec 2019 16:48:50 -0500 Subject: [PATCH] Add support for pulling and pushing all images This changes adds support for pulling the Sonobuoy worker image, the systemd-logs image and the Kubernetes conformance image in addition to the images required for the e2e plugin. The individual images to pull cannot be configured. Only the default version for the cluster will be pulled. The images can only be pushed to one registry which is specified with the new flags `--custom-registry`. This currently only works for the default built in plugins. A follow up change will be made to apply the same approach to any image in a plugin definition. Signed-off-by: Bridget McErlean --- cmd/sonobuoy/app/args.go | 16 ++- cmd/sonobuoy/app/gen.go | 13 +- cmd/sonobuoy/app/gen_test.go | 16 +-- cmd/sonobuoy/app/images.go | 261 ++++++++++++++++++++++------------- pkg/image/image.go | 38 ++--- pkg/image/images_test.go | 25 +++- pkg/image/manifest.go | 66 +++++++-- pkg/image/manifest_test.go | 76 +++++++++- 8 files changed, 351 insertions(+), 160 deletions(-) diff --git a/cmd/sonobuoy/app/args.go b/cmd/sonobuoy/app/args.go index 42408d53b..350ac6405 100644 --- a/cmd/sonobuoy/app/args.go +++ b/cmd/sonobuoy/app/args.go @@ -40,6 +40,7 @@ const ( pluginFlag = "plugin" timeoutFlag = "timeout" waitOutputFlag = "wait-output" + customRegistryFlag = "custom-registry" ) // AddNamespaceFlag initialises a namespace flag. @@ -132,7 +133,15 @@ func AddPluginFlag(cfg *string, flags *pflag.FlagSet) { func AddE2ERegistryConfigFlag(cfg *string, flags *pflag.FlagSet) { flags.StringVar( cfg, e2eRegistryConfigFlag, "", - "Specify a yaml file acting as KUBE_TEST_REPO_LIST, overriding registries for test images.", + "Specify a yaml file acting as KUBE_TEST_REPO_LIST, overriding registries for test images. Required when pushing images for the e2e plugin.", + ) +} + +// AddCustomRepoFlag adds a custom registry flag to the provided command. +func AddCustomRegistryFlag(cfg *string, flags *pflag.FlagSet) { + flags.StringVar( + cfg, customRegistryFlag, "", + "Specify a registry to override the Sonobuoy and Plugin image registries.", ) } @@ -348,6 +357,11 @@ func AddPluginEnvFlag(p *PluginEnvVars, flags *pflag.FlagSet) { flags.Var(p, "plugin-env", "Set env vars on plugins. Values can be given multiple times and are in the form plugin.env=value") } +// AddPluginListFlag adds the flag to keep track of which built-in plugins to use. +func AddPluginListFlag(p *[]string, flags *pflag.FlagSet) { + flags.StringSliceVarP(p, "plugin", "p", []string{"e2e"}, "Describe which plugin's images to interact (Valid plugins are 'e2e', 'systemd-logs').") +} + // AddShortFlag adds a boolean flag to just print the Sonobuoy version and // nothing else. Useful in scripts. func AddShortFlag(flag *bool, flags *pflag.FlagSet) { diff --git a/cmd/sonobuoy/app/gen.go b/cmd/sonobuoy/app/gen.go index 42823eedd..9f64043d5 100644 --- a/cmd/sonobuoy/app/gen.go +++ b/cmd/sonobuoy/app/gen.go @@ -145,9 +145,7 @@ func (g *genFlags) Config() (*client.GenConfig, error) { return nil, err } - image = fmt.Sprintf("%v:%v", - resolveConformanceImage(imageVersion), - imageVersion) + image = resolveConformanceImage(imageVersion) } return &client.GenConfig{ @@ -175,14 +173,17 @@ func resolveConformanceImage(imageVersion string) string { // required as we phase in the use of the upstream k8s kube-conformance // image instead of our own heptio/kube-conformance one. They started // publishing it for v1.14.1. (https://github.com/kubernetes/kubernetes/pull/76101) + var imageURL string switch { case imageVersion == imagepkg.ConformanceImageVersionLatest: - return config.UpstreamKubeConformanceImageURL + imageURL = config.UpstreamKubeConformanceImageURL case imageVersion < "v1.14.1": - return config.DefaultKubeConformanceImageURL + imageURL = config.DefaultKubeConformanceImageURL default: - return config.UpstreamKubeConformanceImageURL + imageURL = config.UpstreamKubeConformanceImageURL } + return fmt.Sprintf("%v:%v", imageURL, imageVersion) + } func NewCmdGen() *cobra.Command { diff --git a/cmd/sonobuoy/app/gen_test.go b/cmd/sonobuoy/app/gen_test.go index 385ca6363..382368ec4 100644 --- a/cmd/sonobuoy/app/gen_test.go +++ b/cmd/sonobuoy/app/gen_test.go @@ -46,35 +46,35 @@ func TestResolveConformanceImage(t *testing.T) { { name: "Comparison is lexical", requestedVersion: "foo", - expected: "gcr.io/heptio-images/kube-conformance", + expected: "gcr.io/heptio-images/kube-conformance:foo", }, { name: "Prior to v1.14.0 uses heptio and major.minor", requestedVersion: "v1.13.99", - expected: "gcr.io/heptio-images/kube-conformance", + expected: "gcr.io/heptio-images/kube-conformance:v1.13.99", }, { name: "v1.14.0 uses heptio and major.minor", requestedVersion: "v1.14.0", - expected: "gcr.io/heptio-images/kube-conformance", + expected: "gcr.io/heptio-images/kube-conformance:v1.14.0", }, { name: "v1.14.1 and after uses upstream and major.minor.patch", requestedVersion: "v1.14.1", - expected: "gcr.io/google-containers/conformance", + expected: "gcr.io/google-containers/conformance:v1.14.1", }, { name: "v1.14.0 and after uses upstream and major.minor.patch", requestedVersion: "v1.15.1", - expected: "gcr.io/google-containers/conformance", + expected: "gcr.io/google-containers/conformance:v1.15.1", }, { name: "latest should use upstream image", requestedVersion: "latest", - expected: "gcr.io/google-containers/conformance", + expected: "gcr.io/google-containers/conformance:latest", }, { name: "explicit version before v1.14.0 should use heptio image and given version", requestedVersion: "v1.12+.0.alpha+", - expected: "gcr.io/heptio-images/kube-conformance", + expected: "gcr.io/heptio-images/kube-conformance:v1.12+.0.alpha+", }, { name: "explicit version after v1.14.0 should use upstream and use given version", requestedVersion: "v1.14.1", - expected: "gcr.io/google-containers/conformance", + expected: "gcr.io/google-containers/conformance:v1.14.1", }, } diff --git a/cmd/sonobuoy/app/images.go b/cmd/sonobuoy/app/images.go index c965086fa..75a7ab642 100644 --- a/cmd/sonobuoy/app/images.go +++ b/cmd/sonobuoy/app/images.go @@ -19,7 +19,9 @@ package app import ( "fmt" "os" + "strings" + "github.com/vmware-tanzu/sonobuoy/pkg/config" "github.com/vmware-tanzu/sonobuoy/pkg/errlog" "github.com/vmware-tanzu/sonobuoy/pkg/image" @@ -31,12 +33,15 @@ import ( const ( numDockerRetries = 1 defaultE2ERegistries = "" + e2ePlugin = "e2e" + systemdLogsPlugin = "systemd-logs" ) type imagesFlags struct { e2eRegistryConfig string - plugin string + plugins []string kubeconfig Kubeconfig + customRegistry string } func NewCmdImages() *cobra.Command { @@ -46,7 +51,7 @@ func NewCmdImages() *cobra.Command { Use: "images", Short: "Manage images used in a plugin. Supported plugins are: 'e2e'", Run: func(cmd *cobra.Command, args []string) { - if err := listImages(flags.plugin, flags.kubeconfig); err != nil { + if err := listImages(flags.plugins, flags.kubeconfig); err != nil { errlog.LogError(err) os.Exit(1) } @@ -55,7 +60,7 @@ func NewCmdImages() *cobra.Command { } AddKubeconfigFlag(&flags.kubeconfig, cmd.Flags()) - AddPluginFlag(&flags.plugin, cmd.Flags()) + AddPluginListFlag(&flags.plugins, cmd.Flags()) cmd.AddCommand(pullCmd()) cmd.AddCommand(pushCmd()) @@ -71,7 +76,7 @@ func pullCmd() *cobra.Command { Use: "pull", Short: "Pulls images to local docker client for a specific plugin", Run: func(cmd *cobra.Command, args []string) { - if errs := pullImages(flags.plugin, flags.kubeconfig, flags.e2eRegistryConfig); len(errs) > 0 { + if errs := pullImages(flags.plugins, flags.kubeconfig, flags.e2eRegistryConfig); len(errs) > 0 { for _, err := range errs { errlog.LogError(err) } @@ -82,7 +87,7 @@ func pullCmd() *cobra.Command { } AddE2ERegistryConfigFlag(&flags.e2eRegistryConfig, pullCmd.Flags()) AddKubeconfigFlag(&flags.kubeconfig, pullCmd.Flags()) - AddPluginFlag(&flags.plugin, pullCmd.Flags()) + AddPluginListFlag(&flags.plugins, pullCmd.Flags()) return pullCmd } @@ -92,8 +97,14 @@ func pushCmd() *cobra.Command { pushCmd := &cobra.Command{ Use: "push", Short: "Pushes images to docker registry for a specific plugin", + PreRunE: func(cmd *cobra.Command, args []string) error { + if contains(flags.plugins, e2ePlugin) && len(flags.e2eRegistryConfig) == 0 { + return fmt.Errorf("Required flag %q not set", e2eRegistryConfigFlag) + } + return nil + }, Run: func(cmd *cobra.Command, args []string) { - if errs := pushImages(flags.plugin, flags.kubeconfig, flags.e2eRegistryConfig); len(errs) > 0 { + if errs := pushImages(flags.plugins, flags.kubeconfig, flags.customRegistry, flags.e2eRegistryConfig); len(errs) > 0 { for _, err := range errs { errlog.LogError(err) } @@ -104,9 +115,9 @@ func pushCmd() *cobra.Command { } AddE2ERegistryConfigFlag(&flags.e2eRegistryConfig, pushCmd.Flags()) AddKubeconfigFlag(&flags.kubeconfig, pushCmd.Flags()) - AddPluginFlag(&flags.plugin, pushCmd.Flags()) - // TODO(bridget): This won't be required when dealing with other plugins - pushCmd.MarkFlagRequired(e2eRegistryConfigFlag) + AddPluginListFlag(&flags.plugins, pushCmd.Flags()) + AddCustomRegistryFlag(&flags.customRegistry, pushCmd.Flags()) + pushCmd.MarkFlagRequired(customRegistryFlag) return pushCmd } @@ -117,7 +128,7 @@ func downloadCmd() *cobra.Command { Use: "download", Short: "Saves downloaded images from local docker client to a tar file", Run: func(cmd *cobra.Command, args []string) { - if err := downloadImages(flags.plugin, flags.kubeconfig, flags.e2eRegistryConfig); err != nil { + if err := downloadImages(flags.plugins, flags.kubeconfig, flags.e2eRegistryConfig); err != nil { errlog.LogError(err) os.Exit(1) } @@ -126,7 +137,7 @@ func downloadCmd() *cobra.Command { } AddE2ERegistryConfigFlag(&flags.e2eRegistryConfig, downloadCmd.Flags()) AddKubeconfigFlag(&flags.kubeconfig, downloadCmd.Flags()) - AddPluginFlag(&flags.plugin, downloadCmd.Flags()) + AddPluginListFlag(&flags.plugins, downloadCmd.Flags()) return downloadCmd } @@ -136,7 +147,7 @@ func deleteCmd() *cobra.Command { Use: "delete", Short: "Deletes all images downloaded to local docker client", Run: func(cmd *cobra.Command, args []string) { - if errs := deleteImages(flags.plugin, flags.kubeconfig, flags.e2eRegistryConfig); len(errs) > 0 { + if errs := deleteImages(flags.plugins, flags.kubeconfig, flags.e2eRegistryConfig); len(errs) > 0 { for _, err := range errs { errlog.LogError(err) } @@ -147,7 +158,7 @@ func deleteCmd() *cobra.Command { } AddE2ERegistryConfigFlag(&flags.e2eRegistryConfig, deleteCmd.Flags()) AddKubeconfigFlag(&flags.kubeconfig, deleteCmd.Flags()) - AddPluginFlag(&flags.plugin, deleteCmd.Flags()) + AddPluginListFlag(&flags.plugins, deleteCmd.Flags()) return deleteCmd } @@ -165,124 +176,180 @@ func getClusterVersion(kubeconfig Kubeconfig) (string, error) { return version, nil } -func listImages(plugin string, kubeconfig Kubeconfig) error { - switch plugin { - case "e2e": - version, err := getClusterVersion(kubeconfig) - if err != nil { - return errors.Wrap(err, "failed to get cluster version") - } +func listImages(plugins []string, kubeconfig Kubeconfig) error { + images := []string{ + config.DefaultImage, + } + for _, plugin := range plugins { + switch plugin { + case systemdLogsPlugin: + images = append(images, config.DefaultSystemdLogsImage) + case e2ePlugin: + version, err := getClusterVersion(kubeconfig) + if err != nil { + return errors.Wrap(err, "failed to get cluster version") + } - defaultImages, err := image.GetE2EImages(defaultE2ERegistries, version) - if err != nil { - return errors.Wrap(err, "couldn't get images") - } + e2eImages, err := image.GetE2EImages(defaultE2ERegistries, version) + if err != nil { + return errors.Wrap(err, "couldn't get images") + } - for _, image := range defaultImages { - fmt.Println(image) + images = append(images, resolveConformanceImage(version)) + images = append(images, e2eImages...) + default: + return errors.Errorf("Unsupported plugin: %v", plugin) } - default: - return errors.Errorf("Unsupported plugin: %v", plugin) + } + + for _, image := range images { + fmt.Println(image) } return nil } -func pullImages(plugin string, kubeconfig Kubeconfig, e2eRegistryConfig string) []error { - switch plugin { - case "e2e": - version, err := getClusterVersion(kubeconfig) - if err != nil { - return []error{errors.Wrap(err, "failed to get cluster version")} - } - - images, err := image.GetE2EImages(e2eRegistryConfig, version) - if err != nil { - return []error{errors.Wrap(err, "couldn't get images")} - } +func pullImages(plugins []string, kubeconfig Kubeconfig, e2eRegistryConfig string) []error { + images := []string{ + config.DefaultImage, + } + for _, plugin := range plugins { + switch plugin { + case systemdLogsPlugin: + images = append(images, config.DefaultSystemdLogsImage) + case e2ePlugin: + version, err := getClusterVersion(kubeconfig) + if err != nil { + return []error{errors.Wrap(err, "failed to get cluster version")} + } - imageClient := image.NewImageClient() + e2eImages, err := image.GetE2EImages(e2eRegistryConfig, version) + if err != nil { + return []error{errors.Wrap(err, "couldn't get images")} + } + images = append(images, resolveConformanceImage(version)) + images = append(images, e2eImages...) - return imageClient.PullImages(images, numDockerRetries) - default: - return []error{errors.Errorf("Unsupported plugin: %v", plugin)} + default: + return []error{errors.Errorf("Unsupported plugin: %v", plugin)} + } } + imageClient := image.NewImageClient() + return imageClient.PullImages(images, numDockerRetries) } -func downloadImages(plugin string, kubeconfig Kubeconfig, e2eRegistryConfig string) error { - switch plugin { - case "e2e": - version, err := getClusterVersion(kubeconfig) - if err != nil { - return errors.Wrap(err, "failed to get cluster version") - } - - images, err := image.GetE2EImages(e2eRegistryConfig, version) - if err != nil { - return errors.Wrap(err, "couldn't get images") - } +func downloadImages(plugins []string, kubeconfig Kubeconfig, e2eRegistryConfig string) error { + for _, plugin := range plugins { + switch plugin { + case e2ePlugin: + version, err := getClusterVersion(kubeconfig) + if err != nil { + return errors.Wrap(err, "failed to get cluster version") + } - imageClient := image.NewImageClient() + images, err := image.GetE2EImages(e2eRegistryConfig, version) + if err != nil { + return errors.Wrap(err, "couldn't get images") + } - fileName, err := imageClient.DownloadImages(images, version) - if err != nil { - return err - } + imageClient := image.NewImageClient() + fileName, err := imageClient.DownloadImages(images, version) + if err != nil { + return err + } - fmt.Println(fileName) + fmt.Println(fileName) - default: - return errors.Errorf("Unsupported plugin: %v", plugin) + default: + return errors.Errorf("Unsupported plugin: %v", plugin) + } } return nil } -func pushImages(plugin string, kubeconfig Kubeconfig, e2eRegistryConfig string) []error { - switch plugin { - case "e2e": - version, err := getClusterVersion(kubeconfig) - if err != nil { - return []error{errors.Wrap(err, "failed to get cluster version")} - } +func pushImages(plugins []string, kubeconfig Kubeconfig, customRegistry, e2eRegistryConfig string) []error { + imagePairs := []image.TagPair{ + { + Src: config.DefaultImage, + Dst: substituteRegistry(config.DefaultImage, customRegistry), + }, + } + for _, plugin := range plugins { + switch plugin { + case systemdLogsPlugin: + imagePairs = append(imagePairs, image.TagPair{ + Src: config.DefaultSystemdLogsImage, + Dst: substituteRegistry(config.DefaultSystemdLogsImage, customRegistry), + }) + case e2ePlugin: + version, err := getClusterVersion(kubeconfig) + if err != nil { + return []error{errors.Wrap(err, "failed to get cluster version")} + } - defaultImages, err := image.GetE2EImages(defaultE2ERegistries, version) - if err != nil { - return []error{errors.Wrap(err, "couldn't get images")} - } + tagPairs, err := image.GetE2EImageTagPairs(e2eRegistryConfig, version) + if err != nil { + return []error{errors.Wrap(err, "couldn't...something")} + } - privateImages, err := image.GetE2EImages(e2eRegistryConfig, version) - if err != nil { - return []error{errors.Wrap(err, "couldn't get images")} + conformanceImage := resolveConformanceImage(version) + imagePairs = append(imagePairs, image.TagPair{ + Src: conformanceImage, + Dst: substituteRegistry(conformanceImage, customRegistry), + }) + imagePairs = append(imagePairs, tagPairs...) + default: + return []error{errors.Errorf("Unsupported plugin: %v", plugin)} } + } - imageClient := image.NewImageClient() + imageClient := image.NewImageClient() + return imageClient.PushImages(imagePairs, numDockerRetries) +} - return imageClient.PushImages(defaultImages, privateImages, numDockerRetries) - default: - return []error{errors.Errorf("Unsupported plugin: %v", plugin)} +func deleteImages(plugins []string, kubeconfig Kubeconfig, e2eRegistryConfig string) []error { + images := []string{ + config.DefaultImage, } -} + for _, plugin := range plugins { + switch plugin { + case systemdLogsPlugin: + images = append(images, config.DefaultSystemdLogsImage) + case e2ePlugin: + version, err := getClusterVersion(kubeconfig) + if err != nil { + return []error{errors.Wrap(err, "failed to get cluster version")} + } -func deleteImages(plugin string, kubeconfig Kubeconfig, e2eRegistryConfig string) []error { - switch plugin { - case "e2e": - version, err := getClusterVersion(kubeconfig) - if err != nil { - return []error{errors.Wrap(err, "failed to get cluster version")} - } + e2eImages, err := image.GetE2EImages(e2eRegistryConfig, version) + if err != nil { + return []error{errors.Wrap(err, "couldn't get images")} + } - images, err := image.GetE2EImages(e2eRegistryConfig, version) - if err != nil { - return []error{errors.Wrap(err, "couldn't get images")} + images = append(images, resolveConformanceImage(version)) + images = append(images, e2eImages...) + default: + return []error{errors.Errorf("Unsupported plugin: %v", plugin)} } + } - imageClient := image.NewImageClient() + imageClient := image.NewImageClient() + return imageClient.DeleteImages(images, numDockerRetries) +} - return imageClient.DeleteImages(images, numDockerRetries) +func substituteRegistry(image string, customRegistry string) string { + trimmedRegistry := strings.TrimRight(customRegistry, "/") + components := strings.SplitAfter(image, "/") + return fmt.Sprintf("%s/%s", trimmedRegistry, components[len(components)-1]) +} - default: - return []error{errors.Errorf("Unsupported plugin: %v", plugin)} +func contains(set []string, val string) bool { + for _, v := range set { + if v == val { + return true + } } + return false } diff --git a/pkg/image/image.go b/pkg/image/image.go index 912a4cd53..58139e921 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -27,6 +27,11 @@ type ImageClient struct { dockerClient docker.Docker } +type TagPair struct { + Src string + Dst string +} + func NewImageClient() ImageClient { return ImageClient{ dockerClient: docker.LocalDocker{}, @@ -44,25 +49,23 @@ func (i ImageClient) PullImages(images []string, retries int) []error { return errs } -func (i ImageClient) PushImages(upstreamImages, privateImages []string, retries int) []error { +func (i ImageClient) PushImages(images []TagPair, retries int) []error { errs := []error{} - for k, upstreamImg := range upstreamImages { - privateImg := privateImages[k] - + for _, image := range images { // Skip if the source/dest are equal - if privateImg == upstreamImg { - fmt.Printf("Skipping public image: %s\n", upstreamImg) + if image.Src == image.Dst { + fmt.Printf("Skipping public image: %s\n", image.Src) continue } - err := i.dockerClient.Tag(upstreamImg, privateImg, retries) + err := i.dockerClient.Tag(image.Src, image.Dst, retries) if err != nil { - errs = append(errs, errors.Wrapf(err, "couldn't tag image %q as %q", upstreamImg, privateImg)) + errs = append(errs, errors.Wrapf(err, "couldn't tag image %q as %q", image.Src, image.Dst)) } - err = i.dockerClient.Push(privateImg, retries) + err = i.dockerClient.Push(image.Dst, retries) if err != nil { - errs = append(errs, errors.Wrapf(err, "couldn't push image: %v", privateImg)) + errs = append(errs, errors.Wrapf(err, "couldn't push image: %v", image.Dst)) } } return errs @@ -92,21 +95,6 @@ func (i ImageClient) DeleteImages(images []string, retries int) []error { return errs } -// GetE2EImages gets a list of E2E image names -func GetE2EImages(e2eRegistryConfig, version string) ([]string, error) { - // Get list of upstream images that match the version - reg, err := NewRegistryList(e2eRegistryConfig, version) - if err != nil { - return nil, errors.Wrap(err, "couldn't create image registry list") - } - - imgs, err := reg.GetImageNames() - if err != nil { - return nil, errors.Wrap(err, "couldn't get images for version") - } - return imgs, nil -} - // getTarFileName returns a filename matching the version of Kubernetes images are exported func getTarFileName(version string) string { return fmt.Sprintf("kubernetes_e2e_images_%s.tar", version) diff --git a/pkg/image/images_test.go b/pkg/image/images_test.go index fbf036704..cf71fca39 100644 --- a/pkg/image/images_test.go +++ b/pkg/image/images_test.go @@ -77,11 +77,22 @@ func (l FakeDockerClient) Save(images []string, filename string) error { } func TestPushImages(t *testing.T) { - var privateImgs = []string{"test1/private.io/sonobuoy:x.y"} + imageTagPairs := []TagPair{ + { + Src: imgs[0], + Dst: "test1/private.io/sonobuoy:x.y", + }, + } + imageTagPairSame := []TagPair{ + { + Src: imgs[0], + Dst: imgs[0], + }, + } tests := map[string]struct { client docker.Docker - privateImgs []string + imageTagPairs []TagPair wantErrorCount int }{ "simple": { @@ -89,7 +100,7 @@ func TestPushImages(t *testing.T) { pushFails: false, tagFails: false, }, - privateImgs: privateImgs, + imageTagPairs: imageTagPairs, wantErrorCount: 0, }, "tag fails": { @@ -97,7 +108,7 @@ func TestPushImages(t *testing.T) { pushFails: false, tagFails: true, }, - privateImgs: privateImgs, + imageTagPairs: imageTagPairs, wantErrorCount: 1, }, "push fails": { @@ -105,7 +116,7 @@ func TestPushImages(t *testing.T) { pushFails: true, tagFails: true, }, - privateImgs: privateImgs, + imageTagPairs: imageTagPairs, wantErrorCount: 2, }, "source images equal destination images": { @@ -113,7 +124,7 @@ func TestPushImages(t *testing.T) { pushFails: true, tagFails: true, }, - privateImgs: imgs, + imageTagPairs: imageTagPairSame, wantErrorCount: 0, }, } @@ -125,7 +136,7 @@ func TestPushImages(t *testing.T) { dockerClient: tc.client, } - got := imgClient.PushImages(imgs, tc.privateImgs, 0) + got := imgClient.PushImages(tc.imageTagPairs, 0) if len(got) != tc.wantErrorCount { t.Fatalf("Expected errors: %d but got %d", tc.wantErrorCount, len(got)) diff --git a/pkg/image/manifest.go b/pkg/image/manifest.go index a051a3beb..1032349bd 100644 --- a/pkg/image/manifest.go +++ b/pkg/image/manifest.go @@ -20,6 +20,7 @@ import ( "io/ioutil" version "github.com/hashicorp/go-version" + "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" ) @@ -109,26 +110,39 @@ func NewRegistryList(repoConfig, k8sVersion string) (*RegistryList, error) { return registry, nil } -// GetImageNames returns the map of image Config -func (r *RegistryList) GetImageNames() ([]string, error) { - imgConfigs := map[string]Config{} +// getImageConfigs returns the map of image Config for the registry version +func (r *RegistryList) getImageConfigs() (map[string]Config, error) { switch r.K8sVersion.Segments()[0] { case 1: switch r.K8sVersion.Segments()[1] { case 13: - imgConfigs = r.v1_13() + return r.v1_13(), nil case 14: - imgConfigs = r.v1_14() + return r.v1_14(), nil case 15: - imgConfigs = r.v1_15() + return r.v1_15(), nil case 16: - imgConfigs = r.v1_16() + return r.v1_16(), nil case 17: - imgConfigs = r.v1_17() - default: - return []string{}, fmt.Errorf("No matching configuration for k8s version: %v", r.K8sVersion) + return r.v1_17(), nil } } + return map[string]Config{}, fmt.Errorf("No matching configuration for k8s version: %v", r.K8sVersion) + +} + +// GetE2EImages gets a list of E2E image names +func GetE2EImages(e2eRegistryConfig, version string) ([]string, error) { + // Get list of upstream images that match the version + reg, err := NewRegistryList(e2eRegistryConfig, version) + if err != nil { + return nil, errors.Wrap(err, "couldn't create image registry list") + } + + imgConfigs, err := reg.getImageConfigs() + if err != nil { + return []string{}, errors.Wrap(err, "couldn't get images for version") + } imageNames := []string{} for _, imageConfig := range imgConfigs { @@ -138,6 +152,36 @@ func (r *RegistryList) GetImageNames() ([]string, error) { return imageNames, nil } +// GetE2EImagePairs gets a list of E2E image tag pairs from the default src to custom destination +func GetE2EImageTagPairs(e2eRegistryConfig, version string) ([]TagPair, error) { + defaultImageRegistry, err := NewRegistryList("", version) + if err != nil { + return nil, errors.Wrap(err, "couldn't create image registry list") + } + defaultImageConfigs, err := defaultImageRegistry.getImageConfigs() + if err != nil { + return []TagPair{}, errors.Wrap(err, "couldn't get images for version") + } + + customImageRegistry, err := NewRegistryList(e2eRegistryConfig, version) + if err != nil { + return nil, errors.Wrap(err, "couldn't create image registry list") + } + customImageConfigs, err := customImageRegistry.getImageConfigs() + if err != nil { + return []TagPair{}, errors.Wrap(err, "couldn't get images for version") + } + + var imageTagPairs []TagPair + for name, cfg := range defaultImageConfigs { + imageTagPairs = append(imageTagPairs, TagPair{ + Src: cfg.GetFullyQualifiedImageName(), + Dst: customImageConfigs[name].GetFullyQualifiedImageName(), + }) + } + return imageTagPairs, nil +} + // GetDefaultImageRegistries returns the default default image registries used for // a given version of the Kubernetes E2E tests func GetDefaultImageRegistries(version string) (*RegistryList, error) { @@ -201,6 +245,6 @@ func GetDefaultImageRegistries(version string) (*RegistryList, error) { } // GetFullyQualifiedImageName returns the fully qualified URI to an image (including tag) -func (i *Config) GetFullyQualifiedImageName() string { +func (i Config) GetFullyQualifiedImageName() string { return fmt.Sprintf("%s/%s:%s", i.registry, i.name, i.tag) } diff --git a/pkg/image/manifest_test.go b/pkg/image/manifest_test.go index e37919759..5fea33cfd 100644 --- a/pkg/image/manifest_test.go +++ b/pkg/image/manifest_test.go @@ -17,8 +17,12 @@ limitations under the License. package image import ( + "fmt" + "io/ioutil" "strings" "testing" + + yaml "gopkg.in/yaml.v2" ) func TestGetDefaultImageRegistryVersionValidation(t *testing.T) { @@ -90,15 +94,16 @@ func TestFullQualifiedImageName(t *testing.T) { } } -func TestGetImageNames(t *testing.T) { - registry, err := NewRegistryList("", "v1.17.0") +func TestGetE2EImages(t *testing.T) { + version := "v1.17.0" + registry, err := NewRegistryList("", version) if err != nil { - t.Fatalf("unexpected error from NewRegistryList %q", err) + t.Fatalf("unexpected error from NewRegistryList: %q", err) } - imageNames, err := registry.GetImageNames() + imageNames, err := GetE2EImages("", version) if err != nil { - t.Fatalf("unexpected error from GetImageNames %q", err) + t.Fatalf("unexpected error from GetE2EImages: %q", err) } expectedRegistry := registry.v1_17() @@ -114,6 +119,67 @@ func TestGetImageNames(t *testing.T) { } } +func createTestRegistryConfig(customRegistry string) (string, error) { + registries, err := GetDefaultImageRegistries("v1.15.0") + if err != nil { + return "", err + } + + registries.E2eRegistry = customRegistry + registries.DockerLibraryRegistry = customRegistry + registries.GcRegistry = customRegistry + registries.SampleRegistry = customRegistry + + tmpfile, err := ioutil.TempFile("", "config.*.yaml") + if err != nil { + return "", err + } + defer tmpfile.Close() + + d, err := yaml.Marshal(®istries) + if err != nil { + return "", err + } + if _, err := tmpfile.Write(d); err != nil { + return "", err + } + return tmpfile.Name(), nil +} + +func TestGetE2EImageTagPairs(t *testing.T) { + version := "v1.15.0" + customRegistry := "my-custom/registry" + customRegistries, err := createTestRegistryConfig(customRegistry) + if err != nil { + t.Fatalf("unexpected error creating temp registry config: %q", err) + } + + imageTagPairs, err := GetE2EImageTagPairs(customRegistries, version) + if err != nil { + t.Fatalf("unexpected error from GetE2ETagPairs: %q", err) + } + + defaultRegistry, err := NewRegistryList("", version) + if err != nil { + t.Fatalf("unexpected error from NewRegistryList: %q", err) + } + expectedDefaultRegistry := defaultRegistry.v1_15() + if len(imageTagPairs) != len(expectedDefaultRegistry) { + t.Fatalf("Unexpected number of image tag pairs returned, expected %v, got %v", len(expectedDefaultRegistry), len(imageTagPairs)) + } + + // Check one of the returned image pairs to ensure correct format + imageTagPair := imageTagPairs[0] + if strings.HasPrefix(imageTagPair.Src, customRegistry) { + t.Errorf("Src image should not have custom registry prefix: %q", imageTagPair.Src) + } + imageComponents := strings.SplitAfter(imageTagPair.Src, "/") + expectedDst := fmt.Sprintf("%s/%s", customRegistry, imageComponents[len(imageComponents)-1]) + if imageTagPair.Dst != expectedDst { + t.Errorf("Expected Dst image to be %q, got %q", expectedDst, imageTagPair.Dst) + } +} + func contains(set []string, val string) bool { for _, v := range set { if v == val {