diff --git a/src/cmd/linuxkit/pkg_remotetag.go b/src/cmd/linuxkit/pkg_remotetag.go index 65c52a0a0..e21000370 100644 --- a/src/cmd/linuxkit/pkg_remotetag.go +++ b/src/cmd/linuxkit/pkg_remotetag.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/crane" namepkg "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/linuxkit/linuxkit/src/cmd/linuxkit/util" @@ -19,9 +20,13 @@ func pkgRemoteTagCmd() *cobra.Command { Long: `Tag a package in a remote registry with another tag, without downloading or pulling. Will simply tag using the identical descriptor. First argument is "from" tag, second is "to" tag. + + If the "to" and "from" repositories are the same, then it is a simple tag operation. + If they are not, then the "from" image is pulled and pushed to the "to" repository. `, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + var finalErr error from := args[0] to := args[1] remoteOptions := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain)} @@ -48,15 +53,19 @@ func pkgRemoteTagCmd() *cobra.Command { } log.Infof("image %s already exists in the registry, but is different from %s, overwriting", toFullname, fromFullname) } - toTag, err := namepkg.NewTag(toFullname) - if err != nil { - return err - } - if err := remote.Tag(toTag, fromDesc, remoteOptions...); err != nil { - return fmt.Errorf("error tagging image %s as %s: %v", fromFullname, toFullname, err) + // see if they are from the same sources + if fromRef.Context().String() == toRef.Context().String() { + toTag, err := namepkg.NewTag(toFullname) + if err != nil { + return err + } + finalErr = remote.Tag(toTag, fromDesc, remoteOptions...) + } else { + // different, so need to copy + finalErr = crane.Copy(fromFullname, toFullname) } - return nil + return finalErr }, } cmd.Flags().StringVar(&release, "release", "", "Release the given version") diff --git a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/LICENSE b/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/LICENSE deleted file mode 100644 index e2e6ae04d..000000000 --- a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2016 Antonio Murdaca - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/name.go b/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/name.go deleted file mode 100644 index e3291cc08..000000000 --- a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/name.go +++ /dev/null @@ -1,48 +0,0 @@ -package util - -import ( - "fmt" - "strings" - - "github.com/docker/distribution/reference" -) - -const ( - // DefaultHostname is the default built-in registry (DockerHub) - DefaultHostname = "docker.io" - // LegacyDefaultHostname is the old hostname used for DockerHub - LegacyDefaultHostname = "index.docker.io" - // DefaultRepoPrefix is the prefix used for official images in DockerHub - DefaultRepoPrefix = "library/" -) - -func ParseName(name string) (reference.Named, error) { - distref, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - hostname, remoteName := splitHostname(distref.String()) - if hostname == "" { - return nil, fmt.Errorf("Please use a fully qualified repository name") - } - return reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", hostname, remoteName)) -} - -// splitHostname splits a repository name to hostname and remotename string. -// If no valid hostname is found, the default hostname is used. Repository name -// needs to be already validated before. -func splitHostname(name string) (hostname, remoteName string) { - i := strings.IndexRune(name, '/') - if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { - hostname, remoteName = DefaultHostname, name - } else { - hostname, remoteName = name[:i], name[i+1:] - } - if hostname == LegacyDefaultHostname { - hostname = DefaultHostname - } - if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') { - remoteName = DefaultRepoPrefix + remoteName - } - return -} diff --git a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/os.go b/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/os.go deleted file mode 100644 index a48f55d99..000000000 --- a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/os.go +++ /dev/null @@ -1,42 +0,0 @@ -package util - -import "fmt" - -//go:generate go run osgen.go - -var ( - armVariants = map[string]bool{ - "v5": true, - "v6": true, - "v7": true, - } -) - -// IsValidOSArch checks against the generated list of os/arch combinations -// from Go as well as checking for valid variants for ARM (the only architecture that uses variants) -func IsValidOSArch(os string, arch string, variant string) bool { - osarch := fmt.Sprintf("%s/%s", os, arch) - if _, ok := validOS[os]; !ok { - return false - } - if _, ok := validArch[arch]; !ok { - return false - } - if variant == "" { - return true - } - - // only arm/arm64 can use variant - switch osarch { - case "linux/arm": - _, ok := armVariants[variant] - return ok - case "linux/arm64": - if variant == "v8" { - return true - } - default: - return false - } - return false -} diff --git a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/oslist.go b/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/oslist.go deleted file mode 100644 index 7c228460d..000000000 --- a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/oslist.go +++ /dev/null @@ -1,36 +0,0 @@ -// Code generated by go generate; DO NOT EDIT. -// This file was generated by robots at -// 2021-02-09 13:11:28.537236306 -0500 EST m=+0.034330659 -// using data from 'go tool dist list' -package util - -var validOS = map[string]bool{ - "darwin": true, - "dragonfly": true, - "illumos": true, - "js": true, - "netbsd": true, - "plan9": true, - "aix": true, - "android": true, - "windows": true, - "openbsd": true, - "solaris": true, - "freebsd": true, - "linux": true, -} -var validArch = map[string]bool{ - "wasm": true, - "mips": true, - "mips64le": true, - "mipsle": true, - "ppc64le": true, - "amd64": true, - "arm64": true, - "arm": true, - "mips64": true, - "riscv64": true, - "s390x": true, - "ppc64": true, - "386": true, -} diff --git a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/resolver.go b/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/resolver.go deleted file mode 100644 index b6ece2325..000000000 --- a/src/cmd/linuxkit/vendor/github.com/estesp/manifest-tool/v2/pkg/util/resolver.go +++ /dev/null @@ -1,33 +0,0 @@ -package util - -import ( - "crypto/tls" - "net/http" - - "github.com/containerd/containerd/remotes" - "github.com/containerd/containerd/remotes/docker" -) - -func NewResolver(username, password string, insecure, plainHTTP bool, configs ...string) remotes.Resolver { - - opts := docker.ResolverOptions{ - PlainHTTP: plainHTTP, - } - client := http.DefaultClient - if insecure { - client.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } - } - opts.Client = client - - if username != "" || password != "" { - opts.Credentials = func(hostName string) (string, string, error) { - return username, password, nil - } - return docker.NewResolver(opts) - } - return docker.NewResolver(opts) -} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/internal/legacy/copy.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/internal/legacy/copy.go new file mode 100644 index 000000000..10467ba10 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/internal/legacy/copy.go @@ -0,0 +1,57 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package legacy provides methods for interacting with legacy image formats. +package legacy + +import ( + "bytes" + "encoding/json" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +// CopySchema1 allows `[g]crane cp` to work with old images without adding +// full support for schema 1 images to this package. +func CopySchema1(desc *remote.Descriptor, srcRef, dstRef name.Reference, opts ...remote.Option) error { + m := schema1{} + if err := json.NewDecoder(bytes.NewReader(desc.Manifest)).Decode(&m); err != nil { + return err + } + + for _, layer := range m.FSLayers { + src := srcRef.Context().Digest(layer.BlobSum) + dst := dstRef.Context().Digest(layer.BlobSum) + + blob, err := remote.Layer(src, opts...) + if err != nil { + return err + } + + if err := remote.WriteLayer(dst.Context(), blob, opts...); err != nil { + return err + } + } + + return remote.Put(dstRef, desc, opts...) +} + +type fslayer struct { + BlobSum string `json:"blobSum"` +} + +type schema1 struct { + FSLayers []fslayer `json:"fsLayers"` +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/append.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/append.go new file mode 100644 index 000000000..4f660d8fc --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/append.go @@ -0,0 +1,72 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + "os" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/stream" + "github.com/google/go-containerregistry/pkg/v1/tarball" +) + +// Append reads a layer from path and appends it the the v1.Image base. +func Append(base v1.Image, paths ...string) (v1.Image, error) { + layers := make([]v1.Layer, 0, len(paths)) + for _, path := range paths { + layer, err := getLayer(path) + if err != nil { + return nil, fmt.Errorf("reading layer %q: %w", path, err) + } + + layers = append(layers, layer) + } + + return mutate.AppendLayers(base, layers...) +} + +func getLayer(path string) (v1.Layer, error) { + f, err := streamFile(path) + if err != nil { + return nil, err + } + if f != nil { + return stream.NewLayer(f), nil + } + + return tarball.LayerFromFile(path) +} + +// If we're dealing with a named pipe, trying to open it multiple times will +// fail, so we need to do a streaming upload. +// +// returns nil, nil for non-streaming files +func streamFile(path string) (*os.File, error) { + if path == "-" { + return os.Stdin, nil + } + fi, err := os.Stat(path) + if err != nil { + return nil, err + } + + if !fi.Mode().IsRegular() { + return os.Open(path) + } + + return nil, nil +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/catalog.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/catalog.go new file mode 100644 index 000000000..f30800cca --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/catalog.go @@ -0,0 +1,35 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +// Catalog returns the repositories in a registry's catalog. +func Catalog(src string, opt ...Option) (res []string, err error) { + o := makeOptions(opt...) + reg, err := name.NewRegistry(src, o.Name...) + if err != nil { + return nil, err + } + + // This context gets overridden by remote.WithContext, which is set by + // crane.WithContext. + return remote.Catalog(context.Background(), reg, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/config.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/config.go new file mode 100644 index 000000000..3e55cc93a --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/config.go @@ -0,0 +1,24 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +// Config returns the config file for the remote image ref. +func Config(ref string, opt ...Option) ([]byte, error) { + i, _, err := getImage(ref, opt...) + if err != nil { + return nil, err + } + return i.RawConfigFile() +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/copy.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/copy.go new file mode 100644 index 000000000..a606f9654 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/copy.go @@ -0,0 +1,88 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + + "github.com/google/go-containerregistry/internal/legacy" + "github.com/google/go-containerregistry/pkg/logs" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +// Copy copies a remote image or index from src to dst. +func Copy(src, dst string, opt ...Option) error { + o := makeOptions(opt...) + srcRef, err := name.ParseReference(src, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference %q: %w", src, err) + } + + dstRef, err := name.ParseReference(dst, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference for %q: %w", dst, err) + } + + logs.Progress.Printf("Copying from %v to %v", srcRef, dstRef) + desc, err := remote.Get(srcRef, o.Remote...) + if err != nil { + return fmt.Errorf("fetching %q: %w", src, err) + } + + switch desc.MediaType { + case types.OCIImageIndex, types.DockerManifestList: + // Handle indexes separately. + if o.Platform != nil { + // If platform is explicitly set, don't copy the whole index, just the appropriate image. + if err := copyImage(desc, dstRef, o); err != nil { + return fmt.Errorf("failed to copy image: %w", err) + } + } else { + if err := copyIndex(desc, dstRef, o); err != nil { + return fmt.Errorf("failed to copy index: %w", err) + } + } + case types.DockerManifestSchema1, types.DockerManifestSchema1Signed: + // Handle schema 1 images separately. + if err := legacy.CopySchema1(desc, srcRef, dstRef, o.Remote...); err != nil { + return fmt.Errorf("failed to copy schema 1 image: %w", err) + } + default: + // Assume anything else is an image, since some registries don't set mediaTypes properly. + if err := copyImage(desc, dstRef, o); err != nil { + return fmt.Errorf("failed to copy image: %w", err) + } + } + + return nil +} + +func copyImage(desc *remote.Descriptor, dstRef name.Reference, o Options) error { + img, err := desc.Image() + if err != nil { + return err + } + return remote.Write(dstRef, img, o.Remote...) +} + +func copyIndex(desc *remote.Descriptor, dstRef name.Reference, o Options) error { + idx, err := desc.ImageIndex() + if err != nil { + return err + } + return remote.WriteIndex(dstRef, idx, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/delete.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/delete.go new file mode 100644 index 000000000..58a8be1f0 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/delete.go @@ -0,0 +1,33 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +// Delete deletes the remote reference at src. +func Delete(src string, opt ...Option) error { + o := makeOptions(opt...) + ref, err := name.ParseReference(src, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference %q: %w", src, err) + } + + return remote.Delete(ref, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/digest.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/digest.go new file mode 100644 index 000000000..868a57010 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/digest.go @@ -0,0 +1,52 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import "github.com/google/go-containerregistry/pkg/logs" + +// Digest returns the sha256 hash of the remote image at ref. +func Digest(ref string, opt ...Option) (string, error) { + o := makeOptions(opt...) + if o.Platform != nil { + desc, err := getManifest(ref, opt...) + if err != nil { + return "", err + } + if !desc.MediaType.IsIndex() { + return desc.Digest.String(), nil + } + + // TODO: does not work for indexes which contain schema v1 manifests + img, err := desc.Image() + if err != nil { + return "", err + } + digest, err := img.Digest() + if err != nil { + return "", err + } + return digest.String(), nil + } + desc, err := Head(ref, opt...) + if err != nil { + logs.Warn.Printf("HEAD request failed, falling back on GET: %v", err) + rdesc, err := getManifest(ref, opt...) + if err != nil { + return "", err + } + return rdesc.Digest.String(), nil + } + return desc.Digest.String(), nil +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/doc.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/doc.go new file mode 100644 index 000000000..7602d7953 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package crane holds libraries used to implement the crane CLI. +package crane diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/export.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/export.go new file mode 100644 index 000000000..29334912e --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/export.go @@ -0,0 +1,29 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "io" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" +) + +// Export writes the filesystem contents (as a tarball) of img to w. +func Export(img v1.Image, w io.Writer) error { + fs := mutate.Extract(img) + _, err := io.Copy(w, fs) + return err +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/filemap.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/filemap.go new file mode 100644 index 000000000..e9eee1ba4 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/filemap.go @@ -0,0 +1,67 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "archive/tar" + "bytes" + "sort" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/tarball" +) + +// Layer creates a layer from a single file map. These layers are reproducible and consistent. +// A filemap is a path -> file content map representing a file system. +func Layer(filemap map[string][]byte) (v1.Layer, error) { + b := &bytes.Buffer{} + w := tar.NewWriter(b) + + fn := []string{} + for f := range filemap { + fn = append(fn, f) + } + sort.Strings(fn) + + for _, f := range fn { + c := filemap[f] + if err := w.WriteHeader(&tar.Header{ + Name: f, + Size: int64(len(c)), + }); err != nil { + return nil, err + } + if _, err := w.Write(c); err != nil { + return nil, err + } + } + if err := w.Close(); err != nil { + return nil, err + } + return tarball.LayerFromReader(b) +} + +// Image creates a image with the given filemaps as its contents. These images are reproducible and consistent. +// A filemap is a path -> file content map representing a file system. +func Image(filemap map[string][]byte) (v1.Image, error) { + y, err := Layer(filemap) + if err != nil { + return nil, err + } + + return mutate.AppendLayers(empty.Image, y) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/get.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/get.go new file mode 100644 index 000000000..1f12f01d0 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/get.go @@ -0,0 +1,56 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +func getImage(r string, opt ...Option) (v1.Image, name.Reference, error) { + o := makeOptions(opt...) + ref, err := name.ParseReference(r, o.Name...) + if err != nil { + return nil, nil, fmt.Errorf("parsing reference %q: %w", r, err) + } + img, err := remote.Image(ref, o.Remote...) + if err != nil { + return nil, nil, fmt.Errorf("reading image %q: %w", ref, err) + } + return img, ref, nil +} + +func getManifest(r string, opt ...Option) (*remote.Descriptor, error) { + o := makeOptions(opt...) + ref, err := name.ParseReference(r, o.Name...) + if err != nil { + return nil, fmt.Errorf("parsing reference %q: %w", r, err) + } + return remote.Get(ref, o.Remote...) +} + +// Head performs a HEAD request for a manifest and returns a content descriptor +// based on the registry's response. +func Head(r string, opt ...Option) (*v1.Descriptor, error) { + o := makeOptions(opt...) + ref, err := name.ParseReference(r, o.Name...) + if err != nil { + return nil, err + } + return remote.Head(ref, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/list.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/list.go new file mode 100644 index 000000000..38352153b --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/list.go @@ -0,0 +1,33 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +// ListTags returns the tags in repository src. +func ListTags(src string, opt ...Option) ([]string, error) { + o := makeOptions(opt...) + repo, err := name.NewRepository(src, o.Name...) + if err != nil { + return nil, fmt.Errorf("parsing repo %q: %w", src, err) + } + + return remote.List(repo, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/manifest.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/manifest.go new file mode 100644 index 000000000..a54926aef --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/manifest.go @@ -0,0 +1,32 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +// Manifest returns the manifest for the remote image or index ref. +func Manifest(ref string, opt ...Option) ([]byte, error) { + desc, err := getManifest(ref, opt...) + if err != nil { + return nil, err + } + o := makeOptions(opt...) + if o.Platform != nil { + img, err := desc.Image() + if err != nil { + return nil, err + } + return img.RawManifest() + } + return desc.Manifest, nil +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/optimize.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/optimize.go new file mode 100644 index 000000000..74c665df7 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/optimize.go @@ -0,0 +1,237 @@ +// Copyright 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "errors" + "fmt" + + "github.com/containerd/stargz-snapshotter/estargz" + "github.com/google/go-containerregistry/pkg/logs" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +// Optimize optimizes a remote image or index from src to dst. +// THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE WITHOUT WARNING. +func Optimize(src, dst string, prioritize []string, opt ...Option) error { + pset := newStringSet(prioritize) + o := makeOptions(opt...) + srcRef, err := name.ParseReference(src, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference %q: %w", src, err) + } + + dstRef, err := name.ParseReference(dst, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference for %q: %w", dst, err) + } + + logs.Progress.Printf("Optimizing from %v to %v", srcRef, dstRef) + desc, err := remote.Get(srcRef, o.Remote...) + if err != nil { + return fmt.Errorf("fetching %q: %w", src, err) + } + + switch desc.MediaType { + case types.OCIImageIndex, types.DockerManifestList: + // Handle indexes separately. + if o.Platform != nil { + // If platform is explicitly set, don't optimize the whole index, just the appropriate image. + if err := optimizeAndPushImage(desc, dstRef, pset, o); err != nil { + return fmt.Errorf("failed to optimize image: %w", err) + } + } else { + if err := optimizeAndPushIndex(desc, dstRef, pset, o); err != nil { + return fmt.Errorf("failed to optimize index: %w", err) + } + } + + case types.DockerManifestSchema1, types.DockerManifestSchema1Signed: + return errors.New("docker schema 1 images are not supported") + + default: + // Assume anything else is an image, since some registries don't set mediaTypes properly. + if err := optimizeAndPushImage(desc, dstRef, pset, o); err != nil { + return fmt.Errorf("failed to optimize image: %w", err) + } + } + + return nil +} + +func optimizeAndPushImage(desc *remote.Descriptor, dstRef name.Reference, prioritize stringSet, o Options) error { + img, err := desc.Image() + if err != nil { + return err + } + + missing, oimg, err := optimizeImage(img, prioritize) + if err != nil { + return err + } + + if len(missing) > 0 { + return fmt.Errorf("the following prioritized files were missing from image: %v", missing.List()) + } + + return remote.Write(dstRef, oimg, o.Remote...) +} + +func optimizeImage(img v1.Image, prioritize stringSet) (stringSet, v1.Image, error) { + cfg, err := img.ConfigFile() + if err != nil { + return nil, nil, err + } + ocfg := cfg.DeepCopy() + ocfg.History = nil + ocfg.RootFS.DiffIDs = nil + + oimg, err := mutate.ConfigFile(empty.Image, ocfg) + if err != nil { + return nil, nil, err + } + + layers, err := img.Layers() + if err != nil { + return nil, nil, err + } + + missingFromImage := newStringSet(prioritize.List()) + olayers := make([]mutate.Addendum, 0, len(layers)) + for _, layer := range layers { + missingFromLayer := []string{} + olayer, err := tarball.LayerFromOpener(layer.Uncompressed, + tarball.WithEstargz, + tarball.WithEstargzOptions( + estargz.WithPrioritizedFiles(prioritize.List()), + estargz.WithAllowPrioritizeNotFound(&missingFromLayer), + )) + if err != nil { + return nil, nil, err + } + missingFromImage = missingFromImage.Intersection(newStringSet(missingFromLayer)) + + olayers = append(olayers, mutate.Addendum{ + Layer: olayer, + MediaType: types.DockerLayer, + }) + } + + oimg, err = mutate.Append(oimg, olayers...) + if err != nil { + return nil, nil, err + } + return missingFromImage, oimg, nil +} + +func optimizeAndPushIndex(desc *remote.Descriptor, dstRef name.Reference, prioritize stringSet, o Options) error { + idx, err := desc.ImageIndex() + if err != nil { + return err + } + + missing, oidx, err := optimizeIndex(idx, prioritize) + if err != nil { + return err + } + + if len(missing) > 0 { + return fmt.Errorf("the following prioritized files were missing from all images: %v", missing.List()) + } + + return remote.WriteIndex(dstRef, oidx, o.Remote...) +} + +func optimizeIndex(idx v1.ImageIndex, prioritize stringSet) (stringSet, v1.ImageIndex, error) { + im, err := idx.IndexManifest() + if err != nil { + return nil, nil, err + } + + missingFromIndex := newStringSet(prioritize.List()) + + // Build an image for each child from the base and append it to a new index to produce the result. + adds := make([]mutate.IndexAddendum, 0, len(im.Manifests)) + for _, desc := range im.Manifests { + img, err := idx.Image(desc.Digest) + if err != nil { + return nil, nil, err + } + + missingFromImage, oimg, err := optimizeImage(img, prioritize) + if err != nil { + return nil, nil, err + } + missingFromIndex = missingFromIndex.Intersection(missingFromImage) + adds = append(adds, mutate.IndexAddendum{ + Add: oimg, + Descriptor: v1.Descriptor{ + URLs: desc.URLs, + MediaType: desc.MediaType, + Annotations: desc.Annotations, + Platform: desc.Platform, + }, + }) + } + + idxType, err := idx.MediaType() + if err != nil { + return nil, nil, err + } + + return missingFromIndex, mutate.IndexMediaType(mutate.AppendManifests(empty.Index, adds...), idxType), nil +} + +type stringSet map[string]struct{} + +func newStringSet(in []string) stringSet { + ss := stringSet{} + for _, s := range in { + ss[s] = struct{}{} + } + return ss +} + +func (s stringSet) List() []string { + result := make([]string, 0, len(s)) + for k := range s { + result = append(result, k) + } + return result +} + +func (s stringSet) Intersection(rhs stringSet) stringSet { + // To appease ST1016 + lhs := s + + // Make sure len(lhs) >= len(rhs) + if len(lhs) < len(rhs) { + return rhs.Intersection(lhs) + } + + result := stringSet{} + for k := range lhs { + if _, ok := rhs[k]; ok { + result[k] = struct{}{} + } + } + return result +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/options.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/options.go new file mode 100644 index 000000000..6e1177e4d --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/options.go @@ -0,0 +1,116 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "context" + "net/http" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +// Options hold the options that crane uses when calling other packages. +type Options struct { + Name []name.Option + Remote []remote.Option + Platform *v1.Platform +} + +// GetOptions exposes the underlying []remote.Option, []name.Option, and +// platform, based on the passed Option. Generally, you shouldn't need to use +// this unless you've painted yourself into a dependency corner as we have +// with the crane and gcrane cli packages. +func GetOptions(opts ...Option) Options { + return makeOptions(opts...) +} + +func makeOptions(opts ...Option) Options { + opt := Options{ + Remote: []remote.Option{ + remote.WithAuthFromKeychain(authn.DefaultKeychain), + }, + } + for _, o := range opts { + o(&opt) + } + return opt +} + +// Option is a functional option for crane. +type Option func(*Options) + +// WithTransport is a functional option for overriding the default transport +// for remote operations. +func WithTransport(t http.RoundTripper) Option { + return func(o *Options) { + o.Remote = append(o.Remote, remote.WithTransport(t)) + } +} + +// Insecure is an Option that allows image references to be fetched without TLS. +func Insecure(o *Options) { + o.Name = append(o.Name, name.Insecure) +} + +// WithPlatform is an Option to specify the platform. +func WithPlatform(platform *v1.Platform) Option { + return func(o *Options) { + if platform != nil { + o.Remote = append(o.Remote, remote.WithPlatform(*platform)) + } + o.Platform = platform + } +} + +// WithAuthFromKeychain is a functional option for overriding the default +// authenticator for remote operations, using an authn.Keychain to find +// credentials. +// +// By default, crane will use authn.DefaultKeychain. +func WithAuthFromKeychain(keys authn.Keychain) Option { + return func(o *Options) { + // Replace the default keychain at position 0. + o.Remote[0] = remote.WithAuthFromKeychain(keys) + } +} + +// WithAuth is a functional option for overriding the default authenticator +// for remote operations. +// +// By default, crane will use authn.DefaultKeychain. +func WithAuth(auth authn.Authenticator) Option { + return func(o *Options) { + // Replace the default keychain at position 0. + o.Remote[0] = remote.WithAuth(auth) + } +} + +// WithUserAgent adds the given string to the User-Agent header for any HTTP +// requests. +func WithUserAgent(ua string) Option { + return func(o *Options) { + o.Remote = append(o.Remote, remote.WithUserAgent(ua)) + } +} + +// WithContext is a functional option for setting the context. +func WithContext(ctx context.Context) Option { + return func(o *Options) { + o.Remote = append(o.Remote, remote.WithContext(ctx)) + } +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/pull.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/pull.go new file mode 100644 index 000000000..7e6e5b7b6 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/pull.go @@ -0,0 +1,142 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + "os" + + legacy "github.com/google/go-containerregistry/pkg/legacy/tarball" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/tarball" +) + +// Tag applied to images that were pulled by digest. This denotes that the +// image was (probably) never tagged with this, but lets us avoid applying the +// ":latest" tag which might be misleading. +const iWasADigestTag = "i-was-a-digest" + +// Pull returns a v1.Image of the remote image src. +func Pull(src string, opt ...Option) (v1.Image, error) { + o := makeOptions(opt...) + ref, err := name.ParseReference(src, o.Name...) + if err != nil { + return nil, fmt.Errorf("parsing reference %q: %w", src, err) + } + + return remote.Image(ref, o.Remote...) +} + +// Save writes the v1.Image img as a tarball at path with tag src. +func Save(img v1.Image, src, path string) error { + imgMap := map[string]v1.Image{src: img} + return MultiSave(imgMap, path) +} + +// MultiSave writes collection of v1.Image img with tag as a tarball. +func MultiSave(imgMap map[string]v1.Image, path string, opt ...Option) error { + o := makeOptions(opt...) + tagToImage := map[name.Tag]v1.Image{} + + for src, img := range imgMap { + ref, err := name.ParseReference(src, o.Name...) + if err != nil { + return fmt.Errorf("parsing ref %q: %w", src, err) + } + + // WriteToFile wants a tag to write to the tarball, but we might have + // been given a digest. + // If the original ref was a tag, use that. Otherwise, if it was a + // digest, tag the image with :i-was-a-digest instead. + tag, ok := ref.(name.Tag) + if !ok { + d, ok := ref.(name.Digest) + if !ok { + return fmt.Errorf("ref wasn't a tag or digest") + } + tag = d.Repository.Tag(iWasADigestTag) + } + tagToImage[tag] = img + } + // no progress channel (for now) + return tarball.MultiWriteToFile(path, tagToImage) +} + +// PullLayer returns the given layer from a registry. +func PullLayer(ref string, opt ...Option) (v1.Layer, error) { + o := makeOptions(opt...) + digest, err := name.NewDigest(ref, o.Name...) + if err != nil { + return nil, err + } + + return remote.Layer(digest, o.Remote...) +} + +// SaveLegacy writes the v1.Image img as a legacy tarball at path with tag src. +func SaveLegacy(img v1.Image, src, path string) error { + imgMap := map[string]v1.Image{src: img} + return MultiSave(imgMap, path) +} + +// MultiSaveLegacy writes collection of v1.Image img with tag as a legacy tarball. +func MultiSaveLegacy(imgMap map[string]v1.Image, path string) error { + refToImage := map[name.Reference]v1.Image{} + + for src, img := range imgMap { + ref, err := name.ParseReference(src) + if err != nil { + return fmt.Errorf("parsing ref %q: %w", src, err) + } + refToImage[ref] = img + } + + w, err := os.Create(path) + if err != nil { + return err + } + defer w.Close() + + return legacy.MultiWrite(refToImage, w) +} + +// SaveOCI writes the v1.Image img as an OCI Image Layout at path. If a layout +// already exists at that path, it will add the image to the index. +func SaveOCI(img v1.Image, path string) error { + imgMap := map[string]v1.Image{"": img} + return MultiSaveOCI(imgMap, path) +} + +// MultiSaveOCI writes collection of v1.Image img as an OCI Image Layout at path. If a layout +// already exists at that path, it will add the image to the index. +func MultiSaveOCI(imgMap map[string]v1.Image, path string) error { + p, err := layout.FromPath(path) + if err != nil { + p, err = layout.Write(path, empty.Index) + if err != nil { + return err + } + } + for _, img := range imgMap { + if err = p.AppendImage(img); err != nil { + return err + } + } + return nil +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/push.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/push.go new file mode 100644 index 000000000..6d1fbd6ce --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/push.go @@ -0,0 +1,65 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/tarball" +) + +// Load reads the tarball at path as a v1.Image. +func Load(path string, opt ...Option) (v1.Image, error) { + return LoadTag(path, "") +} + +// LoadTag reads a tag from the tarball at path as a v1.Image. +// If tag is "", will attempt to read the tarball as a single image. +func LoadTag(path, tag string, opt ...Option) (v1.Image, error) { + if tag == "" { + return tarball.ImageFromPath(path, nil) + } + + o := makeOptions(opt...) + t, err := name.NewTag(tag, o.Name...) + if err != nil { + return nil, fmt.Errorf("parsing tag %q: %w", tag, err) + } + return tarball.ImageFromPath(path, &t) +} + +// Push pushes the v1.Image img to a registry as dst. +func Push(img v1.Image, dst string, opt ...Option) error { + o := makeOptions(opt...) + tag, err := name.ParseReference(dst, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference %q: %w", dst, err) + } + return remote.Write(tag, img, o.Remote...) +} + +// Upload pushes the v1.Layer to a given repo. +func Upload(layer v1.Layer, repo string, opt ...Option) error { + o := makeOptions(opt...) + ref, err := name.NewRepository(repo, o.Name...) + if err != nil { + return fmt.Errorf("parsing repo %q: %w", repo, err) + } + + return remote.WriteLayer(ref, layer, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/tag.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/tag.go new file mode 100644 index 000000000..13bc39587 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/crane/tag.go @@ -0,0 +1,39 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crane + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +// Tag adds tag to the remote img. +func Tag(img, tag string, opt ...Option) error { + o := makeOptions(opt...) + ref, err := name.ParseReference(img, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference %q: %w", img, err) + } + desc, err := remote.Get(ref, o.Remote...) + if err != nil { + return fmt.Errorf("fetching %q: %w", img, err) + } + + dst := ref.Context().Tag(tag) + + return remote.Tag(dst, desc, o.Remote...) +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/config.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/config.go new file mode 100644 index 000000000..3364bec61 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/config.go @@ -0,0 +1,33 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package legacy + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +// LayerConfigFile is the configuration file that holds the metadata describing +// a v1 layer. See: +// https://github.com/moby/moby/blob/master/image/spec/v1.md +type LayerConfigFile struct { + v1.ConfigFile + + ContainerConfig v1.Config `json:"container_config,omitempty"` + + ID string `json:"id,omitempty"` + Parent string `json:"parent,omitempty"` + Throwaway bool `json:"throwaway,omitempty"` + Comment string `json:"comment,omitempty"` +} diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/doc.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/doc.go new file mode 100644 index 000000000..1d1668887 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/doc.go @@ -0,0 +1,18 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package legacy provides functionality to work with docker images in the v1 +// format. +// See: https://github.com/moby/moby/blob/master/image/spec/v1.md +package legacy diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/README.md b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/README.md new file mode 100644 index 000000000..90b88c757 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/README.md @@ -0,0 +1,6 @@ +# `legacy/tarball` + +[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/legacy/tarball?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/legacy/tarball) + +This package implements support for writing legacy tarballs, as described +[here](https://github.com/moby/moby/blob/749d90e10f989802638ae542daf54257f3bf71f2/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format). diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/doc.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/doc.go new file mode 100644 index 000000000..62684d6e7 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/doc.go @@ -0,0 +1,18 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tarball provides facilities for writing v1 docker images +// (https://github.com/moby/moby/blob/master/image/spec/v1.md) from/to a tarball +// on-disk. +package tarball diff --git a/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/write.go b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/write.go new file mode 100644 index 000000000..77b5eabcd --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/google/go-containerregistry/pkg/legacy/tarball/write.go @@ -0,0 +1,373 @@ +// Copyright 2019 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tarball + +import ( + "archive/tar" + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "sort" + "strings" + + "github.com/google/go-containerregistry/pkg/legacy" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/partial" + "github.com/google/go-containerregistry/pkg/v1/tarball" +) + +// repositoriesTarDescriptor represents the repositories file inside a `docker save` tarball. +type repositoriesTarDescriptor map[string]map[string]string + +// v1Layer represents a layer with metadata needed by the v1 image spec https://github.com/moby/moby/blob/master/image/spec/v1.md. +type v1Layer struct { + // config is the layer metadata. + config *legacy.LayerConfigFile + // layer is the v1.Layer object this v1Layer represents. + layer v1.Layer +} + +// json returns the raw bytes of the json metadata of the given v1Layer. +func (l *v1Layer) json() ([]byte, error) { + return json.Marshal(l.config) +} + +// version returns the raw bytes of the "VERSION" file of the given v1Layer. +func (l *v1Layer) version() []byte { + return []byte("1.0") +} + +// v1LayerID computes the v1 image format layer id for the given v1.Layer with the given v1 parent ID and raw image config. +func v1LayerID(layer v1.Layer, parentID string, rawConfig []byte) (string, error) { + d, err := layer.Digest() + if err != nil { + return "", fmt.Errorf("unable to get layer digest to generate v1 layer ID: %w", err) + } + s := fmt.Sprintf("%s %s", d.Hex, parentID) + if len(rawConfig) != 0 { + s = fmt.Sprintf("%s %s", s, string(rawConfig)) + } + rawDigest := sha256.Sum256([]byte(s)) + return hex.EncodeToString(rawDigest[:]), nil +} + +// newTopV1Layer creates a new v1Layer for a layer other than the top layer in a v1 image tarball. +func newV1Layer(layer v1.Layer, parent *v1Layer, history v1.History) (*v1Layer, error) { + parentID := "" + if parent != nil { + parentID = parent.config.ID + } + id, err := v1LayerID(layer, parentID, nil) + if err != nil { + return nil, fmt.Errorf("unable to generate v1 layer ID: %w", err) + } + result := &v1Layer{ + layer: layer, + config: &legacy.LayerConfigFile{ + ConfigFile: v1.ConfigFile{ + Created: history.Created, + Author: history.Author, + }, + ContainerConfig: v1.Config{ + Cmd: []string{history.CreatedBy}, + }, + ID: id, + Parent: parentID, + Throwaway: history.EmptyLayer, + Comment: history.Comment, + }, + } + return result, nil +} + +// newTopV1Layer creates a new v1Layer for the top layer in a v1 image tarball. +func newTopV1Layer(layer v1.Layer, parent *v1Layer, history v1.History, imgConfig *v1.ConfigFile, rawConfig []byte) (*v1Layer, error) { + result, err := newV1Layer(layer, parent, history) + if err != nil { + return nil, err + } + id, err := v1LayerID(layer, result.config.Parent, rawConfig) + if err != nil { + return nil, fmt.Errorf("unable to generate v1 layer ID for top layer: %w", err) + } + result.config.ID = id + result.config.Architecture = imgConfig.Architecture + result.config.Container = imgConfig.Container + result.config.DockerVersion = imgConfig.DockerVersion + result.config.OS = imgConfig.OS + result.config.Config = imgConfig.Config + result.config.Created = imgConfig.Created + return result, nil +} + +// splitTag splits the given tagged image name /: +// into / and . +func splitTag(name string) (string, string) { + // Split on ":" + parts := strings.Split(name, ":") + // Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation. + if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") { + base := strings.Join(parts[:len(parts)-1], ":") + tag := parts[len(parts)-1] + return base, tag + } + return name, "" +} + +// addTags adds the given image tags to the given "repositories" file descriptor in a v1 image tarball. +func addTags(repos repositoriesTarDescriptor, tags []string, topLayerID string) { + for _, t := range tags { + base, tag := splitTag(t) + tagToID, ok := repos[base] + if !ok { + tagToID = make(map[string]string) + repos[base] = tagToID + } + tagToID[tag] = topLayerID + } +} + +// updateLayerSources updates the given layer digest to descriptor map with the descriptor of the given layer in the given image if it's an undistributable layer. +func updateLayerSources(layerSources map[v1.Hash]v1.Descriptor, layer v1.Layer, img v1.Image) error { + d, err := layer.Digest() + if err != nil { + return err + } + // Add to LayerSources if it's a foreign layer. + desc, err := partial.BlobDescriptor(img, d) + if err != nil { + return err + } + if !desc.MediaType.IsDistributable() { + diffid, err := partial.BlobToDiffID(img, d) + if err != nil { + return err + } + layerSources[diffid] = *desc + } + return nil +} + +// Write is a wrapper to write a single image in V1 format and tag to a tarball. +func Write(ref name.Reference, img v1.Image, w io.Writer) error { + return MultiWrite(map[name.Reference]v1.Image{ref: img}, w) +} + +// filterEmpty filters out the history corresponding to empty layers from the +// given history. +func filterEmpty(h []v1.History) []v1.History { + result := []v1.History{} + for _, i := range h { + if i.EmptyLayer { + continue + } + result = append(result, i) + } + return result +} + +// MultiWrite writes the contents of each image to the provided reader, in the V1 image tarball format. +// The contents are written in the following format: +// One manifest.json file at the top level containing information about several images. +// One repositories file mapping from the image / to to the id of the top most layer. +// For every layer, a directory named with the layer ID is created with the following contents: +// layer.tar - The uncompressed layer tarball. +// .json- Layer metadata json. +// VERSION- Schema version string. Always set to "1.0". +// One file for the config blob, named after its SHA. +func MultiWrite(refToImage map[name.Reference]v1.Image, w io.Writer) error { + tf := tar.NewWriter(w) + defer tf.Close() + + sortedImages, imageToTags := dedupRefToImage(refToImage) + var m tarball.Manifest + repos := make(repositoriesTarDescriptor) + + seenLayerIDs := make(map[string]struct{}) + for _, img := range sortedImages { + tags := imageToTags[img] + + // Write the config. + cfgName, err := img.ConfigName() + if err != nil { + return err + } + cfgFileName := fmt.Sprintf("%s.json", cfgName.Hex) + cfgBlob, err := img.RawConfigFile() + if err != nil { + return err + } + if err := writeTarEntry(tf, cfgFileName, bytes.NewReader(cfgBlob), int64(len(cfgBlob))); err != nil { + return err + } + cfg, err := img.ConfigFile() + if err != nil { + return err + } + + // Store foreign layer info. + layerSources := make(map[v1.Hash]v1.Descriptor) + + // Write the layers. + layers, err := img.Layers() + if err != nil { + return err + } + history := filterEmpty(cfg.History) + // Create a blank config history if the config didn't have a history. + if len(history) == 0 && len(layers) != 0 { + history = make([]v1.History, len(layers)) + } else if len(layers) != len(history) { + return fmt.Errorf("image config had layer history which did not match the number of layers, got len(history)=%d, len(layers)=%d, want len(history)=len(layers)", len(history), len(layers)) + } + layerFiles := make([]string, len(layers)) + var prev *v1Layer + for i, l := range layers { + if err := updateLayerSources(layerSources, l, img); err != nil { + return fmt.Errorf("unable to update image metadata to include undistributable layer source information: %w", err) + } + var cur *v1Layer + if i < (len(layers) - 1) { + cur, err = newV1Layer(l, prev, history[i]) + } else { + cur, err = newTopV1Layer(l, prev, history[i], cfg, cfgBlob) + } + if err != nil { + return err + } + layerFiles[i] = fmt.Sprintf("%s/layer.tar", cur.config.ID) + if _, ok := seenLayerIDs[cur.config.ID]; ok { + prev = cur + continue + } + seenLayerIDs[cur.config.ID] = struct{}{} + + // If the v1.Layer implements UncompressedSize efficiently, use that + // for the tar header. Otherwise, this iterates over Uncompressed(). + // NOTE: If using a streaming layer, this may consume the layer. + size, err := partial.UncompressedSize(l) + if err != nil { + return err + } + u, err := l.Uncompressed() + if err != nil { + return err + } + defer u.Close() + if err := writeTarEntry(tf, layerFiles[i], u, size); err != nil { + return err + } + + j, err := cur.json() + if err != nil { + return err + } + if err := writeTarEntry(tf, fmt.Sprintf("%s/json", cur.config.ID), bytes.NewReader(j), int64(len(j))); err != nil { + return err + } + v := cur.version() + if err := writeTarEntry(tf, fmt.Sprintf("%s/VERSION", cur.config.ID), bytes.NewReader(v), int64(len(v))); err != nil { + return err + } + prev = cur + } + + // Generate the tar descriptor and write it. + m = append(m, tarball.Descriptor{ + Config: cfgFileName, + RepoTags: tags, + Layers: layerFiles, + LayerSources: layerSources, + }) + // prev should be the top layer here. Use it to add the image tags + // to the tarball repositories file. + addTags(repos, tags, prev.config.ID) + } + + mBytes, err := json.Marshal(m) + if err != nil { + return err + } + + if err := writeTarEntry(tf, "manifest.json", bytes.NewReader(mBytes), int64(len(mBytes))); err != nil { + return err + } + reposBytes, err := json.Marshal(&repos) + if err != nil { + return err + } + if err := writeTarEntry(tf, "repositories", bytes.NewReader(reposBytes), int64(len(reposBytes))); err != nil { + return err + } + return nil +} + +func dedupRefToImage(refToImage map[name.Reference]v1.Image) ([]v1.Image, map[v1.Image][]string) { + imageToTags := make(map[v1.Image][]string) + + for ref, img := range refToImage { + if tag, ok := ref.(name.Tag); ok { + if tags, ok := imageToTags[img]; ok && tags != nil { + imageToTags[img] = append(tags, tag.String()) + } else { + imageToTags[img] = []string{tag.String()} + } + } else { + if _, ok := imageToTags[img]; !ok { + imageToTags[img] = nil + } + } + } + + // Force specific order on tags + imgs := []v1.Image{} + for img, tags := range imageToTags { + sort.Strings(tags) + imgs = append(imgs, img) + } + + sort.Slice(imgs, func(i, j int) bool { + cfI, err := imgs[i].ConfigName() + if err != nil { + return false + } + cfJ, err := imgs[j].ConfigName() + if err != nil { + return false + } + return cfI.Hex < cfJ.Hex + }) + + return imgs, imageToTags +} + +// Writes a file to the provided writer with a corresponding tar header +func writeTarEntry(tf *tar.Writer, path string, r io.Reader, size int64) error { + hdr := &tar.Header{ + Mode: 0644, + Typeflag: tar.TypeReg, + Size: size, + Name: path, + } + if err := tf.WriteHeader(hdr); err != nil { + return err + } + _, err := io.Copy(tf, r) + return err +} diff --git a/src/cmd/linuxkit/vendor/modules.txt b/src/cmd/linuxkit/vendor/modules.txt index 5137ce974..2b3c4c031 100644 --- a/src/cmd/linuxkit/vendor/modules.txt +++ b/src/cmd/linuxkit/vendor/modules.txt @@ -261,7 +261,6 @@ github.com/docker/go-connections/tlsconfig github.com/docker/go-units # github.com/estesp/manifest-tool/v2 v2.0.7-0.20230216152337-24a86fc0b513 ## explicit; go 1.19 -github.com/estesp/manifest-tool/v2/pkg/util # github.com/felixge/httpsnoop v1.0.2 ## explicit; go 1.13 github.com/felixge/httpsnoop @@ -314,11 +313,15 @@ github.com/google/go-cmp/cmp/internal/value github.com/google/go-containerregistry/internal/and github.com/google/go-containerregistry/internal/estargz github.com/google/go-containerregistry/internal/gzip +github.com/google/go-containerregistry/internal/legacy github.com/google/go-containerregistry/internal/redact github.com/google/go-containerregistry/internal/retry github.com/google/go-containerregistry/internal/retry/wait github.com/google/go-containerregistry/internal/verify github.com/google/go-containerregistry/pkg/authn +github.com/google/go-containerregistry/pkg/crane +github.com/google/go-containerregistry/pkg/legacy +github.com/google/go-containerregistry/pkg/legacy/tarball github.com/google/go-containerregistry/pkg/logs github.com/google/go-containerregistry/pkg/name github.com/google/go-containerregistry/pkg/v1