diff --git a/go.mod b/go.mod index 4a3675d6..c2a4968e 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 73180284..6764263d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/sif/internal/sif_util.go b/sif/internal/sif_util.go new file mode 100644 index 00000000..bbc04791 --- /dev/null +++ b/sif/internal/sif_util.go @@ -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 +} diff --git a/sif/sif_src.go b/sif/sif_src.go new file mode 100644 index 00000000..089ed34b --- /dev/null +++ b/sif/sif_src.go @@ -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 blob’s 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 +} diff --git a/sif/sif_transport.go b/sif/sif_transport.go new file mode 100644 index 00000000..d118b73a --- /dev/null +++ b/sif/sif_transport.go @@ -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") +} diff --git a/transports/alltransports/alltransports.go b/transports/alltransports/alltransports.go index 2110a091..0bae8b25 100644 --- a/transports/alltransports/alltransports.go +++ b/transports/alltransports/alltransports.go @@ -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"