sif: initial sif transport implementation

Signed-off-by: Yannick Cote <ycote@redhat.com>
This commit is contained in:
Yannick Cote 2021-09-27 10:02:53 -04:00 committed by Miloslav Trmač
parent ef7a5c4a7c
commit 13f7888b59
6 changed files with 665 additions and 0 deletions

2
go.mod
View File

@ -30,6 +30,7 @@ require (
github.com/opencontainers/selinux v1.10.0
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913
github.com/pkg/errors v0.9.1
github.com/satori/go.uuid v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/ulikunitz/xz v0.5.10
@ -37,6 +38,7 @@ require (
github.com/vbauerster/mpb/v7 v7.3.0
github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b // indirect
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yhcote/sif v1.0.2-0.20181213102235-838fcf45c762
go.etcd.io/bbolt v1.3.6
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3

3
go.sum
View File

@ -608,6 +608,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@ -690,6 +691,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yhcote/sif v1.0.2-0.20181213102235-838fcf45c762 h1:EjPCknEZriOHBGJX+bCd4tPmNWc326+Gq+yQdLJf4Kc=
github.com/yhcote/sif v1.0.2-0.20181213102235-838fcf45c762/go.mod h1:/5KfDnjXiMcvy8zFJjAwZa658WMEcdYAdh91A7ecN5o=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=

254
sif/internal/sif_util.go Normal file
View File

@ -0,0 +1,254 @@
package internal
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"github.com/yhcote/sif/pkg/sif"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type SifImage struct {
fimg sif.FileImage
rootfs *sif.Descriptor
deffile *sif.Descriptor
defReader *io.SectionReader
cmdlist []string
runscript *bytes.Buffer
env *sif.Descriptor
envReader *io.SectionReader
envlist []string
diffID digest.Digest
}
func LoadSIFImage(path string) (image SifImage, err error) {
// open up the SIF file and get its header
image.fimg, err = sif.LoadContainer(path, true)
if err != nil {
return
}
// check for a system partition and save it
image.rootfs, _, err = image.fimg.GetPartPrimSys()
if err != nil {
return SifImage{}, errors.Wrap(err, "looking up rootfs from SIF file")
}
// look for a definition file object
searchDesc := sif.Descriptor{Datatype: sif.DataDeffile}
resultDescs, _, err := image.fimg.GetFromDescr(searchDesc)
if err == nil && resultDescs != nil {
// we assume in practice that typical SIF files don't hold multiple deffiles
image.deffile = resultDescs[0]
image.defReader = io.NewSectionReader(image.fimg.Fp, image.deffile.Fileoff, image.deffile.Filelen)
}
if err = image.generateConfig(); err != nil {
return SifImage{}, err
}
// look for an environment variable set object
searchDesc = sif.Descriptor{Datatype: sif.DataEnvVar}
resultDescs, _, err = image.fimg.GetFromDescr(searchDesc)
if err == nil && resultDescs != nil {
// we assume in practice that typical SIF files don't hold multiple EnvVar sets
image.env = resultDescs[0]
image.envReader = io.NewSectionReader(image.fimg.Fp, image.env.Fileoff, image.env.Filelen)
}
return image, nil
}
func (image *SifImage) parseEnvironment(scanner *bufio.Scanner) error {
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if s == "" || strings.HasPrefix(s, "#") {
continue
}
if strings.HasPrefix(s, "%") {
return nil
}
image.envlist = append(image.envlist, s)
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "parsing environment from SIF definition file object")
}
return nil
}
func (image *SifImage) parseRunscript(scanner *bufio.Scanner) error {
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(s, "%") {
return nil
}
image.cmdlist = append(image.cmdlist, s)
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "parsing runscript from SIF definition file object")
}
return nil
}
func (image *SifImage) generateRunscript() error {
base := `#!/bin/bash
`
image.runscript = bytes.NewBufferString(base)
for _, s := range image.envlist {
_, err := image.runscript.WriteString(fmt.Sprintln(s))
if err != nil {
return errors.Wrap(err, "writing to runscript buffer")
}
}
for _, s := range image.cmdlist {
_, err := image.runscript.WriteString(fmt.Sprintln(s))
if err != nil {
return errors.Wrap(err, "writing to runscript buffer")
}
}
return nil
}
func (image *SifImage) generateConfig() error {
if image.deffile == nil {
image.cmdlist = append(image.cmdlist, "bash")
return nil
}
// extract %environment/%runscript from definition file
var err error
scanner := bufio.NewScanner(image.defReader)
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
again:
if s == `%environment` {
if err = image.parseEnvironment(scanner); err != nil {
return err
}
} else if s == `%runscript` {
if err = image.parseRunscript(scanner); err != nil {
return err
}
}
s = strings.TrimSpace(scanner.Text())
if s == `%environment` || s == `%runscript` {
goto again
}
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "reading lines from SIF definition file object")
}
if len(image.cmdlist) == 0 && len(image.envlist) == 0 {
image.cmdlist = append(image.cmdlist, "bash")
} else {
image.generateRunscript()
image.cmdlist = []string{"/podman/runscript"}
}
return nil
}
func (image SifImage) GetConfig(config *imgspecv1.Image) error {
config.Config.Cmd = append(config.Config.Cmd, image.cmdlist...)
return nil
}
func (image SifImage) UnloadSIFImage() (err error) {
err = image.fimg.UnloadContainer()
return
}
func (image SifImage) GetSIFID() string {
return image.fimg.Header.ID.String()
}
func (image SifImage) GetSIFArch() string {
return sif.GetGoArch(string(image.fimg.Header.Arch[:sif.HdrArchLen-1]))
}
const squashFilename = "rootfs.squashfs"
const tarFilename = "rootfs.tar"
func runUnSquashFSTar(tempdir string) (err error) {
script := `
#!/bin/sh
unsquashfs -f ` + squashFilename + ` && tar --acls --xattrs -C ./squashfs-root -cpf ` + tarFilename + ` ./
`
if err = ioutil.WriteFile(filepath.Join(tempdir, "script"), []byte(script), 0755); err != nil {
return err
}
cmd := []string{"fakeroot", "--", "./script"}
xcmd := exec.Command(cmd[0], cmd[1:]...)
xcmd.Stderr = os.Stderr
xcmd.Dir = tempdir
err = xcmd.Run()
return
}
func (image *SifImage) writeRunscript(tempdir string) (err error) {
if image.runscript == nil {
return nil
}
rsPath := filepath.Join(tempdir, "squashfs-root", "podman")
if err = os.MkdirAll(rsPath, 0755); err != nil {
return
}
if err = ioutil.WriteFile(filepath.Join(rsPath, "runscript"), image.runscript.Bytes(), 0755); err != nil {
return errors.Wrap(err, "writing /podman/runscript")
}
return nil
}
func (image SifImage) SquashFSToTarLayer(tempdir string) (tarpath string, err error) {
if _, err = image.fimg.Fp.Seek(image.rootfs.Fileoff, 0); err != nil {
return
}
f, err := os.Create(filepath.Join(tempdir, squashFilename))
if err != nil {
return
}
defer f.Close()
if _, err = io.CopyN(f, image.fimg.Fp, image.rootfs.Filelen); err != nil {
return
}
if err = f.Sync(); err != nil {
return
}
if err = image.writeRunscript(tempdir); err != nil {
return
}
if err = runUnSquashFSTar(tempdir); err != nil {
return
}
return filepath.Join(tempdir, tarFilename), nil
}
func createSIF() error {
cinfo := sif.CreateInfo{
Pathname: "container.sif",
Launchstr: sif.HdrLaunch,
Sifversion: sif.HdrVersion,
ID: uuid.NewV4(),
}
image, err := sif.CreateContainer(cinfo)
if err != nil {
return err
}
fmt.Printf("SIF: %+v\n", image)
return nil
}

285
sif/sif_src.go Normal file
View File

@ -0,0 +1,285 @@
package sifimage
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"time"
"github.com/containers/image/v5/internal/tmpdir"
"github.com/containers/image/v5/sif/internal"
"github.com/containers/image/v5/types"
"github.com/klauspost/pgzip"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
imgspecs "github.com/opencontainers/image-spec/specs-go"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type sifImageSource struct {
ref sifReference
sifimg internal.SifImage
workdir string
diffID digest.Digest
diffSize int64
blobID digest.Digest
blobSize int64
blobTime time.Time
blobType string
blobFile string
config []byte
configID digest.Digest
configSize int64
manifest []byte
}
func (s *sifImageSource) getLayerInfo(tarpath string) error {
ftar, err := os.Open(tarpath)
if err != nil {
return fmt.Errorf("error opening %q for reading: %v", tarpath, err)
}
defer ftar.Close()
diffDigester := digest.Canonical.Digester()
s.diffSize, err = io.Copy(diffDigester.Hash(), ftar)
if err != nil {
return fmt.Errorf("error reading %q: %v", tarpath, err)
}
s.diffID = diffDigester.Digest()
return nil
}
func (s *sifImageSource) createBlob(tarpath string) error {
s.blobFile = fmt.Sprintf("%s.%s", tarpath, "gz")
fgz, err := os.Create(s.blobFile)
if err != nil {
return errors.Wrapf(err, "creating file for compressed blob")
}
defer fgz.Close()
fileinfo, err := fgz.Stat()
if err != nil {
return fmt.Errorf("error reading modtime of %q: %v", s.blobFile, err)
}
s.blobTime = fileinfo.ModTime()
ftar, err := os.Open(tarpath)
if err != nil {
return fmt.Errorf("error opening %q for reading: %v", tarpath, err)
}
defer ftar.Close()
writer := pgzip.NewWriter(fgz)
defer writer.Close()
_, err = io.Copy(writer, ftar)
if err != nil {
return fmt.Errorf("error compressing %q: %v", tarpath, err)
}
return nil
}
func (s *sifImageSource) getBlobInfo() error {
fgz, err := os.Open(s.blobFile)
if err != nil {
return fmt.Errorf("error opening %q for reading: %v", s.blobFile, err)
}
defer fgz.Close()
blobDigester := digest.Canonical.Digester()
s.blobSize, err = io.Copy(blobDigester.Hash(), fgz)
if err != nil {
return fmt.Errorf("error reading %q: %v", s.blobFile, err)
}
s.blobID = blobDigester.Digest()
return nil
}
// newImageSource returns an ImageSource for reading from an existing directory.
// newImageSource extracts SIF objects and saves them in a temp directory.
func newImageSource(ctx context.Context, sys *types.SystemContext, ref sifReference) (types.ImageSource, error) {
var imgSrc sifImageSource
sifimg, err := internal.LoadSIFImage(ref.resolvedFile)
if err != nil {
return nil, errors.Wrap(err, "loading SIF file")
}
workdir, err := ioutil.TempDir(tmpdir.TemporaryDirectoryForBigFiles(sys), "sif")
if err != nil {
return nil, errors.Wrapf(err, "creating temp directory")
}
tarpath, err := sifimg.SquashFSToTarLayer(workdir)
if err != nil {
return nil, errors.Wrapf(err, "converting rootfs from SquashFS to Tarball")
}
// generate layer info
err = imgSrc.getLayerInfo(tarpath)
if err != nil {
return nil, errors.Wrapf(err, "gathering layer diff information")
}
// prepare compressed blob
err = imgSrc.createBlob(tarpath)
if err != nil {
return nil, errors.Wrapf(err, "creating blob file")
}
// generate blob info
err = imgSrc.getBlobInfo()
if err != nil {
return nil, errors.Wrapf(err, "gathering blob information")
}
// populate the rootfs section of the config
rootfs := imgspecv1.RootFS{
Type: "layers",
DiffIDs: []digest.Digest{imgSrc.diffID},
}
created := imgSrc.blobTime
history := []imgspecv1.History{
{
Created: &created,
CreatedBy: fmt.Sprintf("/bin/sh -c #(nop) ADD file:%s in %c", imgSrc.diffID.Hex(), os.PathSeparator),
Comment: "imported from SIF, uuid: " + sifimg.GetSIFID(),
},
{
Created: &created,
CreatedBy: "/bin/sh -c #(nop) CMD [\"bash\"]",
EmptyLayer: true,
},
}
// build an OCI image config
var config imgspecv1.Image
config.Created = &created
config.Architecture = sifimg.GetSIFArch()
config.OS = "linux"
config.RootFS = rootfs
config.History = history
err = sifimg.GetConfig(&config)
if err != nil {
return nil, errors.Wrapf(err, "getting config elements from SIF")
}
// Encode and digest the image configuration blob.
configBytes, err := json.Marshal(&config)
if err != nil {
return nil, fmt.Errorf("error generating configuration blob for %q: %v", ref.resolvedFile, err)
}
configID := digest.Canonical.FromBytes(configBytes)
configSize := int64(len(configBytes))
// Populate a manifest with the configuration blob and the SquashFS part as the single layer.
layerDescriptor := imgspecv1.Descriptor{
Digest: imgSrc.blobID,
Size: imgSrc.blobSize,
MediaType: imgspecv1.MediaTypeImageLayerGzip,
}
manifest := imgspecv1.Manifest{
Versioned: imgspecs.Versioned{
SchemaVersion: 2,
},
Config: imgspecv1.Descriptor{
Digest: configID,
Size: configSize,
MediaType: imgspecv1.MediaTypeImageConfig,
},
Layers: []imgspecv1.Descriptor{layerDescriptor},
}
manifestBytes, err := json.Marshal(&manifest)
if err != nil {
return nil, fmt.Errorf("error generating manifest for %q: %v", ref.resolvedFile, err)
}
return &sifImageSource{
ref: ref,
sifimg: sifimg,
workdir: workdir,
diffID: imgSrc.diffID,
diffSize: imgSrc.diffSize,
blobID: imgSrc.blobID,
blobSize: imgSrc.blobSize,
blobType: layerDescriptor.MediaType,
blobFile: imgSrc.blobFile,
config: configBytes,
configID: configID,
configSize: configSize,
manifest: manifestBytes,
}, nil
}
// Reference returns the reference used to set up this source.
func (s *sifImageSource) Reference() types.ImageReference {
return s.ref
}
// Close removes resources associated with an initialized ImageSource, if any.
func (s *sifImageSource) Close() error {
os.RemoveAll(s.workdir)
return s.sifimg.UnloadSIFImage()
}
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
func (s *sifImageSource) HasThreadSafeGetBlob() bool {
return false
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
// The Digest field in BlobInfo is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided.
// May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location.
func (s *sifImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) {
// We should only be asked about things in the manifest. Maybe the configuration blob.
if info.Digest == s.configID {
return ioutil.NopCloser(bytes.NewBuffer(s.config)), s.configSize, nil
}
if info.Digest == s.blobID {
reader, err := os.Open(s.blobFile)
if err != nil {
return nil, -1, fmt.Errorf("error opening %q: %v", s.blobFile, err)
}
return reader, s.blobSize, nil
}
return nil, -1, fmt.Errorf("no blob with digest %q found", info.Digest.String())
}
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
// It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
func (s *sifImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
if instanceDigest != nil {
return nil, "", fmt.Errorf("manifest lists are not supported by the sif transport")
}
return s.manifest, imgspecv1.MediaTypeImageManifest, nil
}
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
// (e.g. if the source never returns manifest lists).
func (s *sifImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
if instanceDigest != nil {
return nil, fmt.Errorf("manifest lists are not supported by the sif transport")
}
return nil, nil
}
// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
// to read the image's layers.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
// (e.g. if the source never returns manifest lists).
// The Digest field is guaranteed to be provided; Size may be -1.
// WARNING: The list may contain duplicates, and they are semantically relevant.
func (s *sifImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
return nil, nil
}

119
sif/sif_transport.go Normal file
View File

@ -0,0 +1,119 @@
package sifimage
import (
"context"
"fmt"
"strings"
"github.com/containers/image/v5/directory/explicitfilepath"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/image"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
"github.com/pkg/errors"
)
func init() {
transports.Register(Transport)
}
// Transport is an ImageTransport for SIF images.
var Transport = sifTransport{}
type sifTransport struct{}
// sifReference is an ImageReference for SIF images.
type sifReference struct {
file string // As specified by the user. May be relative, contain symlinks, etc.
resolvedFile string // Absolute file path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces.
}
func (t sifTransport) Name() string {
return "sif"
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
func (t sifTransport) ParseReference(reference string) (types.ImageReference, error) {
return NewReference(reference)
}
// NewReference returns an image file reference for a specified path.
func NewReference(file string) (types.ImageReference, error) {
resolved, err := explicitfilepath.ResolvePathToFullyExplicit(file)
if err != nil {
return nil, err
}
return sifReference{file: file, resolvedFile: resolved}, nil
}
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value).
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
// scope passed to this function will not be "", that value is always allowed.
func (t sifTransport) ValidatePolicyConfigurationScope(scope string) error {
return errors.New(`sif: does not support any scopes except the default "" one`)
}
func (ref sifReference) Transport() types.ImageTransport {
return Transport
}
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
func (ref sifReference) StringWithinTransport() string {
return fmt.Sprintf("%s", ref.file)
}
// DockerReference returns a Docker reference associated with this reference
func (ref sifReference) DockerReference() reference.Named {
return nil
}
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
func (ref sifReference) PolicyConfigurationIdentity() string {
return ref.resolvedFile
}
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
// for if explicit configuration for PolicyConfigurationIdentity() is not set
func (ref sifReference) PolicyConfigurationNamespaces() []string {
res := []string{}
path := ref.resolvedFile
for {
lastSlash := strings.LastIndex(path, "/")
if lastSlash == -1 || path == "/" {
break
}
res = append(res, path)
path = path[:lastSlash]
}
return res
}
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned ImageCloser.
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
func (ref sifReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
src, err := newImageSource(ctx, sys, ref)
if err != nil {
return nil, err
}
return image.FromSource(ctx, sys, src)
}
// NewImageSource returns a types.ImageSource for this reference.
// The caller must call .Close() on the returned ImageSource.
func (ref sifReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
return newImageSource(ctx, sys, ref)
}
func (ref sifReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
return nil, fmt.Errorf(`"sif:" locations can only be read from, not written to`)
}
// DeleteImage deletes the named image from the registry, if supported.
func (ref sifReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
return errors.Errorf("Deleting images not implemented for sif: images")
}

View File

@ -12,7 +12,9 @@ import (
_ "github.com/containers/image/v5/oci/archive"
_ "github.com/containers/image/v5/oci/layout"
_ "github.com/containers/image/v5/openshift"
_ "github.com/containers/image/v5/sif"
_ "github.com/containers/image/v5/tarball"
// The ostree transport is registered by ostree*.go
// The storage transport is registered by storage*.go
"github.com/containers/image/v5/transports"