mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 17:26:28 +00:00
add cache export format OCI
Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
parent
6d37353ca1
commit
f5dcefc7c2
189
src/cmd/linuxkit/cache/source.go
vendored
189
src/cmd/linuxkit/cache/source.go
vendored
@ -1,6 +1,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -9,10 +10,12 @@ 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"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
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"
|
||||||
@ -21,6 +24,9 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
inTotoJsonMediaType = "application/vnd.in-toto+json"
|
inTotoJsonMediaType = "application/vnd.in-toto+json"
|
||||||
|
layoutFile = `{
|
||||||
|
"imageLayoutVersion": "1.0.0"
|
||||||
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImageSource a source for an image in the OCI distribution cache.
|
// ImageSource a source for an image in the OCI distribution cache.
|
||||||
@ -111,6 +117,189 @@ func (c ImageSource) V1TarReader(overrideName string) (io.ReadCloser, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OCITarReader return an io.ReadCloser to read the image as a v1 tarball
|
||||||
|
func (c ImageSource) 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
|
||||||
|
image, err := c.provider.findImage(imageName, c.architecture)
|
||||||
|
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()
|
||||||
|
// 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 {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(layoutFileBytes); err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// make blobs directory
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "blobs/",
|
||||||
|
Mode: 0755,
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
}); err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// make blobs/sha256 directory
|
||||||
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
|
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 {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configDigest, configSize, err := v1.SHA256(bytes.NewReader(config))
|
||||||
|
if err != nil {
|
||||||
|
_ = w.CloseWithError(err)
|
||||||
|
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
|
||||||
|
desc := v1.Descriptor{
|
||||||
|
MediaType: types.OCIImageIndex,
|
||||||
|
Size: manifestSize,
|
||||||
|
Digest: manifestDigest,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
imagespec.AnnotationRefName: refName.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ii := empty.Index
|
||||||
|
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Descriptor return the descriptor of the image.
|
// Descriptor return the descriptor of the image.
|
||||||
func (c ImageSource) Descriptor() *v1.Descriptor {
|
func (c ImageSource) Descriptor() *v1.Descriptor {
|
||||||
return c.descriptor
|
return c.descriptor
|
||||||
|
@ -45,12 +45,18 @@ func cacheExportCmd() *cobra.Command {
|
|||||||
src := p.NewSource(&ref, arch, desc)
|
src := p.NewSource(&ref, arch, desc)
|
||||||
var reader io.ReadCloser
|
var reader io.ReadCloser
|
||||||
switch format {
|
switch format {
|
||||||
case "oci":
|
case "docker":
|
||||||
fullTagName := fullname
|
fullTagName := fullname
|
||||||
if tagName != "" {
|
if tagName != "" {
|
||||||
fullTagName = util.ReferenceExpand(tagName)
|
fullTagName = util.ReferenceExpand(tagName)
|
||||||
}
|
}
|
||||||
reader, err = src.V1TarReader(fullTagName)
|
reader, err = src.V1TarReader(fullTagName)
|
||||||
|
case "oci":
|
||||||
|
fullTagName := fullname
|
||||||
|
if tagName != "" {
|
||||||
|
fullTagName = util.ReferenceExpand(tagName)
|
||||||
|
}
|
||||||
|
reader, err = src.OCITarReader(fullTagName)
|
||||||
case "filesystem":
|
case "filesystem":
|
||||||
reader, err = src.TarReader()
|
reader, err = src.TarReader()
|
||||||
default:
|
default:
|
||||||
@ -84,7 +90,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(&arch, "arch", runtime.GOARCH, "Architecture 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', '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")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
@ -86,6 +86,11 @@ func (d ImageSource) V1TarReader(overrideName string) (io.ReadCloser, error) {
|
|||||||
return Save(saveName)
|
return Save(saveName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OCITarReader return an io.ReadCloser to read the save of the image
|
||||||
|
func (d ImageSource) OCITarReader(overrideName string) (io.ReadCloser, error) {
|
||||||
|
return nil, fmt.Errorf("unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
// Descriptor return the descriptor of the image.
|
// Descriptor return the descriptor of the image.
|
||||||
func (d ImageSource) Descriptor() *v1.Descriptor {
|
func (d ImageSource) Descriptor() *v1.Descriptor {
|
||||||
return nil
|
return nil
|
||||||
|
@ -494,6 +494,15 @@ func (c cacheMockerSource) V1TarReader(overrideName string) (io.ReadCloser, erro
|
|||||||
_, _ = rand.Read(b)
|
_, _ = rand.Read(b)
|
||||||
return io.NopCloser(bytes.NewReader(b)), nil
|
return io.NopCloser(bytes.NewReader(b)), nil
|
||||||
}
|
}
|
||||||
|
func (c cacheMockerSource) OCITarReader(overrideName string) (io.ReadCloser, error) {
|
||||||
|
_, found := c.c.images[c.ref.String()]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("no image found with ref: %s", c.ref.String())
|
||||||
|
}
|
||||||
|
b := make([]byte, 256)
|
||||||
|
_, _ = rand.Read(b)
|
||||||
|
return io.NopCloser(bytes.NewReader(b)), nil
|
||||||
|
}
|
||||||
func (c cacheMockerSource) Descriptor() *registry.Descriptor {
|
func (c cacheMockerSource) Descriptor() *registry.Descriptor {
|
||||||
return c.descriptor
|
return c.descriptor
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ type ImageSource interface {
|
|||||||
Descriptor() *v1.Descriptor
|
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(overrideName string) (io.ReadCloser, error)
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user