// BuildAndPushImageOfSizeWithBuilder tries to build an image of wanted size and number of layers. Built image // is stored as an image stream tag <name>:<tag>. If shouldSucceed is false, a build is expected to fail with // a denied error. Note the size is only approximate. Resulting image size will be different depending on used // compression algorithm and metadata overhead. func BuildAndPushImageOfSizeWithBuilder( oc *exutil.CLI, dClient *dockerclient.Client, namespace, name, tag string, size uint64, numberOfLayers int, shouldSucceed bool, ) error { istName := name if tag != "" { istName += ":" + tag } bc, err := oc.REST().BuildConfigs(namespace).Get(name) if err == nil { if bc.Spec.CommonSpec.Output.To.Kind != "ImageStreamTag" { return fmt.Errorf("Unexpected kind of buildspec's output (%s != %s)", bc.Spec.CommonSpec.Output.To.Kind, "ImageStreamTag") } bc.Spec.CommonSpec.Output.To.Name = istName if _, err = oc.REST().BuildConfigs(namespace).Update(bc); err != nil { return err } } else { err = oc.Run("new-build").Args("--binary", "--name", name, "--to", istName).Execute() if err != nil { return err } } tempDir, err := ioutil.TempDir("", "name-build") if err != nil { return err } dataSize := calculateRoughDataSize(oc.Stdout(), size, numberOfLayers) lines := make([]string, numberOfLayers+1) lines[0] = "FROM scratch" for i := 1; i <= numberOfLayers; i++ { blobName := fmt.Sprintf("data%d", i) if err := createRandomBlob(path.Join(tempDir, blobName), dataSize); err != nil { return err } lines[i] = fmt.Sprintf("COPY %s /%s", blobName, blobName) } if err := ioutil.WriteFile(path.Join(tempDir, "Dockerfile"), []byte(strings.Join(lines, "\n")+"\n"), 0644); err != nil { return err } out, err := oc.Run("start-build").Args(name, "--from-dir", tempDir, "--wait").Output() fmt.Fprintf(g.GinkgoWriter, "\nstart-build output:\n%s\n", out) buildLog, logsErr := oc.Run("logs").Args("bc/" + name).Output() if match := reSuccessfulBuild.FindStringSubmatch(buildLog); len(match) > 1 { defer dClient.RemoveImageExtended(match[1], dockerclient.RemoveImageOptions{Force: true}) } if shouldSucceed && err != nil { return fmt.Errorf("Got unexpected build error: %v", err) } if !shouldSucceed { if err == nil { return fmt.Errorf("Build unexpectedly succeeded") } if logsErr != nil { return fmt.Errorf("Failed to show log of build config %s: %v", name, err) } if !reExpectedDeniedError.MatchString(buildLog) { return fmt.Errorf("Failed to match expected %q in: %q", reExpectedDeniedError.String(), buildLog) } } return nil }
// BuildAndPushImageOfSizeWithDocker tries to build an image of wanted size and number of layers. It instructs // Docker daemon directly. Built image is stored as an image stream tag <name>:<tag>. If shouldSucceed is // false, a push is expected to fail with a denied error. Note the size is only approximate. Resulting image // size will be different depending on used compression algorithm and metadata overhead. func BuildAndPushImageOfSizeWithDocker( oc *exutil.CLI, dClient *dockerclient.Client, name, tag string, size uint64, numberOfLayers int, outSink io.Writer, shouldSucceed bool, ) (imageDigest string, err error) { registryURL, err := GetDockerRegistryURL(oc) if err != nil { return "", err } tempDir, err := ioutil.TempDir("", "name-build") if err != nil { return "", err } dataSize := calculateRoughDataSize(oc.Stdout(), size, numberOfLayers) lines := make([]string, numberOfLayers+1) lines[0] = "FROM scratch" for i := 1; i <= numberOfLayers; i++ { blobName := fmt.Sprintf("data%d", i) if err := createRandomBlob(path.Join(tempDir, blobName), dataSize); err != nil { return "", err } lines[i] = fmt.Sprintf("COPY %s /%s", blobName, blobName) } if err := ioutil.WriteFile(path.Join(tempDir, "Dockerfile"), []byte(strings.Join(lines, "\n")+"\n"), 0644); err != nil { return "", err } imageName := fmt.Sprintf("%s/%s/%s", registryURL, oc.Namespace(), name) taggedName := fmt.Sprintf("%s:%s", imageName, tag) err = dClient.BuildImage(dockerclient.BuildImageOptions{ Name: taggedName, RmTmpContainer: true, ForceRmTmpContainer: true, ContextDir: tempDir, OutputStream: outSink, }) if err != nil { return "", fmt.Errorf("failed to build %q image: %v", taggedName, err) } image, err := dClient.InspectImage(taggedName) if err != nil { return "", err } defer dClient.RemoveImageExtended(image.ID, dockerclient.RemoveImageOptions{Force: true}) digest := "" if len(image.RepoDigests) == 1 { digest = image.RepoDigests[0] } out, err := oc.Run("whoami").Args("-t").Output() if err != nil { return "", err } token := strings.TrimSpace(out) var buf bytes.Buffer err = dClient.PushImage(dockerclient.PushImageOptions{ Name: imageName, Tag: tag, Registry: registryURL, OutputStream: &buf, }, dockerclient.AuthConfiguration{ Username: "******", Password: token, Email: "*****@*****.**", ServerAddress: registryURL, }) out = buf.String() outSink.Write([]byte(out)) if shouldSucceed { if err != nil { return "", fmt.Errorf("Got unexpected push error: %v", err) } if len(digest) == 0 { outSink.Write([]byte("matching digest string\n")) match := rePushedImageDigest.FindStringSubmatch(out) if len(match) < 2 { return "", fmt.Errorf("Failed to parse digest") } digest = match[1] } return digest, nil } if err == nil { return "", fmt.Errorf("Push unexpectedly succeeded") } if !reExpectedDeniedError.MatchString(err.Error()) { return "", fmt.Errorf("Failed to match expected %q in: %q", reExpectedDeniedError.String(), err.Error()) } return "", nil }