k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/utils/image/manifest.go (about) 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, ®istry) 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 ) 226 227 func initImageConfigs(list RegistryList) (map[ImageID]Config, map[ImageID]Config) { 228 configs := map[ImageID]Config{} 229 configs[Agnhost] = Config{list.PromoterE2eRegistry, "agnhost", "2.52"} 230 configs[AgnhostPrivate] = Config{list.PrivateRegistry, "agnhost", "2.6"} 231 configs[AuthenticatedAlpine] = Config{list.GcAuthenticatedRegistry, "alpine", "3.7"} 232 configs[AuthenticatedWindowsNanoServer] = Config{list.GcAuthenticatedRegistry, "windows-nanoserver", "v1"} 233 configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.29.2"} 234 configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.4"} 235 configs[BusyBox] = Config{list.PromoterE2eRegistry, "busybox", "1.36.1-1"} 236 configs[CudaVectorAdd] = Config{list.PromoterE2eRegistry, "cuda-vector-add", "1.0"} 237 configs[CudaVectorAdd2] = Config{list.PromoterE2eRegistry, "cuda-vector-add", "2.3"} 238 configs[DistrolessIptables] = Config{list.BuildImageRegistry, "distroless-iptables", "v0.5.4"} 239 configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.13-0"} 240 configs[Httpd] = Config{list.PromoterE2eRegistry, "httpd", "2.4.38-4"} 241 configs[HttpdNew] = Config{list.PromoterE2eRegistry, "httpd", "2.4.39-4"} 242 configs[InvalidRegistryImage] = Config{list.InvalidRegistry, "alpine", "3.1"} 243 configs[IpcUtils] = Config{list.PromoterE2eRegistry, "ipc-utils", "1.3"} 244 configs[JessieDnsutils] = Config{list.PromoterE2eRegistry, "jessie-dnsutils", "1.7"} 245 configs[Kitten] = Config{list.PromoterE2eRegistry, "kitten", "1.7"} 246 configs[Nautilus] = Config{list.PromoterE2eRegistry, "nautilus", "1.7"} 247 configs[NFSProvisioner] = Config{list.SigStorageRegistry, "nfs-provisioner", "v4.0.8"} 248 configs[Nginx] = Config{list.PromoterE2eRegistry, "nginx", "1.14-4"} 249 configs[NginxNew] = Config{list.PromoterE2eRegistry, "nginx", "1.15-4"} 250 configs[NodePerfNpbEp] = Config{list.PromoterE2eRegistry, "node-perf/npb-ep", "1.2"} 251 configs[NodePerfNpbIs] = Config{list.PromoterE2eRegistry, "node-perf/npb-is", "1.2"} 252 configs[NodePerfTfWideDeep] = Config{list.PromoterE2eRegistry, "node-perf/tf-wide-deep", "1.3"} 253 configs[Nonewprivs] = Config{list.PromoterE2eRegistry, "nonewprivs", "1.3"} 254 configs[NonRoot] = Config{list.PromoterE2eRegistry, "nonroot", "1.4"} 255 // Pause - when these values are updated, also update cmd/kubelet/app/options/container_runtime.go 256 configs[Pause] = Config{list.GcRegistry, "pause", "3.9"} 257 configs[Perl] = Config{list.PromoterE2eRegistry, "perl", "5.26"} 258 configs[PrometheusDummyExporter] = Config{list.GcRegistry, "prometheus-dummy-exporter", "v0.1.0"} 259 configs[PrometheusToSd] = Config{list.GcRegistry, "prometheus-to-sd", "v0.5.0"} 260 configs[Redis] = Config{list.PromoterE2eRegistry, "redis", "5.0.5-3"} 261 configs[RegressionIssue74839] = Config{list.PromoterE2eRegistry, "regression-issue-74839", "1.2"} 262 configs[ResourceConsumer] = Config{list.PromoterE2eRegistry, "resource-consumer", "1.13"} 263 configs[SdDummyExporter] = Config{list.GcRegistry, "sd-dummy-exporter", "v0.2.0"} 264 configs[VolumeNFSServer] = Config{list.PromoterE2eRegistry, "volume/nfs", "1.4"} 265 configs[VolumeISCSIServer] = Config{list.PromoterE2eRegistry, "volume/iscsi", "2.6"} 266 267 // This adds more config entries. Those have no pre-defined ImageID number, 268 // but will be used via ReplaceRegistryInImageURL when deploying 269 // CSI drivers (test/e2e/storage/util/create.go). 270 appendCSIImageConfigs(configs) 271 272 // if requested, map all the SHAs into a known format based on the input 273 originalImageConfigs := configs 274 if repo := os.Getenv("KUBE_TEST_REPO"); len(repo) > 0 { 275 configs = GetMappedImageConfigs(originalImageConfigs, repo) 276 } 277 278 return configs, originalImageConfigs 279 } 280 281 // GetMappedImageConfigs returns the images if they were mapped to the provided 282 // image repository. 283 func GetMappedImageConfigs(originalImageConfigs map[ImageID]Config, repo string) map[ImageID]Config { 284 configs := make(map[ImageID]Config) 285 for i, config := range originalImageConfigs { 286 switch i { 287 case InvalidRegistryImage, AuthenticatedAlpine, 288 AuthenticatedWindowsNanoServer, AgnhostPrivate: 289 // These images are special and can't be run out of the cloud - some because they 290 // are authenticated, and others because they are not real images. Tests that depend 291 // on these images can't be run without access to the public internet. 292 configs[i] = config 293 continue 294 } 295 296 // Build a new tag with the ImageID, a hash of the image spec (to be unique) and 297 // shorten and make the pull spec "safe" so it will fit in the tag 298 configs[i] = getRepositoryMappedConfig(i, config, repo) 299 } 300 return configs 301 } 302 303 var ( 304 reCharSafe = regexp.MustCompile(`[^\w]`) 305 reDashes = regexp.MustCompile(`-+`) 306 ) 307 308 // getRepositoryMappedConfig maps an existing image to the provided repo, generating a 309 // tag that is unique with the input config. The tag will contain the imageID, a hash of 310 // the image spec (to be unique) and shorten and make the pull spec "safe" so it will 311 // fit in the tag to allow a human to recognize the value. If imageID is None, then no 312 // imageID will be added to the tag. 313 func getRepositoryMappedConfig(imageID ImageID, config Config, repo string) Config { 314 parts := strings.SplitN(repo, "/", 2) 315 registry, name := parts[0], parts[1] 316 317 pullSpec := config.GetE2EImage() 318 319 h := sha256.New() 320 h.Write([]byte(pullSpec)) 321 hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))[:16] 322 323 shortName := reCharSafe.ReplaceAllLiteralString(pullSpec, "-") 324 shortName = reDashes.ReplaceAllLiteralString(shortName, "-") 325 maxLength := 127 - 16 - 6 - 10 326 if len(shortName) > maxLength { 327 shortName = shortName[len(shortName)-maxLength:] 328 } 329 var version string 330 if imageID == None { 331 version = fmt.Sprintf("e2e-%s-%s", shortName, hash) 332 } else { 333 version = fmt.Sprintf("e2e-%d-%s-%s", imageID, shortName, hash) 334 } 335 336 return Config{ 337 registry: registry, 338 name: name, 339 version: version, 340 } 341 } 342 343 // GetOriginalImageConfigs returns the configuration before any mapping rules. 344 func GetOriginalImageConfigs() map[ImageID]Config { 345 return originalImageConfigs 346 } 347 348 // GetImageConfigs returns the map of imageConfigs 349 func GetImageConfigs() map[ImageID]Config { 350 return imageConfigs 351 } 352 353 // GetConfig returns the Config object for an image 354 func GetConfig(image ImageID) Config { 355 return imageConfigs[image] 356 } 357 358 // GetE2EImage returns the fully qualified URI to an image (including version) 359 func GetE2EImage(image ImageID) string { 360 return fmt.Sprintf("%s/%s:%s", imageConfigs[image].registry, imageConfigs[image].name, imageConfigs[image].version) 361 } 362 363 // GetE2EImage returns the fully qualified URI to an image (including version) 364 func (i *Config) GetE2EImage() string { 365 return fmt.Sprintf("%s/%s:%s", i.registry, i.name, i.version) 366 } 367 368 // GetPauseImageName returns the pause image name with proper version 369 func GetPauseImageName() string { 370 return GetE2EImage(Pause) 371 } 372 373 // ReplaceRegistryInImageURL replaces the registry in the image URL with a custom one based 374 // on the configured registries. 375 func ReplaceRegistryInImageURL(imageURL string) (string, error) { 376 return replaceRegistryInImageURLWithList(imageURL, registry) 377 } 378 379 // replaceRegistryInImageURLWithList replaces the registry in the image URL with a custom one based 380 // on the given registry list. 381 func replaceRegistryInImageURLWithList(imageURL string, reg RegistryList) (string, error) { 382 parts := strings.Split(imageURL, "/") 383 countParts := len(parts) 384 registryAndUser := strings.Join(parts[:countParts-1], "/") 385 386 if repo := os.Getenv("KUBE_TEST_REPO"); len(repo) > 0 { 387 imageID := None 388 for i, v := range originalImageConfigs { 389 if v.GetE2EImage() == imageURL { 390 imageID = i 391 break 392 } 393 } 394 last := strings.SplitN(parts[countParts-1], ":", 2) 395 if len(last) == 1 { 396 return "", fmt.Errorf("image %q is required to be in an image:tag format", imageURL) 397 } 398 config := getRepositoryMappedConfig(imageID, Config{ 399 registry: parts[0], 400 name: strings.Join([]string{strings.Join(parts[1:countParts-1], "/"), last[0]}, "/"), 401 version: last[1], 402 }, repo) 403 return config.GetE2EImage(), nil 404 } 405 406 switch registryAndUser { 407 case initRegistry.GcRegistry: 408 registryAndUser = reg.GcRegistry 409 case initRegistry.SigStorageRegistry: 410 registryAndUser = reg.SigStorageRegistry 411 case initRegistry.PrivateRegistry: 412 registryAndUser = reg.PrivateRegistry 413 case initRegistry.InvalidRegistry: 414 registryAndUser = reg.InvalidRegistry 415 case initRegistry.PromoterE2eRegistry: 416 registryAndUser = reg.PromoterE2eRegistry 417 case initRegistry.BuildImageRegistry: 418 registryAndUser = reg.BuildImageRegistry 419 case initRegistry.GcAuthenticatedRegistry: 420 registryAndUser = reg.GcAuthenticatedRegistry 421 case initRegistry.DockerLibraryRegistry: 422 registryAndUser = reg.DockerLibraryRegistry 423 case initRegistry.CloudProviderGcpRegistry: 424 registryAndUser = reg.CloudProviderGcpRegistry 425 default: 426 if countParts == 1 { 427 // We assume we found an image from docker hub library 428 // e.g. openjdk -> docker.io/library/openjdk 429 registryAndUser = reg.DockerLibraryRegistry 430 break 431 } 432 433 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) 434 } 435 436 return fmt.Sprintf("%s/%s", registryAndUser, parts[countParts-1]), nil 437 }