mirror of
https://github.com/containers/podman.git
synced 2025-10-19 20:23:08 +08:00
vendor: update c/{common,image,storage} to latest main
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
1
vendor/github.com/containers/image/v5/copy/single.go
generated
vendored
1
vendor/github.com/containers/image/v5/copy/single.go
generated
vendored
@ -812,6 +812,7 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
|
||||
}
|
||||
uploadedBlob, err := ic.c.dest.PutBlobPartial(ctx, &proxy, srcInfo, private.PutBlobPartialOptions{
|
||||
Cache: ic.c.blobInfoCache,
|
||||
EmptyLayer: emptyLayer,
|
||||
LayerIndex: layerIndex,
|
||||
})
|
||||
if err == nil {
|
||||
|
4
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
4
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
@ -615,11 +615,11 @@ func (d *dockerImageDestination) PutSignaturesWithFormat(ctx context.Context, si
|
||||
}
|
||||
switch {
|
||||
case d.c.supportsSignatures:
|
||||
if err := d.putSignaturesToAPIExtension(ctx, signatures, *instanceDigest); err != nil {
|
||||
if err := d.putSignaturesToAPIExtension(ctx, otherSignatures, *instanceDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
case d.c.signatureBase != nil:
|
||||
if err := d.putSignaturesToLookaside(signatures, *instanceDigest); err != nil {
|
||||
if err := d.putSignaturesToLookaside(otherSignatures, *instanceDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
|
1
vendor/github.com/containers/image/v5/internal/private/private.go
generated
vendored
1
vendor/github.com/containers/image/v5/internal/private/private.go
generated
vendored
@ -118,6 +118,7 @@ type PutBlobOptions struct {
|
||||
// PutBlobPartialOptions are used in PutBlobPartial.
|
||||
type PutBlobPartialOptions struct {
|
||||
Cache blobinfocache.BlobInfoCache2 // Cache to use and/or update.
|
||||
EmptyLayer bool // True if the blob is an "empty"/"throwaway" layer, and may not necessarily be physically represented.
|
||||
LayerIndex int // A zero-based index of the layer within the image (PutBlobPartial is only called with layer-like blobs, not configs)
|
||||
}
|
||||
|
||||
|
547
vendor/github.com/containers/image/v5/ostree/ostree_dest.go
generated
vendored
547
vendor/github.com/containers/image/v5/ostree/ostree_dest.go
generated
vendored
@ -1,547 +0,0 @@
|
||||
//go:build containers_image_ostree
|
||||
// +build containers_image_ostree
|
||||
|
||||
package ostree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/image/v5/internal/imagedestination/impl"
|
||||
"github.com/containers/image/v5/internal/imagedestination/stubs"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/putblobdigest"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/fileutils"
|
||||
"github.com/klauspost/pgzip"
|
||||
"github.com/opencontainers/go-digest"
|
||||
selinux "github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/ostreedev/ostree-go/pkg/otbuiltin"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1 libselinux
|
||||
// #include <glib.h>
|
||||
// #include <glib-object.h>
|
||||
// #include <gio/gio.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <ostree.h>
|
||||
// #include <gio/ginputstream.h>
|
||||
// #include <selinux/selinux.h>
|
||||
// #include <selinux/label.h>
|
||||
import "C"
|
||||
|
||||
type blobToImport struct {
|
||||
Size int64
|
||||
Digest digest.Digest
|
||||
BlobPath string
|
||||
}
|
||||
|
||||
type descriptor struct {
|
||||
Size int64 `json:"size"`
|
||||
Digest digest.Digest `json:"digest"`
|
||||
}
|
||||
|
||||
type fsLayersSchema1 struct {
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
type manifestSchema struct {
|
||||
LayersDescriptors []descriptor `json:"layers"`
|
||||
FSLayers []fsLayersSchema1 `json:"fsLayers"`
|
||||
}
|
||||
|
||||
type ostreeImageDestination struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
stubs.AlwaysSupportsSignatures
|
||||
|
||||
ref ostreeReference
|
||||
manifest string
|
||||
schema manifestSchema
|
||||
tmpDirPath string
|
||||
blobs map[string]*blobToImport
|
||||
digest digest.Digest
|
||||
signaturesLen int
|
||||
repo *C.struct_OstreeRepo
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing ostree.
|
||||
func newImageDestination(ref ostreeReference, tmpDirPath string) (private.ImageDestination, error) {
|
||||
tmpDirPath = filepath.Join(tmpDirPath, ref.branchName)
|
||||
if err := ensureDirectoryExists(tmpDirPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &ostreeImageDestination{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
SupportedManifestMIMETypes: []string{manifest.DockerV2Schema2MediaType},
|
||||
DesiredLayerCompression: types.PreserveOriginal,
|
||||
AcceptsForeignLayerURLs: false,
|
||||
MustMatchRuntimeOS: true,
|
||||
IgnoresEmbeddedDockerReference: false, // N/A, DockerReference() returns nil.
|
||||
HasThreadSafePutBlob: false,
|
||||
}),
|
||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
||||
|
||||
ref: ref,
|
||||
manifest: "",
|
||||
schema: manifestSchema{},
|
||||
tmpDirPath: tmpDirPath,
|
||||
blobs: map[string]*blobToImport{},
|
||||
digest: "",
|
||||
signaturesLen: 0,
|
||||
repo: nil,
|
||||
}
|
||||
d.Compat = impl.AddCompat(d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
|
||||
func (d *ostreeImageDestination) Reference() types.ImageReference {
|
||||
return d.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
func (d *ostreeImageDestination) Close() error {
|
||||
if d.repo != nil {
|
||||
C.g_object_unref(C.gpointer(d.repo))
|
||||
}
|
||||
return os.RemoveAll(d.tmpDirPath)
|
||||
}
|
||||
|
||||
// PutBlobWithOptions writes contents of stream and returns data representing the result.
|
||||
// inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// inputInfo.MediaType describes the blob format, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *ostreeImageDestination) PutBlobWithOptions(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions) (private.UploadedBlob, error) {
|
||||
tmpDir, err := os.MkdirTemp(d.tmpDirPath, "blob")
|
||||
if err != nil {
|
||||
return private.UploadedBlob{}, err
|
||||
}
|
||||
|
||||
digester, stream := putblobdigest.DigestIfCanonicalUnknown(stream, inputInfo)
|
||||
|
||||
blobPath := filepath.Join(tmpDir, "content")
|
||||
blobFile, err := os.Create(blobPath)
|
||||
if err != nil {
|
||||
return private.UploadedBlob{}, err
|
||||
}
|
||||
size, err := func() (_ int64, retErr error) { // A scope for defer
|
||||
// since we are writing to this file, make sure we handle errors
|
||||
defer func() {
|
||||
closeErr := blobFile.Close()
|
||||
if retErr == nil {
|
||||
retErr = closeErr
|
||||
}
|
||||
}()
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
return io.Copy(blobFile, stream)
|
||||
}()
|
||||
if err != nil {
|
||||
return private.UploadedBlob{}, err
|
||||
}
|
||||
blobDigest := digester.Digest()
|
||||
if inputInfo.Size != -1 && size != inputInfo.Size {
|
||||
return private.UploadedBlob{}, fmt.Errorf("Size mismatch when copying %s, expected %d, got %d", blobDigest, inputInfo.Size, size)
|
||||
}
|
||||
if err := blobFile.Sync(); err != nil {
|
||||
return private.UploadedBlob{}, err
|
||||
}
|
||||
|
||||
hash := blobDigest.Encoded()
|
||||
d.blobs[hash] = &blobToImport{Size: size, Digest: blobDigest, BlobPath: blobPath}
|
||||
return private.UploadedBlob{Digest: blobDigest, Size: size}, nil
|
||||
}
|
||||
|
||||
func fixFiles(selinuxHnd *C.struct_selabel_handle, root string, dir string, usermode bool) error {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
fullpath := filepath.Join(dir, entry.Name())
|
||||
if entry.Type()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
|
||||
if err := os.Remove(fullpath); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selinuxHnd != nil {
|
||||
relPath, err := filepath.Rel(root, fullpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle /exports/hostfs as a special case. Files under this directory are copied to the host,
|
||||
// thus we benefit from maintaining the same SELinux label they would have on the host as we could
|
||||
// use hard links instead of copying the files.
|
||||
relPath = fmt.Sprintf("/%s", strings.TrimPrefix(relPath, "exports/hostfs/"))
|
||||
|
||||
relPathC := C.CString(relPath)
|
||||
defer C.free(unsafe.Pointer(relPathC))
|
||||
var context *C.char
|
||||
|
||||
res, err := C.selabel_lookup_raw(selinuxHnd, &context, relPathC, C.int(info.Mode()&os.ModePerm))
|
||||
if int(res) < 0 && err != syscall.ENOENT {
|
||||
return fmt.Errorf("cannot selabel_lookup_raw %s: %w", relPath, err)
|
||||
}
|
||||
if int(res) == 0 {
|
||||
defer C.freecon(context)
|
||||
fullpathC := C.CString(fullpath)
|
||||
defer C.free(unsafe.Pointer(fullpathC))
|
||||
res, err = C.lsetfilecon_raw(fullpathC, context)
|
||||
if int(res) < 0 {
|
||||
return fmt.Errorf("cannot setfilecon_raw %s to %s: %w", fullpath, C.GoString(context), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
if usermode {
|
||||
if err := os.Chmod(fullpath, info.Mode()|0700); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = fixFiles(selinuxHnd, root, fullpath, usermode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if usermode && (entry.Type().IsRegular()) {
|
||||
if err := os.Chmod(fullpath, info.Mode()|0600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) ostreeCommit(repo *otbuiltin.Repo, branch string, root string, metadata []string) error {
|
||||
opts := otbuiltin.NewCommitOptions()
|
||||
opts.AddMetadataString = metadata
|
||||
opts.Timestamp = time.Now()
|
||||
// OCI layers have no parent OSTree commit
|
||||
opts.Parent = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
_, err := repo.Commit(root, branch, opts)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateTarSplitMetadata(output *bytes.Buffer, file string) (_ digest.Digest, _ int64, retErr error) {
|
||||
mfz := pgzip.NewWriter(output)
|
||||
// since we are writing to this, make sure we handle errors
|
||||
defer func() {
|
||||
closeErr := mfz.Close()
|
||||
if retErr == nil {
|
||||
retErr = closeErr
|
||||
}
|
||||
}()
|
||||
metaPacker := storage.NewJSONPacker(mfz)
|
||||
|
||||
stream, err := os.OpenFile(file, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
gzReader, err := archive.DecompressStream(stream)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
defer gzReader.Close()
|
||||
|
||||
its, err := asm.NewInputTarStream(gzReader, metaPacker, nil)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
|
||||
written, err := io.Copy(digester.Hash(), its)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
return digester.Digest(), written, nil
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) importBlob(selinuxHnd *C.struct_selabel_handle, repo *otbuiltin.Repo, blob *blobToImport) error {
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using a context.Context.
|
||||
|
||||
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Encoded())
|
||||
destinationPath := filepath.Join(d.tmpDirPath, blob.Digest.Encoded(), "root")
|
||||
if err := ensureDirectoryExists(destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
os.Remove(blob.BlobPath)
|
||||
os.RemoveAll(destinationPath)
|
||||
}()
|
||||
|
||||
var tarSplitOutput bytes.Buffer
|
||||
uncompressedDigest, uncompressedSize, err := generateTarSplitMetadata(&tarSplitOutput, blob.BlobPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if os.Getuid() == 0 {
|
||||
if err := archive.UntarPath(blob.BlobPath, destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fixFiles(selinuxHnd, destinationPath, destinationPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
os.MkdirAll(destinationPath, 0755)
|
||||
if err := exec.Command("tar", "-C", destinationPath, "--no-same-owner", "--no-same-permissions", "--delay-directory-restore", "-xf", blob.BlobPath).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fixFiles(selinuxHnd, destinationPath, destinationPath, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return d.ostreeCommit(repo, ostreeBranch, destinationPath, []string{fmt.Sprintf("docker.size=%d", blob.Size),
|
||||
fmt.Sprintf("docker.uncompressed_size=%d", uncompressedSize),
|
||||
fmt.Sprintf("docker.uncompressed_digest=%s", uncompressedDigest.String()),
|
||||
fmt.Sprintf("tarsplit.output=%s", base64.StdEncoding.EncodeToString(tarSplitOutput.Bytes()))})
|
||||
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) importConfig(repo *otbuiltin.Repo, blob *blobToImport) error {
|
||||
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Encoded())
|
||||
destinationPath := filepath.Dir(blob.BlobPath)
|
||||
|
||||
return d.ostreeCommit(repo, ostreeBranch, destinationPath, []string{fmt.Sprintf("docker.size=%d", blob.Size)})
|
||||
}
|
||||
|
||||
// TryReusingBlobWithOptions checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
|
||||
// (e.g. if the blob is a filesystem layer, this signifies that the changes it describes need to be applied again when composing a filesystem tree).
|
||||
// info.Digest must not be empty.
|
||||
// If the blob has been successfully reused, returns (true, info, nil); info must contain at least a digest and size, and may
|
||||
// include CompressionOperation and CompressionAlgorithm fields to indicate that a change to the compression type should be
|
||||
// reflected in the manifest that will be written.
|
||||
// If the transport can not reuse the requested blob, TryReusingBlob returns (false, {}, nil); it returns a non-nil error only on an unexpected failure.
|
||||
func (d *ostreeImageDestination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, private.ReusedBlob, error) {
|
||||
if !impl.OriginalCandidateMatchesTryReusingBlobOptions(options) {
|
||||
return false, private.ReusedBlob{}, nil
|
||||
}
|
||||
if d.repo == nil {
|
||||
repo, err := openRepo(d.ref.repo)
|
||||
if err != nil {
|
||||
return false, private.ReusedBlob{}, err
|
||||
}
|
||||
d.repo = repo
|
||||
}
|
||||
|
||||
if err := info.Digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly.
|
||||
return false, private.ReusedBlob{}, err
|
||||
}
|
||||
branch := fmt.Sprintf("ociimage/%s", info.Digest.Encoded())
|
||||
|
||||
found, data, err := readMetadata(d.repo, branch, "docker.uncompressed_digest")
|
||||
if err != nil || !found {
|
||||
return found, private.ReusedBlob{}, err
|
||||
}
|
||||
|
||||
found, data, err = readMetadata(d.repo, branch, "docker.uncompressed_size")
|
||||
if err != nil || !found {
|
||||
return found, private.ReusedBlob{}, err
|
||||
}
|
||||
|
||||
found, data, err = readMetadata(d.repo, branch, "docker.size")
|
||||
if err != nil || !found {
|
||||
return found, private.ReusedBlob{}, err
|
||||
}
|
||||
|
||||
size, err := strconv.ParseInt(data, 10, 64)
|
||||
if err != nil {
|
||||
return false, private.ReusedBlob{}, err
|
||||
}
|
||||
|
||||
return true, private.ReusedBlob{Digest: info.Digest, Size: size}, nil
|
||||
}
|
||||
|
||||
// PutManifest writes manifest to the destination.
|
||||
// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so
|
||||
// there can be no secondary manifests.
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob []byte, instanceDigest *digest.Digest) error {
|
||||
if instanceDigest != nil {
|
||||
return errors.New(`Manifest lists are not supported by "ostree:"`)
|
||||
}
|
||||
|
||||
d.manifest = string(manifestBlob)
|
||||
|
||||
if err := json.Unmarshal(manifestBlob, &d.schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestPath := filepath.Join(d.tmpDirPath, d.ref.manifestPath())
|
||||
if err := ensureParentDirectoryExists(manifestPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digest, err := manifest.Digest(manifestBlob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.digest = digest
|
||||
|
||||
return os.WriteFile(manifestPath, manifestBlob, 0644)
|
||||
}
|
||||
|
||||
// PutSignaturesWithFormat writes a set of signatures to the destination.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for
|
||||
// (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
|
||||
// MUST be called after PutManifest (signatures may reference manifest contents).
|
||||
func (d *ostreeImageDestination) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||
if instanceDigest != nil {
|
||||
return errors.New(`Manifest lists are not supported by "ostree:"`)
|
||||
}
|
||||
|
||||
path := filepath.Join(d.tmpDirPath, d.ref.signaturePath(0))
|
||||
if err := ensureParentDirectoryExists(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, sig := range signatures {
|
||||
signaturePath := filepath.Join(d.tmpDirPath, d.ref.signaturePath(i))
|
||||
blob, err := signature.Blob(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(signaturePath, blob, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
d.signaturesLen = len(signatures)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *ostreeImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
repo, err := otbuiltin.OpenRepo(d.ref.repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = repo.PrepareTransaction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var selinuxHnd *C.struct_selabel_handle
|
||||
|
||||
if os.Getuid() == 0 && selinux.GetEnabled() {
|
||||
selinuxHnd, err = C.selabel_open(C.SELABEL_CTX_FILE, nil, 0)
|
||||
if selinuxHnd == nil {
|
||||
return fmt.Errorf("cannot open the SELinux DB: %w", err)
|
||||
}
|
||||
|
||||
defer C.selabel_close(selinuxHnd)
|
||||
}
|
||||
|
||||
checkLayer := func(hash string) error {
|
||||
blob := d.blobs[hash]
|
||||
// if the blob is not present in d.blobs then it is already stored in OSTree,
|
||||
// and we don't need to import it.
|
||||
if blob == nil {
|
||||
return nil
|
||||
}
|
||||
err := d.importBlob(selinuxHnd, repo, blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(d.blobs, hash)
|
||||
return nil
|
||||
}
|
||||
for _, layer := range d.schema.LayersDescriptors {
|
||||
if err := layer.Digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly.
|
||||
return err
|
||||
}
|
||||
hash := layer.Digest.Encoded()
|
||||
if err = checkLayer(hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, layer := range d.schema.FSLayers {
|
||||
if err := layer.BlobSum.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly.
|
||||
return err
|
||||
}
|
||||
hash := layer.BlobSum.Encoded()
|
||||
if err = checkLayer(hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Import the other blobs that are not layers
|
||||
for _, blob := range d.blobs {
|
||||
err := d.importConfig(repo, blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
manifestPath := filepath.Join(d.tmpDirPath, "manifest")
|
||||
|
||||
metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest)),
|
||||
fmt.Sprintf("signatures=%d", d.signaturesLen),
|
||||
fmt.Sprintf("docker.digest=%s", string(d.digest))}
|
||||
if err := d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = repo.CommitTransaction()
|
||||
return err
|
||||
}
|
||||
|
||||
func ensureDirectoryExists(path string) error {
|
||||
if err := fileutils.Exists(path); err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureParentDirectoryExists(path string) error {
|
||||
return ensureDirectoryExists(filepath.Dir(path))
|
||||
}
|
453
vendor/github.com/containers/image/v5/ostree/ostree_src.go
generated
vendored
453
vendor/github.com/containers/image/v5/ostree/ostree_src.go
generated
vendored
@ -1,453 +0,0 @@
|
||||
//go:build containers_image_ostree
|
||||
// +build containers_image_ostree
|
||||
|
||||
package ostree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/image/v5/internal/imagesource/impl"
|
||||
"github.com/containers/image/v5/internal/imagesource/stubs"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/klauspost/pgzip"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1
|
||||
// #include <glib.h>
|
||||
// #include <glib-object.h>
|
||||
// #include <gio/gio.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <ostree.h>
|
||||
// #include <gio/ginputstream.h>
|
||||
import "C"
|
||||
|
||||
type ostreeImageSource struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
stubs.NoGetBlobAtInitialize
|
||||
|
||||
ref ostreeReference
|
||||
tmpDir string
|
||||
repo *C.struct_OstreeRepo
|
||||
// get the compressed layer by its uncompressed checksum
|
||||
compressed map[digest.Digest]digest.Digest
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
func newImageSource(tmpDir string, ref ostreeReference) (private.ImageSource, error) {
|
||||
s := &ostreeImageSource{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
HasThreadSafeGetBlob: false,
|
||||
}),
|
||||
NoGetBlobAtInitialize: stubs.NoGetBlobAt(ref),
|
||||
|
||||
ref: ref,
|
||||
tmpDir: tmpDir,
|
||||
compressed: nil,
|
||||
}
|
||||
s.Compat = impl.AddCompat(s)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source.
|
||||
func (s *ostreeImageSource) Reference() types.ImageReference {
|
||||
return s.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *ostreeImageSource) Close() error {
|
||||
if s.repo != nil {
|
||||
C.g_object_unref(C.gpointer(s.repo))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) getBlobUncompressedSize(blob string, isCompressed bool) (int64, error) {
|
||||
var metadataKey string
|
||||
if isCompressed {
|
||||
metadataKey = "docker.uncompressed_size"
|
||||
} else {
|
||||
metadataKey = "docker.size"
|
||||
}
|
||||
b := fmt.Sprintf("ociimage/%s", blob)
|
||||
found, data, err := readMetadata(s.repo, b, metadataKey)
|
||||
if err != nil || !found {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(data, 10, 64)
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) getLenSignatures() (int64, error) {
|
||||
b := fmt.Sprintf("ociimage/%s", s.ref.branchName)
|
||||
found, data, err := readMetadata(s.repo, b, "signatures")
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if !found {
|
||||
// if 'signatures' is not present, just return 0 signatures.
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseInt(data, 10, 64)
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) getTarSplitData(blob string) ([]byte, error) {
|
||||
b := fmt.Sprintf("ociimage/%s", blob)
|
||||
found, out, err := readMetadata(s.repo, b, "tarsplit.output")
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(out)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
|
||||
// as the primary manifest can not be a list, so there can be non-default instances.
|
||||
func (s *ostreeImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, "", errors.New(`Manifest lists are not supported by "ostree:"`)
|
||||
}
|
||||
if s.repo == nil {
|
||||
repo, err := openRepo(s.ref.repo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
s.repo = repo
|
||||
}
|
||||
|
||||
b := fmt.Sprintf("ociimage/%s", s.ref.branchName)
|
||||
found, out, err := readMetadata(s.repo, b, "docker.manifest")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if !found {
|
||||
return nil, "", errors.New("manifest not found")
|
||||
}
|
||||
m := []byte(out)
|
||||
return m, manifest.GuessMIMEType(m), nil
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||
return nil, "", errors.New("manifest lists are not supported by this transport")
|
||||
}
|
||||
|
||||
func openRepo(path string) (*C.struct_OstreeRepo, error) {
|
||||
var cerr *C.GError
|
||||
cpath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cpath))
|
||||
file := C.g_file_new_for_path(cpath)
|
||||
defer C.g_object_unref(C.gpointer(file))
|
||||
repo := C.ostree_repo_new(file)
|
||||
r := glib.GoBool(glib.GBoolean(C.ostree_repo_open(repo, nil, &cerr)))
|
||||
if !r {
|
||||
C.g_object_unref(C.gpointer(repo))
|
||||
return nil, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
type ostreePathFileGetter struct {
|
||||
repo *C.struct_OstreeRepo
|
||||
parentRoot *C.GFile
|
||||
}
|
||||
|
||||
type ostreeReader struct {
|
||||
stream *C.GFileInputStream
|
||||
}
|
||||
|
||||
func (o ostreeReader) Close() error {
|
||||
C.g_object_unref(C.gpointer(o.stream))
|
||||
return nil
|
||||
}
|
||||
func (o ostreeReader) Read(p []byte) (int, error) {
|
||||
var cerr *C.GError
|
||||
instanceCast := C.g_type_check_instance_cast((*C.GTypeInstance)(unsafe.Pointer(o.stream)), C.g_input_stream_get_type())
|
||||
stream := (*C.GInputStream)(unsafe.Pointer(instanceCast))
|
||||
|
||||
b := C.g_input_stream_read_bytes(stream, (C.gsize)(cap(p)), nil, &cerr)
|
||||
if b == nil {
|
||||
return 0, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
defer C.g_bytes_unref(b)
|
||||
|
||||
count := int(C.g_bytes_get_size(b))
|
||||
if count == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
data := unsafe.Slice((*byte)(C.g_bytes_get_data(b, nil)), count)
|
||||
copy(p, data)
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func readMetadata(repo *C.struct_OstreeRepo, commit, key string) (bool, string, error) {
|
||||
var cerr *C.GError
|
||||
var ref *C.char
|
||||
defer C.free(unsafe.Pointer(ref))
|
||||
|
||||
cCommit := C.CString(commit)
|
||||
defer C.free(unsafe.Pointer(cCommit))
|
||||
|
||||
if !glib.GoBool(glib.GBoolean(C.ostree_repo_resolve_rev(repo, cCommit, C.gboolean(1), &ref, &cerr))) {
|
||||
return false, "", glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
|
||||
if ref == nil {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
var variant *C.GVariant
|
||||
if !glib.GoBool(glib.GBoolean(C.ostree_repo_load_variant(repo, C.OSTREE_OBJECT_TYPE_COMMIT, ref, &variant, &cerr))) {
|
||||
return false, "", glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
defer C.g_variant_unref(variant)
|
||||
if variant != nil {
|
||||
cKey := C.CString(key)
|
||||
defer C.free(unsafe.Pointer(cKey))
|
||||
|
||||
metadata := C.g_variant_get_child_value(variant, 0)
|
||||
defer C.g_variant_unref(metadata)
|
||||
|
||||
data := C.g_variant_lookup_value(metadata, (*C.gchar)(cKey), nil)
|
||||
if data != nil {
|
||||
defer C.g_variant_unref(data)
|
||||
ptr := (*C.char)(C.g_variant_get_string(data, nil))
|
||||
val := C.GoString(ptr)
|
||||
return true, val, nil
|
||||
}
|
||||
}
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
func newOSTreePathFileGetter(repo *C.struct_OstreeRepo, commit string) (*ostreePathFileGetter, error) {
|
||||
var cerr *C.GError
|
||||
var parentRoot *C.GFile
|
||||
cCommit := C.CString(commit)
|
||||
defer C.free(unsafe.Pointer(cCommit))
|
||||
if !glib.GoBool(glib.GBoolean(C.ostree_repo_read_commit(repo, cCommit, &parentRoot, nil, nil, &cerr))) {
|
||||
return &ostreePathFileGetter{}, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
|
||||
C.g_object_ref(C.gpointer(repo))
|
||||
|
||||
return &ostreePathFileGetter{repo: repo, parentRoot: parentRoot}, nil
|
||||
}
|
||||
|
||||
func (o ostreePathFileGetter) Get(filename string) (io.ReadCloser, error) {
|
||||
var file *C.GFile
|
||||
filename, _ = strings.CutPrefix(filename, "./")
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
|
||||
file = (*C.GFile)(C.g_file_resolve_relative_path(o.parentRoot, cfilename))
|
||||
|
||||
var cerr *C.GError
|
||||
stream := C.g_file_read(file, nil, &cerr)
|
||||
if stream == nil {
|
||||
return nil, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
|
||||
return &ostreeReader{stream: stream}, nil
|
||||
}
|
||||
|
||||
func (o ostreePathFileGetter) Close() {
|
||||
C.g_object_unref(C.gpointer(o.repo))
|
||||
C.g_object_unref(C.gpointer(o.parentRoot))
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) readSingleFile(commit, path string) (io.ReadCloser, error) {
|
||||
getter, err := newOSTreePathFileGetter(s.repo, commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer getter.Close()
|
||||
|
||||
return getter.Get(path)
|
||||
}
|
||||
|
||||
// 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 *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) {
|
||||
if err := info.Digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly.
|
||||
return nil, -1, err
|
||||
}
|
||||
blob := info.Digest.Encoded()
|
||||
|
||||
// Ensure s.compressed is initialized. It is build by LayerInfosForCopy.
|
||||
if s.compressed == nil {
|
||||
_, err := s.LayerInfosForCopy(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
}
|
||||
compressedBlob, isCompressed := s.compressed[info.Digest]
|
||||
if isCompressed {
|
||||
blob = compressedBlob.Encoded()
|
||||
}
|
||||
branch := fmt.Sprintf("ociimage/%s", blob)
|
||||
|
||||
if s.repo == nil {
|
||||
repo, err := openRepo(s.ref.repo)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
s.repo = repo
|
||||
}
|
||||
|
||||
layerSize, err := s.getBlobUncompressedSize(blob, isCompressed)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
tarsplit, err := s.getTarSplitData(blob)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// if tarsplit is nil we are looking at the manifest. Return directly the file in /content
|
||||
if tarsplit == nil {
|
||||
file, err := s.readSingleFile(branch, "/content")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return file, layerSize, nil
|
||||
}
|
||||
|
||||
mf := bytes.NewReader(tarsplit)
|
||||
mfz, err := pgzip.NewReader(mf)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
metaUnpacker := storage.NewJSONUnpacker(mfz)
|
||||
|
||||
getter, err := newOSTreePathFileGetter(s.repo, branch)
|
||||
if err != nil {
|
||||
mfz.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
ots := asm.NewOutputTarStream(getter, metaUnpacker)
|
||||
|
||||
rc := ioutils.NewReadCloserWrapper(ots, func() error {
|
||||
getter.Close()
|
||||
mfz.Close()
|
||||
return ots.Close()
|
||||
})
|
||||
return rc, layerSize, nil
|
||||
}
|
||||
|
||||
// GetSignaturesWithFormat 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 *ostreeImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, errors.New(`Manifest lists are not supported by "ostree:"`)
|
||||
}
|
||||
lenSignatures, err := s.getLenSignatures()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
branch := fmt.Sprintf("ociimage/%s", s.ref.branchName)
|
||||
|
||||
if s.repo == nil {
|
||||
repo, err := openRepo(s.ref.repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.repo = repo
|
||||
}
|
||||
|
||||
signatures := []signature.Signature{}
|
||||
for i := int64(1); i <= lenSignatures; i++ {
|
||||
path := fmt.Sprintf("/signature-%d", i)
|
||||
sigReader, err := s.readSingleFile(branch, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sigReader.Close()
|
||||
|
||||
sigBlob, err := io.ReadAll(sigReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := signature.FromBlob(sigBlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing signature %q: %w", path, err)
|
||||
}
|
||||
signatures = append(signatures, sig)
|
||||
}
|
||||
return signatures, 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.
|
||||
// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
|
||||
// as the primary manifest can not be a list, so there can be secondary manifests.
|
||||
// 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 *ostreeImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, errors.New(`Manifest lists are not supported by "ostree:"`)
|
||||
}
|
||||
|
||||
updatedBlobInfos := []types.BlobInfo{}
|
||||
manifestBlob, manifestType, err := s.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
man, err := manifest.FromBlob(manifestBlob, manifestType)
|
||||
|
||||
s.compressed = make(map[digest.Digest]digest.Digest)
|
||||
|
||||
layerBlobs := man.LayerInfos()
|
||||
|
||||
for _, layerBlob := range layerBlobs {
|
||||
branch := fmt.Sprintf("ociimage/%s", layerBlob.Digest.Encoded())
|
||||
found, uncompressedDigestStr, err := readMetadata(s.repo, branch, "docker.uncompressed_digest")
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found, uncompressedSizeStr, err := readMetadata(s.repo, branch, "docker.uncompressed_size")
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uncompressedSize, err := strconv.ParseInt(uncompressedSizeStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uncompressedDigest, err := digest.Parse(uncompressedDigestStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blobInfo := types.BlobInfo{
|
||||
Digest: uncompressedDigest,
|
||||
Size: uncompressedSize,
|
||||
MediaType: layerBlob.MediaType,
|
||||
}
|
||||
s.compressed[uncompressedDigest] = layerBlob.Digest
|
||||
updatedBlobInfos = append(updatedBlobInfos, blobInfo)
|
||||
}
|
||||
return updatedBlobInfos, nil
|
||||
}
|
242
vendor/github.com/containers/image/v5/ostree/ostree_transport.go
generated
vendored
242
vendor/github.com/containers/image/v5/ostree/ostree_transport.go
generated
vendored
@ -1,242 +0,0 @@
|
||||
//go:build containers_image_ostree
|
||||
// +build containers_image_ostree
|
||||
|
||||
package ostree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/directory/explicitfilepath"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/image"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/regexp"
|
||||
)
|
||||
|
||||
const defaultOSTreeRepo = "/ostree/repo"
|
||||
|
||||
// Transport is an ImageTransport for ostree paths.
|
||||
var Transport = ostreeTransport{}
|
||||
|
||||
type ostreeTransport struct{}
|
||||
|
||||
func (t ostreeTransport) Name() string {
|
||||
return "ostree"
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
|
||||
// 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 ostreeTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
sep := strings.Index(scope, ":")
|
||||
if sep < 0 {
|
||||
return fmt.Errorf("Invalid ostree: scope %s: Must include a repo", scope)
|
||||
}
|
||||
repo := scope[:sep]
|
||||
|
||||
if !strings.HasPrefix(repo, "/") {
|
||||
return fmt.Errorf("Invalid ostree: scope %s: repository must be an absolute path", scope)
|
||||
}
|
||||
cleaned := filepath.Clean(repo)
|
||||
if cleaned != repo {
|
||||
return fmt.Errorf(`Invalid ostree: scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
}
|
||||
|
||||
// FIXME? In the namespaces within a repo,
|
||||
// we could be verifying the various character set and length restrictions
|
||||
// from docker/distribution/reference.regexp.go, but other than that there
|
||||
// are few semantically invalid strings.
|
||||
return nil
|
||||
}
|
||||
|
||||
// ostreeReference is an ImageReference for ostree paths.
|
||||
type ostreeReference struct {
|
||||
image string
|
||||
branchName string
|
||||
repo string
|
||||
}
|
||||
|
||||
type ostreeImageCloser struct {
|
||||
types.ImageCloser
|
||||
size int64
|
||||
}
|
||||
|
||||
func (t ostreeTransport) ParseReference(ref string) (types.ImageReference, error) {
|
||||
var repo = ""
|
||||
image, repoPart, gotRepoPart := strings.Cut(ref, "@/")
|
||||
if !gotRepoPart {
|
||||
repo = defaultOSTreeRepo
|
||||
} else {
|
||||
repo = "/" + repoPart
|
||||
}
|
||||
|
||||
return NewReference(image, repo)
|
||||
}
|
||||
|
||||
// NewReference returns an OSTree reference for a specified repo and image.
|
||||
func NewReference(image string, repo string) (types.ImageReference, error) {
|
||||
// image is not _really_ in a containers/image/docker/reference format;
|
||||
// as far as the libOSTree ociimage/* namespace is concerned, it is more or
|
||||
// less an arbitrary string with an implied tag.
|
||||
// Parse the image using reference.ParseNormalizedNamed so that we can
|
||||
// check whether the images has a tag specified and we can add ":latest" if needed
|
||||
ostreeImage, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reference.IsNameOnly(ostreeImage) {
|
||||
image = image + ":latest"
|
||||
}
|
||||
|
||||
resolved, err := explicitfilepath.ResolvePathToFullyExplicit(repo)
|
||||
if err != nil {
|
||||
// With os.IsNotExist(err), the parent directory of repo is also not existent;
|
||||
// that should ordinarily not happen, but it would be a bit weird to reject
|
||||
// references which do not specify a repo just because the implicit defaultOSTreeRepo
|
||||
// does not exist.
|
||||
if os.IsNotExist(err) && repo == defaultOSTreeRepo {
|
||||
resolved = repo
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces
|
||||
// from being ambiguous with values of PolicyConfigurationIdentity.
|
||||
if strings.Contains(resolved, ":") {
|
||||
return nil, fmt.Errorf("Invalid OSTree reference %s@%s: path %s contains a colon", image, repo, resolved)
|
||||
}
|
||||
|
||||
return ostreeReference{
|
||||
image: image,
|
||||
branchName: encodeOStreeRef(image),
|
||||
repo: resolved,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ref ostreeReference) 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.
|
||||
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
|
||||
// e.g. default attribute values omitted by the user may be filled in the return value, or vice versa.
|
||||
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
|
||||
func (ref ostreeReference) StringWithinTransport() string {
|
||||
return fmt.Sprintf("%s@%s", ref.image, ref.repo)
|
||||
}
|
||||
|
||||
// DockerReference returns a Docker reference associated with this reference
|
||||
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
|
||||
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
|
||||
func (ref ostreeReference) DockerReference() reference.Named {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ref ostreeReference) PolicyConfigurationIdentity() string {
|
||||
return fmt.Sprintf("%s:%s", ref.repo, ref.image)
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref ostreeReference) PolicyConfigurationNamespaces() []string {
|
||||
repo, _, gotTag := strings.Cut(ref.image, ":")
|
||||
if !gotTag { // Coverage: Should never happen, NewReference above ensures ref.image has a :tag.
|
||||
panic(fmt.Sprintf("Internal inconsistency: ref.image value %q does not have a :tag", ref.image))
|
||||
}
|
||||
name := repo
|
||||
res := []string{}
|
||||
for {
|
||||
res = append(res, fmt.Sprintf("%s:%s", ref.repo, name))
|
||||
|
||||
lastSlash := strings.LastIndex(name, "/")
|
||||
if lastSlash == -1 {
|
||||
break
|
||||
}
|
||||
name = name[:lastSlash]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *ostreeImageCloser) Size() (int64, error) {
|
||||
return s.size, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (ref ostreeReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
return image.FromReference(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref ostreeReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
var tmpDir string
|
||||
if sys == nil || sys.OSTreeTmpDirPath == "" {
|
||||
tmpDir = os.TempDir()
|
||||
} else {
|
||||
tmpDir = sys.OSTreeTmpDirPath
|
||||
}
|
||||
return newImageSource(tmpDir, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ostreeReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
var tmpDir string
|
||||
if sys == nil || sys.OSTreeTmpDirPath == "" {
|
||||
tmpDir = os.TempDir()
|
||||
} else {
|
||||
tmpDir = sys.OSTreeTmpDirPath
|
||||
}
|
||||
return newImageDestination(ref, tmpDir)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ostreeReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.New("Deleting images not implemented for ostree: images")
|
||||
}
|
||||
|
||||
var ostreeRefRegexp = regexp.Delayed(`^[A-Za-z0-9.-]$`)
|
||||
|
||||
func encodeOStreeRef(in string) string {
|
||||
var buffer bytes.Buffer
|
||||
for i := range in {
|
||||
sub := in[i : i+1]
|
||||
if ostreeRefRegexp.MatchString(sub) {
|
||||
buffer.WriteString(sub)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("_%02X", sub[0]))
|
||||
}
|
||||
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// manifestPath returns a path for the manifest within a ostree using our conventions.
|
||||
func (ref ostreeReference) manifestPath() string {
|
||||
return filepath.Join("manifest", "manifest.json")
|
||||
}
|
||||
|
||||
// signaturePath returns a path for a signature within a ostree using our conventions.
|
||||
func (ref ostreeReference) signaturePath(index int) string {
|
||||
return filepath.Join("manifest", fmt.Sprintf("signature-%d", index+1))
|
||||
}
|
9
vendor/github.com/containers/image/v5/storage/storage_dest.go
generated
vendored
9
vendor/github.com/containers/image/v5/storage/storage_dest.go
generated
vendored
@ -497,9 +497,12 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces
|
||||
|
||||
succeeded = true
|
||||
return private.UploadedBlob{
|
||||
Digest: blobDigest,
|
||||
Size: srcInfo.Size,
|
||||
}, nil
|
||||
Digest: blobDigest,
|
||||
Size: srcInfo.Size,
|
||||
}, s.queueOrCommit(options.LayerIndex, addedLayerInfo{
|
||||
digest: blobDigest,
|
||||
emptyLayer: options.EmptyLayer,
|
||||
})
|
||||
}
|
||||
|
||||
// TryReusingBlobWithOptions checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
|
||||
|
5
vendor/github.com/containers/image/v5/transports/alltransports/alltransports.go
generated
vendored
5
vendor/github.com/containers/image/v5/transports/alltransports/alltransports.go
generated
vendored
@ -19,10 +19,13 @@ import (
|
||||
_ "github.com/containers/image/v5/sif"
|
||||
_ "github.com/containers/image/v5/tarball"
|
||||
// The docker-daemon transport is registeredy by docker_daemon*.go
|
||||
// The ostree transport is registered by ostree*.go
|
||||
// The storage transport is registered by storage*.go
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports.Register(transports.NewStubTransport("ostree")) // This transport was completely removed.
|
||||
}
|
||||
|
||||
// ParseImageName converts a URL-like image name to a types.ImageReference.
|
||||
func ParseImageName(imgName string) (types.ImageReference, error) {
|
||||
// Keep this in sync with TransportFromImageName!
|
||||
|
8
vendor/github.com/containers/image/v5/transports/alltransports/ostree.go
generated
vendored
8
vendor/github.com/containers/image/v5/transports/alltransports/ostree.go
generated
vendored
@ -1,8 +0,0 @@
|
||||
//go:build containers_image_ostree && linux
|
||||
|
||||
package alltransports
|
||||
|
||||
import (
|
||||
// Register the ostree transport
|
||||
_ "github.com/containers/image/v5/ostree"
|
||||
)
|
9
vendor/github.com/containers/image/v5/transports/alltransports/ostree_stub.go
generated
vendored
9
vendor/github.com/containers/image/v5/transports/alltransports/ostree_stub.go
generated
vendored
@ -1,9 +0,0 @@
|
||||
//go:build !containers_image_ostree || !linux
|
||||
|
||||
package alltransports
|
||||
|
||||
import "github.com/containers/image/v5/transports"
|
||||
|
||||
func init() {
|
||||
transports.Register(transports.NewStubTransport("ostree"))
|
||||
}
|
2
vendor/github.com/containers/image/v5/transports/transports.go
generated
vendored
2
vendor/github.com/containers/image/v5/transports/transports.go
generated
vendored
@ -72,7 +72,7 @@ func ImageName(ref types.ImageReference) string {
|
||||
return ref.Transport().Name() + ":" + ref.StringWithinTransport()
|
||||
}
|
||||
|
||||
var deprecatedTransports = set.NewWithValues("atomic")
|
||||
var deprecatedTransports = set.NewWithValues("atomic", "ostree")
|
||||
|
||||
// ListNames returns a list of non deprecated transport names.
|
||||
// Deprecated transports can be used, but are not presented to users.
|
||||
|
2
vendor/github.com/containers/image/v5/types/types.go
generated
vendored
2
vendor/github.com/containers/image/v5/types/types.go
generated
vendored
@ -659,6 +659,8 @@ type SystemContext struct {
|
||||
// If true, the physical pull source of docker transport images logged as info level
|
||||
DockerLogMirrorChoice bool
|
||||
// Directory to use for OSTree temporary files
|
||||
//
|
||||
// Deprecated: The OSTree transport has been removed.
|
||||
OSTreeTmpDirPath string
|
||||
// If true, all blobs will have precomputed digests to ensure layers are not uploaded that already exist on the registry.
|
||||
// Note that this requires writing blobs to temporary files, and takes more time than the default behavior,
|
||||
|
Reference in New Issue
Block a user