mirror of https://github.com/containers/image.git
sif: initial sif transport implementation
Signed-off-by: Yannick Cote <ycote@redhat.com>
This commit is contained in:
parent
ef7a5c4a7c
commit
13f7888b59
2
go.mod
2
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
|
||||
|
|
|
|||
3
go.sum
3
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue