mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 09:16:29 +00:00
Merge pull request #4084 from deitch/cache-platform-instead-of-arch
internal restructure to use explicit platform instead of implicit arch in cache
This commit is contained in:
commit
e4d41061b6
5
src/cmd/linuxkit/cache/find.go
vendored
5
src/cmd/linuxkit/cache/find.go
vendored
@ -8,6 +8,7 @@ import (
|
|||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// matchPlatformsOSArch because match.Platforms rejects it if the provided
|
// matchPlatformsOSArch because match.Platforms rejects it if the provided
|
||||||
@ -46,7 +47,7 @@ func matchAllAnnotations(annotations map[string]string) match.Matcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) findImage(imageName, architecture string) (v1.Image, error) {
|
func (p *Provider) findImage(imageName string, platform imagespec.Platform) (v1.Image, error) {
|
||||||
root, err := p.FindRoot(imageName)
|
root, err := p.FindRoot(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -58,7 +59,7 @@ func (p *Provider) findImage(imageName, architecture string) (v1.Image, error) {
|
|||||||
ii, err := root.ImageIndex()
|
ii, err := root.ImageIndex()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// we have the index, get the manifest that represents the manifest for the desired architecture
|
// we have the index, get the manifest that represents the manifest for the desired architecture
|
||||||
platform := v1.Platform{OS: "linux", Architecture: architecture}
|
platform := v1.Platform{OS: platform.OS, Architecture: platform.Architecture}
|
||||||
images, err := partial.FindImages(ii, matchPlatformsOSArch(platform))
|
images, err := partial.FindImages(ii, matchPlatformsOSArch(platform))
|
||||||
if err != nil || len(images) < 1 {
|
if err != nil || len(images) < 1 {
|
||||||
return nil, fmt.Errorf("error retrieving image %s for platform %v from cache: %v", imageName, platform, err)
|
return nil, fmt.Errorf("error retrieving image %s for platform %v from cache: %v", imageName, platform, err)
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||||
@ -32,10 +31,10 @@ const (
|
|||||||
// ImageSource a source for an image in the OCI distribution cache.
|
// ImageSource a source for an image in the OCI distribution cache.
|
||||||
// Implements a spec.ImageSource.
|
// Implements a spec.ImageSource.
|
||||||
type ImageSource struct {
|
type ImageSource struct {
|
||||||
ref *reference.Spec
|
ref *reference.Spec
|
||||||
provider *Provider
|
provider *Provider
|
||||||
architecture string
|
platform *imagespec.Platform
|
||||||
descriptor *v1.Descriptor
|
descriptor *v1.Descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
type spdxStatement struct {
|
type spdxStatement struct {
|
||||||
@ -45,12 +44,12 @@ type spdxStatement struct {
|
|||||||
|
|
||||||
// NewSource return an ImageSource for a specific ref and architecture in the given
|
// NewSource return an ImageSource for a specific ref and architecture in the given
|
||||||
// cache directory.
|
// cache directory.
|
||||||
func (p *Provider) NewSource(ref *reference.Spec, architecture string, descriptor *v1.Descriptor) lktspec.ImageSource {
|
func (p *Provider) NewSource(ref *reference.Spec, platform *imagespec.Platform, descriptor *v1.Descriptor) lktspec.ImageSource {
|
||||||
return ImageSource{
|
return ImageSource{
|
||||||
ref: ref,
|
ref: ref,
|
||||||
provider: p,
|
provider: p,
|
||||||
architecture: architecture,
|
platform: platform,
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ func (p *Provider) NewSource(ref *reference.Spec, architecture string, descripto
|
|||||||
// architecture, if necessary.
|
// architecture, if necessary.
|
||||||
func (c ImageSource) Config() (imagespec.ImageConfig, error) {
|
func (c ImageSource) Config() (imagespec.ImageConfig, error) {
|
||||||
imageName := c.ref.String()
|
imageName := c.ref.String()
|
||||||
image, err := c.provider.findImage(imageName, c.architecture)
|
image, err := c.provider.findImage(imageName, *c.platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imagespec.ImageConfig{}, err
|
return imagespec.ImageConfig{}, err
|
||||||
}
|
}
|
||||||
@ -84,7 +83,7 @@ func (c ImageSource) TarReader() (io.ReadCloser, error) {
|
|||||||
imageName := c.ref.String()
|
imageName := c.ref.String()
|
||||||
|
|
||||||
// get a reference to the image
|
// get a reference to the image
|
||||||
image, err := c.provider.findImage(imageName, c.architecture)
|
image, err := c.provider.findImage(imageName, *c.platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -104,7 +103,7 @@ func (c ImageSource) V1TarReader(overrideName string) (io.ReadCloser, error) {
|
|||||||
return nil, fmt.Errorf("error parsing image name: %v", err)
|
return nil, fmt.Errorf("error parsing image name: %v", err)
|
||||||
}
|
}
|
||||||
// get a reference to the image
|
// get a reference to the image
|
||||||
image, err := c.provider.findImage(imageName, c.architecture)
|
image, err := c.provider.findImage(imageName, *c.platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -129,7 +128,7 @@ func (c ImageSource) OCITarReader(overrideName string) (io.ReadCloser, error) {
|
|||||||
return nil, fmt.Errorf("error parsing image name: %v", err)
|
return nil, fmt.Errorf("error parsing image name: %v", err)
|
||||||
}
|
}
|
||||||
// get a reference to the image
|
// get a reference to the image
|
||||||
image, err := c.provider.findImage(imageName, c.architecture)
|
image, err := c.provider.findImage(imageName, *c.platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -139,160 +138,37 @@ func (c ImageSource) OCITarReader(overrideName string) (io.ReadCloser, error) {
|
|||||||
defer w.Close()
|
defer w.Close()
|
||||||
tw := tar.NewWriter(w)
|
tw := tar.NewWriter(w)
|
||||||
defer tw.Close()
|
defer tw.Close()
|
||||||
// layout file
|
if err := writeLayoutHeader(tw); err != nil {
|
||||||
layoutFileBytes := []byte(layoutFile)
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: "oci-layout",
|
|
||||||
Mode: 0644,
|
|
||||||
Size: int64(len(layoutFileBytes)),
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := tw.Write(layoutFileBytes); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// make blobs directory
|
if err := writeLayoutImage(tw, image); err != nil {
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: "blobs/",
|
|
||||||
Mode: 0755,
|
|
||||||
Typeflag: tar.TypeDir,
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// make blobs/sha256 directory
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
imageDigest, err := image.Digest()
|
||||||
Name: "blobs/sha256/",
|
|
||||||
Mode: 0755,
|
|
||||||
Typeflag: tar.TypeDir,
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write config, each layer, manifest, saving the digest for each
|
|
||||||
config, err := image.RawConfigFile()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
configDigest, configSize, err := v1.SHA256(bytes.NewReader(config))
|
imageSize, err := image.Size()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: fmt.Sprintf("blobs/sha256/%s", configDigest.Hex),
|
|
||||||
Mode: 0644,
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Size: configSize,
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := tw.Write(config); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
layers, err := image.Layers()
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, layer := range layers {
|
|
||||||
blob, err := layer.Compressed()
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer blob.Close()
|
|
||||||
blobDigest, err := layer.Digest()
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
blobSize, err := layer.Size()
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: fmt.Sprintf("blobs/sha256/%s", blobDigest.Hex),
|
|
||||||
Mode: 0644,
|
|
||||||
Size: blobSize,
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(tw, blob); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// write the manifest
|
|
||||||
manifest, err := image.RawManifest()
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
manifestDigest, manifestSize, err := v1.SHA256(bytes.NewReader(manifest))
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: fmt.Sprintf("blobs/sha256/%s", manifestDigest.Hex),
|
|
||||||
Mode: 0644,
|
|
||||||
Size: int64(len(manifest)),
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := tw.Write(manifest); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write the index file
|
// write the index file
|
||||||
desc := v1.Descriptor{
|
desc := v1.Descriptor{
|
||||||
MediaType: types.OCIImageIndex,
|
MediaType: types.OCIImageIndex,
|
||||||
Size: manifestSize,
|
Size: imageSize,
|
||||||
Digest: manifestDigest,
|
Digest: imageDigest,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
imagespec.AnnotationRefName: refName.String(),
|
imagespec.AnnotationRefName: refName.String(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ii := empty.Index
|
if err := writeLayoutIndex(tw, desc); err != nil {
|
||||||
|
|
||||||
index, err := ii.IndexManifest()
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
index.Manifests = append(index.Manifests, desc)
|
|
||||||
|
|
||||||
rawIndex, err := json.MarshalIndent(index, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write the index
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: "index.json",
|
|
||||||
Mode: 0644,
|
|
||||||
Size: int64(len(rawIndex)),
|
|
||||||
}); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := tw.Write(rawIndex); err != nil {
|
|
||||||
_ = w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -314,15 +190,15 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the digest of the manifest that represents our targeted architecture
|
// get the digest of the manifest that represents our targeted architecture
|
||||||
descs, err := partial.FindManifests(index, matchPlatformsOSArch(v1.Platform{OS: "linux", Architecture: c.architecture}))
|
descs, err := partial.FindManifests(index, matchPlatformsOSArch(v1.Platform{OS: c.platform.OS, Architecture: c.platform.Architecture}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(descs) < 1 {
|
if len(descs) < 1 {
|
||||||
return nil, fmt.Errorf("no manifest found for %s arch %s", c.ref.String(), c.architecture)
|
return nil, fmt.Errorf("no manifest found for %s platform %s", c.ref.String(), c.platform)
|
||||||
}
|
}
|
||||||
if len(descs) > 1 {
|
if len(descs) > 1 {
|
||||||
return nil, fmt.Errorf("multiple manifests found for %s arch %s", c.ref.String(), c.architecture)
|
return nil, fmt.Errorf("multiple manifests found for %s platform %s", c.ref.String(), c.platform)
|
||||||
}
|
}
|
||||||
// get the digest of the manifest that represents our targeted architecture
|
// get the digest of the manifest that represents our targeted architecture
|
||||||
desc := descs[0]
|
desc := descs[0]
|
||||||
@ -336,7 +212,7 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(descs) > 1 {
|
if len(descs) > 1 {
|
||||||
return nil, fmt.Errorf("multiple manifests found for %s arch %s", c.ref.String(), c.architecture)
|
return nil, fmt.Errorf("multiple manifests found for %s platform %s", c.ref.String(), c.platform)
|
||||||
}
|
}
|
||||||
if len(descs) < 1 {
|
if len(descs) < 1 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -348,10 +224,10 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(images) < 1 {
|
if len(images) < 1 {
|
||||||
return nil, fmt.Errorf("no attestation image found for %s arch %s, even though the manifest exists", c.ref.String(), c.architecture)
|
return nil, fmt.Errorf("no attestation image found for %s platform %s, even though the manifest exists", c.ref.String(), c.platform)
|
||||||
}
|
}
|
||||||
if len(images) > 1 {
|
if len(images) > 1 {
|
||||||
return nil, fmt.Errorf("multiple attestation images found for %s arch %s", c.ref.String(), c.architecture)
|
return nil, fmt.Errorf("multiple attestation images found for %s platform %s", c.ref.String(), c.platform)
|
||||||
}
|
}
|
||||||
image := images[0]
|
image := images[0]
|
||||||
manifest, err := image.Manifest()
|
manifest, err := image.Manifest()
|
||||||
@ -363,7 +239,7 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(manifest.Layers) != len(layers) {
|
if len(manifest.Layers) != len(layers) {
|
||||||
return nil, fmt.Errorf("manifest layers and image layers do not match for the attestation for %s arch %s", c.ref.String(), c.architecture)
|
return nil, fmt.Errorf("manifest layers and image layers do not match for the attestation for %s platform %s", c.ref.String(), c.platform)
|
||||||
}
|
}
|
||||||
var readers []io.ReadCloser
|
var readers []io.ReadCloser
|
||||||
for i, layer := range manifest.Layers {
|
for i, layer := range manifest.Layers {
|
162
src/cmd/linuxkit/cache/indexsource.go
vendored
Normal file
162
src/cmd/linuxkit/cache/indexsource.go
vendored
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/reference"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||||
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
|
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IndexSource a source for an image in the OCI distribution cache.
|
||||||
|
// Implements a spec.ImageSource.
|
||||||
|
type IndexSource struct {
|
||||||
|
ref *reference.Spec
|
||||||
|
provider *Provider
|
||||||
|
descriptor *v1.Descriptor
|
||||||
|
platforms []imagespec.Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIndexSource return an IndexSource for a specific ref in the given
|
||||||
|
// cache directory.
|
||||||
|
func (p *Provider) NewIndexSource(ref *reference.Spec, descriptor *v1.Descriptor, platforms []imagespec.Platform) lktspec.IndexSource {
|
||||||
|
return IndexSource{
|
||||||
|
ref: ref,
|
||||||
|
provider: p,
|
||||||
|
descriptor: descriptor,
|
||||||
|
platforms: platforms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config return the imagespec.ImageConfig for the given source. Resolves to the
|
||||||
|
// architecture, if necessary.
|
||||||
|
func (c IndexSource) Image(platform imagespec.Platform) (spec.ImageSource, error) {
|
||||||
|
imageName := c.ref.String()
|
||||||
|
index, err := c.provider.findIndex(imageName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
manifests, err := index.IndexManifest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, manifest := range manifests.Manifests {
|
||||||
|
if manifest.Platform != nil && manifest.Platform.Architecture == platform.Architecture && manifest.Platform.OS == platform.OS {
|
||||||
|
return c.provider.NewSource(c.ref, &platform, &manifest), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no manifest found for platform %q", platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCITarReader return an io.ReadCloser to read the image as a v1 tarball whose contents match OCI v1 layout spec
|
||||||
|
func (c IndexSource) OCITarReader(overrideName string) (io.ReadCloser, error) {
|
||||||
|
imageName := c.ref.String()
|
||||||
|
saveName := imageName
|
||||||
|
if overrideName != "" {
|
||||||
|
saveName = overrideName
|
||||||
|
}
|
||||||
|
refName, err := name.ParseReference(saveName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing image name: %v", err)
|
||||||
|
}
|
||||||
|
// get a reference to the image
|
||||||
|
index, err := c.provider.findIndex(c.ref.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// convert the writer to a reader
|
||||||
|
r, w := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
defer w.Close()
|
||||||
|
tw := tar.NewWriter(w)
|
||||||
|
defer tw.Close()
|
||||||
|
if err := writeLayoutHeader(tw); err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manifests, err := index.IndexManifest()
|
||||||
|
if err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// for each manifest, write the manifest blob, then go through each manifest and find the image for it
|
||||||
|
// and write its blobs
|
||||||
|
for _, manifest := range manifests.Manifests {
|
||||||
|
// if we restricted this image source to certain platforms, we should only write those
|
||||||
|
if len(c.platforms) > 0 {
|
||||||
|
found := false
|
||||||
|
for _, platform := range c.platforms {
|
||||||
|
if platform.Architecture == manifest.Platform.Architecture && platform.OS == manifest.Platform.OS &&
|
||||||
|
(platform.Variant == "" || platform.Variant == manifest.Platform.Variant) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch manifest.MediaType {
|
||||||
|
case types.OCIManifestSchema1, types.DockerManifestSchema2:
|
||||||
|
// this is an image manifest
|
||||||
|
image, err := index.Image(manifest.Digest)
|
||||||
|
if err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := writeLayoutImage(tw, image); err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the index directly as a blob
|
||||||
|
indexSize, err := index.Size()
|
||||||
|
if err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
indexDigest, err := index.Digest()
|
||||||
|
if err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
indexBytes, err := index.RawManifest()
|
||||||
|
if err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := writeLayoutBlob(tw, indexDigest.Hex, indexSize, bytes.NewReader(indexBytes)); err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := v1.Descriptor{
|
||||||
|
MediaType: types.OCIImageIndex,
|
||||||
|
Size: indexSize,
|
||||||
|
Digest: indexDigest,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
imagespec.AnnotationRefName: refName.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := writeLayoutIndex(tw, desc); err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor return the descriptor of the index.
|
||||||
|
func (c IndexSource) Descriptor() *v1.Descriptor {
|
||||||
|
return c.descriptor
|
||||||
|
}
|
145
src/cmd/linuxkit/cache/layout.go
vendored
Normal file
145
src/cmd/linuxkit/cache/layout.go
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeLayoutHeader(tw *tar.Writer) error {
|
||||||
|
// layout file
|
||||||
|
layoutFileBytes := []byte(layoutFile)
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "oci-layout",
|
||||||
|
Mode: 0644,
|
||||||
|
Size: int64(len(layoutFileBytes)),
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(layoutFileBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// make blobs directory
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "blobs/",
|
||||||
|
Mode: 0755,
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// make blobs/sha256 directory
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "blobs/sha256/",
|
||||||
|
Mode: 0755,
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLayoutImage(tw *tar.Writer, image v1.Image) error {
|
||||||
|
// write config, each layer, manifest, saving the digest for each
|
||||||
|
manifest, err := image.Manifest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configDesc := manifest.Config
|
||||||
|
configBytes, err := image.RawConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeLayoutBlob(tw, configDesc.Digest.Hex, configDesc.Size, bytes.NewReader(configBytes)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
layers, err := image.Layers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, layer := range layers {
|
||||||
|
blob, err := layer.Compressed()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer blob.Close()
|
||||||
|
blobDigest, err := layer.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobSize, err := layer.Size()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeLayoutBlob(tw, blobDigest.Hex, blobSize, blob); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write the manifest
|
||||||
|
manifestSize, err := image.Size()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifestDigest, err := image.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifestBytes, err := image.RawManifest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeLayoutBlob(tw, manifestDigest.Hex, manifestSize, bytes.NewReader(manifestBytes)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLayoutBlob(tw *tar.Writer, digest string, size int64, blob io.Reader) error {
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: fmt.Sprintf("blobs/sha256/%s", digest),
|
||||||
|
Mode: 0644,
|
||||||
|
Size: size,
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(tw, blob); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLayoutIndex(tw *tar.Writer, desc v1.Descriptor) error {
|
||||||
|
ii := empty.Index
|
||||||
|
|
||||||
|
index, err := ii.IndexManifest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
index.Manifests = append(index.Manifests, desc)
|
||||||
|
|
||||||
|
rawIndex, err := json.MarshalIndent(index, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// write the index
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "index.json",
|
||||||
|
Mode: 0644,
|
||||||
|
Size: int64(len(rawIndex)),
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(rawIndex); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
33
src/cmd/linuxkit/cache/platform.go
vendored
Normal file
33
src/cmd/linuxkit/cache/platform.go
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func platformString(p imagespec.Platform) string {
|
||||||
|
parts := []string{p.OS, p.Architecture}
|
||||||
|
if p.Variant != "" {
|
||||||
|
parts = append(parts, p.Variant)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformMessageGenerator(platforms []imagespec.Platform) string {
|
||||||
|
var platformMessage string
|
||||||
|
switch {
|
||||||
|
case len(platforms) == 0:
|
||||||
|
platformMessage = "all platforms"
|
||||||
|
case len(platforms) == 1:
|
||||||
|
platformMessage = fmt.Sprintf("platform %s", platformString(platforms[0]))
|
||||||
|
default:
|
||||||
|
var platStrings []string
|
||||||
|
for _, p := range platforms {
|
||||||
|
platStrings = append(platStrings, platformString(p))
|
||||||
|
}
|
||||||
|
platformMessage = fmt.Sprintf("platforms %s", strings.Join(platStrings, ","))
|
||||||
|
}
|
||||||
|
return platformMessage
|
||||||
|
}
|
63
src/cmd/linuxkit/cache/pull.go
vendored
63
src/cmd/linuxkit/cache/pull.go
vendored
@ -3,6 +3,7 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
@ -30,12 +31,13 @@ const (
|
|||||||
// architecture, and any manifests that have no architecture at all. It will ignore manifests
|
// architecture, and any manifests that have no architecture at all. It will ignore manifests
|
||||||
// for other architectures. If no architecture is provided, it will validate all manifests.
|
// for other architectures. If no architecture is provided, it will validate all manifests.
|
||||||
// It also calculates the hash of each component.
|
// It also calculates the hash of each component.
|
||||||
func (p *Provider) ValidateImage(ref *reference.Spec, architecture string) (lktspec.ImageSource, error) {
|
func (p *Provider) ValidateImage(ref *reference.Spec, platforms []imagespec.Platform) (lktspec.ImageSource, error) {
|
||||||
var (
|
var (
|
||||||
imageIndex v1.ImageIndex
|
imageIndex v1.ImageIndex
|
||||||
image v1.Image
|
image v1.Image
|
||||||
imageName = ref.String()
|
imageName = ref.String()
|
||||||
desc *v1.Descriptor
|
desc *v1.Descriptor
|
||||||
|
platformMessage = platformMessageGenerator(platforms)
|
||||||
)
|
)
|
||||||
// next try the local cache
|
// next try the local cache
|
||||||
root, err := p.FindRoot(imageName)
|
root, err := p.FindRoot(imageName)
|
||||||
@ -71,7 +73,15 @@ func (p *Provider) ValidateImage(ref *reference.Spec, architecture string) (lkts
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("could not get index manifest: %w", err)
|
return ImageSource{}, fmt.Errorf("could not get index manifest: %w", err)
|
||||||
}
|
}
|
||||||
var architectures = make(map[string]bool)
|
var (
|
||||||
|
targetPlatforms = make(map[string]bool)
|
||||||
|
foundPlatforms = make(map[string]bool)
|
||||||
|
)
|
||||||
|
for _, plat := range platforms {
|
||||||
|
pString := platformString(plat)
|
||||||
|
targetPlatforms[pString] = false
|
||||||
|
foundPlatforms[pString] = false
|
||||||
|
}
|
||||||
// ignore only other architectures; manifest entries that have no architectures at all
|
// ignore only other architectures; manifest entries that have no architectures at all
|
||||||
// are going to be additional metadata, so we need to check them
|
// are going to be additional metadata, so we need to check them
|
||||||
for _, m := range im.Manifests {
|
for _, m := range im.Manifests {
|
||||||
@ -80,29 +90,50 @@ func (p *Provider) ValidateImage(ref *reference.Spec, architecture string) (lkts
|
|||||||
return ImageSource{}, fmt.Errorf("invalid image: %w", err)
|
return ImageSource{}, fmt.Errorf("invalid image: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if architecture != "" && m.Platform.Architecture == architecture && m.Platform.OS == linux {
|
// go through each target platform, and see if this one matched. If it did, mark the target as
|
||||||
if err := validateManifestContents(imageIndex, m.Digest); err != nil {
|
for _, plat := range platforms {
|
||||||
return ImageSource{}, fmt.Errorf("invalid image: %w", err)
|
if plat.Architecture == m.Platform.Architecture && plat.OS == m.Platform.OS &&
|
||||||
|
(plat.Variant == "" || plat.Variant == m.Platform.Variant) {
|
||||||
|
targetPlatforms[platformString(plat)] = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
architectures[architecture] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if architecture == "" || architectures[architecture] {
|
|
||||||
|
if len(platforms) == 0 {
|
||||||
return p.NewSource(
|
return p.NewSource(
|
||||||
ref,
|
ref,
|
||||||
architecture,
|
nil,
|
||||||
desc,
|
desc,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
return ImageSource{}, fmt.Errorf("index for %s did not contain image for platform linux/%s", imageName, architecture)
|
// we have cycled through all of the manifests, let's check if we have all of the platforms
|
||||||
|
var missing []string
|
||||||
|
for plat, found := range targetPlatforms {
|
||||||
|
if !found {
|
||||||
|
missing = append(missing, plat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return p.NewSource(
|
||||||
|
ref,
|
||||||
|
nil,
|
||||||
|
desc,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
return ImageSource{}, fmt.Errorf("index for %s did not contain image for platforms %s", imageName, strings.Join(missing, ", "))
|
||||||
case image != nil:
|
case image != nil:
|
||||||
|
if len(platforms) > 1 {
|
||||||
|
return ImageSource{}, fmt.Errorf("image %s is not a multi-arch image, but asked for %s", imageName, platformMessage)
|
||||||
|
}
|
||||||
// we found a local image, make sure it is up to date
|
// we found a local image, make sure it is up to date
|
||||||
if err := validate.Image(image); err != nil {
|
if err := validate.Image(image); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("invalid image, %s", err)
|
return ImageSource{}, fmt.Errorf("invalid image, %s", err)
|
||||||
}
|
}
|
||||||
return p.NewSource(
|
return p.NewSource(
|
||||||
ref,
|
ref,
|
||||||
architecture,
|
&platforms[0],
|
||||||
desc,
|
desc,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
@ -164,7 +195,7 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
|||||||
if err := p.cache.WriteIndex(ii); err != nil {
|
if err := p.cache.WriteIndex(ii); err != nil {
|
||||||
return fmt.Errorf("unable to write index: %v", err)
|
return fmt.Errorf("unable to write index: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := p.DescriptorWrite(&v1ref, desc.Descriptor); err != nil {
|
if err := p.DescriptorWrite(&v1ref, desc.Descriptor); err != nil {
|
||||||
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
}
|
}
|
||||||
if withArchReferences {
|
if withArchReferences {
|
||||||
@ -179,7 +210,7 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to parse arch-specific reference %s: %v", archSpecific, err)
|
return fmt.Errorf("unable to parse arch-specific reference %s: %v", archSpecific, err)
|
||||||
}
|
}
|
||||||
if _, err := p.DescriptorWrite(&archRef, m); err != nil {
|
if err := p.DescriptorWrite(&archRef, m); err != nil {
|
||||||
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
123
src/cmd/linuxkit/cache/write.go
vendored
123
src/cmd/linuxkit/cache/write.go
vendored
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -40,45 +39,42 @@ const (
|
|||||||
// If you just want to check the status of a local ref, use ValidateImage.
|
// If you just want to check the status of a local ref, use ValidateImage.
|
||||||
// Note that ImagePull does try ValidateImage first, so if the image is already in the cache, it will not
|
// Note that ImagePull does try ValidateImage first, so if the image is already in the cache, it will not
|
||||||
// do any network activity at all.
|
// do any network activity at all.
|
||||||
func (p *Provider) ImagePull(ref *reference.Spec, trustedRef, architecture string, alwaysPull bool) (lktspec.ImageSource, error) {
|
func (p *Provider) ImagePull(ref *reference.Spec, platforms []imagespec.Platform, alwaysPull bool) error {
|
||||||
imageName := util.ReferenceExpand(ref.String())
|
imageName := util.ReferenceExpand(ref.String())
|
||||||
canonicalRef, err := reference.Parse(imageName)
|
canonicalRef, err := reference.Parse(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("invalid image name %s: %v", imageName, err)
|
return fmt.Errorf("invalid image name %s: %v", imageName, err)
|
||||||
}
|
}
|
||||||
ref = &canonicalRef
|
ref = &canonicalRef
|
||||||
image := ref.String()
|
image := ref.String()
|
||||||
pullImageName := image
|
pullImageName := image
|
||||||
|
platformMessage := platformMessageGenerator(platforms)
|
||||||
remoteOptions := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain)}
|
remoteOptions := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain)}
|
||||||
if trustedRef != "" {
|
|
||||||
pullImageName = trustedRef
|
|
||||||
}
|
|
||||||
log.Debugf("ImagePull to cache %s trusted reference %s", image, pullImageName)
|
log.Debugf("ImagePull to cache %s trusted reference %s", image, pullImageName)
|
||||||
|
|
||||||
// unless alwaysPull is set to true, check locally first
|
// unless alwaysPull is set to true, check locally first
|
||||||
if alwaysPull {
|
if alwaysPull {
|
||||||
log.Debugf("Instructed always to pull, so pulling image %s arch %s", image, architecture)
|
log.Debugf("Instructed always to pull, so pulling image %s %s", image, platformMessage)
|
||||||
} else {
|
} else {
|
||||||
imgSrc, err := p.ValidateImage(ref, architecture)
|
imgSrc, err := p.ValidateImage(ref, platforms)
|
||||||
switch {
|
switch {
|
||||||
case err == nil && imgSrc != nil:
|
case err == nil && imgSrc != nil:
|
||||||
log.Debugf("Image %s arch %s found in local cache, not pulling", image, architecture)
|
log.Debugf("Image %s %s found in local cache, not pulling", image, platformMessage)
|
||||||
return imgSrc, nil
|
return nil
|
||||||
case err != nil && errors.Is(err, &noReferenceError{}):
|
case err != nil && errors.Is(err, &noReferenceError{}):
|
||||||
log.Debugf("Image %s arch %s not found in local cache, pulling", image, architecture)
|
log.Debugf("Image %s %s not found in local cache, pulling", image, platformMessage)
|
||||||
default:
|
default:
|
||||||
log.Debugf("Image %s arch %s incomplete or invalid in local cache, error %v, pulling", image, architecture, err)
|
log.Debugf("Image %s %s incomplete or invalid in local cache, error %v, pulling", image, platformMessage, err)
|
||||||
}
|
}
|
||||||
// there was an error, so try to pull
|
|
||||||
}
|
}
|
||||||
remoteRef, err := name.ParseReference(pullImageName)
|
remoteRef, err := name.ParseReference(pullImageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("invalid image name %s: %v", pullImageName, err)
|
return fmt.Errorf("invalid image name %s: %v", pullImageName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := remote.Get(remoteRef, remoteOptions...)
|
desc, err := remote.Get(remoteRef, remoteOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("error getting manifest for trusted image %s: %v", pullImageName, err)
|
return fmt.Errorf("error getting manifest for image %s: %v", pullImageName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the original image name in the annotation
|
// use the original image name in the annotation
|
||||||
@ -89,46 +85,57 @@ func (p *Provider) ImagePull(ref *reference.Spec, trustedRef, architecture strin
|
|||||||
// first attempt as an index
|
// first attempt as an index
|
||||||
ii, err := desc.ImageIndex()
|
ii, err := desc.ImageIndex()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Debugf("ImageWrite retrieved %s is index, saving, first checking if it contains target arch %s", pullImageName, architecture)
|
log.Debugf("ImageWrite retrieved %s is index, saving, first checking if it contains target %s", pullImageName, platformMessage)
|
||||||
im, err := ii.IndexManifest()
|
im, err := ii.IndexManifest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to get IndexManifest: %v", err)
|
return fmt.Errorf("unable to get IndexManifest: %v", err)
|
||||||
}
|
}
|
||||||
// only useful if it contains our architecture
|
// only useful if it contains our architecture
|
||||||
var foundArch bool
|
var foundPlatforms []*v1.Platform
|
||||||
for _, m := range im.Manifests {
|
for _, m := range im.Manifests {
|
||||||
if m.MediaType.IsImage() && m.Platform != nil && m.Platform.Architecture == architecture && m.Platform.OS == linux {
|
if m.MediaType.IsImage() && m.Platform != nil {
|
||||||
foundArch = true
|
foundPlatforms = append(foundPlatforms, m.Platform)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !foundArch {
|
// now see if we have all of the platforms we need
|
||||||
return ImageSource{}, fmt.Errorf("index %s does not contain target architecture %s", pullImageName, architecture)
|
var missing []string
|
||||||
|
for _, requiredPlatform := range platforms {
|
||||||
|
// we did not find it, so maybe one satisfies it
|
||||||
|
var matchedPlatform bool
|
||||||
|
for _, p := range foundPlatforms {
|
||||||
|
if p.OS == requiredPlatform.OS && p.Architecture == requiredPlatform.Architecture && (p.Variant == requiredPlatform.Variant || requiredPlatform.Variant == "") {
|
||||||
|
// this one satisfies it, so do not count it missing
|
||||||
|
matchedPlatform = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matchedPlatform {
|
||||||
|
missing = append(missing, platformString(requiredPlatform))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missing) > 0 {
|
||||||
|
return fmt.Errorf("index %s does not contain target platforms %s", pullImageName, strings.Join(missing, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.cache.WriteIndex(ii); err != nil {
|
if err := p.cache.WriteIndex(ii); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to write index: %v", err)
|
return fmt.Errorf("unable to write index: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := p.DescriptorWrite(ref, desc.Descriptor); err != nil {
|
if err := p.DescriptorWrite(ref, desc.Descriptor); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var im v1.Image
|
var im v1.Image
|
||||||
// try an image
|
// try an image
|
||||||
im, err = desc.Image()
|
im, err = desc.Image()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("provided image is neither an image nor an index: %s", image)
|
return fmt.Errorf("provided image is neither an image nor an index: %s", image)
|
||||||
}
|
}
|
||||||
log.Debugf("ImageWrite retrieved %s is image, saving", pullImageName)
|
log.Debugf("ImageWrite retrieved %s is image, saving", pullImageName)
|
||||||
if err = p.cache.ReplaceImage(im, match.Name(image), layout.WithAnnotations(annotations)); err != nil {
|
if err = p.cache.ReplaceImage(im, match.Name(image), layout.WithAnnotations(annotations)); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to save image to cache: %v", err)
|
return fmt.Errorf("unable to save image to cache: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return p.NewSource(
|
return nil
|
||||||
ref,
|
|
||||||
architecture,
|
|
||||||
&desc.Descriptor,
|
|
||||||
), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageLoad takes an OCI format image tar stream and writes it locally. It should be
|
// ImageLoad takes an OCI format image tar stream and writes it locally. It should be
|
||||||
@ -226,27 +233,27 @@ func (p *Provider) ImageLoad(r io.Reader) ([]v1.Descriptor, error) {
|
|||||||
// does not pull down any images; entirely assumes that the subjects of the manifests are present.
|
// does not pull down any images; entirely assumes that the subjects of the manifests are present.
|
||||||
// If a reference to the provided already exists and it is an index, updates the manifests in the
|
// If a reference to the provided already exists and it is an index, updates the manifests in the
|
||||||
// existing index.
|
// existing index.
|
||||||
func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor) (lktspec.ImageSource, error) {
|
func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor) error {
|
||||||
image := ref.String()
|
image := ref.String()
|
||||||
log.Debugf("writing an index for %s", image)
|
log.Debugf("writing an index for %s", image)
|
||||||
if len(descriptors) < 1 {
|
if len(descriptors) < 1 {
|
||||||
return ImageSource{}, errors.New("cannot create index without any manifests")
|
return errors.New("cannot create index without any manifests")
|
||||||
}
|
}
|
||||||
|
|
||||||
ii, err := p.cache.ImageIndex()
|
ii, err := p.cache.ImageIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to get root index: %v", err)
|
return fmt.Errorf("unable to get root index: %v", err)
|
||||||
}
|
}
|
||||||
images, err := partial.FindImages(ii, match.Name(image))
|
images, err := partial.FindImages(ii, match.Name(image))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("error parsing index: %v", err)
|
return fmt.Errorf("error parsing index: %v", err)
|
||||||
}
|
}
|
||||||
if err == nil && len(images) > 0 {
|
if err == nil && len(images) > 0 {
|
||||||
return ImageSource{}, fmt.Errorf("image named %s already exists in cache and is not an index", image)
|
return fmt.Errorf("image named %s already exists in cache and is not an index", image)
|
||||||
}
|
}
|
||||||
indexes, err := partial.FindIndexes(ii, match.Name(image))
|
indexes, err := partial.FindIndexes(ii, match.Name(image))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("error parsing index: %v", err)
|
return fmt.Errorf("error parsing index: %v", err)
|
||||||
}
|
}
|
||||||
var im v1.IndexManifest
|
var im v1.IndexManifest
|
||||||
// do we update an existing one? Or create a new one?
|
// do we update an existing one? Or create a new one?
|
||||||
@ -254,11 +261,11 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
|||||||
// we already had one, so update just the referenced index and return
|
// we already had one, so update just the referenced index and return
|
||||||
manifest, err := indexes[0].IndexManifest()
|
manifest, err := indexes[0].IndexManifest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to convert index for %s into its manifest: %v", image, err)
|
return fmt.Errorf("unable to convert index for %s into its manifest: %v", image, err)
|
||||||
}
|
}
|
||||||
oldhash, err := indexes[0].Digest()
|
oldhash, err := indexes[0].Digest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to get hash of existing index: %v", err)
|
return fmt.Errorf("unable to get hash of existing index: %v", err)
|
||||||
}
|
}
|
||||||
// we only care about avoiding duplicate arch/OS/Variant
|
// we only care about avoiding duplicate arch/OS/Variant
|
||||||
var (
|
var (
|
||||||
@ -335,7 +342,7 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
|||||||
im = *manifest
|
im = *manifest
|
||||||
// remove the old index
|
// remove the old index
|
||||||
if err := p.cache.RemoveBlob(oldhash); err != nil {
|
if err := p.cache.RemoveBlob(oldhash); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to remove old index file: %v", err)
|
return fmt.Errorf("unable to remove old index file: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we did not have one, so create an index, store it, update the root index.json, and return
|
// we did not have one, so create an index, store it, update the root index.json, and return
|
||||||
@ -349,18 +356,18 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
|||||||
// write the updated index, remove the old one
|
// write the updated index, remove the old one
|
||||||
b, err := json.Marshal(im)
|
b, err := json.Marshal(im)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to marshal new index to json: %v", err)
|
return fmt.Errorf("unable to marshal new index to json: %v", err)
|
||||||
}
|
}
|
||||||
hash, size, err := v1.SHA256(bytes.NewReader(b))
|
hash, size, err := v1.SHA256(bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("error calculating hash of index json: %v", err)
|
return fmt.Errorf("error calculating hash of index json: %v", err)
|
||||||
}
|
}
|
||||||
if err := p.cache.WriteBlob(hash, io.NopCloser(bytes.NewReader(b))); err != nil {
|
if err := p.cache.WriteBlob(hash, io.NopCloser(bytes.NewReader(b))); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("error writing new index to json: %v", err)
|
return fmt.Errorf("error writing new index to json: %v", err)
|
||||||
}
|
}
|
||||||
// finally update the descriptor in the root
|
// finally update the descriptor in the root
|
||||||
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to remove old descriptor from index.json: %v", err)
|
return fmt.Errorf("unable to remove old descriptor from index.json: %v", err)
|
||||||
}
|
}
|
||||||
desc := v1.Descriptor{
|
desc := v1.Descriptor{
|
||||||
MediaType: types.OCIImageIndex,
|
MediaType: types.OCIImageIndex,
|
||||||
@ -371,21 +378,17 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
if err := p.cache.AppendDescriptor(desc); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to append new descriptor to index.json: %v", err)
|
return fmt.Errorf("unable to append new descriptor to index.json: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.NewSource(
|
return nil
|
||||||
ref,
|
|
||||||
"",
|
|
||||||
&desc,
|
|
||||||
), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
||||||
// and replaces any existing one
|
// and replaces any existing one
|
||||||
func (p *Provider) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) (lktspec.ImageSource, error) {
|
func (p *Provider) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) error {
|
||||||
if ref == nil {
|
if ref == nil {
|
||||||
return ImageSource{}, errors.New("cannot write descriptor without reference name")
|
return errors.New("cannot write descriptor without reference name")
|
||||||
}
|
}
|
||||||
image := ref.String()
|
image := ref.String()
|
||||||
if desc.Annotations == nil {
|
if desc.Annotations == nil {
|
||||||
@ -396,22 +399,18 @@ func (p *Provider) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) (lkt
|
|||||||
|
|
||||||
// do we update an existing one? Or create a new one?
|
// do we update an existing one? Or create a new one?
|
||||||
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to remove old descriptors for %s: %v", image, err)
|
return fmt.Errorf("unable to remove old descriptors for %s: %v", image, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
if err := p.cache.AppendDescriptor(desc); err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to append new descriptor for %s: %v", image, err)
|
return fmt.Errorf("unable to append new descriptor for %s: %v", image, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.NewSource(
|
return nil
|
||||||
ref,
|
|
||||||
"",
|
|
||||||
&desc,
|
|
||||||
), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
||||||
img, err := p.findImage(ref.String(), architecture)
|
img, err := p.findImage(ref.String(), imagespec.Platform{OS: linux, Architecture: architecture})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,20 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
cachepkg "github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
cachepkg "github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cacheExportCmd() *cobra.Command {
|
func cacheExportCmd() *cobra.Command {
|
||||||
var (
|
var (
|
||||||
arch string
|
platform string
|
||||||
outputFile string
|
outputFile string
|
||||||
format string
|
format string
|
||||||
tagName string
|
tagName string
|
||||||
@ -42,7 +45,16 @@ func cacheExportCmd() *cobra.Command {
|
|||||||
log.Fatalf("unable to find image named %s: %v", name, err)
|
log.Fatalf("unable to find image named %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
src := p.NewSource(&ref, arch, desc)
|
plat, err := v1.ParsePlatform(platform)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid platform %s: %v", platform, err)
|
||||||
|
}
|
||||||
|
platspec := imagespec.Platform{
|
||||||
|
Architecture: plat.Architecture,
|
||||||
|
OS: plat.OS,
|
||||||
|
Variant: plat.Variant,
|
||||||
|
}
|
||||||
|
src := p.NewSource(&ref, &platspec, desc)
|
||||||
var reader io.ReadCloser
|
var reader io.ReadCloser
|
||||||
switch format {
|
switch format {
|
||||||
case "docker":
|
case "docker":
|
||||||
@ -88,7 +100,7 @@ func cacheExportCmd() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVar(&arch, "arch", runtime.GOARCH, "Architecture to resolve an index to an image, if the provided image name is an index")
|
cmd.Flags().StringVar(&platform, "platform", strings.Join([]string{"linux", runtime.GOARCH}, "/"), "Platform to resolve an index to an image, if the provided image name is an index")
|
||||||
cmd.Flags().StringVar(&outputFile, "outfile", "", "Path to file to save output, '-' for stdout")
|
cmd.Flags().StringVar(&outputFile, "outfile", "", "Path to file to save output, '-' for stdout")
|
||||||
cmd.Flags().StringVar(&format, "format", "oci", "export format, one of 'oci' (OCI tar), 'docker' (docker tar), 'filesystem'")
|
cmd.Flags().StringVar(&format, "format", "oci", "export format, one of 'oci' (OCI tar), 'docker' (docker tar), 'filesystem'")
|
||||||
cmd.Flags().StringVar(&tagName, "name", "", "override the provided image name in the exported tar file; useful only for format=oci")
|
cmd.Flags().StringVar(&tagName, "name", "", "override the provided image name in the exported tar file; useful only for format=oci")
|
||||||
|
@ -16,6 +16,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
// drop-in 100% compatible replacement and 17% faster than compress/gzip.
|
// drop-in 100% compatible replacement and 17% faster than compress/gzip.
|
||||||
gzip "github.com/klauspost/pgzip"
|
gzip "github.com/klauspost/pgzip"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/moby"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/moby"
|
||||||
@ -84,6 +86,8 @@ func OutputTypes() []string {
|
|||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// outputImage given an image and a section, such as onboot, onshutdown or services, lay it out with correct location
|
||||||
|
// config, etc. in the filesystem, so runc can use it.
|
||||||
func outputImage(image *moby.Image, section string, index int, prefix string, m moby.Moby, idMap map[string]uint32, dupMap map[string]string, iw *tar.Writer, opts BuildOpts) error {
|
func outputImage(image *moby.Image, section string, index int, prefix string, m moby.Moby, idMap map[string]uint32, dupMap map[string]string, iw *tar.Writer, opts BuildOpts) error {
|
||||||
log.Infof(" Create OCI config for %s", image.Image)
|
log.Infof(" Create OCI config for %s", image.Image)
|
||||||
imageName := util.ReferenceExpand(image.Image)
|
imageName := util.ReferenceExpand(image.Image)
|
||||||
@ -91,7 +95,7 @@ func outputImage(image *moby.Image, section string, index int, prefix string, m
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not resolve references for image %s: %v", image.Image, err)
|
return fmt.Errorf("could not resolve references for image %s: %v", image.Image, err)
|
||||||
}
|
}
|
||||||
src, err := imagePull(&ref, opts.Pull, opts.CacheDir, opts.DockerCache, opts.Arch)
|
src, err := imageSource(&ref, opts.Pull, opts.CacheDir, opts.DockerCache, imagespec.Platform{OS: "linux", Architecture: opts.Arch})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not pull image %s: %v", image.Image, err)
|
return fmt.Errorf("could not pull image %s: %v", image.Image, err)
|
||||||
}
|
}
|
||||||
@ -281,8 +285,9 @@ func Build(m moby.Moby, w io.Writer, opts BuildOpts) error {
|
|||||||
|
|
||||||
// get volume tarball from container
|
// get volume tarball from container
|
||||||
if err := ImageTar(location, vol.ImageRef(), lowerPath, apkTar, resolvconfSymlink, opts); err != nil {
|
if err := ImageTar(location, vol.ImageRef(), lowerPath, apkTar, resolvconfSymlink, opts); err != nil {
|
||||||
return fmt.Errorf("failed to build volume tarball from %s: %v", vol.Name, err)
|
return fmt.Errorf("failed to build volume filesystem tarball from %s: %v", vol.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make upper and merged dirs which will be used for mounting
|
// make upper and merged dirs which will be used for mounting
|
||||||
// no need to make lower dir, as it is made automatically by ImageTar()
|
// no need to make lower dir, as it is made automatically by ImageTar()
|
||||||
tmpPath := strings.TrimPrefix(tmpDir, "/") + "/"
|
tmpPath := strings.TrimPrefix(tmpDir, "/") + "/"
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/moby"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/moby"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -175,14 +176,15 @@ func tarPrefix(path, location, refName string, tw tarWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageTar takes a Docker image and outputs it to a tar stream
|
// ImageTar takes a Docker image and outputs it to a tar stream as a merged filesystem for a specific architecture
|
||||||
|
// defined in opts.
|
||||||
// location is where it is in the linuxkit.yaml file
|
// location is where it is in the linuxkit.yaml file
|
||||||
func ImageTar(location string, ref *reference.Spec, prefix string, tw tarWriter, resolv string, opts BuildOpts) (e error) {
|
func ImageTar(location string, ref *reference.Spec, prefix string, tw tarWriter, resolv string, opts BuildOpts) (e error) {
|
||||||
refName := "empty"
|
refName := "empty"
|
||||||
if ref != nil {
|
if ref != nil {
|
||||||
refName = ref.String()
|
refName = ref.String()
|
||||||
}
|
}
|
||||||
log.Debugf("image tar: %s %s", refName, prefix)
|
log.Debugf("image filesystem tar: %s %s %s", refName, prefix, opts.Arch)
|
||||||
if prefix != "" && prefix[len(prefix)-1] != '/' {
|
if prefix != "" && prefix[len(prefix)-1] != '/' {
|
||||||
return fmt.Errorf("prefix does not end with /: %s", prefix)
|
return fmt.Errorf("prefix does not end with /: %s", prefix)
|
||||||
}
|
}
|
||||||
@ -197,9 +199,8 @@ func ImageTar(location string, ref *reference.Spec, prefix string, tw tarWriter,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pullImage first checks in the cache, then pulls the image.
|
// get a handle on the image, optionally from docker, pulling from registry if necessary.
|
||||||
// If pull==true, then it always tries to pull from registry.
|
src, err := imageSource(ref, opts.Pull, opts.CacheDir, opts.DockerCache, imagespec.Platform{OS: "linux", Architecture: opts.Arch})
|
||||||
src, err := imagePull(ref, opts.Pull, opts.CacheDir, opts.DockerCache, opts.Arch)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not pull image %s: %v", ref, err)
|
return fmt.Errorf("could not pull image %s: %v", ref, err)
|
||||||
}
|
}
|
||||||
@ -356,6 +357,79 @@ func ImageTar(location string, ref *reference.Spec, prefix string, tw tarWriter,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageOCITar takes an OCI image and outputs it to a tar stream as a v1 layout format.
|
||||||
|
// Will include all architectures, or, if specific ones provided, then only those.
|
||||||
|
// location is where it is in the linuxkit.yaml file
|
||||||
|
func ImageOCITar(location string, ref *reference.Spec, prefix string, tw tarWriter, opts BuildOpts, platforms []imagespec.Platform) (e error) {
|
||||||
|
refName := "empty"
|
||||||
|
if ref != nil {
|
||||||
|
refName = ref.String()
|
||||||
|
}
|
||||||
|
log.Debugf("image v1 layout tar: %s %s %s", refName, prefix, opts.Arch)
|
||||||
|
if prefix != "" && prefix[len(prefix)-1] != '/' {
|
||||||
|
return fmt.Errorf("prefix does not end with /: %s", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tarPrefix(prefix, location, refName, tw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the image is blank, we do not need to do any more
|
||||||
|
if ref == nil {
|
||||||
|
return fmt.Errorf("no image reference provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexSource first checks in the cache, then pulls the image.
|
||||||
|
// If pull==true, then it always tries to pull from registry.
|
||||||
|
src, err := indexSource(ref, opts.Pull, opts.CacheDir, platforms)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not pull image %s: %v", ref, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := src.OCITarReader("")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not unpack image %s: %v", ref, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer contents.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(contents)
|
||||||
|
|
||||||
|
for {
|
||||||
|
hdr, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// force PAX format, since it allows for unlimited Name/Linkname
|
||||||
|
// and we move all files below prefix.
|
||||||
|
hdr.Format = tar.FormatPAX
|
||||||
|
// ensure we record the source of the file in the PAX header
|
||||||
|
if hdr.PAXRecords == nil {
|
||||||
|
hdr.PAXRecords = make(map[string]string)
|
||||||
|
}
|
||||||
|
hdr.PAXRecords[moby.PaxRecordLinuxkitSource] = ref.String()
|
||||||
|
hdr.PAXRecords[moby.PaxRecordLinuxkitLocation] = location
|
||||||
|
hdr.Name = prefix + hdr.Name
|
||||||
|
if hdr.Typeflag == tar.TypeLink {
|
||||||
|
// hard links are referenced by full path so need to be adjusted
|
||||||
|
hdr.Linkname = prefix + hdr.Linkname
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(tw, tr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
|
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
|
||||||
func ImageBundle(prefix, location string, ref *reference.Spec, config []byte, runtime moby.Runtime, tw tarWriter, readonly bool, dupMap map[string]string, opts BuildOpts) error { // nolint: lll
|
func ImageBundle(prefix, location string, ref *reference.Spec, config []byte, runtime moby.Runtime, tw tarWriter, readonly bool, dupMap map[string]string, opts BuildOpts) error { // nolint: lll
|
||||||
// if read only, just unpack in rootfs/ but otherwise set up for overlay
|
// if read only, just unpack in rootfs/ but otherwise set up for overlay
|
||||||
|
@ -5,20 +5,23 @@ import (
|
|||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/docker"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/docker"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// imagePull pull an image from the OCI registry to the cache.
|
// imageSource given an image ref, get a handle on the image so it can be used as a source for its configuration
|
||||||
// If the image root already is in the cache, use it, unless
|
// and layers. If the image root already is in the cache, use it.
|
||||||
// the option pull is set to true.
|
// If not in cache, pull it down from the OCI registry.
|
||||||
// if alwaysPull, then do not even bother reading locally
|
// Optionally can look in docker image cache first, before falling back to linuxkit cache and OCI registry.
|
||||||
func imagePull(ref *reference.Spec, alwaysPull bool, cacheDir string, dockerCache bool, architecture string) (lktspec.ImageSource, error) {
|
// Optionally can be told to alwaysPull, in which case it always pulls from the OCI registry.
|
||||||
|
// Always works for a single architecture, as we are referencing a specific image.
|
||||||
|
func imageSource(ref *reference.Spec, alwaysPull bool, cacheDir string, dockerCache bool, platform imagespec.Platform) (lktspec.ImageSource, error) {
|
||||||
// several possibilities:
|
// several possibilities:
|
||||||
// - alwaysPull: try to pull it down from the registry to linuxkit cache, then fail
|
// - alwaysPull: try to pull it down from the registry to linuxkit cache, then fail
|
||||||
// - !alwaysPull && dockerCache: try to read it from docker, then try linuxkit cache, then try to pull from registry, then fail
|
// - !alwaysPull && dockerCache: try to read it from docker, then try linuxkit cache, then try to pull from registry, then fail
|
||||||
// - !alwaysPull && !dockerCache: try linuxkit cache, then try to pull from registry, then fail
|
// - !alwaysPull && !dockerCache: try linuxkit cache, then try to pull from registry, then fail
|
||||||
// first, try docker, if that is available
|
// first, try docker, if that is available
|
||||||
if !alwaysPull && dockerCache {
|
if !alwaysPull && dockerCache {
|
||||||
if err := docker.HasImage(ref, architecture); err == nil {
|
if err := docker.HasImage(ref, platform.Architecture); err == nil {
|
||||||
return docker.NewSource(ref), nil
|
return docker.NewSource(ref), nil
|
||||||
}
|
}
|
||||||
// docker is not required, so any error - image not available, no docker, whatever - just gets ignored
|
// docker is not required, so any error - image not available, no docker, whatever - just gets ignored
|
||||||
@ -31,5 +34,44 @@ func imagePull(ref *reference.Spec, alwaysPull bool, cacheDir string, dockerCach
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we made it here, we either did not have the image, or it was incomplete
|
// if we made it here, we either did not have the image, or it was incomplete
|
||||||
return c.ImagePull(ref, ref.String(), architecture, alwaysPull)
|
if err := c.ImagePull(ref, []imagespec.Platform{platform}, alwaysPull); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
desc, err := c.FindDescriptor(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.NewSource(
|
||||||
|
ref,
|
||||||
|
&platform,
|
||||||
|
desc,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexSource given an image ref, get a handle on the index so it can be used as a source for its underlying images.
|
||||||
|
// If the index root already is in the cache, use it.
|
||||||
|
// If not in cache, pull it down from the OCI registry.
|
||||||
|
// Optionally can look in docker image cache first, before falling back to linuxkit cache and OCI registry.
|
||||||
|
// Optionally can be told to alwaysPull, in which case it always pulls from the OCI registry.
|
||||||
|
// Can provide architectures to list which ones to limit, or leave empty for all available.
|
||||||
|
func indexSource(ref *reference.Spec, alwaysPull bool, cacheDir string, platforms []imagespec.Platform) (lktspec.IndexSource, error) {
|
||||||
|
// get a reference to the local cache; we either will find the ref there or will pull to it
|
||||||
|
c, err := cache.NewProvider(cacheDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we made it here, we either did not have the image, or it was incomplete
|
||||||
|
if err := c.ImagePull(ref, platforms, alwaysPull); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
desc, err := c.FindDescriptor(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.NewIndexSource(
|
||||||
|
ref,
|
||||||
|
desc,
|
||||||
|
platforms,
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,12 @@ type File struct {
|
|||||||
|
|
||||||
// Volume is the type of a volume specification
|
// Volume is the type of a volume specification
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
Name string `yaml:"name" json:"name"`
|
Name string `yaml:"name" json:"name"`
|
||||||
Image string `yaml:"image,omitempty" json:"image,omitempty"`
|
Image string `yaml:"image,omitempty" json:"image,omitempty"`
|
||||||
ReadOnly bool `yaml:"readonly,omitempty" json:"readonly,omitempty"`
|
ReadOnly bool `yaml:"readonly,omitempty" json:"readonly,omitempty"`
|
||||||
ref *reference.Spec
|
Format string `yaml:"format,omitempty" json:"format,omitempty"`
|
||||||
|
Platforms []string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
|
||||||
|
ref *reference.Spec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Volume) ImageRef() *reference.Spec {
|
func (v Volume) ImageRef() *reference.Spec {
|
||||||
|
@ -327,7 +327,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
case bo.pull:
|
case bo.pull:
|
||||||
// need to pull the image from the registry, else build
|
// need to pull the image from the registry, else build
|
||||||
fmt.Fprintf(writer, "%s %s not found in local cache, trying to pull\n", ref, platform.Architecture)
|
fmt.Fprintf(writer, "%s %s not found in local cache, trying to pull\n", ref, platform.Architecture)
|
||||||
if _, err := c.ImagePull(&ref, "", platform.Architecture, false); err == nil {
|
if err := c.ImagePull(&ref, []imagespec.Platform{platform}, false); err == nil {
|
||||||
fmt.Fprintf(writer, "%s pulled\n", ref)
|
fmt.Fprintf(writer, "%s pulled\n", ref)
|
||||||
// successfully pulled, no need to build, continue with next platform
|
// successfully pulled, no need to build, continue with next platform
|
||||||
continue
|
continue
|
||||||
@ -470,7 +470,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
// - potentially create a release, including push and load into docker
|
// - potentially create a release, including push and load into docker
|
||||||
|
|
||||||
// create a multi-arch index
|
// create a multi-arch index
|
||||||
if _, err := c.IndexWrite(&ref, descs...); err != nil {
|
if err := c.IndexWrite(&ref, descs...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,7 +490,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cacheSource := c.NewSource(&ref, platform.Architecture, desc)
|
cacheSource := c.NewSource(&ref, &platform, desc)
|
||||||
reader, err := cacheSource.V1TarReader(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
|
reader, err := cacheSource.V1TarReader(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to get reader from cache: %v", err)
|
return fmt.Errorf("unable to get reader from cache: %v", err)
|
||||||
@ -562,7 +562,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := c.DescriptorWrite(&ref, *desc); err != nil {
|
if err := c.DescriptorWrite(&ref, *desc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Push(fullRelTag, "", bo.manifest, true); err != nil {
|
if err := c.Push(fullRelTag, "", bo.manifest, true); err != nil {
|
||||||
@ -617,7 +617,7 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c lktspec.CacheProvi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not resolve references for image %s: %v", p.Tag(), err)
|
return nil, fmt.Errorf("could not resolve references for image %s: %v", p.Tag(), err)
|
||||||
}
|
}
|
||||||
if _, err := c.ImagePull(&ref, "", arch, false); err == nil {
|
if err := c.ImagePull(&ref, []imagespec.Platform{{Architecture: arch, OS: "linux"}}, false); err == nil {
|
||||||
fmt.Fprintf(writer, "image already found %s for arch %s", ref, arch)
|
fmt.Fprintf(writer, "image already found %s for arch %s", ref, arch)
|
||||||
desc, err := c.FindDescriptor(&ref)
|
desc, err := c.FindDescriptor(&ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -240,21 +240,24 @@ type cacheMocker struct {
|
|||||||
hashes map[string][]byte
|
hashes map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cacheMocker) ImagePull(ref *reference.Spec, trustedRef, architecture string, alwaysPull bool) (lktspec.ImageSource, error) {
|
func (c *cacheMocker) ImagePull(ref *reference.Spec, platforms []imagespec.Platform, alwaysPull bool) error {
|
||||||
if !c.enableImagePull {
|
if !c.enableImagePull {
|
||||||
return nil, errors.New("ImagePull disabled")
|
return errors.New("ImagePull disabled")
|
||||||
}
|
}
|
||||||
// make some random data for a layer
|
// make some random data for a layer
|
||||||
b := make([]byte, 256)
|
b := make([]byte, 256)
|
||||||
_, _ = rand.Read(b)
|
_, _ = rand.Read(b)
|
||||||
descs, err := c.imageWriteStream(bytes.NewReader(b))
|
descs, err := c.imageWriteStream(bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
if len(descs) != 1 {
|
if len(descs) != 1 {
|
||||||
return nil, fmt.Errorf("expected 1 descriptor, got %d", len(descs))
|
return fmt.Errorf("expected 1 descriptor, got %d", len(descs))
|
||||||
}
|
}
|
||||||
return c.NewSource(ref, architecture, &descs[1]), nil
|
if len(platforms) != 1 {
|
||||||
|
return fmt.Errorf("cache does not support multiple platforms %s", platforms)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cacheMocker) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
func (c *cacheMocker) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
||||||
@ -359,9 +362,9 @@ func (c *cacheMocker) imageWriteStream(r io.Reader) ([]registry.Descriptor, erro
|
|||||||
return []registry.Descriptor{desc}, nil
|
return []registry.Descriptor{desc}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cacheMocker) IndexWrite(ref *reference.Spec, descriptors ...registry.Descriptor) (lktspec.ImageSource, error) {
|
func (c *cacheMocker) IndexWrite(ref *reference.Spec, descriptors ...registry.Descriptor) error {
|
||||||
if !c.enableIndexWrite {
|
if !c.enableIndexWrite {
|
||||||
return nil, errors.New("disabled")
|
return errors.New("disabled")
|
||||||
}
|
}
|
||||||
image := ref.String()
|
image := ref.String()
|
||||||
im := registry.IndexManifest{
|
im := registry.IndexManifest{
|
||||||
@ -373,11 +376,11 @@ func (c *cacheMocker) IndexWrite(ref *reference.Spec, descriptors ...registry.De
|
|||||||
// write the updated index, remove the old one
|
// write the updated index, remove the old one
|
||||||
b, err := json.Marshal(im)
|
b, err := json.Marshal(im)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to marshal new index to json: %v", err)
|
return fmt.Errorf("unable to marshal new index to json: %v", err)
|
||||||
}
|
}
|
||||||
hash, size, err := registry.SHA256(bytes.NewReader(b))
|
hash, size, err := registry.SHA256(bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error calculating hash of index json: %v", err)
|
return fmt.Errorf("error calculating hash of index json: %v", err)
|
||||||
}
|
}
|
||||||
c.assignHash(hash.String(), b)
|
c.assignHash(hash.String(), b)
|
||||||
desc := registry.Descriptor{
|
desc := registry.Descriptor{
|
||||||
@ -390,7 +393,7 @@ func (c *cacheMocker) IndexWrite(ref *reference.Spec, descriptors ...registry.De
|
|||||||
}
|
}
|
||||||
c.appendImage(image, desc)
|
c.appendImage(image, desc)
|
||||||
|
|
||||||
return c.NewSource(ref, "", &desc), nil
|
return nil
|
||||||
}
|
}
|
||||||
func (c *cacheMocker) Push(name, remoteName string, withManifest, override bool) error {
|
func (c *cacheMocker) Push(name, remoteName string, withManifest, override bool) error {
|
||||||
if !c.enablePush {
|
if !c.enablePush {
|
||||||
@ -402,9 +405,9 @@ func (c *cacheMocker) Push(name, remoteName string, withManifest, override bool)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cacheMocker) DescriptorWrite(ref *reference.Spec, desc registry.Descriptor) (lktspec.ImageSource, error) {
|
func (c *cacheMocker) DescriptorWrite(ref *reference.Spec, desc registry.Descriptor) error {
|
||||||
if !c.enabledDescriptorWrite {
|
if !c.enabledDescriptorWrite {
|
||||||
return nil, errors.New("descriptor disabled")
|
return errors.New("descriptor disabled")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
image = ref.String()
|
image = ref.String()
|
||||||
@ -417,11 +420,11 @@ func (c *cacheMocker) DescriptorWrite(ref *reference.Spec, desc registry.Descrip
|
|||||||
// write the updated index, remove the old one
|
// write the updated index, remove the old one
|
||||||
b, err := json.Marshal(im)
|
b, err := json.Marshal(im)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to marshal new index to json: %v", err)
|
return fmt.Errorf("unable to marshal new index to json: %v", err)
|
||||||
}
|
}
|
||||||
hash, size, err := registry.SHA256(bytes.NewReader(b))
|
hash, size, err := registry.SHA256(bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error calculating hash of index json: %v", err)
|
return fmt.Errorf("error calculating hash of index json: %v", err)
|
||||||
}
|
}
|
||||||
c.assignHash(hash.String(), b)
|
c.assignHash(hash.String(), b)
|
||||||
root := registry.Descriptor{
|
root := registry.Descriptor{
|
||||||
@ -434,7 +437,7 @@ func (c *cacheMocker) DescriptorWrite(ref *reference.Spec, desc registry.Descrip
|
|||||||
}
|
}
|
||||||
c.appendImage(image, root)
|
c.appendImage(image, root)
|
||||||
|
|
||||||
return c.NewSource(ref, "", &root), nil
|
return nil
|
||||||
}
|
}
|
||||||
func (c *cacheMocker) FindDescriptor(ref *reference.Spec) (*registry.Descriptor, error) {
|
func (c *cacheMocker) FindDescriptor(ref *reference.Spec) (*registry.Descriptor, error) {
|
||||||
name := ref.String()
|
name := ref.String()
|
||||||
@ -443,8 +446,8 @@ func (c *cacheMocker) FindDescriptor(ref *reference.Spec) (*registry.Descriptor,
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("not found %s", name)
|
return nil, fmt.Errorf("not found %s", name)
|
||||||
}
|
}
|
||||||
func (c *cacheMocker) NewSource(ref *reference.Spec, architecture string, descriptor *registry.Descriptor) lktspec.ImageSource {
|
func (c *cacheMocker) NewSource(ref *reference.Spec, platform *imagespec.Platform, descriptor *registry.Descriptor) lktspec.ImageSource {
|
||||||
return cacheMockerSource{c, ref, architecture, descriptor}
|
return cacheMockerSource{c, ref, platform, descriptor}
|
||||||
}
|
}
|
||||||
func (c *cacheMocker) assignHash(hash string, b []byte) {
|
func (c *cacheMocker) assignHash(hash string, b []byte) {
|
||||||
if c.hashes == nil {
|
if c.hashes == nil {
|
||||||
@ -473,10 +476,10 @@ func (c *cacheMocker) GetContent(hash v1.Hash) (io.ReadCloser, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cacheMockerSource struct {
|
type cacheMockerSource struct {
|
||||||
c *cacheMocker
|
c *cacheMocker
|
||||||
ref *reference.Spec
|
ref *reference.Spec
|
||||||
architecture string
|
platform *imagespec.Platform
|
||||||
descriptor *registry.Descriptor
|
descriptor *registry.Descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cacheMockerSource) Config() (imagespec.ImageConfig, error) {
|
func (c cacheMockerSource) Config() (imagespec.ImageConfig, error) {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CacheProvider interface for a provide of a cache.
|
// CacheProvider interface for a provide of a cache.
|
||||||
@ -19,7 +20,7 @@ type CacheProvider interface {
|
|||||||
// ImagePull takes an image name and pulls it from a registry to the cache. It should be
|
// ImagePull takes an image name and pulls it from a registry to the cache. It should be
|
||||||
// efficient and only write missing blobs, based on their content hash. If the ref already
|
// efficient and only write missing blobs, based on their content hash. If the ref already
|
||||||
// exists in the cache, it should not pull anything, unless alwaysPull is set to true.
|
// exists in the cache, it should not pull anything, unless alwaysPull is set to true.
|
||||||
ImagePull(ref *reference.Spec, trustedRef, architecture string, alwaysPull bool) (ImageSource, error)
|
ImagePull(ref *reference.Spec, platform []imagespec.Platform, alwaysPull bool) error
|
||||||
// ImageInCache takes an image name and checks if it exists in the cache, including checking that the given
|
// ImageInCache takes an image name and checks if it exists in the cache, including checking that the given
|
||||||
// architecture is complete. Like ImagePull, it should be efficient and only write missing blobs, based on
|
// architecture is complete. Like ImagePull, it should be efficient and only write missing blobs, based on
|
||||||
// their content hash.
|
// their content hash.
|
||||||
@ -30,20 +31,20 @@ type CacheProvider interface {
|
|||||||
// Cache implementation determines whether it should pull missing blobs from a remote registry.
|
// Cache implementation determines whether it should pull missing blobs from a remote registry.
|
||||||
// If the provided reference already exists and it is an index, updates the manifests in the
|
// If the provided reference already exists and it is an index, updates the manifests in the
|
||||||
// existing index.
|
// existing index.
|
||||||
IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor) (ImageSource, error)
|
IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor) error
|
||||||
// ImageLoad takes an OCI format image tar stream in the io.Reader and writes it to the cache. It should be
|
// ImageLoad takes an OCI format image tar stream in the io.Reader and writes it to the cache. It should be
|
||||||
// efficient and only write missing blobs, based on their content hash.
|
// efficient and only write missing blobs, based on their content hash.
|
||||||
ImageLoad(r io.Reader) ([]v1.Descriptor, error)
|
ImageLoad(r io.Reader) ([]v1.Descriptor, error)
|
||||||
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
||||||
// and replaces any existing one
|
// and replaces any existing one
|
||||||
DescriptorWrite(ref *reference.Spec, descriptors v1.Descriptor) (ImageSource, error)
|
DescriptorWrite(ref *reference.Spec, descriptors v1.Descriptor) error
|
||||||
// Push an image along with a multi-arch index from local cache to remote registry.
|
// Push an image along with a multi-arch index from local cache to remote registry.
|
||||||
// name is the name as referenced in the local cache, remoteName is the name to give it remotely.
|
// name is the name as referenced in the local cache, remoteName is the name to give it remotely.
|
||||||
// If remoteName is empty, it is the same as name.
|
// If remoteName is empty, it is the same as name.
|
||||||
// if withManifest defined will push a multi-arch manifest
|
// if withManifest defined will push a multi-arch manifest
|
||||||
Push(name, remoteName string, withManifest, override bool) error
|
Push(name, remoteName string, withManifest, override bool) error
|
||||||
// NewSource return an ImageSource for a specific ref and architecture in the cache.
|
// NewSource return an ImageSource for a specific ref and architecture in the cache.
|
||||||
NewSource(ref *reference.Spec, architecture string, descriptor *v1.Descriptor) ImageSource
|
NewSource(ref *reference.Spec, platform *imagespec.Platform, descriptor *v1.Descriptor) ImageSource
|
||||||
// GetContent returns an io.Reader to the provided content as is, given a specific digest. It is
|
// GetContent returns an io.Reader to the provided content as is, given a specific digest. It is
|
||||||
// up to the caller to validate it.
|
// up to the caller to validate it.
|
||||||
GetContent(hash v1.Hash) (io.ReadCloser, error)
|
GetContent(hash v1.Hash) (io.ReadCloser, error)
|
||||||
|
@ -10,12 +10,12 @@ import (
|
|||||||
// ImageSource interface to an image. It can have its config read, and a its containers
|
// ImageSource interface to an image. It can have its config read, and a its containers
|
||||||
// can be read via an io.ReadCloser tar stream.
|
// can be read via an io.ReadCloser tar stream.
|
||||||
type ImageSource interface {
|
type ImageSource interface {
|
||||||
|
// Descriptor get the v1.Descriptor of the image
|
||||||
|
Descriptor() *v1.Descriptor
|
||||||
// Config get the config for the image
|
// Config get the config for the image
|
||||||
Config() (imagespec.ImageConfig, error)
|
Config() (imagespec.ImageConfig, error)
|
||||||
// TarReader get the flattened filesystem of the image as a tar stream
|
// TarReader get the flattened filesystem of the image as a tar stream
|
||||||
TarReader() (io.ReadCloser, error)
|
TarReader() (io.ReadCloser, error)
|
||||||
// Descriptor get the v1.Descriptor of the image
|
|
||||||
Descriptor() *v1.Descriptor
|
|
||||||
// V1TarReader get the image as v1 tarball, also compatible with `docker load`. If name arg is not "", override name of image in tarfile from default of image.
|
// V1TarReader get the image as v1 tarball, also compatible with `docker load`. If name arg is not "", override name of image in tarfile from default of image.
|
||||||
V1TarReader(overrideName string) (io.ReadCloser, error)
|
V1TarReader(overrideName string) (io.ReadCloser, error)
|
||||||
// OCITarReader get the image as an OCI tarball, also compatible with `docker load`. If name arg is not "", override name of image in tarfile from default of image.
|
// OCITarReader get the image as an OCI tarball, also compatible with `docker load`. If name arg is not "", override name of image in tarfile from default of image.
|
||||||
@ -23,3 +23,14 @@ type ImageSource interface {
|
|||||||
// SBoM get the sbom for the image, if any is available
|
// SBoM get the sbom for the image, if any is available
|
||||||
SBoMs() ([]io.ReadCloser, error)
|
SBoMs() ([]io.ReadCloser, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IndexSource interface to an image. It can have its config read, and a its containers
|
||||||
|
// can be read via an io.ReadCloser tar stream.
|
||||||
|
type IndexSource interface {
|
||||||
|
// Descriptor get the v1.Descriptor of the index
|
||||||
|
Descriptor() *v1.Descriptor
|
||||||
|
// Image get image for a specific architecture
|
||||||
|
Image(platform imagespec.Platform) (ImageSource, error)
|
||||||
|
// OCITarReader get the image as an OCI tarball, also compatible with `docker load`. If name arg is not "", override name of image in tarfile from default of image.
|
||||||
|
OCITarReader(overrideName string) (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user