From 0f4b6e3a86f7f1aeb972407c4891abddcd90c0d1 Mon Sep 17 00:00:00 2001 From: Stephen Augustus Date: Tue, 10 Nov 2020 19:53:01 -0500 Subject: [PATCH] pkg/build: Validate images exist before proceeding with CI builds Signed-off-by: Stephen Augustus --- pkg/build/ci.go | 16 +++++--- pkg/release/images.go | 88 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/pkg/build/ci.go b/pkg/build/ci.go index 84eccd8239c..b8d83f41193 100644 --- a/pkg/build/ci.go +++ b/pkg/build/ci.go @@ -146,17 +146,23 @@ func (bi *Instance) checkBuildExists() (bool, error) { // TODO: Do we need to handle the errors more effectively? existErrors := []error{} - for i, path := range gcsBuildPaths { + for _, path := range gcsBuildPaths { logrus.Infof("Checking if GCS build path (%s) exists", path) exists, existErr := gcs.PathExists(path) if existErr != nil || !exists { existErrors = append(existErrors, existErr) } + } - if i == len(gcsBuildPaths)-1 && len(existErrors) == 0 { - logrus.Infof("Build already exists. Exiting...") - return true, nil - } + images := release.NewImages() + imagesExist, imagesExistErr := images.Exists(bi.opts.Registry, version, bi.opts.Fast) + if imagesExistErr != nil { + existErrors = append(existErrors, imagesExistErr) + } + + if imagesExist && len(existErrors) == 0 { + logrus.Infof("Build already exists. Exiting...") + return true, nil } logrus.Infof("The following error(s) occurred while looking for a build: %v", existErrors) diff --git a/pkg/release/images.go b/pkg/release/images.go index fd505d15246..e122ada5fe0 100644 --- a/pkg/release/images.go +++ b/pkg/release/images.go @@ -79,7 +79,7 @@ func (*defaultCommandClient) RepoTagFromTarball(path string) (string, error) { var tagRegex = regexp.MustCompile(`^.+/(.+):.+$`) -// PublishImages relases container images to the provided target registry +// PublishImages releases container images to the provided target registry // was in releaselib.sh: release::docker::release func (i *Images) Publish(registry, version, buildPath string) error { version = i.normalizeVersion(version) @@ -239,6 +239,92 @@ func (i *Images) Validate(registry, version, buildPath string) error { return nil } +// Exists verifies that a set of image manifests exists on a specified remote +// registry. This is a simpler check than Validate, which doesn't presuppose the +// existence of a local build directory. Used in CI builds to quickly validate +// if a build is actually required. +func (i *Images) Exists(registry, version string, fast bool) (bool, error) { + logrus.Infof("Validating image manifests in %s", registry) + version = i.normalizeVersion(version) + + // TODO: Maybe pull this out into a var? + manifestImages := []string{ + "conformance", + "kube-apiserver", + "kube-controller-manager", + "kube-proxy", + "kube-scheduler", + } + + // TODO: Maybe pull this out into a var? + arches := []string{ + "amd64", + "arm", + "arm64", + "ppc64le", + "s390x", + } + + // TODO: Maybe pull this out into a var? + if fast { + arches = []string{ + "amd64", + } + } + + for _, image := range manifestImages { + imageVersion := fmt.Sprintf("%s/%s:%s", registry, image, version) + + manifest, err := i.client.ExecuteOutput( + "skopeo", "inspect", fmt.Sprintf("docker://%s", imageVersion), "--raw", + ) + if err != nil { + return false, errors.Wrapf( + err, "get remote manifest from %s", imageVersion, + ) + } + manifestFile, err := ioutil.TempFile("", "manifest-") + if err != nil { + return false, errors.Wrap(err, "create temp file for manifest") + } + if _, err := manifestFile.WriteString(manifest); err != nil { + return false, errors.Wrapf( + err, "write manifest to %s", manifestFile.Name(), + ) + } + defer os.RemoveAll(manifestFile.Name()) + + for _, arch := range arches { + logrus.Infof( + "Checking image digest for %s on %s architecture", image, arch, + ) + + digest, err := i.client.ExecuteOutput( + "jq", "--arg", "a", arch, "-r", + ".manifests[] | select(.platform.architecture == $a) | .digest", + manifestFile.Name(), + ) + if err != nil { + return false, errors.Wrapf( + err, "get digest from manifest file %s for arch %s", + manifestFile.Name(), arch, + ) + } + + if digest == "" { + return false, errors.Errorf( + "could not find the image digest for %s on %s", + imageVersion, arch, + ) + } + + logrus.Infof("Digest for %s on %s: %s", imageVersion, arch, digest) + } + } + + return true, nil +} + func (i *Images) getManifestImages( registry, version, buildPath string, forTarballFn func(path, origTag, newTagWithArch string) error,