...

Source file src/k8s.io/kubernetes/test/utils/image/manifest.go

Documentation: k8s.io/kubernetes/test/utils/image

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package image
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"crypto/sha256"
    23  	"encoding/base64"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"os"
    28  	"regexp"
    29  	"strings"
    30  
    31  	"gopkg.in/yaml.v2"
    32  )
    33  
    34  // RegistryList holds public and private image registries
    35  type RegistryList struct {
    36  	GcAuthenticatedRegistry  string `yaml:"gcAuthenticatedRegistry"`
    37  	PromoterE2eRegistry      string `yaml:"promoterE2eRegistry"`
    38  	BuildImageRegistry       string `yaml:"buildImageRegistry"`
    39  	InvalidRegistry          string `yaml:"invalidRegistry"`
    40  	GcEtcdRegistry           string `yaml:"gcEtcdRegistry"`
    41  	GcRegistry               string `yaml:"gcRegistry"`
    42  	SigStorageRegistry       string `yaml:"sigStorageRegistry"`
    43  	PrivateRegistry          string `yaml:"privateRegistry"`
    44  	DockerLibraryRegistry    string `yaml:"dockerLibraryRegistry"`
    45  	CloudProviderGcpRegistry string `yaml:"cloudProviderGcpRegistry"`
    46  }
    47  
    48  // Config holds an images registry, name, and version
    49  type Config struct {
    50  	registry string
    51  	name     string
    52  	version  string
    53  }
    54  
    55  // SetRegistry sets an image registry in a Config struct
    56  func (i *Config) SetRegistry(registry string) {
    57  	i.registry = registry
    58  }
    59  
    60  // SetName sets an image name in a Config struct
    61  func (i *Config) SetName(name string) {
    62  	i.name = name
    63  }
    64  
    65  // SetVersion sets an image version in a Config struct
    66  func (i *Config) SetVersion(version string) {
    67  	i.version = version
    68  }
    69  
    70  func Init(repoList string) {
    71  	registry, imageConfigs, originalImageConfigs = readRepoList(repoList)
    72  }
    73  
    74  func readRepoList(repoList string) (RegistryList, map[ImageID]Config, map[ImageID]Config) {
    75  	registry := initRegistry
    76  
    77  	if repoList == "" {
    78  		imageConfigs, originalImageConfigs := initImageConfigs(registry)
    79  		return registry, imageConfigs, originalImageConfigs
    80  	}
    81  
    82  	var fileContent []byte
    83  	var err error
    84  	if strings.HasPrefix(repoList, "https://") || strings.HasPrefix(repoList, "http://") {
    85  		var b bytes.Buffer
    86  		err = readFromURL(repoList, bufio.NewWriter(&b))
    87  		if err != nil {
    88  			panic(fmt.Errorf("error reading '%v' url contents: %v", repoList, err))
    89  		}
    90  		fileContent = b.Bytes()
    91  	} else {
    92  		fileContent, err = os.ReadFile(repoList)
    93  		if err != nil {
    94  			panic(fmt.Errorf("error reading '%v' file contents: %v", repoList, err))
    95  		}
    96  	}
    97  
    98  	err = yaml.Unmarshal(fileContent, &registry)
    99  	if err != nil {
   100  		panic(fmt.Errorf("error unmarshalling '%v' YAML file: %v", repoList, err))
   101  	}
   102  
   103  	imageConfigs, originalImageConfigs := initImageConfigs(registry)
   104  
   105  	return registry, imageConfigs, originalImageConfigs
   106  
   107  }
   108  
   109  // Essentially curl url | writer
   110  func readFromURL(url string, writer io.Writer) error {
   111  	httpTransport := new(http.Transport)
   112  	httpTransport.Proxy = http.ProxyFromEnvironment
   113  
   114  	c := &http.Client{Transport: httpTransport}
   115  	r, err := c.Get(url)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer r.Body.Close()
   120  	if r.StatusCode >= 400 {
   121  		return fmt.Errorf("%v returned %d", url, r.StatusCode)
   122  	}
   123  	_, err = io.Copy(writer, r.Body)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	return nil
   128  }
   129  
   130  var (
   131  	initRegistry = RegistryList{
   132  		GcAuthenticatedRegistry:  "gcr.io/authenticated-image-pulling",
   133  		PromoterE2eRegistry:      "registry.k8s.io/e2e-test-images",
   134  		BuildImageRegistry:       "registry.k8s.io/build-image",
   135  		InvalidRegistry:          "invalid.registry.k8s.io/invalid",
   136  		GcEtcdRegistry:           "registry.k8s.io",
   137  		GcRegistry:               "registry.k8s.io",
   138  		SigStorageRegistry:       "registry.k8s.io/sig-storage",
   139  		PrivateRegistry:          "gcr.io/k8s-authenticated-test",
   140  		DockerLibraryRegistry:    "docker.io/library",
   141  		CloudProviderGcpRegistry: "registry.k8s.io/cloud-provider-gcp",
   142  	}
   143  
   144  	registry, imageConfigs, originalImageConfigs = readRepoList(os.Getenv("KUBE_TEST_REPO_LIST"))
   145  )
   146  
   147  type ImageID int
   148  
   149  const (
   150  	// None is to be used for unset/default images
   151  	None ImageID = iota
   152  	// Agnhost image
   153  	Agnhost
   154  	// AgnhostPrivate image
   155  	AgnhostPrivate
   156  	// APIServer image
   157  	APIServer
   158  	// AppArmorLoader image
   159  	AppArmorLoader
   160  	// AuthenticatedAlpine image
   161  	AuthenticatedAlpine
   162  	// AuthenticatedWindowsNanoServer image
   163  	AuthenticatedWindowsNanoServer
   164  	// BusyBox image
   165  	BusyBox
   166  	// CudaVectorAdd image
   167  	CudaVectorAdd
   168  	// CudaVectorAdd2 image
   169  	CudaVectorAdd2
   170  	// DistrolessIptables Image
   171  	DistrolessIptables
   172  	// Etcd image
   173  	Etcd
   174  	// Httpd image
   175  	Httpd
   176  	// HttpdNew image
   177  	HttpdNew
   178  	// InvalidRegistryImage image
   179  	InvalidRegistryImage
   180  	// IpcUtils image
   181  	IpcUtils
   182  	// JessieDnsutils image
   183  	JessieDnsutils
   184  	// Kitten image
   185  	Kitten
   186  	// Nautilus image
   187  	Nautilus
   188  	// NFSProvisioner image
   189  	NFSProvisioner
   190  	// Nginx image
   191  	Nginx
   192  	// NginxNew image
   193  	NginxNew
   194  	// NodePerfNpbEp image
   195  	NodePerfNpbEp
   196  	// NodePerfNpbIs image
   197  	NodePerfNpbIs
   198  	// NodePerfTfWideDeep image
   199  	NodePerfTfWideDeep
   200  	// Nonewprivs image
   201  	Nonewprivs
   202  	// NonRoot runs with a default user of 1234
   203  	NonRoot
   204  	// Pause - when these values are updated, also update cmd/kubelet/app/options/container_runtime.go
   205  	// Pause image
   206  	Pause
   207  	// Perl image
   208  	Perl
   209  	// PrometheusDummyExporter image
   210  	PrometheusDummyExporter
   211  	// PrometheusToSd image
   212  	PrometheusToSd
   213  	// Redis image
   214  	Redis
   215  	// RegressionIssue74839 image
   216  	RegressionIssue74839
   217  	// ResourceConsumer image
   218  	ResourceConsumer
   219  	// SdDummyExporter image
   220  	SdDummyExporter
   221  	// VolumeNFSServer image
   222  	VolumeNFSServer
   223  	// VolumeISCSIServer image
   224  	VolumeISCSIServer
   225  	// VolumeRBDServer image
   226  	VolumeRBDServer
   227  )
   228  
   229  func initImageConfigs(list RegistryList) (map[ImageID]Config, map[ImageID]Config) {
   230  	configs := map[ImageID]Config{}
   231  	configs[Agnhost] = Config{list.PromoterE2eRegistry, "agnhost", "2.47"}
   232  	configs[AgnhostPrivate] = Config{list.PrivateRegistry, "agnhost", "2.6"}
   233  	configs[AuthenticatedAlpine] = Config{list.GcAuthenticatedRegistry, "alpine", "3.7"}
   234  	configs[AuthenticatedWindowsNanoServer] = Config{list.GcAuthenticatedRegistry, "windows-nanoserver", "v1"}
   235  	configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.29.2"}
   236  	configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.4"}
   237  	configs[BusyBox] = Config{list.PromoterE2eRegistry, "busybox", "1.36.1-1"}
   238  	configs[CudaVectorAdd] = Config{list.PromoterE2eRegistry, "cuda-vector-add", "1.0"}
   239  	configs[CudaVectorAdd2] = Config{list.PromoterE2eRegistry, "cuda-vector-add", "2.3"}
   240  	configs[DistrolessIptables] = Config{list.BuildImageRegistry, "distroless-iptables", "v0.5.3"}
   241  	configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.12-0"}
   242  	configs[Httpd] = Config{list.PromoterE2eRegistry, "httpd", "2.4.38-4"}
   243  	configs[HttpdNew] = Config{list.PromoterE2eRegistry, "httpd", "2.4.39-4"}
   244  	configs[InvalidRegistryImage] = Config{list.InvalidRegistry, "alpine", "3.1"}
   245  	configs[IpcUtils] = Config{list.PromoterE2eRegistry, "ipc-utils", "1.3"}
   246  	configs[JessieDnsutils] = Config{list.PromoterE2eRegistry, "jessie-dnsutils", "1.7"}
   247  	configs[Kitten] = Config{list.PromoterE2eRegistry, "kitten", "1.7"}
   248  	configs[Nautilus] = Config{list.PromoterE2eRegistry, "nautilus", "1.7"}
   249  	configs[NFSProvisioner] = Config{list.SigStorageRegistry, "nfs-provisioner", "v4.0.8"}
   250  	configs[Nginx] = Config{list.PromoterE2eRegistry, "nginx", "1.14-4"}
   251  	configs[NginxNew] = Config{list.PromoterE2eRegistry, "nginx", "1.15-4"}
   252  	configs[NodePerfNpbEp] = Config{list.PromoterE2eRegistry, "node-perf/npb-ep", "1.2"}
   253  	configs[NodePerfNpbIs] = Config{list.PromoterE2eRegistry, "node-perf/npb-is", "1.2"}
   254  	configs[NodePerfTfWideDeep] = Config{list.PromoterE2eRegistry, "node-perf/tf-wide-deep", "1.3"}
   255  	configs[Nonewprivs] = Config{list.PromoterE2eRegistry, "nonewprivs", "1.3"}
   256  	configs[NonRoot] = Config{list.PromoterE2eRegistry, "nonroot", "1.4"}
   257  	// Pause - when these values are updated, also update cmd/kubelet/app/options/container_runtime.go
   258  	configs[Pause] = Config{list.GcRegistry, "pause", "3.9"}
   259  	configs[Perl] = Config{list.PromoterE2eRegistry, "perl", "5.26"}
   260  	configs[PrometheusDummyExporter] = Config{list.GcRegistry, "prometheus-dummy-exporter", "v0.1.0"}
   261  	configs[PrometheusToSd] = Config{list.GcRegistry, "prometheus-to-sd", "v0.5.0"}
   262  	configs[Redis] = Config{list.PromoterE2eRegistry, "redis", "5.0.5-3"}
   263  	configs[RegressionIssue74839] = Config{list.PromoterE2eRegistry, "regression-issue-74839", "1.2"}
   264  	configs[ResourceConsumer] = Config{list.PromoterE2eRegistry, "resource-consumer", "1.13"}
   265  	configs[SdDummyExporter] = Config{list.GcRegistry, "sd-dummy-exporter", "v0.2.0"}
   266  	configs[VolumeNFSServer] = Config{list.PromoterE2eRegistry, "volume/nfs", "1.4"}
   267  	configs[VolumeISCSIServer] = Config{list.PromoterE2eRegistry, "volume/iscsi", "2.6"}
   268  	configs[VolumeRBDServer] = Config{list.PromoterE2eRegistry, "volume/rbd", "1.0.6"}
   269  
   270  	// This adds more config entries. Those have no pre-defined ImageID number,
   271  	// but will be used via ReplaceRegistryInImageURL when deploying
   272  	// CSI drivers (test/e2e/storage/util/create.go).
   273  	appendCSIImageConfigs(configs)
   274  
   275  	// if requested, map all the SHAs into a known format based on the input
   276  	originalImageConfigs := configs
   277  	if repo := os.Getenv("KUBE_TEST_REPO"); len(repo) > 0 {
   278  		configs = GetMappedImageConfigs(originalImageConfigs, repo)
   279  	}
   280  
   281  	return configs, originalImageConfigs
   282  }
   283  
   284  // GetMappedImageConfigs returns the images if they were mapped to the provided
   285  // image repository.
   286  func GetMappedImageConfigs(originalImageConfigs map[ImageID]Config, repo string) map[ImageID]Config {
   287  	configs := make(map[ImageID]Config)
   288  	for i, config := range originalImageConfigs {
   289  		switch i {
   290  		case InvalidRegistryImage, AuthenticatedAlpine,
   291  			AuthenticatedWindowsNanoServer, AgnhostPrivate:
   292  			// These images are special and can't be run out of the cloud - some because they
   293  			// are authenticated, and others because they are not real images. Tests that depend
   294  			// on these images can't be run without access to the public internet.
   295  			configs[i] = config
   296  			continue
   297  		}
   298  
   299  		// Build a new tag with the ImageID, a hash of the image spec (to be unique) and
   300  		// shorten and make the pull spec "safe" so it will fit in the tag
   301  		configs[i] = getRepositoryMappedConfig(i, config, repo)
   302  	}
   303  	return configs
   304  }
   305  
   306  var (
   307  	reCharSafe = regexp.MustCompile(`[^\w]`)
   308  	reDashes   = regexp.MustCompile(`-+`)
   309  )
   310  
   311  // getRepositoryMappedConfig maps an existing image to the provided repo, generating a
   312  // tag that is unique with the input config. The tag will contain the imageID, a hash of
   313  // the image spec (to be unique) and shorten and make the pull spec "safe" so it will
   314  // fit in the tag to allow a human to recognize the value. If imageID is None, then no
   315  // imageID will be added to the tag.
   316  func getRepositoryMappedConfig(imageID ImageID, config Config, repo string) Config {
   317  	parts := strings.SplitN(repo, "/", 2)
   318  	registry, name := parts[0], parts[1]
   319  
   320  	pullSpec := config.GetE2EImage()
   321  
   322  	h := sha256.New()
   323  	h.Write([]byte(pullSpec))
   324  	hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))[:16]
   325  
   326  	shortName := reCharSafe.ReplaceAllLiteralString(pullSpec, "-")
   327  	shortName = reDashes.ReplaceAllLiteralString(shortName, "-")
   328  	maxLength := 127 - 16 - 6 - 10
   329  	if len(shortName) > maxLength {
   330  		shortName = shortName[len(shortName)-maxLength:]
   331  	}
   332  	var version string
   333  	if imageID == None {
   334  		version = fmt.Sprintf("e2e-%s-%s", shortName, hash)
   335  	} else {
   336  		version = fmt.Sprintf("e2e-%d-%s-%s", imageID, shortName, hash)
   337  	}
   338  
   339  	return Config{
   340  		registry: registry,
   341  		name:     name,
   342  		version:  version,
   343  	}
   344  }
   345  
   346  // GetOriginalImageConfigs returns the configuration before any mapping rules.
   347  func GetOriginalImageConfigs() map[ImageID]Config {
   348  	return originalImageConfigs
   349  }
   350  
   351  // GetImageConfigs returns the map of imageConfigs
   352  func GetImageConfigs() map[ImageID]Config {
   353  	return imageConfigs
   354  }
   355  
   356  // GetConfig returns the Config object for an image
   357  func GetConfig(image ImageID) Config {
   358  	return imageConfigs[image]
   359  }
   360  
   361  // GetE2EImage returns the fully qualified URI to an image (including version)
   362  func GetE2EImage(image ImageID) string {
   363  	return fmt.Sprintf("%s/%s:%s", imageConfigs[image].registry, imageConfigs[image].name, imageConfigs[image].version)
   364  }
   365  
   366  // GetE2EImage returns the fully qualified URI to an image (including version)
   367  func (i *Config) GetE2EImage() string {
   368  	return fmt.Sprintf("%s/%s:%s", i.registry, i.name, i.version)
   369  }
   370  
   371  // GetPauseImageName returns the pause image name with proper version
   372  func GetPauseImageName() string {
   373  	return GetE2EImage(Pause)
   374  }
   375  
   376  // ReplaceRegistryInImageURL replaces the registry in the image URL with a custom one based
   377  // on the configured registries.
   378  func ReplaceRegistryInImageURL(imageURL string) (string, error) {
   379  	return replaceRegistryInImageURLWithList(imageURL, registry)
   380  }
   381  
   382  // replaceRegistryInImageURLWithList replaces the registry in the image URL with a custom one based
   383  // on the given registry list.
   384  func replaceRegistryInImageURLWithList(imageURL string, reg RegistryList) (string, error) {
   385  	parts := strings.Split(imageURL, "/")
   386  	countParts := len(parts)
   387  	registryAndUser := strings.Join(parts[:countParts-1], "/")
   388  
   389  	if repo := os.Getenv("KUBE_TEST_REPO"); len(repo) > 0 {
   390  		imageID := None
   391  		for i, v := range originalImageConfigs {
   392  			if v.GetE2EImage() == imageURL {
   393  				imageID = i
   394  				break
   395  			}
   396  		}
   397  		last := strings.SplitN(parts[countParts-1], ":", 2)
   398  		if len(last) == 1 {
   399  			return "", fmt.Errorf("image %q is required to be in an image:tag format", imageURL)
   400  		}
   401  		config := getRepositoryMappedConfig(imageID, Config{
   402  			registry: parts[0],
   403  			name:     strings.Join([]string{strings.Join(parts[1:countParts-1], "/"), last[0]}, "/"),
   404  			version:  last[1],
   405  		}, repo)
   406  		return config.GetE2EImage(), nil
   407  	}
   408  
   409  	switch registryAndUser {
   410  	case initRegistry.GcRegistry:
   411  		registryAndUser = reg.GcRegistry
   412  	case initRegistry.SigStorageRegistry:
   413  		registryAndUser = reg.SigStorageRegistry
   414  	case initRegistry.PrivateRegistry:
   415  		registryAndUser = reg.PrivateRegistry
   416  	case initRegistry.InvalidRegistry:
   417  		registryAndUser = reg.InvalidRegistry
   418  	case initRegistry.PromoterE2eRegistry:
   419  		registryAndUser = reg.PromoterE2eRegistry
   420  	case initRegistry.BuildImageRegistry:
   421  		registryAndUser = reg.BuildImageRegistry
   422  	case initRegistry.GcAuthenticatedRegistry:
   423  		registryAndUser = reg.GcAuthenticatedRegistry
   424  	case initRegistry.DockerLibraryRegistry:
   425  		registryAndUser = reg.DockerLibraryRegistry
   426  	case initRegistry.CloudProviderGcpRegistry:
   427  		registryAndUser = reg.CloudProviderGcpRegistry
   428  	default:
   429  		if countParts == 1 {
   430  			// We assume we found an image from docker hub library
   431  			// e.g. openjdk -> docker.io/library/openjdk
   432  			registryAndUser = reg.DockerLibraryRegistry
   433  			break
   434  		}
   435  
   436  		return "", fmt.Errorf("Registry: %s is missing in test/utils/image/manifest.go, please add the registry, otherwise the test will fail on air-gapped clusters", registryAndUser)
   437  	}
   438  
   439  	return fmt.Sprintf("%s/%s", registryAndUser, parts[countParts-1]), nil
   440  }
   441  

View as plain text