diff --git a/pkg/build/nodeimage/buildcontext.go b/pkg/build/nodeimage/buildcontext.go index fed540dcf0..804692523d 100644 --- a/pkg/build/nodeimage/buildcontext.go +++ b/pkg/build/nodeimage/buildcontext.go @@ -18,7 +18,6 @@ package nodeimage import ( "fmt" - "io" "math/rand" "os" "path" @@ -185,13 +184,16 @@ func (c *buildContext) prePullImagesAndWriteManifests(bits kube.Bits, parsedVers // correct set of built tags using the same logic we will use to rewrite // the tags as we load the archives fixedImages := sets.NewString() + fixedImagesMap := make(map[string]string, builtImages.Len()) // key: original images, value: fixed images for _, image := range builtImages.List() { registry, tag, err := docker.SplitImage(image) if err != nil { return nil, err } registry = fixRepository(registry) - fixedImages.Insert(registry + ":" + tag) + fixedImage := registry + ":" + tag + fixedImages.Insert(fixedImage) + fixedImagesMap[image] = fixedImage } builtImages = fixedImages c.logger.V(1).Info("Detected built images: " + strings.Join(builtImages.List(), ", ")) @@ -286,14 +288,8 @@ func (c *buildContext) prePullImagesAndWriteManifests(bits kube.Bits, parsedVers return err } defer f.Close() - //return importer.LoadCommand().SetStdout(os.Stdout).SetStderr(os.Stderr).SetStdin(f).Run() - // we will rewrite / correct the tags as we load the image - if err := exec.RunWithStdinWriter(importer.LoadCommand().SetStdout(os.Stdout).SetStderr(os.Stdout), func(w io.Writer) error { - return docker.EditArchive(f, w, fixRepository, c.arch) - }); err != nil { - return err - } - return nil + return importer.LoadCommand().SetStdout(os.Stdout).SetStderr(os.Stderr).SetStdin(f).Run() + // we will rewrite / correct the tags in tagFns below }) } @@ -303,6 +299,23 @@ func (c *buildContext) prePullImagesAndWriteManifests(bits kube.Bits, parsedVers return nil, err } + // create a plan of image re-tagging + tagFns := []func() error{} + for unfixed, fixed := range fixedImagesMap { + unfixed, fixed := unfixed, fixed // capture loop var + if unfixed != fixed { + tagFns = append(tagFns, func() error { + return importer.Tag(unfixed, fixed) + }) + } + } + + // run all image re-tragging concurrently until one fails or all succeed + if err := errors.UntilErrorConcurrent(tagFns); err != nil { + c.logger.Errorf("Image build Failed! Failed to re-tag images %v", err) + return nil, err + } + return importer.ListImported() } diff --git a/pkg/build/nodeimage/imageimporter.go b/pkg/build/nodeimage/imageimporter.go index 2894f57d1d..7839263fe7 100644 --- a/pkg/build/nodeimage/imageimporter.go +++ b/pkg/build/nodeimage/imageimporter.go @@ -59,6 +59,12 @@ func (c *containerdImporter) LoadCommand() exec.Cmd { ) } +func (c *containerdImporter) Tag(src, target string) error { + return c.containerCmder.Command( + "ctr", "--namespace=k8s.io", "images", "tag", "--force", src, target, + ).Run() +} + func (c *containerdImporter) ListImported() ([]string, error) { return exec.OutputLines(c.containerCmder.Command("ctr", "--namespace=k8s.io", "images", "list", "-q")) } diff --git a/pkg/build/nodeimage/internal/container/docker/archive.go b/pkg/build/nodeimage/internal/container/docker/archive.go index 66d163f89c..0006552050 100644 --- a/pkg/build/nodeimage/internal/container/docker/archive.go +++ b/pkg/build/nodeimage/internal/container/docker/archive.go @@ -24,7 +24,6 @@ import ( "fmt" "io" "os" - "strings" "sigs.k8s.io/kind/pkg/errors" ) @@ -84,84 +83,6 @@ func GetArchiveTags(path string) ([]string, error) { return res, nil } -// EditArchive applies edit to reader's image repositories, -// IE the repository part of repository:tag in image tags -// This supports v1 / v1.1 / v1.2 Docker Image Archives -// -// editRepositories should be a function that returns the input or an edited -// form, where the input is the image repository -// -// https://github.com/moby/moby/blob/master/image/spec/v1.md -// https://github.com/moby/moby/blob/master/image/spec/v1.1.md -// https://github.com/moby/moby/blob/master/image/spec/v1.2.md -func EditArchive(reader io.Reader, writer io.Writer, editRepositories func(string) string, architectureOverride string) error { - tarReader := tar.NewReader(reader) - tarWriter := tar.NewWriter(writer) - // iterate all entries in the tarball - for { - // read an entry - hdr, err := tarReader.Next() - if err == io.EOF { - return tarWriter.Close() - } else if err != nil { - return err - } - b, err := io.ReadAll(tarReader) - if err != nil { - return err - } - - // edit the repostories and manifests files when we find them - if hdr.Name == "repositories" { - b, err = editRepositoriesFile(b, editRepositories) - if err != nil { - return err - } - hdr.Size = int64(len(b)) - } else if hdr.Name == "manifest.json" { - b, err = editManifestRepositories(b, editRepositories) - if err != nil { - return err - } - hdr.Size = int64(len(b)) - // edit image config when we find that - } else if strings.HasSuffix(hdr.Name, ".json") { - if architectureOverride != "" { - b, err = editConfigArchitecture(b, architectureOverride) - if err != nil { - return err - } - hdr.Size = int64(len(b)) - } - } - - // write to the output tarball - if err := tarWriter.WriteHeader(hdr); err != nil { - return err - } - if len(b) > 0 { - if _, err := tarWriter.Write(b); err != nil { - return err - } - } - } -} - -/* helpers */ - -func editConfigArchitecture(raw []byte, architectureOverride string) ([]byte, error) { - var cfg map[string]interface{} - if err := json.Unmarshal(raw, &cfg); err != nil { - return nil, err - } - const architecture = "architecture" - if _, ok := cfg[architecture]; !ok { - return raw, nil - } - cfg[architecture] = architectureOverride - return json.Marshal(cfg) -} - // archiveRepositories represents repository:tag:ref // // https://github.com/moby/moby/blob/master/image/spec/v1.md @@ -169,20 +90,6 @@ func editConfigArchitecture(raw []byte, architectureOverride string) ([]byte, er // https://github.com/moby/moby/blob/master/image/spec/v1.2.md type archiveRepositories map[string]map[string]string -func editRepositoriesFile(raw []byte, editRepositories func(string) string) ([]byte, error) { - tags, err := parseRepositories(raw) - if err != nil { - return nil, err - } - - fixed := make(archiveRepositories) - for repository, tagsToRefs := range tags { - fixed[editRepositories(repository)] = tagsToRefs - } - - return json.Marshal(fixed) -} - // https://github.com/moby/moby/blob/master/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format type metadataEntry struct { Config string `json:"Config"` @@ -190,30 +97,6 @@ type metadataEntry struct { Layers []string `json:"Layers"` } -// applies -func editManifestRepositories(raw []byte, editRepositories func(string) string) ([]byte, error) { - var entries []metadataEntry - if err := json.Unmarshal(raw, &entries); err != nil { - return nil, err - } - - for i, entry := range entries { - fixed := make([]string, len(entry.RepoTags)) - for i, tag := range entry.RepoTags { - parts := strings.Split(tag, ":") - if len(parts) > 2 { - return nil, fmt.Errorf("invalid repotag: %s", entry) - } - parts[0] = editRepositories(parts[0]) - fixed[i] = strings.Join(parts, ":") - } - - entries[i].RepoTags = fixed - } - - return json.Marshal(entries) -} - // returns repository:tag:ref func parseRepositories(data []byte) (archiveRepositories, error) { var repoTags archiveRepositories