go.mod: update github.com/containers/image/v5
Version 5.22 introduced a new option to /etc/containers/policy.json called
keyPaths, see
https://github.com/containers/image/pull/1609
EL9 immediately took advantage of this new feature and started using it, see
04645c4a84
This quickly became an issue in our code: The go library (containers/image)
parses the configuration file very strictly and refuses to create a client
when policy.json with an unknown key is present on the filesystem. As we
used 5.21.1 that doesn't know the new key, our unit tests started to
failing when containers-common was present.
Reproducer:
podman run --pull=always --rm -it centos:stream9
dnf install -y dnf-plugins-core
dnf config-manager --set-enabled crb
dnf install -y gpgme-devel libassuan-devel krb5-devel golang git-core
git clone https://github.com/osbuild/osbuild-composer
cd osbuild-composer
# install the new containers-common and run the test
dnf install -y https://kojihub.stream.centos.org/kojifiles/packages/containers-common/1/44.el9/x86_64/containers-common-1-44.el9.x86_64.rpm
go test -count 1 ./...
# this returns:
--- FAIL: TestClientResolve (0.00s)
client_test.go:31:
Error Trace: client_test.go:31
Error: Received unexpected error:
Unknown key "keyPaths"
invalid policy in "/etc/containers/policy.json"
github.com/containers/image/v5/signature.NewPolicyFromFile
/osbuild-composer/vendor/github.com/containers/image/v5/signature/policy_config.go:88
github.com/osbuild/osbuild-composer/internal/container.NewClient
/osbuild-composer/internal/container/client.go:123
github.com/osbuild/osbuild-composer/internal/container_test.TestClientResolve
/osbuild-composer/internal/container/client_test.go:29
testing.tRunner
/usr/lib/golang/src/testing/testing.go:1439
runtime.goexit
/usr/lib/golang/src/runtime/asm_amd64.s:1571
Test: TestClientResolve
client_test.go:32:
Error Trace: client_test.go:32
Error: Expected value not to be nil.
Test: TestClientResolve
When run with an older containers-common, it succeeds:
dnf install -y https://kojihub.stream.centos.org/kojifiles/packages/containers-common/1/40.el9/x86_64/containers-common-1-40.el9.x86_64.rpm
go test -count 1 ./...
PASS
To sum it up, I had to upgrade github.com/containers/image/v5 to v5.22.0.
Unfortunately, this wasn't so simple, see
go get github.com/containers/image/v5@latest
go: github.com/containers/image/v5@v5.22.0 requires
github.com/letsencrypt/boulder@v0.0.0-20220331220046-b23ab962616e requires
github.com/honeycombio/beeline-go@v1.1.1 requires
github.com/gobuffalo/pop/v5@v5.3.1 requires
github.com/mattn/go-sqlite3@v2.0.3+incompatible: reading github.com/mattn/go-sqlite3/go.mod at revision v2.0.3: unknown revision v2.0.3
It turns out that github.com/mattn/go-sqlite3@v2.0.3+incompatible has been
recently retracted https://github.com/mattn/go-sqlite3/pull/998 and this
broke a ton of packages depending on it. I was able to fix it by adding
exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
to our go.mod, see
https://github.com/mattn/go-sqlite3/issues/975#issuecomment-955661657
After adding it,
go get github.com/containers/image/v5@latest
succeeded and tools/prepare-source.sh took care of the rest.
Signed-off-by: Ondřej Budai <ondrej@budai.cz>
This commit is contained in:
parent
fa514c5326
commit
29f66a251f
694 changed files with 90636 additions and 50426 deletions
171
vendor/github.com/containers/image/v5/copy/blob.go
generated
vendored
Normal file
171
vendor/github.com/containers/image/v5/copy/blob.go
generated
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
package copy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
compressiontypes "github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// copyBlobFromStream copies a blob with srcInfo (with known Digest and Annotations and possibly known Size) from srcReader to dest,
|
||||
// perhaps sending a copy to an io.Writer if getOriginalLayerCopyWriter != nil,
|
||||
// perhaps (de/re/)compressing it if canModifyBlob,
|
||||
// and returns a complete blobInfo of the copied blob.
|
||||
func (ic *imageCopier) copyBlobFromStream(ctx context.Context, srcReader io.Reader, srcInfo types.BlobInfo,
|
||||
getOriginalLayerCopyWriter func(decompressor compressiontypes.DecompressorFunc) io.Writer,
|
||||
isConfig bool, toEncrypt bool, bar *progressBar, layerIndex int, emptyLayer bool) (types.BlobInfo, error) {
|
||||
// The copying happens through a pipeline of connected io.Readers;
|
||||
// that pipeline is built by updating stream.
|
||||
// === Input: srcReader
|
||||
stream := sourceStream{
|
||||
reader: srcReader,
|
||||
info: srcInfo,
|
||||
}
|
||||
|
||||
// === Process input through digestingReader to validate against the expected digest.
|
||||
// Be paranoid; in case PutBlob somehow managed to ignore an error from digestingReader,
|
||||
// use a separate validation failure indicator.
|
||||
// Note that for this check we don't use the stronger "validationSucceeded" indicator, because
|
||||
// dest.PutBlob may detect that the layer already exists, in which case we don't
|
||||
// read stream to the end, and validation does not happen.
|
||||
digestingReader, err := newDigestingReader(stream.reader, srcInfo.Digest)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, fmt.Errorf("preparing to verify blob %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
stream.reader = digestingReader
|
||||
|
||||
// === Update progress bars
|
||||
stream.reader = bar.ProxyReader(stream.reader)
|
||||
|
||||
// === Decrypt the stream, if required.
|
||||
decryptionStep, err := ic.c.blobPipelineDecryptionStep(&stream, srcInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// === Detect compression of the input stream.
|
||||
// This requires us to “peek ahead” into the stream to read the initial part, which requires us to chain through another io.Reader returned by DetectCompression.
|
||||
detectedCompression, err := blobPipelineDetectCompressionStep(&stream, srcInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// === Send a copy of the original, uncompressed, stream, to a separate path if necessary.
|
||||
var originalLayerReader io.Reader // DO NOT USE this other than to drain the input if no other consumer in the pipeline has done so.
|
||||
if getOriginalLayerCopyWriter != nil {
|
||||
stream.reader = io.TeeReader(stream.reader, getOriginalLayerCopyWriter(detectedCompression.decompressor))
|
||||
originalLayerReader = stream.reader
|
||||
}
|
||||
|
||||
// WARNING: If you are adding new reasons to change the blob, update also the OptimizeDestinationImageAlreadyExists
|
||||
// short-circuit conditions
|
||||
canModifyBlob := !isConfig && ic.cannotModifyManifestReason == ""
|
||||
// === Deal with layer compression/decompression if necessary
|
||||
compressionStep, err := ic.blobPipelineCompressionStep(&stream, canModifyBlob, srcInfo, detectedCompression)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer compressionStep.close()
|
||||
|
||||
// === Encrypt the stream for valid mediatypes if ociEncryptConfig provided
|
||||
if decryptionStep.decrypting && toEncrypt {
|
||||
// If nothing else, we can only set uploadedInfo.CryptoOperation to a single value.
|
||||
// Before relaxing this, see the original pull request’s review if there are other reasons to reject this.
|
||||
return types.BlobInfo{}, errors.New("Unable to support both decryption and encryption in the same copy")
|
||||
}
|
||||
encryptionStep, err := ic.c.blobPipelineEncryptionStep(&stream, toEncrypt, srcInfo, decryptionStep)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// === Report progress using the ic.c.progress channel, if required.
|
||||
if ic.c.progress != nil && ic.c.progressInterval > 0 {
|
||||
progressReader := newProgressReader(
|
||||
stream.reader,
|
||||
ic.c.progress,
|
||||
ic.c.progressInterval,
|
||||
srcInfo,
|
||||
)
|
||||
defer progressReader.reportDone()
|
||||
stream.reader = progressReader
|
||||
}
|
||||
|
||||
// === Finally, send the layer stream to dest.
|
||||
options := private.PutBlobOptions{
|
||||
Cache: ic.c.blobInfoCache,
|
||||
IsConfig: isConfig,
|
||||
EmptyLayer: emptyLayer,
|
||||
}
|
||||
if !isConfig {
|
||||
options.LayerIndex = &layerIndex
|
||||
}
|
||||
uploadedInfo, err := ic.c.dest.PutBlobWithOptions(ctx, &errorAnnotationReader{stream.reader}, stream.info, options)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, fmt.Errorf("writing blob: %w", err)
|
||||
}
|
||||
|
||||
uploadedInfo.Annotations = stream.info.Annotations
|
||||
|
||||
compressionStep.updateCompressionEdits(&uploadedInfo.CompressionOperation, &uploadedInfo.CompressionAlgorithm, &uploadedInfo.Annotations)
|
||||
decryptionStep.updateCryptoOperation(&uploadedInfo.CryptoOperation)
|
||||
if err := encryptionStep.updateCryptoOperationAndAnnotations(&uploadedInfo.CryptoOperation, &uploadedInfo.Annotations); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// This is fairly horrible: the writer from getOriginalLayerCopyWriter wants to consume
|
||||
// all of the input (to compute DiffIDs), even if dest.PutBlob does not need it.
|
||||
// So, read everything from originalLayerReader, which will cause the rest to be
|
||||
// sent there if we are not already at EOF.
|
||||
if getOriginalLayerCopyWriter != nil {
|
||||
logrus.Debugf("Consuming rest of the original blob to satisfy getOriginalLayerCopyWriter")
|
||||
_, err := io.Copy(io.Discard, originalLayerReader)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, fmt.Errorf("reading input blob %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
}
|
||||
|
||||
if digestingReader.validationFailed { // Coverage: This should never happen.
|
||||
return types.BlobInfo{}, fmt.Errorf("Internal error writing blob %s, digest verification failed but was ignored", srcInfo.Digest)
|
||||
}
|
||||
if stream.info.Digest != "" && uploadedInfo.Digest != stream.info.Digest {
|
||||
return types.BlobInfo{}, fmt.Errorf("Internal error writing blob %s, blob with digest %s saved with digest %s", srcInfo.Digest, stream.info.Digest, uploadedInfo.Digest)
|
||||
}
|
||||
if digestingReader.validationSucceeded {
|
||||
if err := compressionStep.recordValidatedDigestData(ic.c, uploadedInfo, srcInfo, encryptionStep, decryptionStep); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return uploadedInfo, nil
|
||||
}
|
||||
|
||||
// sourceStream encapsulates an input consumed by copyBlobFromStream, in progress of being built.
|
||||
// This allows handles of individual aspects to build the copy pipeline without _too much_
|
||||
// specific cooperation by the caller.
|
||||
//
|
||||
// We are currently very far from a generalized plug-and-play API for building/consuming the pipeline
|
||||
// without specific knowledge of various aspects in copyBlobFromStream; that may come one day.
|
||||
type sourceStream struct {
|
||||
reader io.Reader
|
||||
info types.BlobInfo // corresponding to the data available in reader.
|
||||
}
|
||||
|
||||
// errorAnnotationReader wraps the io.Reader passed to PutBlob for annotating the error happened during read.
|
||||
// These errors are reported as PutBlob errors, so we would otherwise misleadingly attribute them to the copy destination.
|
||||
type errorAnnotationReader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// Read annotates the error happened during read
|
||||
func (r errorAnnotationReader) Read(b []byte) (n int, err error) {
|
||||
n, err = r.reader.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
return n, fmt.Errorf("happened during read: %w", err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
321
vendor/github.com/containers/image/v5/copy/compression.go
generated
vendored
Normal file
321
vendor/github.com/containers/image/v5/copy/compression.go
generated
vendored
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
package copy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
internalblobinfocache "github.com/containers/image/v5/internal/blobinfocache"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
compressiontypes "github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// bpDetectCompressionStepData contains data that the copy pipeline needs about the “detect compression” step.
|
||||
type bpDetectCompressionStepData struct {
|
||||
isCompressed bool
|
||||
format compressiontypes.Algorithm // Valid if isCompressed
|
||||
decompressor compressiontypes.DecompressorFunc // Valid if isCompressed
|
||||
srcCompressorName string // Compressor name to possibly record in the blob info cache for the source blob.
|
||||
}
|
||||
|
||||
// blobPipelineDetectCompressionStep updates *stream to detect its current compression format.
|
||||
// srcInfo is only used for error messages.
|
||||
// Returns data for other steps.
|
||||
func blobPipelineDetectCompressionStep(stream *sourceStream, srcInfo types.BlobInfo) (bpDetectCompressionStepData, error) {
|
||||
// This requires us to “peek ahead” into the stream to read the initial part, which requires us to chain through another io.Reader returned by DetectCompression.
|
||||
format, decompressor, reader, err := compression.DetectCompressionFormat(stream.reader) // We could skip this in some cases, but let's keep the code path uniform
|
||||
if err != nil {
|
||||
return bpDetectCompressionStepData{}, fmt.Errorf("reading blob %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
stream.reader = reader
|
||||
|
||||
res := bpDetectCompressionStepData{
|
||||
isCompressed: decompressor != nil,
|
||||
format: format,
|
||||
decompressor: decompressor,
|
||||
}
|
||||
if res.isCompressed {
|
||||
res.srcCompressorName = format.Name()
|
||||
} else {
|
||||
res.srcCompressorName = internalblobinfocache.Uncompressed
|
||||
}
|
||||
|
||||
if expectedFormat, known := expectedCompressionFormats[stream.info.MediaType]; known && res.isCompressed && format.Name() != expectedFormat.Name() {
|
||||
logrus.Debugf("blob %s with type %s should be compressed with %s, but compressor appears to be %s", srcInfo.Digest.String(), srcInfo.MediaType, expectedFormat.Name(), format.Name())
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// bpCompressionStepData contains data that the copy pipeline needs about the compression step.
|
||||
type bpCompressionStepData struct {
|
||||
operation types.LayerCompression // Operation to use for updating the blob metadata.
|
||||
uploadedAlgorithm *compressiontypes.Algorithm // An algorithm parameter for the compressionOperation edits.
|
||||
uploadedAnnotations map[string]string // Annotations that should be set on the uploaded blob. WARNING: This is only set after the srcStream.reader is fully consumed.
|
||||
srcCompressorName string // Compressor name to record in the blob info cache for the source blob.
|
||||
uploadedCompressorName string // Compressor name to record in the blob info cache for the uploaded blob.
|
||||
closers []io.Closer // Objects to close after the upload is done, if any.
|
||||
}
|
||||
|
||||
// blobPipelineCompressionStep updates *stream to compress and/or decompress it.
|
||||
// srcInfo is primarily used for error messages.
|
||||
// Returns data for other steps; the caller should eventually call updateCompressionEdits and perhaps recordValidatedBlobData,
|
||||
// and must eventually call close.
|
||||
func (ic *imageCopier) blobPipelineCompressionStep(stream *sourceStream, canModifyBlob bool, srcInfo types.BlobInfo,
|
||||
detected bpDetectCompressionStepData) (*bpCompressionStepData, error) {
|
||||
// WARNING: If you are adding new reasons to change the blob, update also the OptimizeDestinationImageAlreadyExists
|
||||
// short-circuit conditions
|
||||
layerCompressionChangeSupported := ic.src.CanChangeLayerCompression(stream.info.MediaType)
|
||||
if !layerCompressionChangeSupported {
|
||||
logrus.Debugf("Compression change for blob %s (%q) not supported", srcInfo.Digest, stream.info.MediaType)
|
||||
}
|
||||
if canModifyBlob && layerCompressionChangeSupported {
|
||||
for _, fn := range []func(*sourceStream, bpDetectCompressionStepData) (*bpCompressionStepData, error){
|
||||
ic.bpcPreserveEncrypted,
|
||||
ic.bpcCompressUncompressed,
|
||||
ic.bpcRecompressCompressed,
|
||||
ic.bpcDecompressCompressed,
|
||||
} {
|
||||
res, err := fn(stream, detected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res != nil {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return ic.bpcPreserveOriginal(stream, detected, layerCompressionChangeSupported), nil
|
||||
}
|
||||
|
||||
// bpcPreserveEncrypted checks if the input is encrypted, and returns a *bpCompressionStepData if so.
|
||||
func (ic *imageCopier) bpcPreserveEncrypted(stream *sourceStream, _ bpDetectCompressionStepData) (*bpCompressionStepData, error) {
|
||||
if isOciEncrypted(stream.info.MediaType) {
|
||||
logrus.Debugf("Using original blob without modification for encrypted blob")
|
||||
// PreserveOriginal due to any compression not being able to be done on an encrypted blob unless decrypted
|
||||
return &bpCompressionStepData{
|
||||
operation: types.PreserveOriginal,
|
||||
uploadedAlgorithm: nil,
|
||||
srcCompressorName: internalblobinfocache.UnknownCompression,
|
||||
uploadedCompressorName: internalblobinfocache.UnknownCompression,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// bpcCompressUncompressed checks if we should be compressing an uncompressed input, and returns a *bpCompressionStepData if so.
|
||||
func (ic *imageCopier) bpcCompressUncompressed(stream *sourceStream, detected bpDetectCompressionStepData) (*bpCompressionStepData, error) {
|
||||
if ic.c.dest.DesiredLayerCompression() == types.Compress && !detected.isCompressed {
|
||||
logrus.Debugf("Compressing blob on the fly")
|
||||
var uploadedAlgorithm *compressiontypes.Algorithm
|
||||
if ic.c.compressionFormat != nil {
|
||||
uploadedAlgorithm = ic.c.compressionFormat
|
||||
} else {
|
||||
uploadedAlgorithm = defaultCompressionFormat
|
||||
}
|
||||
|
||||
reader, annotations := ic.c.compressedStream(stream.reader, *uploadedAlgorithm)
|
||||
// Note: reader must be closed on all return paths.
|
||||
stream.reader = reader
|
||||
stream.info = types.BlobInfo{ // FIXME? Should we preserve more data in src.info?
|
||||
Digest: "",
|
||||
Size: -1,
|
||||
}
|
||||
return &bpCompressionStepData{
|
||||
operation: types.Compress,
|
||||
uploadedAlgorithm: uploadedAlgorithm,
|
||||
uploadedAnnotations: annotations,
|
||||
srcCompressorName: detected.srcCompressorName,
|
||||
uploadedCompressorName: uploadedAlgorithm.Name(),
|
||||
closers: []io.Closer{reader},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// bpcRecompressCompressed checks if we should be recompressing a compressed input to another format, and returns a *bpCompressionStepData if so.
|
||||
func (ic *imageCopier) bpcRecompressCompressed(stream *sourceStream, detected bpDetectCompressionStepData) (*bpCompressionStepData, error) {
|
||||
if ic.c.dest.DesiredLayerCompression() == types.Compress && detected.isCompressed &&
|
||||
ic.c.compressionFormat != nil && ic.c.compressionFormat.Name() != detected.format.Name() {
|
||||
// When the blob is compressed, but the desired format is different, it first needs to be decompressed and finally
|
||||
// re-compressed using the desired format.
|
||||
logrus.Debugf("Blob will be converted")
|
||||
|
||||
decompressed, err := detected.decompressor(stream.reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
succeeded := false
|
||||
defer func() {
|
||||
if !succeeded {
|
||||
decompressed.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
recompressed, annotations := ic.c.compressedStream(decompressed, *ic.c.compressionFormat)
|
||||
// Note: recompressed must be closed on all return paths.
|
||||
stream.reader = recompressed
|
||||
stream.info = types.BlobInfo{ // FIXME? Should we preserve more data in src.info?
|
||||
Digest: "",
|
||||
Size: -1,
|
||||
}
|
||||
succeeded = true
|
||||
return &bpCompressionStepData{
|
||||
operation: types.PreserveOriginal,
|
||||
uploadedAlgorithm: ic.c.compressionFormat,
|
||||
uploadedAnnotations: annotations,
|
||||
srcCompressorName: detected.srcCompressorName,
|
||||
uploadedCompressorName: ic.c.compressionFormat.Name(),
|
||||
closers: []io.Closer{decompressed, recompressed},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// bpcDecompressCompressed checks if we should be decompressing a compressed input, and returns a *bpCompressionStepData if so.
|
||||
func (ic *imageCopier) bpcDecompressCompressed(stream *sourceStream, detected bpDetectCompressionStepData) (*bpCompressionStepData, error) {
|
||||
if ic.c.dest.DesiredLayerCompression() == types.Decompress && detected.isCompressed {
|
||||
logrus.Debugf("Blob will be decompressed")
|
||||
s, err := detected.decompressor(stream.reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Note: s must be closed on all return paths.
|
||||
stream.reader = s
|
||||
stream.info = types.BlobInfo{ // FIXME? Should we preserve more data in src.info?
|
||||
Digest: "",
|
||||
Size: -1,
|
||||
}
|
||||
return &bpCompressionStepData{
|
||||
operation: types.Decompress,
|
||||
uploadedAlgorithm: nil,
|
||||
srcCompressorName: detected.srcCompressorName,
|
||||
uploadedCompressorName: internalblobinfocache.Uncompressed,
|
||||
closers: []io.Closer{s},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// bpcPreserveOriginal returns a *bpCompressionStepData for not changing the original blob.
|
||||
func (ic *imageCopier) bpcPreserveOriginal(stream *sourceStream, detected bpDetectCompressionStepData,
|
||||
layerCompressionChangeSupported bool) *bpCompressionStepData {
|
||||
logrus.Debugf("Using original blob without modification")
|
||||
// Remember if the original blob was compressed, and if so how, so that if
|
||||
// LayerInfosForCopy() returned something that differs from what was in the
|
||||
// source's manifest, and UpdatedImage() needs to call UpdateLayerInfos(),
|
||||
// it will be able to correctly derive the MediaType for the copied blob.
|
||||
//
|
||||
// But don’t touch blobs in objects where we can’t change compression,
|
||||
// so that src.UpdatedImage() doesn’t fail; assume that for such blobs
|
||||
// LayerInfosForCopy() should not be making any changes in the first place.
|
||||
var algorithm *compressiontypes.Algorithm
|
||||
if layerCompressionChangeSupported && detected.isCompressed {
|
||||
algorithm = &detected.format
|
||||
} else {
|
||||
algorithm = nil
|
||||
}
|
||||
return &bpCompressionStepData{
|
||||
operation: types.PreserveOriginal,
|
||||
uploadedAlgorithm: algorithm,
|
||||
srcCompressorName: detected.srcCompressorName,
|
||||
uploadedCompressorName: detected.srcCompressorName,
|
||||
}
|
||||
}
|
||||
|
||||
// updateCompressionEdits sets *operation, *algorithm and updates *annotations, if necessary.
|
||||
func (d *bpCompressionStepData) updateCompressionEdits(operation *types.LayerCompression, algorithm **compressiontypes.Algorithm, annotations *map[string]string) {
|
||||
*operation = d.operation
|
||||
// If we can modify the layer's blob, set the desired algorithm for it to be set in the manifest.
|
||||
*algorithm = d.uploadedAlgorithm
|
||||
if *annotations == nil {
|
||||
*annotations = map[string]string{}
|
||||
}
|
||||
for k, v := range d.uploadedAnnotations {
|
||||
(*annotations)[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// recordValidatedBlobData updates b.blobInfoCache with data about the created uploadedInfo adnd the original srcInfo.
|
||||
// This must ONLY be called if all data has been validated by OUR code, and is not comming from third parties.
|
||||
func (d *bpCompressionStepData) recordValidatedDigestData(c *copier, uploadedInfo types.BlobInfo, srcInfo types.BlobInfo,
|
||||
encryptionStep *bpEncryptionStepData, decryptionStep *bpDecryptionStepData) error {
|
||||
// Don’t record any associations that involve encrypted data. This is a bit crude,
|
||||
// some blob substitutions (replacing pulls of encrypted data with local reuse of known decryption outcomes)
|
||||
// might be safe, but it’s not trivially obvious, so let’s be conservative for now.
|
||||
// This crude approach also means we don’t need to record whether a blob is encrypted
|
||||
// in the blob info cache (which would probably be necessary for any more complex logic),
|
||||
// and the simplicity is attractive.
|
||||
if !encryptionStep.encrypting && !decryptionStep.decrypting {
|
||||
// If d.operation != types.PreserveOriginal, we now have two reliable digest values:
|
||||
// srcinfo.Digest describes the pre-d.operation input, verified by digestingReader
|
||||
// uploadedInfo.Digest describes the post-d.operation output, computed by PutBlob
|
||||
// (because stream.info.Digest == "", this must have been computed afresh).
|
||||
switch d.operation {
|
||||
case types.PreserveOriginal:
|
||||
break // Do nothing, we have only one digest and we might not have even verified it.
|
||||
case types.Compress:
|
||||
c.blobInfoCache.RecordDigestUncompressedPair(uploadedInfo.Digest, srcInfo.Digest)
|
||||
case types.Decompress:
|
||||
c.blobInfoCache.RecordDigestUncompressedPair(srcInfo.Digest, uploadedInfo.Digest)
|
||||
default:
|
||||
return fmt.Errorf("Internal error: Unexpected d.operation value %#v", d.operation)
|
||||
}
|
||||
}
|
||||
if d.uploadedCompressorName != "" && d.uploadedCompressorName != internalblobinfocache.UnknownCompression {
|
||||
c.blobInfoCache.RecordDigestCompressorName(uploadedInfo.Digest, d.uploadedCompressorName)
|
||||
}
|
||||
if srcInfo.Digest != "" && d.srcCompressorName != "" && d.srcCompressorName != internalblobinfocache.UnknownCompression {
|
||||
c.blobInfoCache.RecordDigestCompressorName(srcInfo.Digest, d.srcCompressorName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close closes objects that carry state throughout the compression/decompression operation.
|
||||
func (d *bpCompressionStepData) close() {
|
||||
for _, c := range d.closers {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// doCompression reads all input from src and writes its compressed equivalent to dest.
|
||||
func doCompression(dest io.Writer, src io.Reader, metadata map[string]string, compressionFormat compressiontypes.Algorithm, compressionLevel *int) error {
|
||||
compressor, err := compression.CompressStreamWithMetadata(dest, metadata, compressionFormat, compressionLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, compressionBufferSize)
|
||||
|
||||
_, err = io.CopyBuffer(compressor, src, buf) // Sets err to nil, i.e. causes dest.Close()
|
||||
if err != nil {
|
||||
compressor.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return compressor.Close()
|
||||
}
|
||||
|
||||
// compressGoroutine reads all input from src and writes its compressed equivalent to dest.
|
||||
func (c *copier) compressGoroutine(dest *io.PipeWriter, src io.Reader, metadata map[string]string, compressionFormat compressiontypes.Algorithm) {
|
||||
err := errors.New("Internal error: unexpected panic in compressGoroutine")
|
||||
defer func() { // Note that this is not the same as {defer dest.CloseWithError(err)}; we need err to be evaluated lazily.
|
||||
_ = dest.CloseWithError(err) // CloseWithError(nil) is equivalent to Close(), always returns nil
|
||||
}()
|
||||
|
||||
err = doCompression(dest, src, metadata, compressionFormat, c.compressionLevel)
|
||||
}
|
||||
|
||||
// compressedStream returns a stream the input reader compressed using format, and a metadata map.
|
||||
// The caller must close the returned reader.
|
||||
// AFTER the stream is consumed, metadata will be updated with annotations to use on the data.
|
||||
func (c *copier) compressedStream(reader io.Reader, algorithm compressiontypes.Algorithm) (io.ReadCloser, map[string]string) {
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
annotations := map[string]string{}
|
||||
// If this fails while writing data, it will do pipeWriter.CloseWithError(); if it fails otherwise,
|
||||
// e.g. because we have exited and due to pipeReader.Close() above further writing to the pipe has failed,
|
||||
// we don’t care.
|
||||
go c.compressGoroutine(pipeWriter, reader, annotations, algorithm) // Closes pipeWriter
|
||||
return pipeReader, annotations
|
||||
}
|
||||
642
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
642
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
File diff suppressed because it is too large
Load diff
10
vendor/github.com/containers/image/v5/copy/digesting_reader.go
generated
vendored
10
vendor/github.com/containers/image/v5/copy/digesting_reader.go
generated
vendored
|
|
@ -1,11 +1,11 @@
|
|||
package copy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type digestingReader struct {
|
||||
|
|
@ -23,11 +23,11 @@ type digestingReader struct {
|
|||
func newDigestingReader(source io.Reader, expectedDigest digest.Digest) (*digestingReader, error) {
|
||||
var digester digest.Digester
|
||||
if err := expectedDigest.Validate(); err != nil {
|
||||
return nil, errors.Errorf("Invalid digest specification %s", expectedDigest)
|
||||
return nil, fmt.Errorf("Invalid digest specification %s", expectedDigest)
|
||||
}
|
||||
digestAlgorithm := expectedDigest.Algorithm()
|
||||
if !digestAlgorithm.Available() {
|
||||
return nil, errors.Errorf("Invalid digest specification %s: unsupported digest algorithm %s", expectedDigest, digestAlgorithm)
|
||||
return nil, fmt.Errorf("Invalid digest specification %s: unsupported digest algorithm %s", expectedDigest, digestAlgorithm)
|
||||
}
|
||||
digester = digestAlgorithm.Digester()
|
||||
|
||||
|
|
@ -47,14 +47,14 @@ func (d *digestingReader) Read(p []byte) (int, error) {
|
|||
// Coverage: This should not happen, the hash.Hash interface requires
|
||||
// d.digest.Write to never return an error, and the io.Writer interface
|
||||
// requires n2 == len(input) if no error is returned.
|
||||
return 0, errors.Wrapf(err, "updating digest during verification: %d vs. %d", n2, n)
|
||||
return 0, fmt.Errorf("updating digest during verification: %d vs. %d: %w", n2, n, err)
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
actualDigest := d.digester.Digest()
|
||||
if actualDigest != d.expectedDigest {
|
||||
d.validationFailed = true
|
||||
return 0, errors.Errorf("Digest did not match, expected %s, got %s", d.expectedDigest, actualDigest)
|
||||
return 0, fmt.Errorf("Digest did not match, expected %s, got %s", d.expectedDigest, actualDigest)
|
||||
}
|
||||
d.validationSucceeded = true
|
||||
}
|
||||
|
|
|
|||
24
vendor/github.com/containers/image/v5/copy/encrypt.go
generated
vendored
24
vendor/github.com/containers/image/v5/copy/encrypt.go
generated
vendored
|
|
@ -1,24 +0,0 @@
|
|||
package copy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// isOciEncrypted returns a bool indicating if a mediatype is encrypted
|
||||
// This function will be moved to be part of OCI spec when adopted.
|
||||
func isOciEncrypted(mediatype string) bool {
|
||||
return strings.HasSuffix(mediatype, "+encrypted")
|
||||
}
|
||||
|
||||
// isEncrypted checks if an image is encrypted
|
||||
func isEncrypted(i types.Image) bool {
|
||||
layers := i.LayerInfos()
|
||||
for _, l := range layers {
|
||||
if isOciEncrypted(l.MediaType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
129
vendor/github.com/containers/image/v5/copy/encryption.go
generated
vendored
Normal file
129
vendor/github.com/containers/image/v5/copy/encryption.go
generated
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package copy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/ocicrypt"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// isOciEncrypted returns a bool indicating if a mediatype is encrypted
|
||||
// This function will be moved to be part of OCI spec when adopted.
|
||||
func isOciEncrypted(mediatype string) bool {
|
||||
return strings.HasSuffix(mediatype, "+encrypted")
|
||||
}
|
||||
|
||||
// isEncrypted checks if an image is encrypted
|
||||
func isEncrypted(i types.Image) bool {
|
||||
layers := i.LayerInfos()
|
||||
for _, l := range layers {
|
||||
if isOciEncrypted(l.MediaType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// bpDecryptionStepData contains data that the copy pipeline needs about the decryption step.
|
||||
type bpDecryptionStepData struct {
|
||||
decrypting bool // We are actually decrypting the stream
|
||||
}
|
||||
|
||||
// blobPipelineDecryptionStep updates *stream to decrypt if, it necessary.
|
||||
// srcInfo is only used for error messages.
|
||||
// Returns data for other steps; the caller should eventually use updateCryptoOperation.
|
||||
func (c *copier) blobPipelineDecryptionStep(stream *sourceStream, srcInfo types.BlobInfo) (*bpDecryptionStepData, error) {
|
||||
if isOciEncrypted(stream.info.MediaType) && c.ociDecryptConfig != nil {
|
||||
desc := imgspecv1.Descriptor{
|
||||
Annotations: stream.info.Annotations,
|
||||
}
|
||||
reader, decryptedDigest, err := ocicrypt.DecryptLayer(c.ociDecryptConfig, stream.reader, desc, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypting layer %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
|
||||
stream.reader = reader
|
||||
stream.info.Digest = decryptedDigest
|
||||
stream.info.Size = -1
|
||||
for k := range stream.info.Annotations {
|
||||
if strings.HasPrefix(k, "org.opencontainers.image.enc") {
|
||||
delete(stream.info.Annotations, k)
|
||||
}
|
||||
}
|
||||
return &bpDecryptionStepData{
|
||||
decrypting: true,
|
||||
}, nil
|
||||
}
|
||||
return &bpDecryptionStepData{
|
||||
decrypting: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// updateCryptoOperation sets *operation, if necessary.
|
||||
func (d *bpDecryptionStepData) updateCryptoOperation(operation *types.LayerCrypto) {
|
||||
if d.decrypting {
|
||||
*operation = types.Decrypt
|
||||
}
|
||||
}
|
||||
|
||||
// bpdData contains data that the copy pipeline needs about the encryption step.
|
||||
type bpEncryptionStepData struct {
|
||||
encrypting bool // We are actually encrypting the stream
|
||||
finalizer ocicrypt.EncryptLayerFinalizer
|
||||
}
|
||||
|
||||
// blobPipelineEncryptionStep updates *stream to encrypt if, it required by toEncrypt.
|
||||
// srcInfo is primarily used for error messages.
|
||||
// Returns data for other steps; the caller should eventually call updateCryptoOperationAndAnnotations.
|
||||
func (c *copier) blobPipelineEncryptionStep(stream *sourceStream, toEncrypt bool, srcInfo types.BlobInfo,
|
||||
decryptionStep *bpDecryptionStepData) (*bpEncryptionStepData, error) {
|
||||
if toEncrypt && !isOciEncrypted(srcInfo.MediaType) && c.ociEncryptConfig != nil {
|
||||
var annotations map[string]string
|
||||
if !decryptionStep.decrypting {
|
||||
annotations = srcInfo.Annotations
|
||||
}
|
||||
desc := imgspecv1.Descriptor{
|
||||
MediaType: srcInfo.MediaType,
|
||||
Digest: srcInfo.Digest,
|
||||
Size: srcInfo.Size,
|
||||
Annotations: annotations,
|
||||
}
|
||||
reader, finalizer, err := ocicrypt.EncryptLayer(c.ociEncryptConfig, stream.reader, desc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypting blob %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
|
||||
stream.reader = reader
|
||||
stream.info.Digest = ""
|
||||
stream.info.Size = -1
|
||||
return &bpEncryptionStepData{
|
||||
encrypting: true,
|
||||
finalizer: finalizer,
|
||||
}, nil
|
||||
}
|
||||
return &bpEncryptionStepData{
|
||||
encrypting: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// updateCryptoOperationAndAnnotations sets *operation and updates *annotations, if necessary.
|
||||
func (d *bpEncryptionStepData) updateCryptoOperationAndAnnotations(operation *types.LayerCrypto, annotations *map[string]string) error {
|
||||
if !d.encrypting {
|
||||
return nil
|
||||
}
|
||||
|
||||
encryptAnnotations, err := d.finalizer()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to finalize encryption: %w", err)
|
||||
}
|
||||
*operation = types.Encrypt
|
||||
if *annotations == nil {
|
||||
*annotations = map[string]string{}
|
||||
}
|
||||
for k, v := range encryptAnnotations {
|
||||
(*annotations)[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
73
vendor/github.com/containers/image/v5/copy/manifest.go
generated
vendored
73
vendor/github.com/containers/image/v5/copy/manifest.go
generated
vendored
|
|
@ -2,11 +2,12 @@ package copy
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -38,31 +39,50 @@ func (os *orderedSet) append(s string) {
|
|||
}
|
||||
}
|
||||
|
||||
// determineManifestConversion updates ic.manifestUpdates to convert manifest to a supported MIME type, if necessary and ic.canModifyManifest.
|
||||
// Note that the conversion will only happen later, through ic.src.UpdatedImage
|
||||
// Returns the preferred manifest MIME type (whether we are converting to it or using it unmodified),
|
||||
// and a list of other possible alternatives, in order.
|
||||
func (ic *imageCopier) determineManifestConversion(ctx context.Context, destSupportedManifestMIMETypes []string, forceManifestMIMEType string, requiresOciEncryption bool) (string, []string, error) {
|
||||
_, srcType, err := ic.src.Manifest(ctx)
|
||||
if err != nil { // This should have been cached?!
|
||||
return "", nil, errors.Wrap(err, "reading manifest")
|
||||
}
|
||||
// determineManifestConversionInputs contains the inputs for determineManifestConversion.
|
||||
type determineManifestConversionInputs struct {
|
||||
srcMIMEType string // MIME type of the input manifest
|
||||
|
||||
destSupportedManifestMIMETypes []string // MIME types supported by the destination, per types.ImageDestination.SupportedManifestMIMETypes()
|
||||
|
||||
forceManifestMIMEType string // User’s choice of forced manifest MIME type
|
||||
requiresOCIEncryption bool // Restrict to manifest formats that can support OCI encryption
|
||||
cannotModifyManifestReason string // The reason the manifest cannot be modified, or an empty string if it can
|
||||
}
|
||||
|
||||
// manifestConversionPlan contains the decisions made by determineManifestConversion.
|
||||
type manifestConversionPlan struct {
|
||||
// The preferred manifest MIME type (whether we are converting to it or using it unmodified).
|
||||
// We compute this only to show it in error messages; without having to add this context
|
||||
// in an error message, we would be happy enough to know only that no conversion is needed.
|
||||
preferredMIMEType string
|
||||
preferredMIMETypeNeedsConversion bool // True if using preferredMIMEType requires a conversion step.
|
||||
otherMIMETypeCandidates []string // Other possible alternatives, in order
|
||||
}
|
||||
|
||||
// determineManifestConversion returns a plan for what formats, and possibly conversions, to use based on in.
|
||||
func determineManifestConversion(in determineManifestConversionInputs) (manifestConversionPlan, error) {
|
||||
srcType := in.srcMIMEType
|
||||
normalizedSrcType := manifest.NormalizedMIMEType(srcType)
|
||||
if srcType != normalizedSrcType {
|
||||
logrus.Debugf("Source manifest MIME type %s, treating it as %s", srcType, normalizedSrcType)
|
||||
srcType = normalizedSrcType
|
||||
}
|
||||
|
||||
if forceManifestMIMEType != "" {
|
||||
destSupportedManifestMIMETypes = []string{forceManifestMIMEType}
|
||||
destSupportedManifestMIMETypes := in.destSupportedManifestMIMETypes
|
||||
if in.forceManifestMIMEType != "" {
|
||||
destSupportedManifestMIMETypes = []string{in.forceManifestMIMEType}
|
||||
}
|
||||
|
||||
if len(destSupportedManifestMIMETypes) == 0 && (!requiresOciEncryption || manifest.MIMETypeSupportsEncryption(srcType)) {
|
||||
return srcType, []string{}, nil // Anything goes; just use the original as is, do not try any conversions.
|
||||
if len(destSupportedManifestMIMETypes) == 0 && (!in.requiresOCIEncryption || manifest.MIMETypeSupportsEncryption(srcType)) {
|
||||
return manifestConversionPlan{ // Anything goes; just use the original as is, do not try any conversions.
|
||||
preferredMIMEType: srcType,
|
||||
otherMIMETypeCandidates: []string{},
|
||||
}, nil
|
||||
}
|
||||
supportedByDest := map[string]struct{}{}
|
||||
for _, t := range destSupportedManifestMIMETypes {
|
||||
if !requiresOciEncryption || manifest.MIMETypeSupportsEncryption(t) {
|
||||
if !in.requiresOCIEncryption || manifest.MIMETypeSupportsEncryption(t) {
|
||||
supportedByDest[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,13 +99,16 @@ func (ic *imageCopier) determineManifestConversion(ctx context.Context, destSupp
|
|||
if _, ok := supportedByDest[srcType]; ok {
|
||||
prioritizedTypes.append(srcType)
|
||||
}
|
||||
if ic.cannotModifyManifestReason != "" {
|
||||
if in.cannotModifyManifestReason != "" {
|
||||
// We could also drop this check and have the caller
|
||||
// make the choice; it is already doing that to an extent, to improve error
|
||||
// messages. But it is nice to hide the “if we can't modify, do no conversion”
|
||||
// special case in here; the caller can then worry (or not) only about a good UI.
|
||||
logrus.Debugf("We can't modify the manifest, hoping for the best...")
|
||||
return srcType, []string{}, nil // Take our chances - FIXME? Or should we fail without trying?
|
||||
return manifestConversionPlan{ // Take our chances - FIXME? Or should we fail without trying?
|
||||
preferredMIMEType: srcType,
|
||||
otherMIMETypeCandidates: []string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Then use our list of preferred types.
|
||||
|
|
@ -102,15 +125,17 @@ func (ic *imageCopier) determineManifestConversion(ctx context.Context, destSupp
|
|||
|
||||
logrus.Debugf("Manifest has MIME type %s, ordered candidate list [%s]", srcType, strings.Join(prioritizedTypes.list, ", "))
|
||||
if len(prioritizedTypes.list) == 0 { // Coverage: destSupportedManifestMIMETypes is not empty (or we would have exited in the “Anything goes” case above), so this should never happen.
|
||||
return "", nil, errors.New("Internal error: no candidate MIME types")
|
||||
return manifestConversionPlan{}, errors.New("Internal error: no candidate MIME types")
|
||||
}
|
||||
preferredType := prioritizedTypes.list[0]
|
||||
if preferredType != srcType {
|
||||
ic.manifestUpdates.ManifestMIMEType = preferredType
|
||||
} else {
|
||||
res := manifestConversionPlan{
|
||||
preferredMIMEType: prioritizedTypes.list[0],
|
||||
otherMIMETypeCandidates: prioritizedTypes.list[1:],
|
||||
}
|
||||
res.preferredMIMETypeNeedsConversion = res.preferredMIMEType != srcType
|
||||
if !res.preferredMIMETypeNeedsConversion {
|
||||
logrus.Debugf("... will first try using the original manifest unmodified")
|
||||
}
|
||||
return preferredType, prioritizedTypes.list[1:], nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// isMultiImage returns true if img is a list of images
|
||||
|
|
@ -156,7 +181,7 @@ func (c *copier) determineListConversion(currentListMIMEType string, destSupport
|
|||
|
||||
logrus.Debugf("Manifest list has MIME type %s, ordered candidate list [%s]", currentListMIMEType, strings.Join(destSupportedMIMETypes, ", "))
|
||||
if len(prioritizedTypes.list) == 0 {
|
||||
return "", nil, errors.Errorf("destination does not support any supported manifest list types (%v)", manifest.SupportedListMIMETypes)
|
||||
return "", nil, fmt.Errorf("destination does not support any supported manifest list types (%v)", manifest.SupportedListMIMETypes)
|
||||
}
|
||||
selectedType := prioritizedTypes.list[0]
|
||||
otherSupportedTypes := prioritizedTypes.list[1:]
|
||||
|
|
|
|||
15
vendor/github.com/containers/image/v5/copy/progress_bars.go
generated
vendored
15
vendor/github.com/containers/image/v5/copy/progress_bars.go
generated
vendored
|
|
@ -38,7 +38,8 @@ type progressBar struct {
|
|||
}
|
||||
|
||||
// createProgressBar creates a progressBar in pool. Note that if the copier's reportWriter
|
||||
// is io.Discard, the progress bar's output will be discarded
|
||||
// is io.Discard, the progress bar's output will be discarded. Callers may call printCopyInfo()
|
||||
// to print a single line instead.
|
||||
//
|
||||
// NOTE: Every progress bar created within a progress pool must either successfully
|
||||
// complete or be aborted, or pool.Wait() will hang. That is typically done
|
||||
|
|
@ -95,15 +96,21 @@ func (c *copier) createProgressBar(pool *mpb.Progress, partial bool, info types.
|
|||
),
|
||||
)
|
||||
}
|
||||
if c.progressOutput == io.Discard {
|
||||
c.Printf("Copying %s %s\n", kind, info.Digest)
|
||||
}
|
||||
return &progressBar{
|
||||
Bar: bar,
|
||||
originalSize: info.Size,
|
||||
}
|
||||
}
|
||||
|
||||
// printCopyInfo prints a "Copying ..." message on the copier if the output is
|
||||
// set to `io.Discard`. In that case, the progress bars won't be rendered but
|
||||
// we still want to indicate when blobs and configs are copied.
|
||||
func (c *copier) printCopyInfo(kind string, info types.BlobInfo) {
|
||||
if c.progressOutput == io.Discard {
|
||||
c.Printf("Copying %s %s\n", kind, info.Digest)
|
||||
}
|
||||
}
|
||||
|
||||
// mark100PercentComplete marks the progres bars as 100% complete;
|
||||
// it may do so by possibly advancing the current state if it is below the known total.
|
||||
func (bar *progressBar) mark100PercentComplete() {
|
||||
|
|
|
|||
67
vendor/github.com/containers/image/v5/copy/sign.go
generated
vendored
67
vendor/github.com/containers/image/v5/copy/sign.go
generated
vendored
|
|
@ -1,38 +1,89 @@
|
|||
package copy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
internalsig "github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/signature/sigstore"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// sourceSignatures returns signatures from unparsedSource based on options,
|
||||
// and verifies that they can be used (to avoid copying a large image when we
|
||||
// can tell in advance that it would ultimately fail)
|
||||
func (c *copier) sourceSignatures(ctx context.Context, unparsed private.UnparsedImage, options *Options,
|
||||
gettingSignaturesMessage, checkingDestMessage string) ([]internalsig.Signature, error) {
|
||||
var sigs []internalsig.Signature
|
||||
if options.RemoveSignatures {
|
||||
sigs = []internalsig.Signature{}
|
||||
} else {
|
||||
c.Printf("%s\n", gettingSignaturesMessage)
|
||||
s, err := unparsed.UntrustedSignatures(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading signatures: %w", err)
|
||||
}
|
||||
sigs = s
|
||||
}
|
||||
if len(sigs) != 0 {
|
||||
c.Printf("%s\n", checkingDestMessage)
|
||||
if err := c.dest.SupportsSignatures(ctx); err != nil {
|
||||
return nil, fmt.Errorf("Can not copy signatures to %s: %w", transports.ImageName(c.dest.Reference()), err)
|
||||
}
|
||||
}
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
// createSignature creates a new signature of manifest using keyIdentity.
|
||||
func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string, identity reference.Named) ([]byte, error) {
|
||||
func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string, identity reference.Named) (internalsig.Signature, error) {
|
||||
mech, err := signature.NewGPGSigningMechanism()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "initializing GPG")
|
||||
return nil, fmt.Errorf("initializing GPG: %w", err)
|
||||
}
|
||||
defer mech.Close()
|
||||
if err := mech.SupportsSigning(); err != nil {
|
||||
return nil, errors.Wrap(err, "Signing not supported")
|
||||
return nil, fmt.Errorf("Signing not supported: %w", err)
|
||||
}
|
||||
|
||||
if identity != nil {
|
||||
if reference.IsNameOnly(identity) {
|
||||
return nil, errors.Errorf("Sign identity must be a fully specified reference %s", identity)
|
||||
return nil, fmt.Errorf("Sign identity must be a fully specified reference %s", identity)
|
||||
}
|
||||
} else {
|
||||
identity = c.dest.Reference().DockerReference()
|
||||
if identity == nil {
|
||||
return nil, errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(c.dest.Reference()))
|
||||
return nil, fmt.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(c.dest.Reference()))
|
||||
}
|
||||
}
|
||||
|
||||
c.Printf("Signing manifest\n")
|
||||
c.Printf("Signing manifest using simple signing\n")
|
||||
newSig, err := signature.SignDockerManifestWithOptions(manifest, identity.String(), mech, keyIdentity, &signature.SignOptions{Passphrase: passphrase})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating signature")
|
||||
return nil, fmt.Errorf("creating signature: %w", err)
|
||||
}
|
||||
return internalsig.SimpleSigningFromBlob(newSig), nil
|
||||
}
|
||||
|
||||
// createSigstoreSignature creates a new sigstore signature of manifest using privateKeyFile and identity.
|
||||
func (c *copier) createSigstoreSignature(manifest []byte, privateKeyFile string, passphrase []byte, identity reference.Named) (internalsig.Signature, error) {
|
||||
if identity != nil {
|
||||
if reference.IsNameOnly(identity) {
|
||||
return nil, fmt.Errorf("Sign identity must be a fully specified reference %s", identity.String())
|
||||
}
|
||||
} else {
|
||||
identity = c.dest.Reference().DockerReference()
|
||||
if identity == nil {
|
||||
return nil, fmt.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(c.dest.Reference()))
|
||||
}
|
||||
}
|
||||
|
||||
c.Printf("Signing manifest using a sigstore signature\n")
|
||||
newSig, err := sigstore.SignDockerManifestWithPrivateKeyFileUnstable(manifest, identity, privateKeyFile, passphrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating signature: %w", err)
|
||||
}
|
||||
return newSig, nil
|
||||
}
|
||||
|
|
|
|||
7
vendor/github.com/containers/image/v5/directory/explicitfilepath/path.go
generated
vendored
7
vendor/github.com/containers/image/v5/directory/explicitfilepath/path.go
generated
vendored
|
|
@ -1,10 +1,9 @@
|
|||
package explicitfilepath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ResolvePathToFullyExplicit returns the input path converted to an absolute, no-symlinks, cleaned up path.
|
||||
|
|
@ -26,14 +25,14 @@ func ResolvePathToFullyExplicit(path string) (string, error) {
|
|||
// This can still happen if there is a filesystem race condition, causing the Lstat() above to fail but the later resolution to succeed.
|
||||
// We do not care to promise anything if such filesystem race conditions can happen, but we definitely don't want to return "."/".." components
|
||||
// in the resulting path, and especially not at the end.
|
||||
return "", errors.Errorf("Unexpectedly missing special filename component in %s", path)
|
||||
return "", fmt.Errorf("Unexpectedly missing special filename component in %s", path)
|
||||
}
|
||||
resolvedPath := filepath.Join(resolvedParent, file)
|
||||
// As a sanity check, ensure that there are no "." or ".." components.
|
||||
cleanedResolvedPath := filepath.Clean(resolvedPath)
|
||||
if cleanedResolvedPath != resolvedPath {
|
||||
// Coverage: This should never happen.
|
||||
return "", errors.Errorf("Internal inconsistency: Path %s resolved to %s still cleaned up to %s", path, resolvedPath, cleanedResolvedPath)
|
||||
return "", fmt.Errorf("Internal inconsistency: Path %s resolved to %s still cleaned up to %s", path, resolvedPath, cleanedResolvedPath)
|
||||
}
|
||||
return resolvedPath, nil
|
||||
default: // err != nil, unrecognized
|
||||
|
|
|
|||
14
vendor/github.com/containers/image/v5/docker/archive/dest.go
generated
vendored
14
vendor/github.com/containers/image/v5/docker/archive/dest.go
generated
vendored
|
|
@ -2,11 +2,12 @@ package archive
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type archiveImageDestination struct {
|
||||
|
|
@ -16,9 +17,9 @@ type archiveImageDestination struct {
|
|||
writer io.Closer // May be nil if the archive is shared
|
||||
}
|
||||
|
||||
func newImageDestination(sys *types.SystemContext, ref archiveReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(sys *types.SystemContext, ref archiveReference) (private.ImageDestination, error) {
|
||||
if ref.sourceIndex != -1 {
|
||||
return nil, errors.Errorf("Destination reference must not contain a manifest index @%d", ref.sourceIndex)
|
||||
return nil, fmt.Errorf("Destination reference must not contain a manifest index @%d", ref.sourceIndex)
|
||||
}
|
||||
|
||||
var archive *tarfile.Writer
|
||||
|
|
@ -35,7 +36,7 @@ func newImageDestination(sys *types.SystemContext, ref archiveReference) (types.
|
|||
archive = tarfile.NewWriter(fh)
|
||||
writer = fh
|
||||
}
|
||||
tarDest := tarfile.NewDestination(sys, archive, ref.ref)
|
||||
tarDest := tarfile.NewDestination(sys, archive, ref.Transport().Name(), ref.ref)
|
||||
if sys != nil && sys.DockerArchiveAdditionalTags != nil {
|
||||
tarDest.AddRepoTags(sys.DockerArchiveAdditionalTags)
|
||||
}
|
||||
|
|
@ -47,11 +48,6 @@ func newImageDestination(sys *types.SystemContext, ref archiveReference) (types.
|
|||
}, nil
|
||||
}
|
||||
|
||||
// DesiredLayerCompression indicates if layers must be compressed, decompressed or preserved
|
||||
func (d *archiveImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.Decompress
|
||||
}
|
||||
|
||||
// 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 *archiveImageDestination) Reference() types.ImageReference {
|
||||
|
|
|
|||
17
vendor/github.com/containers/image/v5/docker/archive/reader.go
generated
vendored
17
vendor/github.com/containers/image/v5/docker/archive/reader.go
generated
vendored
|
|
@ -1,11 +1,12 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Reader manages a single Docker archive, allows listing its contents and accessing
|
||||
|
|
@ -40,10 +41,10 @@ func (r *Reader) Close() error {
|
|||
func NewReaderForReference(sys *types.SystemContext, ref types.ImageReference) (*Reader, types.ImageReference, error) {
|
||||
standalone, ok := ref.(archiveReference)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("Internal error: NewReaderForReference called for a non-docker/archive ImageReference %s", transports.ImageName(ref))
|
||||
return nil, nil, fmt.Errorf("Internal error: NewReaderForReference called for a non-docker/archive ImageReference %s", transports.ImageName(ref))
|
||||
}
|
||||
if standalone.archiveReader != nil {
|
||||
return nil, nil, errors.Errorf("Internal error: NewReaderForReference called for a reader-bound reference %s", standalone.StringWithinTransport())
|
||||
return nil, nil, fmt.Errorf("Internal error: NewReaderForReference called for a reader-bound reference %s", standalone.StringWithinTransport())
|
||||
}
|
||||
reader, err := NewReader(sys, standalone.path)
|
||||
if err != nil {
|
||||
|
|
@ -73,22 +74,22 @@ func (r *Reader) List() ([][]types.ImageReference, error) {
|
|||
for _, tag := range image.RepoTags {
|
||||
parsedTag, err := reference.ParseNormalizedNamed(tag)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Invalid tag %#v in manifest item @%d", tag, imageIndex)
|
||||
return nil, fmt.Errorf("Invalid tag %#v in manifest item @%d: %w", tag, imageIndex, err)
|
||||
}
|
||||
nt, ok := parsedTag.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Invalid tag %s (%s): does not contain a tag", tag, parsedTag.String())
|
||||
return nil, fmt.Errorf("Invalid tag %s (%s): does not contain a tag", tag, parsedTag.String())
|
||||
}
|
||||
ref, err := newReference(r.path, nt, -1, r.archive, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "creating a reference for tag %#v in manifest item @%d", tag, imageIndex)
|
||||
return nil, fmt.Errorf("creating a reference for tag %#v in manifest item @%d: %w", tag, imageIndex, err)
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
ref, err := newReference(r.path, nil, imageIndex, r.archive, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "creating a reference for manifest item @%d", imageIndex)
|
||||
return nil, fmt.Errorf("creating a reference for manifest item @%d: %w", imageIndex, err)
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
|
|
@ -107,7 +108,7 @@ func (r *Reader) List() ([][]types.ImageReference, error) {
|
|||
func (r *Reader) ManifestTagsForReference(ref types.ImageReference) ([]string, error) {
|
||||
archiveRef, ok := ref.(archiveReference)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Internal error: ManifestTagsForReference called for a non-docker/archive ImageReference %s", transports.ImageName(ref))
|
||||
return nil, fmt.Errorf("Internal error: ManifestTagsForReference called for a non-docker/archive ImageReference %s", transports.ImageName(ref))
|
||||
}
|
||||
manifestItem, tagIndex, err := r.archive.ChooseManifestItem(archiveRef.ref, archiveRef.sourceIndex)
|
||||
if err != nil {
|
||||
|
|
|
|||
5
vendor/github.com/containers/image/v5/docker/archive/src.go
generated
vendored
5
vendor/github.com/containers/image/v5/docker/archive/src.go
generated
vendored
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ type archiveImageSource struct {
|
|||
|
||||
// newImageSource returns a types.ImageSource for the specified image reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref archiveReference) (types.ImageSource, error) {
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref archiveReference) (private.ImageSource, error) {
|
||||
var archive *tarfile.Reader
|
||||
var closeArchive bool
|
||||
if ref.archiveReader != nil {
|
||||
|
|
@ -28,7 +29,7 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref archiveRe
|
|||
archive = a
|
||||
closeArchive = true
|
||||
}
|
||||
src := tarfile.NewSource(archive, closeArchive, ref.ref, ref.sourceIndex)
|
||||
src := tarfile.NewSource(archive, closeArchive, ref.Transport().Name(), ref.ref, ref.sourceIndex)
|
||||
return &archiveImageSource{
|
||||
Source: src,
|
||||
ref: ref,
|
||||
|
|
|
|||
28
vendor/github.com/containers/image/v5/docker/archive/transport.go
generated
vendored
28
vendor/github.com/containers/image/v5/docker/archive/transport.go
generated
vendored
|
|
@ -2,16 +2,16 @@ package archive
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
ctrImage "github.com/containers/image/v5/image"
|
||||
ctrImage "github.com/containers/image/v5/internal/image"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -59,7 +59,7 @@ type archiveReference struct {
|
|||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference.
|
||||
func ParseReference(refString string) (types.ImageReference, error) {
|
||||
if refString == "" {
|
||||
return nil, errors.Errorf("docker-archive reference %s isn't of the form <path>[:<reference>]", refString)
|
||||
return nil, fmt.Errorf("docker-archive reference %s isn't of the form <path>[:<reference>]", refString)
|
||||
}
|
||||
|
||||
parts := strings.SplitN(refString, ":", 2)
|
||||
|
|
@ -72,21 +72,21 @@ func ParseReference(refString string) (types.ImageReference, error) {
|
|||
if len(parts[1]) > 0 && parts[1][0] == '@' {
|
||||
i, err := strconv.Atoi(parts[1][1:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Invalid source index %s", parts[1])
|
||||
return nil, fmt.Errorf("Invalid source index %s: %w", parts[1], err)
|
||||
}
|
||||
if i < 0 {
|
||||
return nil, errors.Errorf("Invalid source index @%d: must not be negative", i)
|
||||
return nil, fmt.Errorf("Invalid source index @%d: must not be negative", i)
|
||||
}
|
||||
sourceIndex = i
|
||||
} else {
|
||||
ref, err := reference.ParseNormalizedNamed(parts[1])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "docker-archive parsing reference")
|
||||
return nil, fmt.Errorf("docker-archive parsing reference: %w", err)
|
||||
}
|
||||
ref = reference.TagNameOnly(ref)
|
||||
refTagged, isTagged := ref.(reference.NamedTagged)
|
||||
if !isTagged { // If ref contains a digest, TagNameOnly does not change it
|
||||
return nil, errors.Errorf("reference does not include a tag: %s", ref.String())
|
||||
return nil, fmt.Errorf("reference does not include a tag: %s", ref.String())
|
||||
}
|
||||
nt = refTagged
|
||||
}
|
||||
|
|
@ -110,16 +110,16 @@ func NewIndexReference(path string, sourceIndex int) (types.ImageReference, erro
|
|||
func newReference(path string, ref reference.NamedTagged, sourceIndex int,
|
||||
archiveReader *tarfile.Reader, archiveWriter *tarfile.Writer) (types.ImageReference, error) {
|
||||
if strings.Contains(path, ":") {
|
||||
return nil, errors.Errorf("Invalid docker-archive: reference: colon in path %q is not supported", path)
|
||||
return nil, fmt.Errorf("Invalid docker-archive: reference: colon in path %q is not supported", path)
|
||||
}
|
||||
if ref != nil && sourceIndex != -1 {
|
||||
return nil, errors.Errorf("Invalid docker-archive: reference: cannot use both a tag and a source index")
|
||||
return nil, fmt.Errorf("Invalid docker-archive: reference: cannot use both a tag and a source index")
|
||||
}
|
||||
if _, isDigest := ref.(reference.Canonical); isDigest {
|
||||
return nil, errors.Errorf("docker-archive doesn't support digest references: %s", ref.String())
|
||||
return nil, fmt.Errorf("docker-archive doesn't support digest references: %s", ref.String())
|
||||
}
|
||||
if sourceIndex != -1 && sourceIndex < 0 {
|
||||
return nil, errors.Errorf("Invalid docker-archive: reference: index @%d must not be negative", sourceIndex)
|
||||
return nil, fmt.Errorf("Invalid docker-archive: reference: index @%d must not be negative", sourceIndex)
|
||||
}
|
||||
return archiveReference{
|
||||
path: path,
|
||||
|
|
@ -185,11 +185,7 @@ func (ref archiveReference) PolicyConfigurationNamespaces() []string {
|
|||
// 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 archiveReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctrImage.FromSource(ctx, sys, src)
|
||||
return ctrImage.FromReference(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
|
|
|
|||
7
vendor/github.com/containers/image/v5/docker/archive/writer.go
generated
vendored
7
vendor/github.com/containers/image/v5/docker/archive/writer.go
generated
vendored
|
|
@ -1,13 +1,14 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Writer manages a single in-progress Docker archive and allows adding images to it.
|
||||
|
|
@ -60,7 +61,7 @@ func openArchiveForWriting(path string) (*os.File, error) {
|
|||
// only in a different way. Either way, it’s up to the user to not have two writers to the same path.)
|
||||
fh, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "opening file %q", path)
|
||||
return nil, fmt.Errorf("opening file %q: %w", path, err)
|
||||
}
|
||||
succeeded := false
|
||||
defer func() {
|
||||
|
|
@ -70,7 +71,7 @@ func openArchiveForWriting(path string) (*os.File, error) {
|
|||
}()
|
||||
fhStat, err := fh.Stat()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "statting file %q", path)
|
||||
return nil, fmt.Errorf("statting file %q: %w", path, err)
|
||||
}
|
||||
|
||||
if fhStat.Mode().IsRegular() && fhStat.Size() != 0 {
|
||||
|
|
|
|||
218
vendor/github.com/containers/image/v5/docker/docker_client.go
generated
vendored
218
vendor/github.com/containers/image/v5/docker/docker_client.go
generated
vendored
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
|
@ -17,16 +18,19 @@ import (
|
|||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/iolimits"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/docker/config"
|
||||
"github.com/containers/image/v5/pkg/sysregistriesv2"
|
||||
"github.com/containers/image/v5/pkg/tlsclientconfig"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/image/v5/version"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
v2 "github.com/docker/distribution/registry/api/v2"
|
||||
clientLib "github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -61,8 +65,8 @@ type certPath struct {
|
|||
var (
|
||||
homeCertDir = filepath.FromSlash(".config/containers/certs.d")
|
||||
perHostCertDirs = []certPath{
|
||||
{path: "/etc/containers/certs.d", absolute: true},
|
||||
{path: "/etc/docker/certs.d", absolute: true},
|
||||
{path: etcDir + "/containers/certs.d", absolute: true},
|
||||
{path: etcDir + "/docker/certs.d", absolute: true},
|
||||
}
|
||||
|
||||
defaultUserAgent = "containers/" + version.Version + " (github.com/containers/image)"
|
||||
|
|
@ -101,10 +105,11 @@ type dockerClient struct {
|
|||
// by detectProperties(). Callers can edit tlsClientConfig.InsecureSkipVerify in the meantime.
|
||||
tlsClientConfig *tls.Config
|
||||
// The following members are not set by newDockerClient and must be set by callers if needed.
|
||||
auth types.DockerAuthConfig
|
||||
registryToken string
|
||||
signatureBase signatureStorageBase
|
||||
scope authScope
|
||||
auth types.DockerAuthConfig
|
||||
registryToken string
|
||||
signatureBase lookasideStorageBase
|
||||
useSigstoreAttachments bool
|
||||
scope authScope
|
||||
|
||||
// The following members are detected registry properties:
|
||||
// They are set after a successful detectProperties(), and never change afterwards.
|
||||
|
|
@ -163,9 +168,8 @@ func newBearerTokenFromJSONBlob(blob []byte) (*bearerToken, error) {
|
|||
func serverDefault() *tls.Config {
|
||||
return &tls.Config{
|
||||
// Avoid fallback to SSL protocols < TLS1.0
|
||||
MinVersion: tls.VersionTLS10,
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: tlsconfig.DefaultServerAcceptedCiphers,
|
||||
MinVersion: tls.VersionTLS10,
|
||||
CipherSuites: tlsconfig.DefaultServerAcceptedCiphers,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,13 +214,13 @@ func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) {
|
|||
// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||
// signatureBase is always set in the return value
|
||||
func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||
func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, registryConfig *registryConfiguration, write bool, actions string) (*dockerClient, error) {
|
||||
auth, err := config.GetCredentialsForRef(sys, ref.ref)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "getting username and password")
|
||||
return nil, fmt.Errorf("getting username and password: %w", err)
|
||||
}
|
||||
|
||||
sigBase, err := SignatureStorageBaseURL(sys, ref, write)
|
||||
sigBase, err := registryConfig.lookasideStorageBaseURL(ref, write)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -231,6 +235,7 @@ func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, write
|
|||
client.registryToken = sys.DockerBearerRegistryToken
|
||||
}
|
||||
client.signatureBase = sigBase
|
||||
client.useSigstoreAttachments = registryConfig.useSigstoreAttachments(ref)
|
||||
client.scope.actions = actions
|
||||
client.scope.remoteName = reference.Path(ref.ref)
|
||||
return client, nil
|
||||
|
|
@ -267,7 +272,7 @@ func newDockerClient(sys *types.SystemContext, registry, reference string) (*doc
|
|||
skipVerify := false
|
||||
reg, err := sysregistriesv2.FindRegistry(sys, reference)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "loading registries")
|
||||
return nil, fmt.Errorf("loading registries: %w", err)
|
||||
}
|
||||
if reg != nil {
|
||||
if reg.Blocked {
|
||||
|
|
@ -295,7 +300,7 @@ func newDockerClient(sys *types.SystemContext, registry, reference string) (*doc
|
|||
func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password, registry string) error {
|
||||
client, err := newDockerClient(sys, registry, registry)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating new docker client")
|
||||
return fmt.Errorf("creating new docker client: %w", err)
|
||||
}
|
||||
client.auth = types.DockerAuthConfig{
|
||||
Username: username,
|
||||
|
|
@ -344,7 +349,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
|
|||
// We can't use GetCredentialsForRef here because we want to search the whole registry.
|
||||
auth, err := config.GetCredentials(sys, registry)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "getting username and password")
|
||||
return nil, fmt.Errorf("getting username and password: %w", err)
|
||||
}
|
||||
|
||||
// The /v2/_catalog endpoint has been disabled for docker.io therefore
|
||||
|
|
@ -358,7 +363,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
|
|||
|
||||
client, err := newDockerClient(sys, hostname, registry)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "creating new docker client")
|
||||
return nil, fmt.Errorf("creating new docker client: %w", err)
|
||||
}
|
||||
client.auth = auth
|
||||
if sys != nil {
|
||||
|
|
@ -401,13 +406,13 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
|
|||
resp, err := client.makeRequest(ctx, http.MethodGet, path, nil, nil, v2Auth, nil)
|
||||
if err != nil {
|
||||
logrus.Debugf("error getting search results from v2 endpoint %q: %v", registry, err)
|
||||
return nil, errors.Wrapf(err, "couldn't search registry %q", registry)
|
||||
return nil, fmt.Errorf("couldn't search registry %q: %w", registry, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err := httpResponseToError(resp, "")
|
||||
logrus.Errorf("error getting search results from v2 endpoint %q: %v", registry, err)
|
||||
return nil, errors.Wrapf(err, "couldn't search registry %q", registry)
|
||||
return nil, fmt.Errorf("couldn't search registry %q: %w", registry, err)
|
||||
}
|
||||
v2Res := &V2Results{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(v2Res); err != nil {
|
||||
|
|
@ -629,7 +634,7 @@ func (c *dockerClient) getBearerTokenOAuth2(ctx context.Context, challenge chall
|
|||
scopes []authScope) (*bearerToken, error) {
|
||||
realm, ok := challenge.Parameters["realm"]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("missing realm in bearer auth challenge")
|
||||
return nil, errors.New("missing realm in bearer auth challenge")
|
||||
}
|
||||
|
||||
authReq, err := http.NewRequestWithContext(ctx, http.MethodPost, realm, nil)
|
||||
|
|
@ -677,7 +682,7 @@ func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge,
|
|||
scopes []authScope) (*bearerToken, error) {
|
||||
realm, ok := challenge.Parameters["realm"]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("missing realm in bearer auth challenge")
|
||||
return nil, errors.New("missing realm in bearer auth challenge")
|
||||
}
|
||||
|
||||
authReq, err := http.NewRequestWithContext(ctx, http.MethodGet, realm, nil)
|
||||
|
|
@ -761,7 +766,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error {
|
|||
err = ping("http")
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "pinging container registry %s", c.registry)
|
||||
err = fmt.Errorf("pinging container registry %s: %w", c.registry, err)
|
||||
if c.sys != nil && c.sys.DockerDisableV1Ping {
|
||||
return err
|
||||
}
|
||||
|
|
@ -801,6 +806,166 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
|
|||
return c.detectPropertiesError
|
||||
}
|
||||
|
||||
func (c *dockerClient) fetchManifest(ctx context.Context, ref dockerReference, tagOrDigest string) ([]byte, string, error) {
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(ref.ref), tagOrDigest)
|
||||
headers := map[string][]string{
|
||||
"Accept": manifest.DefaultRequestedManifestMIMETypes,
|
||||
}
|
||||
res, err := c.makeRequest(ctx, http.MethodGet, path, headers, nil, v2Auth, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
logrus.Debugf("Content-Type from manifest GET is %q", res.Header.Get("Content-Type"))
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, "", fmt.Errorf("reading manifest %s in %s: %w", tagOrDigest, ref.ref.Name(), registryHTTPResponseToError(res))
|
||||
}
|
||||
|
||||
manblob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxManifestBodySize)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
|
||||
}
|
||||
|
||||
// getExternalBlob returns the reader of the first available blob URL from urls, which must not be empty.
|
||||
// This function can return nil reader when no url is supported by this function. In this case, the caller
|
||||
// should fallback to fetch the non-external blob (i.e. pull from the registry).
|
||||
func (c *dockerClient) getExternalBlob(ctx context.Context, urls []string) (io.ReadCloser, int64, error) {
|
||||
var (
|
||||
resp *http.Response
|
||||
err error
|
||||
)
|
||||
if len(urls) == 0 {
|
||||
return nil, 0, errors.New("internal error: getExternalBlob called with no URLs")
|
||||
}
|
||||
for _, u := range urls {
|
||||
url, err := url.Parse(u)
|
||||
if err != nil || (url.Scheme != "http" && url.Scheme != "https") {
|
||||
continue // unsupported url. skip this url.
|
||||
}
|
||||
// NOTE: we must not authenticate on additional URLs as those
|
||||
// can be abused to leak credentials or tokens. Please
|
||||
// refer to CVE-2020-15157 for more information.
|
||||
resp, err = c.makeRequestToResolvedURL(ctx, http.MethodGet, url, nil, nil, -1, noAuth, nil)
|
||||
if err == nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("error fetching external blob from %q: %d (%s)", u, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
logrus.Debug(err)
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if resp == nil && err == nil {
|
||||
return nil, 0, nil // fallback to non-external blob
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return resp.Body, getBlobSize(resp), nil
|
||||
}
|
||||
|
||||
func getBlobSize(resp *http.Response) int64 {
|
||||
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
size = -1
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// getBlob returns a stream for the specified blob in ref, 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 (c *dockerClient) getBlob(ctx context.Context, ref dockerReference, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) {
|
||||
if len(info.URLs) != 0 {
|
||||
r, s, err := c.getExternalBlob(ctx, info.URLs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
} else if r != nil {
|
||||
return r, s, nil
|
||||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(blobsPath, reference.Path(ref.ref), info.Digest.String())
|
||||
logrus.Debugf("Downloading %s", path)
|
||||
res, err := c.makeRequest(ctx, http.MethodGet, path, nil, nil, v2Auth, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := httpResponseToError(res, "Error fetching blob"); err != nil {
|
||||
res.Body.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
cache.RecordKnownLocation(ref.Transport(), bicTransportScope(ref), info.Digest, newBICLocationReference(ref))
|
||||
return res.Body, getBlobSize(res), nil
|
||||
}
|
||||
|
||||
// getOCIDescriptorContents returns the contents a blob spcified by descriptor in ref, which must fit within limit.
|
||||
func (c *dockerClient) getOCIDescriptorContents(ctx context.Context, ref dockerReference, desc imgspecv1.Descriptor, maxSize int, cache types.BlobInfoCache) ([]byte, error) {
|
||||
// Note that this copies all kinds of attachments: attestations, and whatever else is there,
|
||||
// not just signatures. We leave the signature consumers to decide based on the MIME type.
|
||||
reader, _, err := c.getBlob(ctx, ref, manifest.BlobInfoFromOCI1Descriptor(desc), cache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
payload, err := iolimits.ReadAtMost(reader, iolimits.MaxSignatureBodySize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading blob %s in %s: %w", desc.Digest.String(), ref.ref.Name(), err)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// isManifestUnknownError returns true iff err from fetchManifest is a “manifest unknown” error.
|
||||
func isManifestUnknownError(err error) bool {
|
||||
var errs errcode.Errors
|
||||
if !errors.As(err, &errs) || len(errs) == 0 {
|
||||
return false
|
||||
}
|
||||
err = errs[0]
|
||||
ec, ok := err.(errcode.ErrorCoder)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return ec.ErrorCode() == v2.ErrorCodeManifestUnknown
|
||||
}
|
||||
|
||||
// getSigstoreAttachmentManifest loads and parses the manifest for sigstore attachments for
|
||||
// digest in ref.
|
||||
// It returns (nil, nil) if the manifest does not exist.
|
||||
func (c *dockerClient) getSigstoreAttachmentManifest(ctx context.Context, ref dockerReference, digest digest.Digest) (*manifest.OCI1, error) {
|
||||
tag := sigstoreAttachmentTag(digest)
|
||||
sigstoreRef, err := reference.WithTag(reference.TrimNamed(ref.ref), tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("Looking for sigstore attachments in %s", sigstoreRef.String())
|
||||
manifestBlob, mimeType, err := c.fetchManifest(ctx, ref, tag)
|
||||
if err != nil {
|
||||
// FIXME: Are we going to need better heuristics??
|
||||
// This alone is probably a good enough reason for sigstore to be opt-in only,
|
||||
// otherwise we would just break ordinary copies.
|
||||
if isManifestUnknownError(err) {
|
||||
logrus.Debugf("Fetching sigstore attachment manifest failed, assuming it does not exist: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
logrus.Debugf("Fetching sigstore attachment manifest failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if mimeType != imgspecv1.MediaTypeImageManifest {
|
||||
// FIXME: Try anyway??
|
||||
return nil, fmt.Errorf("unexpected MIME type for sigstore attachment manifest %s: %q",
|
||||
sigstoreRef.String(), mimeType)
|
||||
}
|
||||
res, err := manifest.OCI1FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing manifest %s: %w", sigstoreRef.String(), err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// getExtensionsSignatures returns signatures from the X-Registry-Supports-Signatures API extension,
|
||||
// using the original data structures.
|
||||
func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) {
|
||||
|
|
@ -812,7 +977,7 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe
|
|||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, errors.Wrapf(clientLib.HandleErrorResponse(res), "downloading signatures for %s in %s", manifestDigest, ref.ref.Name())
|
||||
return nil, fmt.Errorf("downloading signatures for %s in %s: %w", manifestDigest, ref.ref.Name(), clientLib.HandleErrorResponse(res))
|
||||
}
|
||||
|
||||
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureListBodySize)
|
||||
|
|
@ -822,7 +987,12 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe
|
|||
|
||||
var parsedBody extensionSignatureList
|
||||
if err := json.Unmarshal(body, &parsedBody); err != nil {
|
||||
return nil, errors.Wrapf(err, "decoding signature list")
|
||||
return nil, fmt.Errorf("decoding signature list: %w", err)
|
||||
}
|
||||
return &parsedBody, nil
|
||||
}
|
||||
|
||||
// sigstoreAttachmentTag returns a sigstore attachment tag for the specified digest.
|
||||
func sigstoreAttachmentTag(d digest.Digest) string {
|
||||
return strings.Replace(d.String(), ":", "-", 1) + ".sig"
|
||||
}
|
||||
|
|
|
|||
28
vendor/github.com/containers/image/v5/docker/docker_image.go
generated
vendored
28
vendor/github.com/containers/image/v5/docker/docker_image.go
generated
vendored
|
|
@ -3,17 +3,17 @@ package docker
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/internal/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Image is a Docker-specific implementation of types.ImageCloser with a few extra methods
|
||||
|
|
@ -56,13 +56,17 @@ func (i *Image) GetRepositoryTags(ctx context.Context) ([]string, error) {
|
|||
func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) ([]string, error) {
|
||||
dr, ok := ref.(dockerReference)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("ref must be a dockerReference")
|
||||
return nil, errors.New("ref must be a dockerReference")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(tagsPath, reference.Path(dr.ref))
|
||||
client, err := newDockerClientFromRef(sys, dr, false, "pull")
|
||||
registryConfig, err := loadRegistryConfiguration(sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client")
|
||||
return nil, err
|
||||
}
|
||||
path := fmt.Sprintf(tagsPath, reference.Path(dr.ref))
|
||||
client, err := newDockerClientFromRef(sys, dr, registryConfig, false, "pull")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create client: %w", err)
|
||||
}
|
||||
|
||||
tags := make([]string, 0)
|
||||
|
|
@ -116,7 +120,7 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.
|
|||
func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) (digest.Digest, error) {
|
||||
dr, ok := ref.(dockerReference)
|
||||
if !ok {
|
||||
return "", errors.Errorf("ref must be a dockerReference")
|
||||
return "", errors.New("ref must be a dockerReference")
|
||||
}
|
||||
|
||||
tagOrDigest, err := dr.tagOrDigest()
|
||||
|
|
@ -124,9 +128,13 @@ func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageRef
|
|||
return "", err
|
||||
}
|
||||
|
||||
client, err := newDockerClientFromRef(sys, dr, false, "pull")
|
||||
registryConfig, err := loadRegistryConfiguration(sys)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create client")
|
||||
return "", err
|
||||
}
|
||||
client, err := newDockerClientFromRef(sys, dr, registryConfig, false, "pull")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create client: %w", err)
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(dr.ref), tagOrDigest)
|
||||
|
|
@ -141,7 +149,7 @@ func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageRef
|
|||
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return "", errors.Wrapf(registryHTTPResponseToError(res), "reading digest %s in %s", tagOrDigest, dr.ref.Name())
|
||||
return "", fmt.Errorf("reading digest %s in %s: %w", tagOrDigest, dr.ref.Name(), registryHTTPResponseToError(res))
|
||||
}
|
||||
|
||||
dig, err := digest.Parse(res.Header.Get("Docker-Content-Digest"))
|
||||
|
|
|
|||
378
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
378
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
|
@ -15,20 +16,29 @@ import (
|
|||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/blobinfocache"
|
||||
"github.com/containers/image/v5/internal/imagedestination/impl"
|
||||
"github.com/containers/image/v5/internal/imagedestination/stubs"
|
||||
"github.com/containers/image/v5/internal/iolimits"
|
||||
"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/internal/streamdigest"
|
||||
"github.com/containers/image/v5/internal/uploadreader"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/blobinfocache/none"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
v2 "github.com/docker/distribution/registry/api/v2"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerImageDestination struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
|
||||
ref dockerReference
|
||||
c *dockerClient
|
||||
// State
|
||||
|
|
@ -36,15 +46,40 @@ type dockerImageDestination struct {
|
|||
}
|
||||
|
||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||
func newImageDestination(sys *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||
c, err := newDockerClientFromRef(sys, ref, true, "pull,push")
|
||||
func newImageDestination(sys *types.SystemContext, ref dockerReference) (private.ImageDestination, error) {
|
||||
registryConfig, err := loadRegistryConfiguration(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dockerImageDestination{
|
||||
c, err := newDockerClientFromRef(sys, ref, registryConfig, true, "pull,push")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mimeTypes := []string{
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
manifest.DockerV2Schema2MediaType,
|
||||
imgspecv1.MediaTypeImageIndex,
|
||||
manifest.DockerV2ListMediaType,
|
||||
}
|
||||
if c.sys == nil || !c.sys.DockerDisableDestSchema1MIMETypes {
|
||||
mimeTypes = append(mimeTypes, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema1MediaType)
|
||||
}
|
||||
|
||||
dest := &dockerImageDestination{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
SupportedManifestMIMETypes: mimeTypes,
|
||||
DesiredLayerCompression: types.Compress,
|
||||
MustMatchRuntimeOS: false,
|
||||
IgnoresEmbeddedDockerReference: false, // We do want the manifest updated; older registry versions refuse manifests if the embedded reference does not match.
|
||||
HasThreadSafePutBlob: true,
|
||||
}),
|
||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
||||
|
||||
ref: ref,
|
||||
c: c,
|
||||
}, nil
|
||||
}
|
||||
dest.Compat = impl.AddCompat(dest)
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
|
|
@ -58,19 +93,6 @@ func (d *dockerImageDestination) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
|
||||
mimeTypes := []string{
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
manifest.DockerV2Schema2MediaType,
|
||||
imgspecv1.MediaTypeImageIndex,
|
||||
manifest.DockerV2ListMediaType,
|
||||
}
|
||||
if d.c.sys == nil || !d.c.sys.DockerDisableDestSchema1MIMETypes {
|
||||
mimeTypes = append(mimeTypes, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema1MediaType)
|
||||
}
|
||||
return mimeTypes
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *dockerImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
|
|
@ -83,32 +105,16 @@ func (d *dockerImageDestination) SupportsSignatures(ctx context.Context) error {
|
|||
case d.c.signatureBase != nil:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
|
||||
return errors.New("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.Compress
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (d *dockerImageDestination) AcceptsForeignLayerURLs() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime architecture and OS. False otherwise.
|
||||
func (d *dockerImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *dockerImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // We do want the manifest updated; older registry versions refuse manifests if the embedded reference does not match.
|
||||
}
|
||||
|
||||
// sizeCounter is an io.Writer which only counts the total size of its input.
|
||||
type sizeCounter struct{ size int64 }
|
||||
|
||||
|
|
@ -117,19 +123,14 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) {
|
|||
return len(p), nil
|
||||
}
|
||||
|
||||
// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently.
|
||||
func (d *dockerImageDestination) HasThreadSafePutBlob() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// 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.
|
||||
// May update cache.
|
||||
// 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 *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) {
|
||||
func (d *dockerImageDestination) PutBlobWithOptions(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions) (types.BlobInfo, error) {
|
||||
// If requested, precompute the blob digest to prevent uploading layers that already exist on the registry.
|
||||
// This functionality is particularly useful when BlobInfoCache has not been populated with compressed digests,
|
||||
// the source blob is uncompressed, and the destination blob is being compressed "on the fly".
|
||||
|
|
@ -146,7 +147,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
|
|||
if inputInfo.Digest != "" {
|
||||
// This should not really be necessary, at least the copy code calls TryReusingBlob automatically.
|
||||
// Still, we need to check, if only because the "initiate upload" endpoint does not have a documented "blob already exists" return value.
|
||||
haveBlob, reusedInfo, err := d.tryReusingExactBlob(ctx, inputInfo, cache)
|
||||
haveBlob, reusedInfo, err := d.tryReusingExactBlob(ctx, inputInfo, options.Cache)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
|
@ -165,11 +166,11 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
|
|||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusAccepted {
|
||||
logrus.Debugf("Error initiating layer upload, response %#v", *res)
|
||||
return types.BlobInfo{}, errors.Wrapf(registryHTTPResponseToError(res), "initiating layer upload to %s in %s", uploadPath, d.c.registry)
|
||||
return types.BlobInfo{}, fmt.Errorf("initiating layer upload to %s in %s: %w", uploadPath, d.c.registry, registryHTTPResponseToError(res))
|
||||
}
|
||||
uploadLocation, err := res.Location()
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "determining upload URL")
|
||||
return types.BlobInfo{}, fmt.Errorf("determining upload URL: %w", err)
|
||||
}
|
||||
|
||||
digester, stream := putblobdigest.DigestIfCanonicalUnknown(stream, inputInfo)
|
||||
|
|
@ -188,11 +189,11 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
|
|||
}
|
||||
defer res.Body.Close()
|
||||
if !successStatus(res.StatusCode) {
|
||||
return nil, errors.Wrapf(registryHTTPResponseToError(res), "uploading layer chunked")
|
||||
return nil, fmt.Errorf("uploading layer chunked: %w", registryHTTPResponseToError(res))
|
||||
}
|
||||
uploadLocation, err := res.Location()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "determining upload URL")
|
||||
return nil, fmt.Errorf("determining upload URL: %w", err)
|
||||
}
|
||||
return uploadLocation, nil
|
||||
}()
|
||||
|
|
@ -213,11 +214,11 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
|
|||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
logrus.Debugf("Error uploading layer, response %#v", *res)
|
||||
return types.BlobInfo{}, errors.Wrapf(registryHTTPResponseToError(res), "uploading layer to %s", uploadLocation)
|
||||
return types.BlobInfo{}, fmt.Errorf("uploading layer to %s: %w", uploadLocation, registryHTTPResponseToError(res))
|
||||
}
|
||||
|
||||
logrus.Debugf("Upload of layer %s complete", blobDigest)
|
||||
cache.RecordKnownLocation(d.ref.Transport(), bicTransportScope(d.ref), blobDigest, newBICLocationReference(d.ref))
|
||||
options.Cache.RecordKnownLocation(d.ref.Transport(), bicTransportScope(d.ref), blobDigest, newBICLocationReference(d.ref))
|
||||
return types.BlobInfo{Digest: blobDigest, Size: sizeCounter.size}, nil
|
||||
}
|
||||
|
||||
|
|
@ -238,12 +239,12 @@ func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.
|
|||
return true, getBlobSize(res), nil
|
||||
case http.StatusUnauthorized:
|
||||
logrus.Debugf("... not authorized")
|
||||
return false, -1, errors.Wrapf(registryHTTPResponseToError(res), "checking whether a blob %s exists in %s", digest, repo.Name())
|
||||
return false, -1, fmt.Errorf("checking whether a blob %s exists in %s: %w", digest, repo.Name(), registryHTTPResponseToError(res))
|
||||
case http.StatusNotFound:
|
||||
logrus.Debugf("... not present")
|
||||
return false, -1, nil
|
||||
default:
|
||||
return false, -1, errors.Errorf("failed to read from destination repository %s: %d (%s)", reference.Path(d.ref.ref), res.StatusCode, http.StatusText(res.StatusCode))
|
||||
return false, -1, fmt.Errorf("failed to read from destination repository %s: %d (%s)", reference.Path(d.ref.ref), res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +273,7 @@ func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo referenc
|
|||
// NOTE: This does not really work in docker/distribution servers, which incorrectly require the "delete" action in the token's scope, and is thus entirely untested.
|
||||
uploadLocation, err := res.Location()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "determining upload URL after a mount attempt")
|
||||
return fmt.Errorf("determining upload URL after a mount attempt: %w", err)
|
||||
}
|
||||
logrus.Debugf("... started an upload instead of mounting, trying to cancel at %s", uploadLocation.Redacted())
|
||||
res2, err := d.c.makeRequestToResolvedURL(ctx, http.MethodDelete, uploadLocation, nil, nil, -1, v2Auth, extraScope)
|
||||
|
|
@ -288,14 +289,14 @@ func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo referenc
|
|||
return fmt.Errorf("Mounting %s from %s to %s started an upload instead", srcDigest, srcRepo.Name(), d.ref.ref.Name())
|
||||
default:
|
||||
logrus.Debugf("Error mounting, response %#v", *res)
|
||||
return errors.Wrapf(registryHTTPResponseToError(res), "mounting %s from %s to %s", srcDigest, srcRepo.Name(), d.ref.ref.Name())
|
||||
return fmt.Errorf("mounting %s from %s to %s: %w", srcDigest, srcRepo.Name(), d.ref.ref.Name(), registryHTTPResponseToError(res))
|
||||
}
|
||||
}
|
||||
|
||||
// tryReusingExactBlob is a subset of TryReusingBlob which _only_ looks for exactly the specified
|
||||
// blob in the current repository, with no cross-repo reuse or mounting; cache may be updated, it is not read.
|
||||
// The caller must ensure info.Digest is set.
|
||||
func (d *dockerImageDestination) tryReusingExactBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (bool, types.BlobInfo, error) {
|
||||
func (d *dockerImageDestination) tryReusingExactBlob(ctx context.Context, info types.BlobInfo, cache blobinfocache.BlobInfoCache2) (bool, types.BlobInfo, error) {
|
||||
exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest, nil)
|
||||
if err != nil {
|
||||
return false, types.BlobInfo{}, err
|
||||
|
|
@ -307,22 +308,20 @@ func (d *dockerImageDestination) tryReusingExactBlob(ctx context.Context, info t
|
|||
return false, types.BlobInfo{}, nil
|
||||
}
|
||||
|
||||
// TryReusingBlob checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
|
||||
// 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 canSubstitute, TryReusingBlob can use an equivalent equivalent of the desired blob; in that case the returned info may not match the input.
|
||||
// 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.
|
||||
// May use and/or update cache.
|
||||
func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache, canSubstitute bool) (bool, types.BlobInfo, error) {
|
||||
func (d *dockerImageDestination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, types.BlobInfo, error) {
|
||||
if info.Digest == "" {
|
||||
return false, types.BlobInfo{}, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
return false, types.BlobInfo{}, errors.New("Can not check for a blob with unknown digest")
|
||||
}
|
||||
|
||||
// First, check whether the blob happens to already exist at the destination.
|
||||
haveBlob, reusedInfo, err := d.tryReusingExactBlob(ctx, info, cache)
|
||||
haveBlob, reusedInfo, err := d.tryReusingExactBlob(ctx, info, options.Cache)
|
||||
if err != nil {
|
||||
return false, types.BlobInfo{}, err
|
||||
}
|
||||
|
|
@ -331,8 +330,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
|
|||
}
|
||||
|
||||
// Then try reusing blobs from other locations.
|
||||
bic := blobinfocache.FromBlobInfoCache(cache)
|
||||
candidates := bic.CandidateLocations2(d.ref.Transport(), bicTransportScope(d.ref), info.Digest, canSubstitute)
|
||||
candidates := options.Cache.CandidateLocations2(d.ref.Transport(), bicTransportScope(d.ref), info.Digest, options.CanSubstitute)
|
||||
for _, candidate := range candidates {
|
||||
candidateRepo, err := parseBICLocationReference(candidate.Location)
|
||||
if err != nil {
|
||||
|
|
@ -386,7 +384,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
|
|||
}
|
||||
}
|
||||
|
||||
bic.RecordKnownLocation(d.ref.Transport(), bicTransportScope(d.ref), candidate.Digest, newBICLocationReference(d.ref))
|
||||
options.Cache.RecordKnownLocation(d.ref.Transport(), bicTransportScope(d.ref), candidate.Digest, newBICLocationReference(d.ref))
|
||||
|
||||
compressionOperation, compressionAlgorithm, err := blobinfocache.OperationAndAlgorithmForCompressor(candidate.CompressorName)
|
||||
if err != nil {
|
||||
|
|
@ -417,14 +415,14 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte, inst
|
|||
// Double-check that the manifest we've been given matches the digest we've been given.
|
||||
matches, err := manifest.MatchesDigest(m, *instanceDigest)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "digesting manifest in PutManifest")
|
||||
return fmt.Errorf("digesting manifest in PutManifest: %w", err)
|
||||
}
|
||||
if !matches {
|
||||
manifestDigest, merr := manifest.Digest(m)
|
||||
if merr != nil {
|
||||
return errors.Wrapf(err, "Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%v attempting to compute it)", instanceDigest.String(), merr)
|
||||
return fmt.Errorf("Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest: %w", instanceDigest.String(), merr)
|
||||
}
|
||||
return errors.Errorf("Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%q)", instanceDigest.String(), manifestDigest.String())
|
||||
return fmt.Errorf("Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%q)", instanceDigest.String(), manifestDigest.String())
|
||||
}
|
||||
} else {
|
||||
// Compute the digest of the main manifest, or the list if it's a list, so that we
|
||||
|
|
@ -442,7 +440,12 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte, inst
|
|||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(d.ref.ref), refTail)
|
||||
return d.uploadManifest(ctx, m, refTail)
|
||||
}
|
||||
|
||||
// uploadManifest writes manifest to tagOrDigest.
|
||||
func (d *dockerImageDestination) uploadManifest(ctx context.Context, m []byte, tagOrDigest string) error {
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(d.ref.ref), tagOrDigest)
|
||||
|
||||
headers := map[string][]string{}
|
||||
mimeType := manifest.GuessMIMEType(m)
|
||||
|
|
@ -456,7 +459,7 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte, inst
|
|||
defer res.Body.Close()
|
||||
if !successStatus(res.StatusCode) {
|
||||
rawErr := registryHTTPResponseToError(res)
|
||||
err := errors.Wrapf(rawErr, "uploading manifest %s to %s", refTail, d.ref.ref.Name())
|
||||
err := fmt.Errorf("uploading manifest %s to %s: %w", tagOrDigest, d.ref.ref.Name(), rawErr)
|
||||
if isManifestInvalidError(rawErr) {
|
||||
err = types.ManifestTypeRejectedError{Err: err}
|
||||
}
|
||||
|
|
@ -514,38 +517,63 @@ func isManifestInvalidError(err error) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// PutSignatures uploads a set of signatures to the relevant lookaside or API extension point.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to upload 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.
|
||||
func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
|
||||
// Do not fail if we don’t really need to support signatures.
|
||||
if len(signatures) == 0 {
|
||||
return nil
|
||||
}
|
||||
// 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 *dockerImageDestination) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||
if instanceDigest == nil {
|
||||
if d.manifestDigest == "" {
|
||||
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||
return errors.Errorf("Unknown manifest digest, can't add signatures")
|
||||
return errors.New("Unknown manifest digest, can't add signatures")
|
||||
}
|
||||
instanceDigest = &d.manifestDigest
|
||||
}
|
||||
|
||||
if err := d.c.detectProperties(ctx); err != nil {
|
||||
return err
|
||||
sigstoreSignatures := []signature.Sigstore{}
|
||||
otherSignatures := []signature.Signature{}
|
||||
for _, sig := range signatures {
|
||||
if sigstoreSig, ok := sig.(signature.Sigstore); ok {
|
||||
sigstoreSignatures = append(sigstoreSignatures, sigstoreSig)
|
||||
} else {
|
||||
otherSignatures = append(otherSignatures, sig)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case d.c.supportsSignatures:
|
||||
return d.putSignaturesToAPIExtension(ctx, signatures, *instanceDigest)
|
||||
case d.c.signatureBase != nil:
|
||||
return d.putSignaturesToLookaside(signatures, *instanceDigest)
|
||||
default:
|
||||
return errors.Errorf("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
|
||||
|
||||
// Only write sigstores signatures to sigstores attachments. We _could_ store them to lookaside
|
||||
// instead, but that would probably be rather surprising.
|
||||
// FIXME: So should we enable sigstores in all cases? Or write in all cases, but opt-in to read?
|
||||
|
||||
if len(sigstoreSignatures) != 0 {
|
||||
if err := d.putSignaturesToSigstoreAttachments(ctx, sigstoreSignatures, *instanceDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(otherSignatures) != 0 {
|
||||
if err := d.c.detectProperties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case d.c.supportsSignatures:
|
||||
if err := d.putSignaturesToAPIExtension(ctx, signatures, *instanceDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
case d.c.signatureBase != nil:
|
||||
if err := d.putSignaturesToLookaside(signatures, *instanceDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// putSignaturesToLookaside implements PutSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||
// putSignaturesToLookaside implements PutSignaturesWithFormat() from the lookaside location configured in s.c.signatureBase,
|
||||
// which is not nil, for a manifest with manifestDigest.
|
||||
func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, manifestDigest digest.Digest) error {
|
||||
func (d *dockerImageDestination) putSignaturesToLookaside(signatures []signature.Signature, manifestDigest digest.Digest) error {
|
||||
// FIXME? This overwrites files one at a time, definitely not atomic.
|
||||
// A failure when updating signatures with a reordered copy could lose some of them.
|
||||
|
||||
|
|
@ -556,7 +584,7 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, m
|
|||
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
for i, signature := range signatures {
|
||||
url := signatureStorageURL(d.c.signatureBase, manifestDigest, i)
|
||||
url := lookasideStorageURL(d.c.signatureBase, manifestDigest, i)
|
||||
err := d.putOneSignature(url, signature)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -568,7 +596,7 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, m
|
|||
// is enough for dockerImageSource to stop looking for other signatures, so that
|
||||
// is sufficient.
|
||||
for i := len(signatures); ; i++ {
|
||||
url := signatureStorageURL(d.c.signatureBase, manifestDigest, i)
|
||||
url := lookasideStorageURL(d.c.signatureBase, manifestDigest, i)
|
||||
missing, err := d.c.deleteOneSignature(url)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -581,9 +609,9 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, m
|
|||
return nil
|
||||
}
|
||||
|
||||
// putOneSignature stores one signature to url.
|
||||
// putOneSignature stores sig to url.
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte) error {
|
||||
func (d *dockerImageDestination) putOneSignature(url *url.URL, sig signature.Signature) error {
|
||||
switch url.Scheme {
|
||||
case "file":
|
||||
logrus.Debugf("Writing to %s", url.Path)
|
||||
|
|
@ -591,19 +619,157 @@ func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte)
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(url.Path, signature, 0644)
|
||||
blob, err := signature.Blob(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(url.Path, blob, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case "http", "https":
|
||||
return errors.Errorf("Writing directly to a %s sigstore %s is not supported. Configure a sigstore-staging: location", url.Scheme, url.Redacted())
|
||||
return fmt.Errorf("Writing directly to a %s lookaside %s is not supported. Configure a lookaside-staging: location", url.Scheme, url.Redacted())
|
||||
default:
|
||||
return errors.Errorf("Unsupported scheme when writing signature to %s", url.Redacted())
|
||||
return fmt.Errorf("Unsupported scheme when writing signature to %s", url.Redacted())
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) putSignaturesToSigstoreAttachments(ctx context.Context, signatures []signature.Sigstore, manifestDigest digest.Digest) error {
|
||||
if !d.c.useSigstoreAttachments {
|
||||
return errors.New("writing sigstore attachments is disabled by configuration")
|
||||
}
|
||||
|
||||
ociManifest, err := d.c.getSigstoreAttachmentManifest(ctx, d.ref, manifestDigest)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var ociConfig imgspecv1.Image // Most fields empty by default
|
||||
if ociManifest == nil {
|
||||
ociManifest = manifest.OCI1FromComponents(imgspecv1.Descriptor{
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
Digest: "", // We will fill this in later.
|
||||
Size: 0,
|
||||
}, nil)
|
||||
} else {
|
||||
logrus.Debugf("Fetching sigstore attachment config %s", ociManifest.Config.Digest.String())
|
||||
// We don’t benefit from a real BlobInfoCache here because we never try to reuse/mount configs.
|
||||
configBlob, err := d.c.getOCIDescriptorContents(ctx, d.ref, ociManifest.Config, iolimits.MaxConfigBodySize,
|
||||
none.NoCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(configBlob, &ociConfig); err != nil {
|
||||
return fmt.Errorf("parsing sigstore attachment config %s in %s: %w", ociManifest.Config.Digest.String(),
|
||||
d.ref.ref.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, sig := range signatures {
|
||||
mimeType := sig.UntrustedMIMEType()
|
||||
payloadBlob := sig.UntrustedPayload()
|
||||
annotations := sig.UntrustedAnnotations()
|
||||
|
||||
alreadyOnRegistry := false
|
||||
for _, layer := range ociManifest.Layers {
|
||||
if layerMatchesSigstoreSignature(layer, mimeType, payloadBlob, annotations) {
|
||||
logrus.Debugf("Signature with digest %s already exists on the registry", layer.Digest.String())
|
||||
alreadyOnRegistry = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if alreadyOnRegistry {
|
||||
continue
|
||||
}
|
||||
|
||||
// We don’t benefit from a real BlobInfoCache here because we never try to reuse/mount attachment payloads.
|
||||
// That might eventually need to change if payloads grow to be not just signatures, but something
|
||||
// significantly large.
|
||||
sigDesc, err := d.putBlobBytesAsOCI(ctx, payloadBlob, mimeType, private.PutBlobOptions{
|
||||
Cache: none.NoCache,
|
||||
IsConfig: false,
|
||||
EmptyLayer: false,
|
||||
LayerIndex: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigDesc.Annotations = annotations
|
||||
ociManifest.Layers = append(ociManifest.Layers, sigDesc)
|
||||
ociConfig.RootFS.DiffIDs = append(ociConfig.RootFS.DiffIDs, sigDesc.Digest)
|
||||
logrus.Debugf("Adding new signature, digest %s", sigDesc.Digest.String())
|
||||
}
|
||||
|
||||
configBlob, err := json.Marshal(ociConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Uploading updated sigstore attachment config")
|
||||
// We don’t benefit from a real BlobInfoCache here because we never try to reuse/mount configs.
|
||||
configDesc, err := d.putBlobBytesAsOCI(ctx, configBlob, imgspecv1.MediaTypeImageConfig, private.PutBlobOptions{
|
||||
Cache: none.NoCache,
|
||||
IsConfig: true,
|
||||
EmptyLayer: false,
|
||||
LayerIndex: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ociManifest.Config = configDesc
|
||||
|
||||
manifestBlob, err := ociManifest.Serialize()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
logrus.Debugf("Uploading sigstore attachment manifest")
|
||||
return d.uploadManifest(ctx, manifestBlob, sigstoreAttachmentTag(manifestDigest))
|
||||
}
|
||||
|
||||
func layerMatchesSigstoreSignature(layer imgspecv1.Descriptor, mimeType string,
|
||||
payloadBlob []byte, annotations map[string]string) bool {
|
||||
if layer.MediaType != mimeType ||
|
||||
layer.Size != int64(len(payloadBlob)) ||
|
||||
// This is not quite correct, we should use the layer’s digest algorithm.
|
||||
// But right now we don’t want to deal with corner cases like bad digest formats
|
||||
// or unavailable algorithms; in the worst case we end up with duplicate signature
|
||||
// entries.
|
||||
layer.Digest.String() != digest.FromBytes(payloadBlob).String() {
|
||||
return false
|
||||
}
|
||||
if len(layer.Annotations) != len(annotations) {
|
||||
return false
|
||||
}
|
||||
for k, v1 := range layer.Annotations {
|
||||
if v2, ok := annotations[k]; !ok || v1 != v2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// All annotations in layer exist in sig, and the number of annotations is the same, so all annotations
|
||||
// in sig also exist in layer.
|
||||
return true
|
||||
}
|
||||
|
||||
// putBlobBytesAsOCI uploads a blob with the specified contents, and returns an appropriate
|
||||
// OCI descriptior.
|
||||
func (d *dockerImageDestination) putBlobBytesAsOCI(ctx context.Context, contents []byte, mimeType string, options private.PutBlobOptions) (imgspecv1.Descriptor, error) {
|
||||
blobDigest := digest.FromBytes(contents)
|
||||
info, err := d.PutBlobWithOptions(ctx, bytes.NewReader(contents),
|
||||
types.BlobInfo{
|
||||
Digest: blobDigest,
|
||||
Size: int64(len(contents)),
|
||||
MediaType: mimeType,
|
||||
}, options)
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, fmt.Errorf("writing blob %s: %w", blobDigest.String(), err)
|
||||
}
|
||||
return imgspecv1.Descriptor{
|
||||
MediaType: mimeType,
|
||||
Digest: info.Digest,
|
||||
Size: info.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// deleteOneSignature deletes a signature from url, if it exists.
|
||||
// If it successfully determines that the signature does not exist, returns (true, nil)
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
|
|
@ -618,15 +784,15 @@ func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error
|
|||
return false, err
|
||||
|
||||
case "http", "https":
|
||||
return false, errors.Errorf("Writing directly to a %s sigstore %s is not supported. Configure a sigstore-staging: location", url.Scheme, url.Redacted())
|
||||
return false, fmt.Errorf("Writing directly to a %s lookaside %s is not supported. Configure a lookaside-staging: location", url.Scheme, url.Redacted())
|
||||
default:
|
||||
return false, errors.Errorf("Unsupported scheme when deleting signature from %s", url.Redacted())
|
||||
return false, fmt.Errorf("Unsupported scheme when deleting signature from %s", url.Redacted())
|
||||
}
|
||||
}
|
||||
|
||||
// putSignaturesToAPIExtension implements PutSignatures() using the X-Registry-Supports-Signatures API extension,
|
||||
// putSignaturesToAPIExtension implements PutSignaturesWithFormat() using the X-Registry-Supports-Signatures API extension,
|
||||
// for a manifest with manifestDigest.
|
||||
func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures [][]byte, manifestDigest digest.Digest) error {
|
||||
func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures []signature.Signature, manifestDigest digest.Digest) error {
|
||||
// Skip dealing with the manifest digest, or reading the old state, if not necessary.
|
||||
if len(signatures) == 0 {
|
||||
return nil
|
||||
|
|
@ -646,7 +812,13 @@ func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context
|
|||
}
|
||||
|
||||
sigExists:
|
||||
for _, newSig := range signatures {
|
||||
for _, newSigWithFormat := range signatures {
|
||||
newSigSimple, ok := newSigWithFormat.(signature.SimpleSigning)
|
||||
if !ok {
|
||||
return signature.UnsupportedFormatError(newSigWithFormat)
|
||||
}
|
||||
newSig := newSigSimple.UntrustedSignature()
|
||||
|
||||
for _, existingSig := range existingSignatures.Signatures {
|
||||
if existingSig.Version == extensionSignatureSchemaVersion && existingSig.Type == extensionSignatureTypeAtomic && bytes.Equal(existingSig.Content, newSig) {
|
||||
continue sigExists
|
||||
|
|
@ -659,7 +831,7 @@ sigExists:
|
|||
randBytes := make([]byte, 16)
|
||||
n, err := rand.Read(randBytes)
|
||||
if err != nil || n != 16 {
|
||||
return errors.Wrapf(err, "generating random signature len %d", n)
|
||||
return fmt.Errorf("generating random signature len %d: %w", n, err)
|
||||
}
|
||||
signatureName = fmt.Sprintf("%s@%032x", manifestDigest.String(), randBytes)
|
||||
if _, ok := existingSigNames[signatureName]; !ok {
|
||||
|
|
@ -685,7 +857,7 @@ sigExists:
|
|||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
logrus.Debugf("Error uploading signature, status %d, %#v", res.StatusCode, res)
|
||||
return errors.Wrapf(registryHTTPResponseToError(res), "uploading signature to %s in %s", path, d.c.registry)
|
||||
return fmt.Errorf("uploading signature to %s in %s: %w", path, d.c.registry, registryHTTPResponseToError(res))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
295
vendor/github.com/containers/image/v5/docker/docker_image_src.go
generated
vendored
295
vendor/github.com/containers/image/v5/docker/docker_image_src.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package docker
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
|
|
@ -9,22 +10,30 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/imagesource/impl"
|
||||
"github.com/containers/image/v5/internal/imagesource/stubs"
|
||||
"github.com/containers/image/v5/internal/iolimits"
|
||||
"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/pkg/blobinfocache/none"
|
||||
"github.com/containers/image/v5/pkg/sysregistriesv2"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerImageSource struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
impl.DoesNotAffectLayerInfosForCopy
|
||||
stubs.ImplementsGetBlobAt
|
||||
|
||||
logicalRef dockerReference // The reference the user requested.
|
||||
physicalRef dockerReference // The actual reference we are accessing (possibly a mirror)
|
||||
c *dockerClient
|
||||
|
|
@ -36,9 +45,13 @@ type dockerImageSource struct {
|
|||
// newImageSource creates a new ImageSource for the specified image reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
||||
registryConfig, err := loadRegistryConfiguration(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registry, err := sysregistriesv2.FindRegistry(sys, ref.ref.Name())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "loading registries configuration")
|
||||
return nil, fmt.Errorf("loading registries configuration: %w", err)
|
||||
}
|
||||
if registry == nil {
|
||||
// No configuration was found for the provided reference, so use the
|
||||
|
|
@ -71,7 +84,7 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef
|
|||
} else {
|
||||
logrus.Debugf("Trying to access %q", pullSource.Reference)
|
||||
}
|
||||
s, err := newImageSourceAttempt(ctx, sys, ref, pullSource)
|
||||
s, err := newImageSourceAttempt(ctx, sys, ref, pullSource, registryConfig)
|
||||
if err == nil {
|
||||
return s, nil
|
||||
}
|
||||
|
|
@ -95,14 +108,15 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef
|
|||
// The paired [] at least have some chance of being unambiguous.
|
||||
extras = append(extras, fmt.Sprintf("[%s: %v]", attempts[i].ref.String(), attempts[i].err))
|
||||
}
|
||||
return nil, errors.Wrapf(primary.err, "(Mirrors also failed: %s): %s", strings.Join(extras, "\n"), primary.ref.String())
|
||||
return nil, fmt.Errorf("(Mirrors also failed: %s): %s: %w", strings.Join(extras, "\n"), primary.ref.String(), primary.err)
|
||||
}
|
||||
}
|
||||
|
||||
// newImageSourceAttempt is an internal helper for newImageSource. Everyone else must call newImageSource.
|
||||
// Given a logicalReference and a pullSource, return a dockerImageSource if it is reachable.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logicalRef dockerReference, pullSource sysregistriesv2.PullSource) (*dockerImageSource, error) {
|
||||
func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logicalRef dockerReference, pullSource sysregistriesv2.PullSource,
|
||||
registryConfig *registryConfiguration) (*dockerImageSource, error) {
|
||||
physicalRef, err := newReference(pullSource.Reference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -117,17 +131,22 @@ func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logica
|
|||
endpointSys = ©
|
||||
}
|
||||
|
||||
client, err := newDockerClientFromRef(endpointSys, physicalRef, false, "pull")
|
||||
client, err := newDockerClientFromRef(endpointSys, physicalRef, registryConfig, false, "pull")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure
|
||||
|
||||
s := &dockerImageSource{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
HasThreadSafeGetBlob: true,
|
||||
}),
|
||||
|
||||
logicalRef: logicalRef,
|
||||
physicalRef: physicalRef,
|
||||
c: client,
|
||||
}
|
||||
s.Compat = impl.AddCompat(s)
|
||||
|
||||
if err := s.ensureManifestIsLoaded(ctx); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -146,23 +165,6 @@ func (s *dockerImageSource) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SupportsGetBlobAt() returns true if GetBlobAt (BlobChunkAccessor) is supported.
|
||||
func (s *dockerImageSource) SupportsGetBlobAt() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 *dockerImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
|
||||
// Alternatively, an empty string is returned unchanged, and invalid values are "simplified" to an empty string.
|
||||
func simplifyContentType(contentType string) string {
|
||||
|
|
@ -192,25 +194,7 @@ func (s *dockerImageSource) GetManifest(ctx context.Context, instanceDigest *dig
|
|||
}
|
||||
|
||||
func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest string) ([]byte, string, error) {
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(s.physicalRef.ref), tagOrDigest)
|
||||
headers := map[string][]string{
|
||||
"Accept": manifest.DefaultRequestedManifestMIMETypes,
|
||||
}
|
||||
res, err := s.c.makeRequest(ctx, http.MethodGet, path, headers, nil, v2Auth, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
logrus.Debugf("Content-Type from manifest GET is %q", res.Header.Get("Content-Type"))
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, "", errors.Wrapf(registryHTTPResponseToError(res), "reading manifest %s in %s", tagOrDigest, s.physicalRef.ref.Name())
|
||||
}
|
||||
|
||||
manblob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxManifestBodySize)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
|
||||
return s.c.fetchManifest(ctx, s.physicalRef, tagOrDigest)
|
||||
}
|
||||
|
||||
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
|
||||
|
|
@ -240,58 +224,6 @@ func (s *dockerImageSource) ensureManifestIsLoaded(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getExternalBlob returns the reader of the first available blob URL from urls, which must not be empty.
|
||||
// This function can return nil reader when no url is supported by this function. In this case, the caller
|
||||
// should fallback to fetch the non-external blob (i.e. pull from the registry).
|
||||
func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) (io.ReadCloser, int64, error) {
|
||||
var (
|
||||
resp *http.Response
|
||||
err error
|
||||
)
|
||||
if len(urls) == 0 {
|
||||
return nil, 0, errors.New("internal error: getExternalBlob called with no URLs")
|
||||
}
|
||||
for _, u := range urls {
|
||||
url, err := url.Parse(u)
|
||||
if err != nil || (url.Scheme != "http" && url.Scheme != "https") {
|
||||
continue // unsupported url. skip this url.
|
||||
}
|
||||
// NOTE: we must not authenticate on additional URLs as those
|
||||
// can be abused to leak credentials or tokens. Please
|
||||
// refer to CVE-2020-15157 for more information.
|
||||
resp, err = s.c.makeRequestToResolvedURL(ctx, http.MethodGet, url, nil, nil, -1, noAuth, nil)
|
||||
if err == nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = errors.Errorf("error fetching external blob from %q: %d (%s)", u, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
logrus.Debug(err)
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if resp == nil && err == nil {
|
||||
return nil, 0, nil // fallback to non-external blob
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return resp.Body, getBlobSize(resp), nil
|
||||
}
|
||||
|
||||
func getBlobSize(resp *http.Response) int64 {
|
||||
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
size = -1
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
|
||||
func (s *dockerImageSource) HasThreadSafeGetBlob() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// splitHTTP200ResponseToPartial splits a 200 response in multiple streams as specified by the chunks
|
||||
func splitHTTP200ResponseToPartial(streams chan io.ReadCloser, errs chan error, body io.ReadCloser, chunks []private.ImageSourceChunk) {
|
||||
defer close(streams)
|
||||
|
|
@ -336,19 +268,23 @@ func handle206Response(streams chan io.ReadCloser, errs chan error, body io.Read
|
|||
}
|
||||
boundary, found := params["boundary"]
|
||||
if !found {
|
||||
errs <- errors.Errorf("could not find boundary")
|
||||
errs <- errors.New("could not find boundary")
|
||||
body.Close()
|
||||
return
|
||||
}
|
||||
buffered := makeBufferedNetworkReader(body, 64, 16384)
|
||||
defer buffered.Close()
|
||||
mr := multipart.NewReader(buffered, boundary)
|
||||
parts := 0
|
||||
for {
|
||||
p, err := mr.NextPart()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
errs <- err
|
||||
}
|
||||
if parts != len(chunks) {
|
||||
errs <- errors.New("invalid number of chunks returned by the server")
|
||||
}
|
||||
return
|
||||
}
|
||||
s := signalCloseReader{
|
||||
|
|
@ -359,9 +295,34 @@ func handle206Response(streams chan io.ReadCloser, errs chan error, body io.Read
|
|||
// NextPart() cannot be called while the current part
|
||||
// is being read, so wait until it is closed
|
||||
<-s.closed
|
||||
parts++
|
||||
}
|
||||
}
|
||||
|
||||
var multipartByteRangesRe = regexp.MustCompile("multipart/byteranges; boundary=([A-Za-z-0-9:]+)")
|
||||
|
||||
func parseMediaType(contentType string) (string, map[string]string, error) {
|
||||
mediaType, params, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
if err == mime.ErrInvalidMediaParameter {
|
||||
// CloudFront returns an invalid MIME type, that contains an unquoted ":" in the boundary
|
||||
// param, let's handle it here.
|
||||
matches := multipartByteRangesRe.FindStringSubmatch(contentType)
|
||||
if len(matches) == 2 {
|
||||
mediaType = "multipart/byteranges"
|
||||
params = map[string]string{
|
||||
"boundary": matches[1],
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
return mediaType, params, err
|
||||
}
|
||||
|
||||
// GetBlobAt returns a sequential channel of readers that contain data for the requested
|
||||
// blob chunks, and a channel that might get a single error value.
|
||||
// The specified chunks must be not overlapping and sorted by their offset.
|
||||
|
|
@ -397,7 +358,7 @@ func (s *dockerImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo,
|
|||
go splitHTTP200ResponseToPartial(streams, errs, res.Body, chunks)
|
||||
return streams, errs, nil
|
||||
case http.StatusPartialContent:
|
||||
mediaType, params, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
|
||||
mediaType, params, err := parseMediaType(res.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -413,7 +374,7 @@ func (s *dockerImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo,
|
|||
default:
|
||||
err := httpResponseToError(res, "Error fetching partial blob")
|
||||
if err == nil {
|
||||
err = errors.Errorf("invalid status code returned when fetching blob %d (%s)", res.StatusCode, http.StatusText(res.StatusCode))
|
||||
err = fmt.Errorf("invalid status code returned when fetching blob %d (%s)", res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
res.Body.Close()
|
||||
return nil, nil, err
|
||||
|
|
@ -424,45 +385,41 @@ func (s *dockerImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo,
|
|||
// 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 *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) {
|
||||
if len(info.URLs) != 0 {
|
||||
r, s, err := s.getExternalBlob(ctx, info.URLs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
} else if r != nil {
|
||||
return r, s, nil
|
||||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(blobsPath, reference.Path(s.physicalRef.ref), info.Digest.String())
|
||||
logrus.Debugf("Downloading %s", path)
|
||||
res, err := s.c.makeRequest(ctx, http.MethodGet, path, nil, nil, v2Auth, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := httpResponseToError(res, "Error fetching blob"); err != nil {
|
||||
res.Body.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
cache.RecordKnownLocation(s.physicalRef.Transport(), bicTransportScope(s.physicalRef), info.Digest, newBICLocationReference(s.physicalRef))
|
||||
return res.Body, getBlobSize(res), nil
|
||||
return s.c.getBlob(ctx, s.physicalRef, info, cache)
|
||||
}
|
||||
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
// 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 *dockerImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
func (s *dockerImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
if err := s.c.detectProperties(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []signature.Signature
|
||||
switch {
|
||||
case s.c.supportsSignatures:
|
||||
return s.getSignaturesFromAPIExtension(ctx, instanceDigest)
|
||||
sigs, err := s.getSignaturesFromAPIExtension(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, sigs...)
|
||||
case s.c.signatureBase != nil:
|
||||
return s.getSignaturesFromLookaside(ctx, instanceDigest)
|
||||
sigs, err := s.getSignaturesFromLookaside(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, sigs...)
|
||||
default:
|
||||
return nil, errors.Errorf("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
|
||||
return nil, errors.New("Internal error: X-Registry-Supports-Signatures extension not supported, and lookaside should not be empty configuration")
|
||||
}
|
||||
|
||||
sigstoreSigs, err := s.getSignaturesFromSigstoreAttachments(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, sigstoreSigs...)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// manifestDigest returns a digest of the manifest, from instanceDigest if non-nil; or from the supplied reference,
|
||||
|
|
@ -483,18 +440,18 @@ func (s *dockerImageSource) manifestDigest(ctx context.Context, instanceDigest *
|
|||
return manifest.Digest(s.cachedManifest)
|
||||
}
|
||||
|
||||
// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||
// getSignaturesFromLookaside implements GetSignaturesWithFormat() from the lookaside location configured in s.c.signatureBase,
|
||||
// which is not nil.
|
||||
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
signatures := [][]byte{}
|
||||
signatures := []signature.Signature{}
|
||||
for i := 0; ; i++ {
|
||||
url := signatureStorageURL(s.c.signatureBase, manifestDigest, i)
|
||||
url := lookasideStorageURL(s.c.signatureBase, manifestDigest, i)
|
||||
signature, missing, err := s.getOneSignature(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -507,20 +464,24 @@ func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, inst
|
|||
return signatures, nil
|
||||
}
|
||||
|
||||
// getOneSignature downloads one signature from url.
|
||||
// If it successfully determines that the signature does not exist, returns with missing set to true and error set to nil.
|
||||
// getOneSignature downloads one signature from url, and returns (signature, false, nil)
|
||||
// If it successfully determines that the signature does not exist, returns (nil, true, nil).
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (signature []byte, missing bool, err error) {
|
||||
func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (signature.Signature, bool, error) {
|
||||
switch url.Scheme {
|
||||
case "file":
|
||||
logrus.Debugf("Reading %s", url.Path)
|
||||
sig, err := os.ReadFile(url.Path)
|
||||
sigBlob, err := os.ReadFile(url.Path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, true, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
sig, err := signature.FromBlob(sigBlob)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("parsing signature %q: %w", url.Path, err)
|
||||
}
|
||||
return sig, false, nil
|
||||
|
||||
case "http", "https":
|
||||
|
|
@ -537,21 +498,25 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (
|
|||
if res.StatusCode == http.StatusNotFound {
|
||||
return nil, true, nil
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
return nil, false, errors.Errorf("Error reading signature from %s: status %d (%s)", url.Redacted(), res.StatusCode, http.StatusText(res.StatusCode))
|
||||
return nil, false, fmt.Errorf("reading signature from %s: status %d (%s)", url.Redacted(), res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
sig, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureBodySize)
|
||||
sigBlob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureBodySize)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
sig, err := signature.FromBlob(sigBlob)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("parsing signature %s: %w", url.Redacted(), err)
|
||||
}
|
||||
return sig, false, nil
|
||||
|
||||
default:
|
||||
return nil, false, errors.Errorf("Unsupported scheme when reading signature from %s", url.Redacted())
|
||||
return nil, false, fmt.Errorf("Unsupported scheme when reading signature from %s", url.Redacted())
|
||||
}
|
||||
}
|
||||
|
||||
// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
// getSignaturesFromAPIExtension implements GetSignaturesWithFormat() using the X-Registry-Supports-Signatures API extension.
|
||||
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -562,17 +527,59 @@ func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, i
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var sigs [][]byte
|
||||
var sigs []signature.Signature
|
||||
for _, sig := range parsedBody.Signatures {
|
||||
if sig.Version == extensionSignatureSchemaVersion && sig.Type == extensionSignatureTypeAtomic {
|
||||
sigs = append(sigs, sig.Content)
|
||||
sigs = append(sigs, signature.SimpleSigningFromBlob(sig.Content))
|
||||
}
|
||||
}
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
func (s *dockerImageSource) getSignaturesFromSigstoreAttachments(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
if !s.c.useSigstoreAttachments {
|
||||
logrus.Debugf("Not looking for sigstore attachments: disabled by configuration")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ociManifest, err := s.c.getSigstoreAttachmentManifest(ctx, s.physicalRef, manifestDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ociManifest == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Found a sigstore attachment manifest with %d layers", len(ociManifest.Layers))
|
||||
res := []signature.Signature{}
|
||||
for layerIndex, layer := range ociManifest.Layers {
|
||||
// Note that this copies all kinds of attachments: attestations, and whatever else is there,
|
||||
// not just signatures. We leave the signature consumers to decide based on the MIME type.
|
||||
logrus.Debugf("Fetching sigstore attachment %d/%d: %s", layerIndex+1, len(ociManifest.Layers), layer.Digest.String())
|
||||
// We don’t benefit from a real BlobInfoCache here because we never try to reuse/mount attachment payloads.
|
||||
// That might eventually need to change if payloads grow to be not just signatures, but something
|
||||
// significantly large.
|
||||
payload, err := s.c.getOCIDescriptorContents(ctx, s.physicalRef, layer, iolimits.MaxSignatureBodySize,
|
||||
none.NoCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, signature.SigstoreFromComponents(layer.MediaType, payload, layer.Annotations))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// deleteImage deletes the named image from the registry, if supported.
|
||||
func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerReference) error {
|
||||
registryConfig, err := loadRegistryConfiguration(sys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// docker/distribution does not document what action should be used for deleting images.
|
||||
//
|
||||
// Current docker/distribution requires "pull" for reading the manifest and "delete" for deleting it.
|
||||
|
|
@ -580,7 +587,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
|
|||
// OpenShift ignores the action string (both the password and the token is an OpenShift API token identifying a user).
|
||||
//
|
||||
// We have to hard-code a single string, luckily both docker/distribution and quay.io support "*" to mean "everything".
|
||||
c, err := newDockerClientFromRef(sys, ref, true, "*")
|
||||
c, err := newDockerClientFromRef(sys, ref, registryConfig, true, "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -605,9 +612,9 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
|
|||
switch get.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusNotFound:
|
||||
return errors.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry", ref.ref)
|
||||
return fmt.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry", ref.ref)
|
||||
default:
|
||||
return errors.Errorf("Failed to delete %v: %s (%v)", ref.ref, manifestBody, get.Status)
|
||||
return fmt.Errorf("Failed to delete %v: %s (%v)", ref.ref, manifestBody, get.Status)
|
||||
}
|
||||
|
||||
manifestDigest, err := manifest.Digest(manifestBody)
|
||||
|
|
@ -629,11 +636,11 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
|
|||
return err
|
||||
}
|
||||
if delete.StatusCode != http.StatusAccepted {
|
||||
return errors.Errorf("Failed to delete %v: %s (%v)", deletePath, string(body), delete.Status)
|
||||
return fmt.Errorf("Failed to delete %v: %s (%v)", deletePath, string(body), delete.Status)
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
url := signatureStorageURL(c.signatureBase, manifestDigest, i)
|
||||
url := lookasideStorageURL(c.signatureBase, manifestDigest, i)
|
||||
missing, err := c.deleteOneSignature(url)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
10
vendor/github.com/containers/image/v5/docker/docker_transport.go
generated
vendored
10
vendor/github.com/containers/image/v5/docker/docker_transport.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package docker
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
|
@ -9,7 +10,6 @@ import (
|
|||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -49,7 +49,7 @@ type dockerReference struct {
|
|||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference.
|
||||
func ParseReference(refString string) (types.ImageReference, error) {
|
||||
if !strings.HasPrefix(refString, "//") {
|
||||
return nil, errors.Errorf("docker: image reference %s does not start with //", refString)
|
||||
return nil, fmt.Errorf("docker: image reference %s does not start with //", refString)
|
||||
}
|
||||
ref, err := reference.ParseNormalizedNamed(strings.TrimPrefix(refString, "//"))
|
||||
if err != nil {
|
||||
|
|
@ -67,7 +67,7 @@ func NewReference(ref reference.Named) (types.ImageReference, error) {
|
|||
// newReference returns a dockerReference for a named reference.
|
||||
func newReference(ref reference.Named) (dockerReference, error) {
|
||||
if reference.IsNameOnly(ref) {
|
||||
return dockerReference{}, errors.Errorf("Docker reference %s has neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
return dockerReference{}, fmt.Errorf("Docker reference %s has neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
}
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// The docker/distribution API does not really support that (we can’t ask for an image with a specific
|
||||
|
|
@ -77,7 +77,7 @@ func newReference(ref reference.Named) (dockerReference, error) {
|
|||
_, isTagged := ref.(reference.NamedTagged)
|
||||
_, isDigested := ref.(reference.Canonical)
|
||||
if isTagged && isDigested {
|
||||
return dockerReference{}, errors.Errorf("Docker references with both a tag and digest are currently not supported")
|
||||
return dockerReference{}, errors.New("Docker references with both a tag and digest are currently not supported")
|
||||
}
|
||||
|
||||
return dockerReference{
|
||||
|
|
@ -164,5 +164,5 @@ func (ref dockerReference) tagOrDigest() (string, error) {
|
|||
return ref.Tag(), nil
|
||||
}
|
||||
// This should not happen, NewReference above refuses reference.IsNameOnly values.
|
||||
return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", reference.FamiliarString(ref.ref))
|
||||
return "", fmt.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", reference.FamiliarString(ref.ref))
|
||||
}
|
||||
|
|
|
|||
3
vendor/github.com/containers/image/v5/docker/errors.go
generated
vendored
3
vendor/github.com/containers/image/v5/docker/errors.go
generated
vendored
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/registry/client"
|
||||
perrors "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -42,7 +41,7 @@ func httpResponseToError(res *http.Response, context string) error {
|
|||
if context != "" {
|
||||
context = context + ": "
|
||||
}
|
||||
return perrors.Errorf("%sinvalid status code from registry %d (%s)", context, res.StatusCode, http.StatusText(res.StatusCode))
|
||||
return fmt.Errorf("%sinvalid status code from registry %d (%s)", context, res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
110
vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go
generated
vendored
110
vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go
generated
vendored
|
|
@ -4,20 +4,29 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/imagedestination/impl"
|
||||
"github.com/containers/image/v5/internal/imagedestination/stubs"
|
||||
"github.com/containers/image/v5/internal/iolimits"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/streamdigest"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Destination is a partial implementation of types.ImageDestination for writing to an io.Writer.
|
||||
// Destination is a partial implementation of private.ImageDestination for writing to an io.Writer.
|
||||
type Destination struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
stubs.NoSignaturesInitialize
|
||||
|
||||
archive *Writer
|
||||
repoTags []reference.NamedTagged
|
||||
// Other state.
|
||||
|
|
@ -26,16 +35,34 @@ type Destination struct {
|
|||
}
|
||||
|
||||
// NewDestination returns a tarfile.Destination adding images to the specified Writer.
|
||||
func NewDestination(sys *types.SystemContext, archive *Writer, ref reference.NamedTagged) *Destination {
|
||||
func NewDestination(sys *types.SystemContext, archive *Writer, transportName string, ref reference.NamedTagged) *Destination {
|
||||
repoTags := []reference.NamedTagged{}
|
||||
if ref != nil {
|
||||
repoTags = append(repoTags, ref)
|
||||
}
|
||||
return &Destination{
|
||||
dest := &Destination{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
SupportedManifestMIMETypes: []string{
|
||||
manifest.DockerV2Schema2MediaType, // We rely on the types.Image.UpdatedImage schema conversion capabilities.
|
||||
},
|
||||
DesiredLayerCompression: types.Decompress,
|
||||
AcceptsForeignLayerURLs: false,
|
||||
MustMatchRuntimeOS: false,
|
||||
IgnoresEmbeddedDockerReference: false, // N/A, we only accept schema2 images where EmbeddedDockerReferenceConflicts() is always false.
|
||||
// The code _is_ actually thread-safe, but apart from computing sizes/digests of layers where
|
||||
// this is unknown in advance, the actual copy is serialized by d.archive, so there probably isn’t
|
||||
// much benefit from concurrency, mostly just extra CPU, memory and I/O contention.
|
||||
HasThreadSafePutBlob: false,
|
||||
}),
|
||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartialRaw(transportName),
|
||||
NoSignaturesInitialize: stubs.NoSignatures("Storing signatures for docker tar files is not supported"),
|
||||
|
||||
archive: archive,
|
||||
repoTags: repoTags,
|
||||
sysCtx: sys,
|
||||
}
|
||||
dest.Compat = impl.AddCompat(dest)
|
||||
return dest
|
||||
}
|
||||
|
||||
// AddRepoTags adds the specified tags to the destination's repoTags.
|
||||
|
|
@ -43,54 +70,14 @@ func (d *Destination) AddRepoTags(tags []reference.NamedTagged) {
|
|||
d.repoTags = append(d.repoTags, tags...)
|
||||
}
|
||||
|
||||
// SupportedManifestMIMETypes tells which manifest mime types the destination supports
|
||||
// If an empty slice or nil it's returned, then any mime type can be tried to upload
|
||||
func (d *Destination) SupportedManifestMIMETypes() []string {
|
||||
return []string{
|
||||
manifest.DockerV2Schema2MediaType, // We rely on the types.Image.UpdatedImage schema conversion capabilities.
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *Destination) SupportsSignatures(ctx context.Context) error {
|
||||
return errors.Errorf("Storing signatures for docker tar files is not supported")
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (d *Destination) AcceptsForeignLayerURLs() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime architecture and OS. False otherwise.
|
||||
func (d *Destination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *Destination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // N/A, we only accept schema2 images where EmbeddedDockerReferenceConflicts() is always false.
|
||||
}
|
||||
|
||||
// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently.
|
||||
func (d *Destination) HasThreadSafePutBlob() bool {
|
||||
// The code _is_ actually thread-safe, but apart from computing sizes/digests of layers where
|
||||
// this is unknown in advance, the actual copy is serialized by d.archive, so there probably isn’t
|
||||
// much benefit from concurrency, mostly just extra CPU, memory and I/O contention.
|
||||
return false
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// 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.
|
||||
// May update cache.
|
||||
// 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 *Destination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) {
|
||||
func (d *Destination) PutBlobWithOptions(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions) (types.BlobInfo, error) {
|
||||
// Ouch, we need to stream the blob into a temporary file just to determine the size.
|
||||
// When the layer is decompressed, we also have to generate the digest on uncompressed data.
|
||||
if inputInfo.Size == -1 || inputInfo.Digest == "" {
|
||||
|
|
@ -118,14 +105,14 @@ func (d *Destination) PutBlob(ctx context.Context, stream io.Reader, inputInfo t
|
|||
return reusedInfo, nil
|
||||
}
|
||||
|
||||
if isConfig {
|
||||
if options.IsConfig {
|
||||
buf, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "reading Config file stream")
|
||||
return types.BlobInfo{}, fmt.Errorf("reading Config file stream: %w", err)
|
||||
}
|
||||
d.config = buf
|
||||
if err := d.archive.sendFileLocked(d.archive.configPath(inputInfo.Digest), inputInfo.Size, bytes.NewReader(buf)); err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "writing Config file")
|
||||
return types.BlobInfo{}, fmt.Errorf("writing Config file: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := d.archive.sendFileLocked(d.archive.physicalLayerPath(inputInfo.Digest), inputInfo.Size, stream); err != nil {
|
||||
|
|
@ -136,16 +123,14 @@ func (d *Destination) PutBlob(ctx context.Context, stream io.Reader, inputInfo t
|
|||
return types.BlobInfo{Digest: inputInfo.Digest, Size: inputInfo.Size}, nil
|
||||
}
|
||||
|
||||
// TryReusingBlob checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
|
||||
// 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 canSubstitute, TryReusingBlob can use an equivalent equivalent of the desired blob; in that case the returned info may not match the input.
|
||||
// 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.
|
||||
// May use and/or update cache.
|
||||
func (d *Destination) TryReusingBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache, canSubstitute bool) (bool, types.BlobInfo, error) {
|
||||
func (d *Destination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, types.BlobInfo, error) {
|
||||
if err := d.archive.lock(); err != nil {
|
||||
return false, types.BlobInfo{}, err
|
||||
}
|
||||
|
|
@ -168,10 +153,10 @@ func (d *Destination) PutManifest(ctx context.Context, m []byte, instanceDigest
|
|||
// so the caller trying a different manifest kind would be pointless.
|
||||
var man manifest.Schema2
|
||||
if err := json.Unmarshal(m, &man); err != nil {
|
||||
return errors.Wrap(err, "parsing manifest")
|
||||
return fmt.Errorf("parsing manifest: %w", err)
|
||||
}
|
||||
if man.SchemaVersion != 2 || man.MediaType != manifest.DockerV2Schema2MediaType {
|
||||
return errors.Errorf("Unsupported manifest type, need a Docker schema 2 manifest")
|
||||
return errors.New("Unsupported manifest type, need a Docker schema 2 manifest")
|
||||
}
|
||||
|
||||
if err := d.archive.lock(); err != nil {
|
||||
|
|
@ -185,16 +170,3 @@ func (d *Destination) PutManifest(ctx context.Context, m []byte, instanceDigest
|
|||
|
||||
return d.archive.ensureManifestItemLocked(man.LayersDescriptors, man.ConfigDescriptor.Digest, d.repoTags)
|
||||
}
|
||||
|
||||
// PutSignatures would add the given signatures to the docker tarfile (currently not supported).
|
||||
// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so
|
||||
// there can be no secondary manifests. MUST be called after PutManifest (signatures reference manifest contents).
|
||||
func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
|
||||
if instanceDigest != nil {
|
||||
return errors.Errorf(`Manifest lists are not supported for docker tar files`)
|
||||
}
|
||||
if len(signatures) != 0 {
|
||||
return errors.Errorf("Storing signatures for docker tar files is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
29
vendor/github.com/containers/image/v5/docker/internal/tarfile/reader.go
generated
vendored
29
vendor/github.com/containers/image/v5/docker/internal/tarfile/reader.go
generated
vendored
|
|
@ -3,6 +3,8 @@ package tarfile
|
|||
import (
|
||||
"archive/tar"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
|
@ -12,7 +14,6 @@ import (
|
|||
"github.com/containers/image/v5/internal/tmpdir"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Reader is a ((docker save)-formatted) tar archive that allows random access to any component.
|
||||
|
|
@ -29,7 +30,7 @@ type Reader struct {
|
|||
func NewReaderFromFile(sys *types.SystemContext, path string) (*Reader, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "opening file %q", path)
|
||||
return nil, fmt.Errorf("opening file %q: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ func NewReaderFromFile(sys *types.SystemContext, path string) (*Reader, error) {
|
|||
// as a source. Otherwise we pass the stream to NewReaderFromStream.
|
||||
stream, isCompressed, err := compression.AutoDecompress(file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "detecting compression for file %q", path)
|
||||
return nil, fmt.Errorf("detecting compression for file %q: %w", path, err)
|
||||
}
|
||||
defer stream.Close()
|
||||
if !isCompressed {
|
||||
|
|
@ -54,7 +55,7 @@ func NewReaderFromStream(sys *types.SystemContext, inputStream io.Reader) (*Read
|
|||
// Save inputStream to a temporary file
|
||||
tarCopyFile, err := os.CreateTemp(tmpdir.TemporaryDirectoryForBigFiles(sys), "docker-tar")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating temporary file")
|
||||
return nil, fmt.Errorf("creating temporary file: %w", err)
|
||||
}
|
||||
defer tarCopyFile.Close()
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ func NewReaderFromStream(sys *types.SystemContext, inputStream io.Reader) (*Read
|
|||
// giving users really confusing "invalid tar header" errors).
|
||||
uncompressedStream, _, err := compression.AutoDecompress(inputStream)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "auto-decompressing input")
|
||||
return nil, fmt.Errorf("auto-decompressing input: %w", err)
|
||||
}
|
||||
defer uncompressedStream.Close()
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ func NewReaderFromStream(sys *types.SystemContext, inputStream io.Reader) (*Read
|
|||
// TODO: This can take quite some time, and should ideally be cancellable
|
||||
// using a context.Context.
|
||||
if _, err := io.Copy(tarCopyFile, uncompressedStream); err != nil {
|
||||
return nil, errors.Wrapf(err, "copying contents to temporary file %q", tarCopyFile.Name())
|
||||
return nil, fmt.Errorf("copying contents to temporary file %q: %w", tarCopyFile.Name(), err)
|
||||
}
|
||||
succeeded = true
|
||||
|
||||
|
|
@ -112,7 +113,7 @@ func newReader(path string, removeOnClose bool) (*Reader, error) {
|
|||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(bytes, &r.Manifest); err != nil {
|
||||
return nil, errors.Wrap(err, "decoding tar manifest.json")
|
||||
return nil, fmt.Errorf("decoding tar manifest.json: %w", err)
|
||||
}
|
||||
|
||||
succeeded = true
|
||||
|
|
@ -136,7 +137,7 @@ func (r *Reader) Close() error {
|
|||
func (r *Reader) ChooseManifestItem(ref reference.NamedTagged, sourceIndex int) (*ManifestItem, int, error) {
|
||||
switch {
|
||||
case ref != nil && sourceIndex != -1:
|
||||
return nil, -1, errors.Errorf("Internal error: Cannot have both ref %s and source index @%d",
|
||||
return nil, -1, fmt.Errorf("Internal error: Cannot have both ref %s and source index @%d",
|
||||
ref.String(), sourceIndex)
|
||||
|
||||
case ref != nil:
|
||||
|
|
@ -145,25 +146,25 @@ func (r *Reader) ChooseManifestItem(ref reference.NamedTagged, sourceIndex int)
|
|||
for tagIndex, tag := range r.Manifest[i].RepoTags {
|
||||
parsedTag, err := reference.ParseNormalizedNamed(tag)
|
||||
if err != nil {
|
||||
return nil, -1, errors.Wrapf(err, "Invalid tag %#v in manifest.json item @%d", tag, i)
|
||||
return nil, -1, fmt.Errorf("Invalid tag %#v in manifest.json item @%d: %w", tag, i, err)
|
||||
}
|
||||
if parsedTag.String() == refString {
|
||||
return &r.Manifest[i], tagIndex, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, -1, errors.Errorf("Tag %#v not found", refString)
|
||||
return nil, -1, fmt.Errorf("Tag %#v not found", refString)
|
||||
|
||||
case sourceIndex != -1:
|
||||
if sourceIndex >= len(r.Manifest) {
|
||||
return nil, -1, errors.Errorf("Invalid source index @%d, only %d manifest items available",
|
||||
return nil, -1, fmt.Errorf("Invalid source index @%d, only %d manifest items available",
|
||||
sourceIndex, len(r.Manifest))
|
||||
}
|
||||
return &r.Manifest[sourceIndex], -1, nil
|
||||
|
||||
default:
|
||||
if len(r.Manifest) != 1 {
|
||||
return nil, -1, errors.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(r.Manifest))
|
||||
return nil, -1, fmt.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(r.Manifest))
|
||||
}
|
||||
return &r.Manifest[0], -1, nil
|
||||
}
|
||||
|
|
@ -226,7 +227,7 @@ func (r *Reader) openTarComponent(componentPath string) (io.ReadCloser, error) {
|
|||
}
|
||||
|
||||
if !header.FileInfo().Mode().IsRegular() {
|
||||
return nil, errors.Errorf("Error reading tar archive component %s: not a regular file", header.Name)
|
||||
return nil, fmt.Errorf("Error reading tar archive component %s: not a regular file", header.Name)
|
||||
}
|
||||
succeeded = true
|
||||
return &tarReadCloser{Reader: tarReader, backingFile: f}, nil
|
||||
|
|
@ -257,7 +258,7 @@ func findTarComponent(inputFile io.Reader, componentPath string) (*tar.Reader, *
|
|||
func (r *Reader) readTarComponent(path string, limit int) ([]byte, error) {
|
||||
file, err := r.openTarComponent(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "loading tar component %s", path)
|
||||
return nil, fmt.Errorf("loading tar component %s: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
bytes, err := iolimits.ReadAtMost(file, limit)
|
||||
|
|
|
|||
69
vendor/github.com/containers/image/v5/docker/internal/tarfile/src.go
generated
vendored
69
vendor/github.com/containers/image/v5/docker/internal/tarfile/src.go
generated
vendored
|
|
@ -5,22 +5,31 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/imagesource/impl"
|
||||
"github.com/containers/image/v5/internal/imagesource/stubs"
|
||||
"github.com/containers/image/v5/internal/iolimits"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Source is a partial implementation of types.ImageSource for reading from tarPath.
|
||||
type Source struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
impl.NoSignatures
|
||||
impl.DoesNotAffectLayerInfosForCopy
|
||||
stubs.NoGetBlobAtInitialize
|
||||
|
||||
archive *Reader
|
||||
closeArchive bool // .Close() the archive when the source is closed.
|
||||
// If ref is nil and sourceIndex is -1, indicates the only image in the archive.
|
||||
|
|
@ -46,13 +55,20 @@ type layerInfo struct {
|
|||
// NewSource returns a tarfile.Source for an image in the specified archive matching ref
|
||||
// and sourceIndex (or the only image if they are (nil, -1)).
|
||||
// The archive will be closed if closeArchive
|
||||
func NewSource(archive *Reader, closeArchive bool, ref reference.NamedTagged, sourceIndex int) *Source {
|
||||
return &Source{
|
||||
func NewSource(archive *Reader, closeArchive bool, transportName string, ref reference.NamedTagged, sourceIndex int) *Source {
|
||||
s := &Source{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
HasThreadSafeGetBlob: true,
|
||||
}),
|
||||
NoGetBlobAtInitialize: stubs.NoGetBlobAtRaw(transportName),
|
||||
|
||||
archive: archive,
|
||||
closeArchive: closeArchive,
|
||||
ref: ref,
|
||||
sourceIndex: sourceIndex,
|
||||
}
|
||||
s.Compat = impl.AddCompat(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// ensureCachedDataIsPresent loads data necessary for any of the public accessors.
|
||||
|
|
@ -79,10 +95,10 @@ func (s *Source) ensureCachedDataIsPresentPrivate() error {
|
|||
}
|
||||
var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
|
||||
if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
|
||||
return errors.Wrapf(err, "decoding tar config %s", tarManifest.Config)
|
||||
return fmt.Errorf("decoding tar config %s: %w", tarManifest.Config, err)
|
||||
}
|
||||
if parsedConfig.RootFS == nil {
|
||||
return errors.Errorf("Invalid image config (rootFS is not set): %s", tarManifest.Config)
|
||||
return fmt.Errorf("Invalid image config (rootFS is not set): %s", tarManifest.Config)
|
||||
}
|
||||
|
||||
knownLayers, err := s.prepareLayerData(tarManifest, &parsedConfig)
|
||||
|
|
@ -115,7 +131,7 @@ func (s *Source) TarManifest() []ManifestItem {
|
|||
func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manifest.Schema2Image) (map[digest.Digest]*layerInfo, error) {
|
||||
// Collect layer data available in manifest and config.
|
||||
if len(tarManifest.Layers) != len(parsedConfig.RootFS.DiffIDs) {
|
||||
return nil, errors.Errorf("Inconsistent layer count: %d in manifest, %d in config", len(tarManifest.Layers), len(parsedConfig.RootFS.DiffIDs))
|
||||
return nil, fmt.Errorf("Inconsistent layer count: %d in manifest, %d in config", len(tarManifest.Layers), len(parsedConfig.RootFS.DiffIDs))
|
||||
}
|
||||
knownLayers := map[digest.Digest]*layerInfo{}
|
||||
unknownLayerSizes := map[string]*layerInfo{} // Points into knownLayers, a "to do list" of items with unknown sizes.
|
||||
|
|
@ -128,7 +144,7 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manif
|
|||
}
|
||||
layerPath := path.Clean(tarManifest.Layers[i])
|
||||
if _, ok := unknownLayerSizes[layerPath]; ok {
|
||||
return nil, errors.Errorf("Layer tarfile %s used for two different DiffID values", layerPath)
|
||||
return nil, fmt.Errorf("Layer tarfile %s used for two different DiffID values", layerPath)
|
||||
}
|
||||
li := &layerInfo{ // A new element in each iteration
|
||||
path: layerPath,
|
||||
|
|
@ -163,7 +179,7 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manif
|
|||
// the slower method of checking if it's compressed.
|
||||
uncompressedStream, isCompressed, err := compression.AutoDecompress(t)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "auto-decompressing %s to determine its size", layerPath)
|
||||
return nil, fmt.Errorf("auto-decompressing %s to determine its size: %w", layerPath, err)
|
||||
}
|
||||
defer uncompressedStream.Close()
|
||||
|
||||
|
|
@ -171,7 +187,7 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manif
|
|||
if isCompressed {
|
||||
uncompressedSize, err = io.Copy(io.Discard, uncompressedStream)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading %s to find its size", layerPath)
|
||||
return nil, fmt.Errorf("reading %s to find its size: %w", layerPath, err)
|
||||
}
|
||||
}
|
||||
li.size = uncompressedSize
|
||||
|
|
@ -179,7 +195,7 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manif
|
|||
}
|
||||
}
|
||||
if len(unknownLayerSizes) != 0 {
|
||||
return nil, errors.Errorf("Some layer tarfiles are missing in the tarball") // This could do with a better error reporting, if this ever happened in practice.
|
||||
return nil, errors.New("Some layer tarfiles are missing in the tarball") // This could do with a better error reporting, if this ever happened in practice.
|
||||
}
|
||||
|
||||
return knownLayers, nil
|
||||
|
|
@ -213,7 +229,7 @@ func (s *Source) GetManifest(ctx context.Context, instanceDigest *digest.Digest)
|
|||
for _, diffID := range s.orderedDiffIDList {
|
||||
li, ok := s.knownLayers[diffID]
|
||||
if !ok {
|
||||
return nil, "", errors.Errorf("Internal inconsistency: Information about layer %s missing", diffID)
|
||||
return nil, "", fmt.Errorf("Internal inconsistency: Information about layer %s missing", diffID)
|
||||
}
|
||||
m.LayersDescriptors = append(m.LayersDescriptors, manifest.Schema2Descriptor{
|
||||
Digest: diffID, // diffID is a digest of the uncompressed tarball
|
||||
|
|
@ -248,11 +264,6 @@ func (r uncompressedReadCloser) Close() error {
|
|||
return res
|
||||
}
|
||||
|
||||
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
|
||||
func (s *Source) HasThreadSafeGetBlob() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
@ -291,7 +302,7 @@ func (s *Source) GetBlob(ctx context.Context, info types.BlobInfo, cache types.B
|
|||
|
||||
uncompressedStream, _, err := compression.AutoDecompress(underlyingStream)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "auto-decompressing blob %s", info.Digest)
|
||||
return nil, 0, fmt.Errorf("auto-decompressing blob %s: %w", info.Digest, err)
|
||||
}
|
||||
|
||||
newStream := uncompressedReadCloser{
|
||||
|
|
@ -304,27 +315,5 @@ func (s *Source) GetBlob(ctx context.Context, info types.BlobInfo, cache types.B
|
|||
return newStream, li.size, nil
|
||||
}
|
||||
|
||||
return nil, 0, errors.Errorf("Unknown blob %s", info.Digest)
|
||||
}
|
||||
|
||||
// GetSignatures returns the image's signatures. 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 there can be no secondary manifests.
|
||||
func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
if instanceDigest != nil {
|
||||
// How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
return nil, errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
return [][]byte{}, 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 no 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 *Source) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
return nil, 0, fmt.Errorf("Unknown blob %s", info.Digest)
|
||||
}
|
||||
|
|
|
|||
22
vendor/github.com/containers/image/v5/docker/internal/tarfile/writer.go
generated
vendored
22
vendor/github.com/containers/image/v5/docker/internal/tarfile/writer.go
generated
vendored
|
|
@ -4,6 +4,7 @@ import (
|
|||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
|
@ -15,7 +16,6 @@ import (
|
|||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ func (w *Writer) unlock() {
|
|||
// The caller must have locked the Writer.
|
||||
func (w *Writer) tryReusingBlobLocked(info types.BlobInfo) (bool, types.BlobInfo, error) {
|
||||
if info.Digest == "" {
|
||||
return false, types.BlobInfo{}, errors.Errorf("Can not check for a blob with unknown digest")
|
||||
return false, types.BlobInfo{}, errors.New("Can not check for a blob with unknown digest")
|
||||
}
|
||||
if blob, ok := w.blobs[info.Digest]; ok {
|
||||
return true, types.BlobInfo{Digest: info.Digest, Size: blob.Size}, nil
|
||||
|
|
@ -94,16 +94,16 @@ func (w *Writer) ensureSingleLegacyLayerLocked(layerID string, layerDigest diges
|
|||
// See also the comment in physicalLayerPath.
|
||||
physicalLayerPath := w.physicalLayerPath(layerDigest)
|
||||
if err := w.sendSymlinkLocked(filepath.Join(layerID, legacyLayerFileName), filepath.Join("..", physicalLayerPath)); err != nil {
|
||||
return errors.Wrap(err, "creating layer symbolic link")
|
||||
return fmt.Errorf("creating layer symbolic link: %w", err)
|
||||
}
|
||||
|
||||
b := []byte("1.0")
|
||||
if err := w.sendBytesLocked(filepath.Join(layerID, legacyVersionFileName), b); err != nil {
|
||||
return errors.Wrap(err, "writing VERSION file")
|
||||
return fmt.Errorf("writing VERSION file: %w", err)
|
||||
}
|
||||
|
||||
if err := w.sendBytesLocked(filepath.Join(layerID, legacyConfigFileName), configBytes); err != nil {
|
||||
return errors.Wrap(err, "writing config json file")
|
||||
return fmt.Errorf("writing config json file: %w", err)
|
||||
}
|
||||
|
||||
w.legacyLayers[layerID] = struct{}{}
|
||||
|
|
@ -128,7 +128,7 @@ func (w *Writer) writeLegacyMetadataLocked(layerDescriptors []manifest.Schema2De
|
|||
var config map[string]*json.RawMessage
|
||||
err := json.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshaling config")
|
||||
return fmt.Errorf("unmarshaling config: %w", err)
|
||||
}
|
||||
for _, attr := range [7]string{"architecture", "config", "container", "container_config", "created", "docker_version", "os"} {
|
||||
layerConfig[attr] = config[attr]
|
||||
|
|
@ -152,7 +152,7 @@ func (w *Writer) writeLegacyMetadataLocked(layerDescriptors []manifest.Schema2De
|
|||
layerConfig["layer_id"] = chainID
|
||||
b, err := json.Marshal(layerConfig) // Note that layerConfig["id"] is not set yet at this point.
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshaling layer config")
|
||||
return fmt.Errorf("marshaling layer config: %w", err)
|
||||
}
|
||||
delete(layerConfig, "layer_id")
|
||||
layerID := digest.Canonical.FromBytes(b).Hex()
|
||||
|
|
@ -160,7 +160,7 @@ func (w *Writer) writeLegacyMetadataLocked(layerDescriptors []manifest.Schema2De
|
|||
|
||||
configBytes, err := json.Marshal(layerConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshaling layer config")
|
||||
return fmt.Errorf("marshaling layer config: %w", err)
|
||||
}
|
||||
|
||||
if err := w.ensureSingleLegacyLayerLocked(layerID, l.Digest, configBytes); err != nil {
|
||||
|
|
@ -280,10 +280,10 @@ func (w *Writer) Close() error {
|
|||
|
||||
b, err = json.Marshal(w.repositories)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshaling repositories")
|
||||
return fmt.Errorf("marshaling repositories: %w", err)
|
||||
}
|
||||
if err := w.sendBytesLocked(legacyRepositoriesFileName, b); err != nil {
|
||||
return errors.Wrap(err, "writing config json file")
|
||||
return fmt.Errorf("writing config json file: %w", err)
|
||||
}
|
||||
|
||||
if err := w.tar.Close(); err != nil {
|
||||
|
|
@ -375,7 +375,7 @@ func (w *Writer) sendFileLocked(path string, expectedSize int64, stream io.Reade
|
|||
return err
|
||||
}
|
||||
if size != expectedSize {
|
||||
return errors.Errorf("Size mismatch when copying %s, expected %d, got %d", path, expectedSize, size)
|
||||
return fmt.Errorf("Size mismatch when copying %s, expected %d, got %d", path, expectedSize, size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
6
vendor/github.com/containers/image/v5/docker/paths_common.go
generated
vendored
Normal file
6
vendor/github.com/containers/image/v5/docker/paths_common.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
//go:build !freebsd
|
||||
// +build !freebsd
|
||||
|
||||
package docker
|
||||
|
||||
const etcDir = "/etc"
|
||||
6
vendor/github.com/containers/image/v5/docker/paths_freebsd.go
generated
vendored
Normal file
6
vendor/github.com/containers/image/v5/docker/paths_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package docker
|
||||
|
||||
const etcDir = "/usr/local/etc"
|
||||
7
vendor/github.com/containers/image/v5/docker/policyconfiguration/naming.go
generated
vendored
7
vendor/github.com/containers/image/v5/docker/policyconfiguration/naming.go
generated
vendored
|
|
@ -1,10 +1,11 @@
|
|||
package policyconfiguration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup,
|
||||
|
|
@ -16,9 +17,9 @@ func DockerReferenceIdentity(ref reference.Named) (string, error) {
|
|||
digested, isDigested := ref.(reference.Canonical)
|
||||
switch {
|
||||
case isTagged && isDigested: // Note that this CAN actually happen.
|
||||
return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", reference.FamiliarString(ref))
|
||||
return "", fmt.Errorf("Unexpected Docker reference %s with both a name and a digest", reference.FamiliarString(ref))
|
||||
case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.IsNameOnly()
|
||||
return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
return "", fmt.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
case isTagged:
|
||||
res = res + ":" + tagged.Tag()
|
||||
case isDigested:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
@ -14,7 +15,6 @@ import (
|
|||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -25,15 +25,15 @@ var systemRegistriesDirPath = builtinRegistriesDirPath
|
|||
|
||||
// builtinRegistriesDirPath is the path to registries.d.
|
||||
// DO NOT change this, instead see systemRegistriesDirPath above.
|
||||
const builtinRegistriesDirPath = "/etc/containers/registries.d"
|
||||
const builtinRegistriesDirPath = etcDir + "/containers/registries.d"
|
||||
|
||||
// userRegistriesDirPath is the path to the per user registries.d.
|
||||
var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d")
|
||||
|
||||
// defaultUserDockerDir is the default sigstore directory for unprivileged user
|
||||
// defaultUserDockerDir is the default lookaside directory for unprivileged user
|
||||
var defaultUserDockerDir = filepath.FromSlash(".local/share/containers/sigstore")
|
||||
|
||||
// defaultDockerDir is the default sigstore directory for root
|
||||
// defaultDockerDir is the default lookaside directory for root
|
||||
var defaultDockerDir = "/var/lib/containers/sigstore"
|
||||
|
||||
// registryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all.
|
||||
|
|
@ -46,51 +46,39 @@ type registryConfiguration struct {
|
|||
|
||||
// registryNamespace defines lookaside locations for a single namespace.
|
||||
type registryNamespace struct {
|
||||
SigStore string `json:"sigstore"` // For reading, and if SigStoreStaging is not present, for writing.
|
||||
SigStoreStaging string `json:"sigstore-staging"` // For writing only.
|
||||
Lookaside string `json:"lookaside"` // For reading, and if LookasideStaging is not present, for writing.
|
||||
LookasideStaging string `json:"lookaside-staging"` // For writing only.
|
||||
SigStore string `json:"sigstore"` // For compatibility, deprecated in favor of Lookaside.
|
||||
SigStoreStaging string `json:"sigstore-staging"` // For compatibility, deprecated in favor of LookasideStaging.
|
||||
UseSigstoreAttachments *bool `json:"use-sigstore-attachments,omitempty"`
|
||||
}
|
||||
|
||||
// signatureStorageBase is an "opaque" type representing a lookaside Docker signature storage.
|
||||
// Users outside of this file should use SignatureStorageBaseURL and signatureStorageURL below.
|
||||
type signatureStorageBase *url.URL
|
||||
// lookasideStorageBase is an "opaque" type representing a lookaside Docker signature storage.
|
||||
// Users outside of this file should use SignatureStorageBaseURL and lookasideStorageURL below.
|
||||
type lookasideStorageBase *url.URL
|
||||
|
||||
// SignatureStorageBaseURL reads configuration to find an appropriate signature storage URL for ref, for write access if “write”.
|
||||
// SignatureStorageBaseURL reads configuration to find an appropriate lookaside storage URL for ref, for write access if “write”.
|
||||
// the usage of the BaseURL is defined under docker/distribution registries—separate storage of docs/signature-protocols.md
|
||||
// Warning: This function only exposes configuration in registries.d;
|
||||
// just because this function returns an URL does not mean that the URL will be used by c/image/docker (e.g. if the registry natively supports X-R-S-S).
|
||||
func SignatureStorageBaseURL(sys *types.SystemContext, ref types.ImageReference, write bool) (*url.URL, error) {
|
||||
dr, ok := ref.(dockerReference)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("ref must be a dockerReference")
|
||||
return nil, errors.New("ref must be a dockerReference")
|
||||
}
|
||||
// FIXME? Loading and parsing the config could be cached across calls.
|
||||
dirPath := registriesDirPath(sys)
|
||||
logrus.Debugf(`Using registries.d directory %s for sigstore configuration`, dirPath)
|
||||
config, err := loadAndMergeConfig(dirPath)
|
||||
config, err := loadRegistryConfiguration(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
topLevel := config.signatureTopLevel(dr, write)
|
||||
var url *url.URL
|
||||
if topLevel != "" {
|
||||
url, err = url.Parse(topLevel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel)
|
||||
}
|
||||
} else {
|
||||
// returns default directory if no sigstore specified in configuration file
|
||||
url = builtinDefaultSignatureStorageDir(rootless.GetRootlessEUID())
|
||||
logrus.Debugf(" No signature storage configuration found for %s, using built-in default %s", dr.PolicyConfigurationIdentity(), url.Redacted())
|
||||
}
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
// FIXME? Restrict to explicitly supported schemes?
|
||||
repo := reference.Path(dr.ref) // Note that this is without a tag or digest.
|
||||
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
|
||||
return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dr.ref.String())
|
||||
}
|
||||
url.Path = url.Path + "/" + repo
|
||||
return url, nil
|
||||
return config.lookasideStorageBaseURL(dr, write)
|
||||
}
|
||||
|
||||
// loadRegistryConfiguration returns a registryConfiguration appropriate for sys.
|
||||
func loadRegistryConfiguration(sys *types.SystemContext) (*registryConfiguration, error) {
|
||||
dirPath := registriesDirPath(sys)
|
||||
logrus.Debugf(`Using registries.d directory %s`, dirPath)
|
||||
return loadAndMergeConfig(dirPath)
|
||||
}
|
||||
|
||||
// registriesDirPath returns a path to registries.d
|
||||
|
|
@ -115,15 +103,8 @@ func registriesDirPathWithHomeDir(sys *types.SystemContext, homeDir string) stri
|
|||
return systemRegistriesDirPath
|
||||
}
|
||||
|
||||
// builtinDefaultSignatureStorageDir returns default signature storage URL as per euid
|
||||
func builtinDefaultSignatureStorageDir(euid int) *url.URL {
|
||||
if euid != 0 {
|
||||
return &url.URL{Scheme: "file", Path: filepath.Join(homedir.Get(), defaultUserDockerDir)}
|
||||
}
|
||||
return &url.URL{Scheme: "file", Path: defaultDockerDir}
|
||||
}
|
||||
|
||||
// loadAndMergeConfig loads configuration files in dirPath
|
||||
// FIXME: Probably rename to loadRegistryConfigurationForPath
|
||||
func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
|
||||
mergedConfig := registryConfiguration{Docker: map[string]registryNamespace{}}
|
||||
dockerDefaultMergedFrom := ""
|
||||
|
|
@ -153,12 +134,12 @@ func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
|
|||
var config registryConfiguration
|
||||
err = yaml.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parsing %s", configPath)
|
||||
return nil, fmt.Errorf("parsing %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
if config.DefaultDocker != nil {
|
||||
if mergedConfig.DefaultDocker != nil {
|
||||
return nil, errors.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
|
||||
return nil, fmt.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
|
||||
dockerDefaultMergedFrom, configPath)
|
||||
}
|
||||
mergedConfig.DefaultDocker = config.DefaultDocker
|
||||
|
|
@ -167,7 +148,7 @@ func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
|
|||
|
||||
for nsName, nsConfig := range config.Docker { // includes config.Docker == nil
|
||||
if _, ok := mergedConfig.Docker[nsName]; ok {
|
||||
return nil, errors.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
|
||||
return nil, fmt.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
|
||||
nsName, nsMergedFrom[nsName], configPath)
|
||||
}
|
||||
mergedConfig.Docker[nsName] = nsConfig
|
||||
|
|
@ -178,6 +159,40 @@ func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
|
|||
return &mergedConfig, nil
|
||||
}
|
||||
|
||||
// lookasideStorageBaseURL returns an appropriate signature storage URL for ref, for write access if “write”.
|
||||
// the usage of the BaseURL is defined under docker/distribution registries—separate storage of docs/signature-protocols.md
|
||||
func (config *registryConfiguration) lookasideStorageBaseURL(dr dockerReference, write bool) (*url.URL, error) {
|
||||
topLevel := config.signatureTopLevel(dr, write)
|
||||
var url *url.URL
|
||||
if topLevel != "" {
|
||||
u, err := url.Parse(topLevel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid signature storage URL %s: %w", topLevel, err)
|
||||
}
|
||||
url = u
|
||||
} else {
|
||||
// returns default directory if no lookaside specified in configuration file
|
||||
url = builtinDefaultLookasideStorageDir(rootless.GetRootlessEUID())
|
||||
logrus.Debugf(" No signature storage configuration found for %s, using built-in default %s", dr.PolicyConfigurationIdentity(), url.Redacted())
|
||||
}
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
// FIXME? Restrict to explicitly supported schemes?
|
||||
repo := reference.Path(dr.ref) // Note that this is without a tag or digest.
|
||||
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
|
||||
return nil, fmt.Errorf("Unexpected path elements in Docker reference %s for signature storage", dr.ref.String())
|
||||
}
|
||||
url.Path = url.Path + "/" + repo
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// builtinDefaultLookasideStorageDir returns default signature storage URL as per euid
|
||||
func builtinDefaultLookasideStorageDir(euid int) *url.URL {
|
||||
if euid != 0 {
|
||||
return &url.URL{Scheme: "file", Path: filepath.Join(homedir.Get(), defaultUserDockerDir)}
|
||||
}
|
||||
return &url.URL{Scheme: "file", Path: defaultDockerDir}
|
||||
}
|
||||
|
||||
// config.signatureTopLevel returns an URL string configured in config for ref, for write access if “write”.
|
||||
// (the top level of the storage, namespaced by repo.FullName etc.), or "" if nothing has been configured.
|
||||
func (config *registryConfiguration) signatureTopLevel(ref dockerReference, write bool) string {
|
||||
|
|
@ -185,7 +200,7 @@ func (config *registryConfiguration) signatureTopLevel(ref dockerReference, writ
|
|||
// Look for a full match.
|
||||
identity := ref.PolicyConfigurationIdentity()
|
||||
if ns, ok := config.Docker[identity]; ok {
|
||||
logrus.Debugf(` Using "docker" namespace %s`, identity)
|
||||
logrus.Debugf(` Lookaside configuration: using "docker" namespace %s`, identity)
|
||||
if url := ns.signatureTopLevel(write); url != "" {
|
||||
return url
|
||||
}
|
||||
|
|
@ -194,7 +209,7 @@ func (config *registryConfiguration) signatureTopLevel(ref dockerReference, writ
|
|||
// Look for a match of the possible parent namespaces.
|
||||
for _, name := range ref.PolicyConfigurationNamespaces() {
|
||||
if ns, ok := config.Docker[name]; ok {
|
||||
logrus.Debugf(` Using "docker" namespace %s`, name)
|
||||
logrus.Debugf(` Lookaside configuration: using "docker" namespace %s`, name)
|
||||
if url := ns.signatureTopLevel(write); url != "" {
|
||||
return url
|
||||
}
|
||||
|
|
@ -203,7 +218,7 @@ func (config *registryConfiguration) signatureTopLevel(ref dockerReference, writ
|
|||
}
|
||||
// Look for a default location
|
||||
if config.DefaultDocker != nil {
|
||||
logrus.Debugf(` Using "default-docker" configuration`)
|
||||
logrus.Debugf(` Lookaside configuration: using "default-docker" configuration`)
|
||||
if url := config.DefaultDocker.signatureTopLevel(write); url != "" {
|
||||
return url
|
||||
}
|
||||
|
|
@ -211,24 +226,67 @@ func (config *registryConfiguration) signatureTopLevel(ref dockerReference, writ
|
|||
return ""
|
||||
}
|
||||
|
||||
// config.useSigstoreAttachments returns whether we should look for and write sigstore attachments.
|
||||
// for ref.
|
||||
func (config *registryConfiguration) useSigstoreAttachments(ref dockerReference) bool {
|
||||
if config.Docker != nil {
|
||||
// Look for a full match.
|
||||
identity := ref.PolicyConfigurationIdentity()
|
||||
if ns, ok := config.Docker[identity]; ok {
|
||||
logrus.Debugf(` Sigstore attachments: using "docker" namespace %s`, identity)
|
||||
if ns.UseSigstoreAttachments != nil {
|
||||
return *ns.UseSigstoreAttachments
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a match of the possible parent namespaces.
|
||||
for _, name := range ref.PolicyConfigurationNamespaces() {
|
||||
if ns, ok := config.Docker[name]; ok {
|
||||
logrus.Debugf(` Sigstore attachments: using "docker" namespace %s`, name)
|
||||
if ns.UseSigstoreAttachments != nil {
|
||||
return *ns.UseSigstoreAttachments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Look for a default location
|
||||
if config.DefaultDocker != nil {
|
||||
logrus.Debugf(` Sigstore attachments: using "default-docker" configuration`)
|
||||
if config.DefaultDocker.UseSigstoreAttachments != nil {
|
||||
return *config.DefaultDocker.UseSigstoreAttachments
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ns.signatureTopLevel returns an URL string configured in ns for ref, for write access if “write”.
|
||||
// or "" if nothing has been configured.
|
||||
func (ns registryNamespace) signatureTopLevel(write bool) string {
|
||||
if write && ns.SigStoreStaging != "" {
|
||||
logrus.Debugf(` Using %s`, ns.SigStoreStaging)
|
||||
return ns.SigStoreStaging
|
||||
if write {
|
||||
if ns.LookasideStaging != "" {
|
||||
logrus.Debugf(` Using "lookaside-staging" %s`, ns.LookasideStaging)
|
||||
return ns.LookasideStaging
|
||||
}
|
||||
if ns.SigStoreStaging != "" {
|
||||
logrus.Debugf(` Using "sigstore-staging" %s`, ns.SigStoreStaging)
|
||||
return ns.SigStoreStaging
|
||||
}
|
||||
}
|
||||
if ns.Lookaside != "" {
|
||||
logrus.Debugf(` Using "lookaside" %s`, ns.Lookaside)
|
||||
return ns.Lookaside
|
||||
}
|
||||
if ns.SigStore != "" {
|
||||
logrus.Debugf(` Using %s`, ns.SigStore)
|
||||
logrus.Debugf(` Using "sigstore" %s`, ns.SigStore)
|
||||
return ns.SigStore
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// signatureStorageURL returns an URL usable for accessing signature index in base with known manifestDigest.
|
||||
// lookasideStorageURL returns an URL usable for accessing signature index in base with known manifestDigest.
|
||||
// base is not nil from the caller
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func signatureStorageURL(base signatureStorageBase, manifestDigest digest.Digest, index int) *url.URL {
|
||||
func lookasideStorageURL(base lookasideStorageBase, manifestDigest digest.Digest, index int) *url.URL {
|
||||
url := *base
|
||||
url.Path = fmt.Sprintf("%s@%s=%s/signature-%d", url.Path, manifestDigest.Algorithm(), manifestDigest.Hex(), index+1)
|
||||
return &url
|
||||
|
|
@ -2,32 +2,32 @@ package image
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func manifestSchema2FromManifestList(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
|
||||
list, err := manifest.Schema2ListFromManifest(manblob)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parsing schema2 manifest list")
|
||||
return nil, fmt.Errorf("parsing schema2 manifest list: %w", err)
|
||||
}
|
||||
targetManifestDigest, err := list.ChooseInstance(sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "choosing image instance")
|
||||
return nil, fmt.Errorf("choosing image instance: %w", err)
|
||||
}
|
||||
manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "loading manifest for target platform")
|
||||
return nil, fmt.Errorf("fetching target platform image selected from manifest list: %w", err)
|
||||
}
|
||||
|
||||
matches, err := manifest.MatchesDigest(manblob, targetManifestDigest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "computing manifest digest")
|
||||
return nil, fmt.Errorf("computing manifest digest: %w", err)
|
||||
}
|
||||
if !matches {
|
||||
return nil, errors.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest)
|
||||
return nil, fmt.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest)
|
||||
}
|
||||
|
||||
return manifestInstanceFromBlob(ctx, sys, src, manblob, mt)
|
||||
|
|
@ -2,13 +2,13 @@ package image
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type manifestSchema1 struct {
|
||||
|
|
@ -165,22 +165,22 @@ func (m *manifestSchema1) convertToManifestSchema2(_ context.Context, options *t
|
|||
|
||||
if len(m.m.ExtractedV1Compatibility) == 0 {
|
||||
// What would this even mean?! Anyhow, the rest of the code depends on FSLayers[0] and ExtractedV1Compatibility[0] existing.
|
||||
return nil, errors.Errorf("Cannot convert an image with 0 history entries to %s", manifest.DockerV2Schema2MediaType)
|
||||
return nil, fmt.Errorf("Cannot convert an image with 0 history entries to %s", manifest.DockerV2Schema2MediaType)
|
||||
}
|
||||
if len(m.m.ExtractedV1Compatibility) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.m.ExtractedV1Compatibility), len(m.m.FSLayers))
|
||||
return nil, fmt.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.m.ExtractedV1Compatibility), len(m.m.FSLayers))
|
||||
}
|
||||
if uploadedLayerInfos != nil && len(uploadedLayerInfos) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: uploaded %d blobs, but schema1 manifest has %d fsLayers", len(uploadedLayerInfos), len(m.m.FSLayers))
|
||||
return nil, fmt.Errorf("Internal error: uploaded %d blobs, but schema1 manifest has %d fsLayers", len(uploadedLayerInfos), len(m.m.FSLayers))
|
||||
}
|
||||
if layerDiffIDs != nil && len(layerDiffIDs) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: collected %d DiffID values, but schema1 manifest has %d fsLayers", len(layerDiffIDs), len(m.m.FSLayers))
|
||||
return nil, fmt.Errorf("Internal error: collected %d DiffID values, but schema1 manifest has %d fsLayers", len(layerDiffIDs), len(m.m.FSLayers))
|
||||
}
|
||||
|
||||
var convertedLayerUpdates []types.BlobInfo // Only used if options.LayerInfos != nil
|
||||
if options.LayerInfos != nil {
|
||||
if len(options.LayerInfos) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Error converting image: layer edits for %d layers vs %d existing layers",
|
||||
return nil, fmt.Errorf("Error converting image: layer edits for %d layers vs %d existing layers",
|
||||
len(options.LayerInfos), len(m.m.FSLayers))
|
||||
}
|
||||
convertedLayerUpdates = []types.BlobInfo{}
|
||||
|
|
@ -246,3 +246,12 @@ func (m *manifestSchema1) convertToManifestOCI1(ctx context.Context, options *ty
|
|||
func (m *manifestSchema1) SupportsEncryption(context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CanChangeLayerCompression returns true if we can compress/decompress layers with mimeType in the current image
|
||||
// (and the code can handle that).
|
||||
// NOTE: Even if this returns true, the relevant format might not accept all compression algorithms; the set of accepted
|
||||
// algorithms depends not on the current format, but possibly on the target of a conversion (if UpdatedImage converts
|
||||
// to a different manifest format).
|
||||
func (m *manifestSchema1) CanChangeLayerCompression(mimeType string) bool {
|
||||
return true // There are no MIME types in the manifest, so we must assume a valid image.
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
|
@ -16,7 +17,6 @@ import (
|
|||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -24,12 +24,16 @@ import (
|
|||
// This comes from github.com/docker/distribution/manifest/schema1/config_builder.go; there is
|
||||
// a non-zero embedded timestamp; we could zero that, but that would just waste storage space
|
||||
// in registries, so let’s use the same values.
|
||||
//
|
||||
// This is publicly visible as c/image/image.GzippedEmptyLayer.
|
||||
var GzippedEmptyLayer = []byte{
|
||||
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
|
||||
0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
|
||||
}
|
||||
|
||||
// GzippedEmptyLayerDigest is a digest of GzippedEmptyLayer
|
||||
//
|
||||
// This is publicly visible as c/image/image.GzippedEmptyLayerDigest.
|
||||
const GzippedEmptyLayerDigest = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
|
||||
|
||||
type manifestSchema2 struct {
|
||||
|
|
@ -95,7 +99,7 @@ func (m *manifestSchema2) OCIConfig(ctx context.Context) (*imgspecv1.Image, erro
|
|||
func (m *manifestSchema2) ConfigBlob(ctx context.Context) ([]byte, error) {
|
||||
if m.configBlob == nil {
|
||||
if m.src == nil {
|
||||
return nil, errors.Errorf("Internal error: neither src nor configBlob set in manifestSchema2")
|
||||
return nil, fmt.Errorf("Internal error: neither src nor configBlob set in manifestSchema2")
|
||||
}
|
||||
stream, _, err := m.src.GetBlob(ctx, manifest.BlobInfoFromSchema2Descriptor(m.m.ConfigDescriptor), none.NoCache)
|
||||
if err != nil {
|
||||
|
|
@ -108,7 +112,7 @@ func (m *manifestSchema2) ConfigBlob(ctx context.Context) ([]byte, error) {
|
|||
}
|
||||
computedDigest := digest.FromBytes(blob)
|
||||
if computedDigest != m.m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.ConfigDescriptor.Digest)
|
||||
return nil, fmt.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.ConfigDescriptor.Digest)
|
||||
}
|
||||
m.configBlob = blob
|
||||
}
|
||||
|
|
@ -273,7 +277,7 @@ func (m *manifestSchema2) convertToManifestSchema1(ctx context.Context, options
|
|||
haveGzippedEmptyLayer := false
|
||||
if len(imageConfig.History) == 0 {
|
||||
// What would this even mean?! Anyhow, the rest of the code depends on fsLayers[0] and history[0] existing.
|
||||
return nil, errors.Errorf("Cannot convert an image with 0 history entries to %s", manifest.DockerV2Schema1SignedMediaType)
|
||||
return nil, fmt.Errorf("Cannot convert an image with 0 history entries to %s", manifest.DockerV2Schema1SignedMediaType)
|
||||
}
|
||||
for v2Index, historyEntry := range imageConfig.History {
|
||||
parentV1ID = v1ID
|
||||
|
|
@ -289,10 +293,10 @@ func (m *manifestSchema2) convertToManifestSchema1(ctx context.Context, options
|
|||
// and anyway this blob is so small that it’s easier to just copy it than to worry about figuring out another location where to get it.
|
||||
info, err := dest.PutBlob(ctx, bytes.NewReader(GzippedEmptyLayer), emptyLayerBlobInfo, none.NoCache, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "uploading empty layer")
|
||||
return nil, fmt.Errorf("uploading empty layer: %w", err)
|
||||
}
|
||||
if info.Digest != emptyLayerBlobInfo.Digest {
|
||||
return nil, errors.Errorf("Internal error: Uploaded empty layer has digest %#v instead of %s", info.Digest, emptyLayerBlobInfo.Digest)
|
||||
return nil, fmt.Errorf("Internal error: Uploaded empty layer has digest %#v instead of %s", info.Digest, emptyLayerBlobInfo.Digest)
|
||||
}
|
||||
haveGzippedEmptyLayer = true
|
||||
}
|
||||
|
|
@ -302,7 +306,7 @@ func (m *manifestSchema2) convertToManifestSchema1(ctx context.Context, options
|
|||
blobDigest = emptyLayerBlobInfo.Digest
|
||||
} else {
|
||||
if nonemptyLayerIndex >= len(m.m.LayersDescriptors) {
|
||||
return nil, errors.Errorf("Invalid image configuration, needs more than the %d distributed layers", len(m.m.LayersDescriptors))
|
||||
return nil, fmt.Errorf("Invalid image configuration, needs more than the %d distributed layers", len(m.m.LayersDescriptors))
|
||||
}
|
||||
if options.LayerInfos != nil {
|
||||
convertedLayerUpdates = append(convertedLayerUpdates, options.LayerInfos[nonemptyLayerIndex])
|
||||
|
|
@ -329,7 +333,7 @@ func (m *manifestSchema2) convertToManifestSchema1(ctx context.Context, options
|
|||
fakeImage.ContainerConfig.Cmd = []string{historyEntry.CreatedBy}
|
||||
v1CompatibilityBytes, err := json.Marshal(&fakeImage)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Internal error: Error creating v1compatibility for %#v", fakeImage)
|
||||
return nil, fmt.Errorf("Internal error: Error creating v1compatibility for %#v", fakeImage)
|
||||
}
|
||||
|
||||
fsLayers[v1Index] = manifest.Schema1FSLayers{BlobSum: blobDigest}
|
||||
|
|
@ -398,3 +402,12 @@ func v1ConfigFromConfigJSON(configJSON []byte, v1ID, parentV1ID string, throwawa
|
|||
func (m *manifestSchema2) SupportsEncryption(context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CanChangeLayerCompression returns true if we can compress/decompress layers with mimeType in the current image
|
||||
// (and the code can handle that).
|
||||
// NOTE: Even if this returns true, the relevant format might not accept all compression algorithms; the set of accepted
|
||||
// algorithms depends not on the current format, but possibly on the target of a conversion (if UpdatedImage converts
|
||||
// to a different manifest format).
|
||||
func (m *manifestSchema2) CanChangeLayerCompression(mimeType string) bool {
|
||||
return m.m.CanChangeLayerCompression(mimeType)
|
||||
}
|
||||
|
|
@ -8,13 +8,11 @@ import (
|
|||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// genericManifest is an interface for parsing, modifying image manifests and related data.
|
||||
// Note that the public methods are intended to be a subset of types.Image
|
||||
// so that embedding a genericManifest into structs works.
|
||||
// will support v1 one day...
|
||||
// The public methods are related to types.Image so that embedding a genericManifest implements most of it,
|
||||
// but there are also public methods that are only visible by packages that can import c/image/internal/image.
|
||||
type genericManifest interface {
|
||||
serialize() ([]byte, error)
|
||||
manifestMIMEType() string
|
||||
|
|
@ -51,6 +49,16 @@ type genericManifest interface {
|
|||
// the process of updating a manifest between different manifest types was to update then convert.
|
||||
// This resulted in some fields in the update being lost. This has been fixed by: https://github.com/containers/image/pull/836
|
||||
SupportsEncryption(ctx context.Context) bool
|
||||
|
||||
// The following methods are not a part of types.Image:
|
||||
// ===
|
||||
|
||||
// CanChangeLayerCompression returns true if we can compress/decompress layers with mimeType in the current image
|
||||
// (and the code can handle that).
|
||||
// NOTE: Even if this returns true, the relevant format might not accept all compression algorithms; the set of accepted
|
||||
// algorithms depends not on the current format, but possibly on the target of a conversion (if UpdatedImage converts
|
||||
// to a different manifest format).
|
||||
CanChangeLayerCompression(mimeType string) bool
|
||||
}
|
||||
|
||||
// manifestInstanceFromBlob returns a genericManifest implementation for (manblob, mt) in src.
|
||||
|
|
@ -98,7 +106,7 @@ func convertManifestIfRequiredWithUpdate(ctx context.Context, options types.Mani
|
|||
|
||||
converter, ok := converters[options.ManifestMIMEType]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Unsupported conversion type: %v", options.ManifestMIMEType)
|
||||
return nil, fmt.Errorf("Unsupported conversion type: %v", options.ManifestMIMEType)
|
||||
}
|
||||
|
||||
optionsCopy := options
|
||||
|
|
@ -2,9 +2,9 @@ package image
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// memoryImage is a mostly-implementation of types.Image assembled from data
|
||||
|
|
@ -3,16 +3,17 @@ package image
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/iolimits"
|
||||
internalManifest "github.com/containers/image/v5/internal/manifest"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/blobinfocache/none"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type manifestOCI1 struct {
|
||||
|
|
@ -60,7 +61,7 @@ func (m *manifestOCI1) ConfigInfo() types.BlobInfo {
|
|||
func (m *manifestOCI1) ConfigBlob(ctx context.Context) ([]byte, error) {
|
||||
if m.configBlob == nil {
|
||||
if m.src == nil {
|
||||
return nil, errors.Errorf("Internal error: neither src nor configBlob set in manifestOCI1")
|
||||
return nil, errors.New("Internal error: neither src nor configBlob set in manifestOCI1")
|
||||
}
|
||||
stream, _, err := m.src.GetBlob(ctx, manifest.BlobInfoFromOCI1Descriptor(m.m.Config), none.NoCache)
|
||||
if err != nil {
|
||||
|
|
@ -73,7 +74,7 @@ func (m *manifestOCI1) ConfigBlob(ctx context.Context) ([]byte, error) {
|
|||
}
|
||||
computedDigest := digest.FromBytes(blob)
|
||||
if computedDigest != m.m.Config.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.Config.Digest)
|
||||
return nil, fmt.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.Config.Digest)
|
||||
}
|
||||
m.configBlob = blob
|
||||
}
|
||||
|
|
@ -84,6 +85,10 @@ func (m *manifestOCI1) ConfigBlob(ctx context.Context) ([]byte, error) {
|
|||
// layers in the resulting configuration isn't guaranteed to be returned to due how
|
||||
// old image manifests work (docker v2s1 especially).
|
||||
func (m *manifestOCI1) OCIConfig(ctx context.Context) (*imgspecv1.Image, error) {
|
||||
if m.m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
|
||||
return nil, internalManifest.NewNonImageArtifactError(m.m.Config.MediaType)
|
||||
}
|
||||
|
||||
cb, err := m.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -194,10 +199,15 @@ func (m *manifestOCI1) convertToManifestSchema2Generic(ctx context.Context, opti
|
|||
// value.
|
||||
// This does not change the state of the original manifestOCI1 object.
|
||||
func (m *manifestOCI1) convertToManifestSchema2(_ context.Context, _ *types.ManifestUpdateOptions) (*manifestSchema2, error) {
|
||||
if m.m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
|
||||
return nil, internalManifest.NewNonImageArtifactError(m.m.Config.MediaType)
|
||||
}
|
||||
|
||||
// Create a copy of the descriptor.
|
||||
config := schema2DescriptorFromOCI1Descriptor(m.m.Config)
|
||||
|
||||
// The only difference between OCI and DockerSchema2 is the mediatypes. The
|
||||
// Above, we have already checked that this manifest refers to an image, not an OCI artifact,
|
||||
// so the only difference between OCI and DockerSchema2 is the mediatypes. The
|
||||
// media type of the manifest is handled by manifestSchema2FromComponents.
|
||||
config.MediaType = manifest.DockerV2Schema2ConfigMediaType
|
||||
|
||||
|
|
@ -233,7 +243,11 @@ func (m *manifestOCI1) convertToManifestSchema2(_ context.Context, _ *types.Mani
|
|||
// value.
|
||||
// This does not change the state of the original manifestOCI1 object.
|
||||
func (m *manifestOCI1) convertToManifestSchema1(ctx context.Context, options *types.ManifestUpdateOptions) (genericManifest, error) {
|
||||
// We can't directly convert to V1, but we can transitively convert via a V2 image
|
||||
if m.m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
|
||||
return nil, internalManifest.NewNonImageArtifactError(m.m.Config.MediaType)
|
||||
}
|
||||
|
||||
// We can't directly convert images to V1, but we can transitively convert via a V2 image
|
||||
m2, err := m.convertToManifestSchema2(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -246,3 +260,12 @@ func (m *manifestOCI1) convertToManifestSchema1(ctx context.Context, options *ty
|
|||
func (m *manifestOCI1) SupportsEncryption(context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CanChangeLayerCompression returns true if we can compress/decompress layers with mimeType in the current image
|
||||
// (and the code can handle that).
|
||||
// NOTE: Even if this returns true, the relevant format might not accept all compression algorithms; the set of accepted
|
||||
// algorithms depends not on the current format, but possibly on the target of a conversion (if UpdatedImage converts
|
||||
// to a different manifest format).
|
||||
func (m *manifestOCI1) CanChangeLayerCompression(mimeType string) bool {
|
||||
return m.m.CanChangeLayerCompression(mimeType)
|
||||
}
|
||||
|
|
@ -2,32 +2,32 @@ package image
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func manifestOCI1FromImageIndex(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
|
||||
index, err := manifest.OCI1IndexFromManifest(manblob)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parsing OCI1 index")
|
||||
return nil, fmt.Errorf("parsing OCI1 index: %w", err)
|
||||
}
|
||||
targetManifestDigest, err := index.ChooseInstance(sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "choosing image instance")
|
||||
return nil, fmt.Errorf("choosing image instance: %w", err)
|
||||
}
|
||||
manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "loading manifest for target platform")
|
||||
return nil, fmt.Errorf("fetching target platform image selected from image index: %w", err)
|
||||
}
|
||||
|
||||
matches, err := manifest.MatchesDigest(manblob, targetManifestDigest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "computing manifest digest")
|
||||
return nil, fmt.Errorf("computing manifest digest: %w", err)
|
||||
}
|
||||
if !matches {
|
||||
return nil, errors.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest)
|
||||
return nil, fmt.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest)
|
||||
}
|
||||
|
||||
return manifestInstanceFromBlob(ctx, sys, src, manblob, mt)
|
||||
|
|
@ -9,6 +9,27 @@ import (
|
|||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// FromReference returns a types.ImageCloser implementation for the default instance reading from reference.
|
||||
// If reference poitns to a manifest list, .Manifest() still returns the manifest list,
|
||||
// but other methods transparently return data from an appropriate image instance.
|
||||
//
|
||||
// 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 instead of calling this function.
|
||||
func FromReference(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) (types.ImageCloser, error) {
|
||||
src, err := ref.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := FromSource(ctx, sys, src)
|
||||
if err != nil {
|
||||
src.Close()
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// imageCloser implements types.ImageCloser, perhaps allowing simple users
|
||||
// to use a single object without having keep a reference to a types.ImageSource
|
||||
// only to call types.ImageSource.Close().
|
||||
|
|
@ -30,6 +51,10 @@ type imageCloser struct {
|
|||
//
|
||||
// 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 instead of calling this function.
|
||||
//
|
||||
// Most callers can use either FromUnparsedImage or FromReference instead.
|
||||
//
|
||||
// This is publicly visible as c/image/image.FromSource.
|
||||
func FromSource(ctx context.Context, sys *types.SystemContext, src types.ImageSource) (types.ImageCloser, error) {
|
||||
img, err := FromUnparsedImage(ctx, sys, UnparsedInstance(src, nil))
|
||||
if err != nil {
|
||||
|
|
@ -45,15 +70,18 @@ func (ic *imageCloser) Close() error {
|
|||
return ic.src.Close()
|
||||
}
|
||||
|
||||
// sourcedImage is a general set of utilities for working with container images,
|
||||
// whatever is their underlying location (i.e. dockerImageSource-independent).
|
||||
// Note the existence of skopeo/docker.Image: some instances of a `types.Image`
|
||||
// may not be a `sourcedImage` directly. However, most users of `types.Image`
|
||||
// do not care, and those who care about `skopeo/docker.Image` know they do.
|
||||
type sourcedImage struct {
|
||||
// SourcedImage is a general set of utilities for working with container images,
|
||||
// whatever is their underlying transport (i.e. ImageSource-independent).
|
||||
// Note the existence of docker.Image and image.memoryImage: various instances
|
||||
// of a types.Image may not be a SourcedImage directly.
|
||||
//
|
||||
// Most external users of `types.Image` do not care, and those who care about `docker.Image` know they do.
|
||||
//
|
||||
// Internal users may depend on methods available in SourcedImage but not (yet?) in types.Image.
|
||||
type SourcedImage struct {
|
||||
*UnparsedImage
|
||||
manifestBlob []byte
|
||||
manifestMIMEType string
|
||||
ManifestBlob []byte // The manifest of the relevant instance
|
||||
ManifestMIMEType string // MIME type of ManifestBlob
|
||||
// genericManifest contains data corresponding to manifestBlob.
|
||||
// NOTE: The manifest may have been modified in the process; DO NOT reserialize and store genericManifest
|
||||
// if you want to preserve the original manifest; use manifestBlob directly.
|
||||
|
|
@ -65,7 +93,9 @@ type sourcedImage struct {
|
|||
// but other methods transparently return data from an appropriate single image.
|
||||
//
|
||||
// The Image must not be used after the underlying ImageSource is Close()d.
|
||||
func FromUnparsedImage(ctx context.Context, sys *types.SystemContext, unparsed *UnparsedImage) (types.Image, error) {
|
||||
//
|
||||
// This is publicly visible as c/image/image.FromUnparsedImage.
|
||||
func FromUnparsedImage(ctx context.Context, sys *types.SystemContext, unparsed *UnparsedImage) (*SourcedImage, error) {
|
||||
// Note that the input parameter above is specifically *image.UnparsedImage, not types.UnparsedImage:
|
||||
// we want to be able to use unparsed.src. We could make that an explicit interface, but, well,
|
||||
// this is the only UnparsedImage implementation around, anyway.
|
||||
|
|
@ -81,24 +111,24 @@ func FromUnparsedImage(ctx context.Context, sys *types.SystemContext, unparsed *
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &sourcedImage{
|
||||
return &SourcedImage{
|
||||
UnparsedImage: unparsed,
|
||||
manifestBlob: manifestBlob,
|
||||
manifestMIMEType: manifestMIMEType,
|
||||
ManifestBlob: manifestBlob,
|
||||
ManifestMIMEType: manifestMIMEType,
|
||||
genericManifest: parsedManifest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the image as stored, if it's known, or -1 if it isn't.
|
||||
func (i *sourcedImage) Size() (int64, error) {
|
||||
func (i *SourcedImage) Size() (int64, error) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// Manifest overrides the UnparsedImage.Manifest to always use the fields which we have already fetched.
|
||||
func (i *sourcedImage) Manifest(ctx context.Context) ([]byte, string, error) {
|
||||
return i.manifestBlob, i.manifestMIMEType, nil
|
||||
func (i *SourcedImage) Manifest(ctx context.Context) ([]byte, string, error) {
|
||||
return i.ManifestBlob, i.ManifestMIMEType, nil
|
||||
}
|
||||
|
||||
func (i *sourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
func (i *SourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return i.UnparsedImage.src.LayerInfosForCopy(ctx, i.UnparsedImage.instanceDigest)
|
||||
}
|
||||
|
|
@ -2,33 +2,40 @@ package image
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/imagesource"
|
||||
"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/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UnparsedImage implements types.UnparsedImage .
|
||||
// An UnparsedImage is a pair of (ImageSource, instance digest); it can represent either a manifest list or a single image instance.
|
||||
//
|
||||
// This is publicly visible as c/image/image.UnparsedImage.
|
||||
type UnparsedImage struct {
|
||||
src types.ImageSource
|
||||
src private.ImageSource
|
||||
instanceDigest *digest.Digest
|
||||
cachedManifest []byte // A private cache for Manifest(); nil if not yet known.
|
||||
// A private cache for Manifest(), may be the empty string if guessing failed.
|
||||
// Valid iff cachedManifest is not nil.
|
||||
cachedManifestMIMEType string
|
||||
cachedSignatures [][]byte // A private cache for Signatures(); nil if not yet known.
|
||||
cachedSignatures []signature.Signature // A private cache for Signatures(); nil if not yet known.
|
||||
}
|
||||
|
||||
// UnparsedInstance returns a types.UnparsedImage implementation for (source, instanceDigest).
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list).
|
||||
//
|
||||
// The UnparsedImage must not be used after the underlying ImageSource is Close()d.
|
||||
//
|
||||
// This is publicly visible as c/image/image.UnparsedInstance.
|
||||
func UnparsedInstance(src types.ImageSource, instanceDigest *digest.Digest) *UnparsedImage {
|
||||
return &UnparsedImage{
|
||||
src: src,
|
||||
src: imagesource.FromPublic(src),
|
||||
instanceDigest: instanceDigest,
|
||||
}
|
||||
}
|
||||
|
|
@ -53,10 +60,10 @@ func (i *UnparsedImage) Manifest(ctx context.Context) ([]byte, string, error) {
|
|||
if digest, haveDigest := i.expectedManifestDigest(); haveDigest {
|
||||
matches, err := manifest.MatchesDigest(m, digest)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "computing manifest digest")
|
||||
return nil, "", fmt.Errorf("computing manifest digest: %w", err)
|
||||
}
|
||||
if !matches {
|
||||
return nil, "", errors.Errorf("Manifest does not match provided manifest digest %s", digest)
|
||||
return nil, "", fmt.Errorf("Manifest does not match provided manifest digest %s", digest)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,8 +91,25 @@ func (i *UnparsedImage) expectedManifestDigest() (digest.Digest, bool) {
|
|||
|
||||
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
|
||||
func (i *UnparsedImage) Signatures(ctx context.Context) ([][]byte, error) {
|
||||
// It would be consistent to make this an internal/unparsedimage/impl.Compat wrapper,
|
||||
// but this is very likely to be the only implementation ever.
|
||||
sigs, err := i.UntrustedSignatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
simpleSigs := [][]byte{}
|
||||
for _, sig := range sigs {
|
||||
if sig, ok := sig.(signature.SimpleSigning); ok {
|
||||
simpleSigs = append(simpleSigs, sig.UntrustedSignature())
|
||||
}
|
||||
}
|
||||
return simpleSigs, nil
|
||||
}
|
||||
|
||||
// UntrustedSignatures is like ImageSource.GetSignaturesWithFormat, but the result is cached; it is OK to call this however often you need.
|
||||
func (i *UnparsedImage) UntrustedSignatures(ctx context.Context) ([]signature.Signature, error) {
|
||||
if i.cachedSignatures == nil {
|
||||
sigs, err := i.src.GetSignatures(ctx, i.instanceDigest)
|
||||
sigs, err := i.src.GetSignaturesWithFormat(ctx, i.instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
77
vendor/github.com/containers/image/v5/internal/imagedestination/impl/compat.go
generated
vendored
Normal file
77
vendor/github.com/containers/image/v5/internal/imagedestination/impl/compat.go
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/internal/blobinfocache"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Compat implements the obsolete parts of types.ImageDestination
|
||||
// for implementations of private.ImageDestination.
|
||||
// See AddCompat below.
|
||||
type Compat struct {
|
||||
dest private.ImageDestinationInternalOnly
|
||||
}
|
||||
|
||||
// AddCompat initializes Compat to implement the obsolete parts of types.ImageDestination
|
||||
// for implementations of private.ImageDestination.
|
||||
//
|
||||
// Use it like this:
|
||||
// type yourDestination struct {
|
||||
// impl.Compat
|
||||
// …
|
||||
// }
|
||||
// dest := &yourDestination{…}
|
||||
// dest.Compat = impl.AddCompat(dest)
|
||||
//
|
||||
func AddCompat(dest private.ImageDestinationInternalOnly) Compat {
|
||||
return Compat{dest}
|
||||
}
|
||||
|
||||
// PutBlob 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.
|
||||
// May update cache.
|
||||
// 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 (c *Compat) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) {
|
||||
return c.dest.PutBlobWithOptions(ctx, stream, inputInfo, private.PutBlobOptions{
|
||||
Cache: blobinfocache.FromBlobInfoCache(cache),
|
||||
IsConfig: isConfig,
|
||||
})
|
||||
}
|
||||
|
||||
// TryReusingBlob 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 canSubstitute, TryReusingBlob can use an equivalent equivalent of the desired blob; in that case the returned info may not match the input.
|
||||
// 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.
|
||||
// May use and/or update cache.
|
||||
func (c *Compat) TryReusingBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache, canSubstitute bool) (bool, types.BlobInfo, error) {
|
||||
return c.dest.TryReusingBlobWithOptions(ctx, info, private.TryReusingBlobOptions{
|
||||
Cache: blobinfocache.FromBlobInfoCache(cache),
|
||||
CanSubstitute: canSubstitute,
|
||||
})
|
||||
}
|
||||
|
||||
// PutSignatures 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 (c *Compat) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
|
||||
withFormat := []signature.Signature{}
|
||||
for _, sig := range signatures {
|
||||
withFormat = append(withFormat, signature.SimpleSigningFromBlob(sig))
|
||||
}
|
||||
return c.dest.PutSignaturesWithFormat(ctx, withFormat, instanceDigest)
|
||||
}
|
||||
72
vendor/github.com/containers/image/v5/internal/imagedestination/impl/properties.go
generated
vendored
Normal file
72
vendor/github.com/containers/image/v5/internal/imagedestination/impl/properties.go
generated
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package impl
|
||||
|
||||
import "github.com/containers/image/v5/types"
|
||||
|
||||
// Properties collects properties of an ImageDestination that are constant throughout its lifetime
|
||||
// (but might differ across instances).
|
||||
type Properties struct {
|
||||
// SupportedManifestMIMETypes tells which manifest MIME types the destination supports.
|
||||
// A empty slice or nil means any MIME type can be tried to upload.
|
||||
SupportedManifestMIMETypes []string
|
||||
// DesiredLayerCompression indicates the kind of compression to apply on layers
|
||||
DesiredLayerCompression types.LayerCompression
|
||||
// AcceptsForeignLayerURLs is false if foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
AcceptsForeignLayerURLs bool
|
||||
// MustMatchRuntimeOS is set to true if the destination can store only images targeted for the current runtime architecture and OS.
|
||||
MustMatchRuntimeOS bool
|
||||
// IgnoresEmbeddedDockerReference is set to true if the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
IgnoresEmbeddedDockerReference bool
|
||||
// HasThreadSafePutBlob indicates that PutBlob can be executed concurrently.
|
||||
HasThreadSafePutBlob bool
|
||||
}
|
||||
|
||||
// PropertyMethodsInitialize implements parts of private.ImageDestination corresponding to Properties.
|
||||
type PropertyMethodsInitialize struct {
|
||||
// We need two separate structs, PropertyMethodsInitialize and Properties, because Go prohibits fields and methods with the same name.
|
||||
|
||||
vals Properties
|
||||
}
|
||||
|
||||
// PropertyMethods creates an PropertyMethodsInitialize for vals.
|
||||
func PropertyMethods(vals Properties) PropertyMethodsInitialize {
|
||||
return PropertyMethodsInitialize{
|
||||
vals: vals,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedManifestMIMETypes tells which manifest mime types the destination supports
|
||||
// If an empty slice or nil it's returned, then any mime type can be tried to upload
|
||||
func (o PropertyMethodsInitialize) SupportedManifestMIMETypes() []string {
|
||||
return o.vals.SupportedManifestMIMETypes
|
||||
}
|
||||
|
||||
// DesiredLayerCompression indicates the kind of compression to apply on layers
|
||||
func (o PropertyMethodsInitialize) DesiredLayerCompression() types.LayerCompression {
|
||||
return o.vals.DesiredLayerCompression
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (o PropertyMethodsInitialize) AcceptsForeignLayerURLs() bool {
|
||||
return o.vals.AcceptsForeignLayerURLs
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime architecture and OS. False otherwise.
|
||||
func (o PropertyMethodsInitialize) MustMatchRuntimeOS() bool {
|
||||
return o.vals.MustMatchRuntimeOS
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference() returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (o PropertyMethodsInitialize) IgnoresEmbeddedDockerReference() bool {
|
||||
return o.vals.IgnoresEmbeddedDockerReference
|
||||
}
|
||||
|
||||
// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently.
|
||||
func (o PropertyMethodsInitialize) HasThreadSafePutBlob() bool {
|
||||
return o.vals.HasThreadSafePutBlob
|
||||
}
|
||||
52
vendor/github.com/containers/image/v5/internal/imagedestination/stubs/put_blob_partial.go
generated
vendored
Normal file
52
vendor/github.com/containers/image/v5/internal/imagedestination/stubs/put_blob_partial.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/internal/blobinfocache"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// NoPutBlobPartialInitialize implements parts of private.ImageDestination
|
||||
// for transports that don’t support PutBlobPartial().
|
||||
// See NoPutBlobPartial() below.
|
||||
type NoPutBlobPartialInitialize struct {
|
||||
transportName string
|
||||
}
|
||||
|
||||
// NoPutBlobPartial creates a NoPutBlobPartialInitialize for ref.
|
||||
func NoPutBlobPartial(ref types.ImageReference) NoPutBlobPartialInitialize {
|
||||
return NoPutBlobPartialRaw(ref.Transport().Name())
|
||||
}
|
||||
|
||||
// NoPutBlobPartialRaw is the same thing as NoPutBlobPartial, but it can be used
|
||||
// in situations where no ImageReference is available.
|
||||
func NoPutBlobPartialRaw(transportName string) NoPutBlobPartialInitialize {
|
||||
return NoPutBlobPartialInitialize{
|
||||
transportName: transportName,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsPutBlobPartial returns true if PutBlobPartial is supported.
|
||||
func (stub NoPutBlobPartialInitialize) SupportsPutBlobPartial() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PutBlobPartial attempts to create a blob using the data that is already present
|
||||
// at the destination. chunkAccessor is accessed in a non-sequential way to retrieve the missing chunks.
|
||||
// It is available only if SupportsPutBlobPartial().
|
||||
// Even if SupportsPutBlobPartial() returns true, the call can fail, in which case the caller
|
||||
// should fall back to PutBlobWithOptions.
|
||||
func (stub NoPutBlobPartialInitialize) PutBlobPartial(ctx context.Context, chunkAccessor private.BlobChunkAccessor, srcInfo types.BlobInfo, cache blobinfocache.BlobInfoCache2) (types.BlobInfo, error) {
|
||||
return types.BlobInfo{}, fmt.Errorf("internal error: PutBlobPartial is not supported by the %q transport", stub.transportName)
|
||||
}
|
||||
|
||||
// ImplementsPutBlobPartial implements SupportsPutBlobPartial() that returns true.
|
||||
type ImplementsPutBlobPartial struct{}
|
||||
|
||||
// SupportsPutBlobPartial returns true if PutBlobPartial is supported.
|
||||
func (stub ImplementsPutBlobPartial) SupportsPutBlobPartial() bool {
|
||||
return true
|
||||
}
|
||||
50
vendor/github.com/containers/image/v5/internal/imagedestination/stubs/signatures.go
generated
vendored
Normal file
50
vendor/github.com/containers/image/v5/internal/imagedestination/stubs/signatures.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// NoSignaturesInitialize implements parts of private.ImageDestination
|
||||
// for transports that don’t support storing signatures.
|
||||
// See NoSignatures() below.
|
||||
type NoSignaturesInitialize struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// NoSignatures creates a NoSignaturesInitialize, failing with message.
|
||||
func NoSignatures(message string) NoSignaturesInitialize {
|
||||
return NoSignaturesInitialize{
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (stub NoSignaturesInitialize) SupportsSignatures(ctx context.Context) error {
|
||||
return errors.New(stub.message)
|
||||
}
|
||||
|
||||
// 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 (stub NoSignaturesInitialize) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||
if len(signatures) != 0 {
|
||||
return errors.New(stub.message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsSignatures implements SupportsSignatures() that returns nil.
|
||||
// Note that it might be even more useful to return a value dynamically detected based on
|
||||
type AlwaysSupportsSignatures struct{}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (stub AlwaysSupportsSignatures) SupportsSignatures(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
25
vendor/github.com/containers/image/v5/internal/imagedestination/stubs/stubs.go
generated
vendored
Normal file
25
vendor/github.com/containers/image/v5/internal/imagedestination/stubs/stubs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Package stubs contains trivial stubs for parts of private.ImageDestination.
|
||||
// It can be used from internal/wrapper, so it should not drag in any extra dependencies.
|
||||
// Compare with imagedestination/impl, which might require non-trivial implementation work.
|
||||
//
|
||||
// There are two kinds of stubs:
|
||||
// - Pure stubs, like ImplementsPutBlobPartial. Those can just be included in an imageDestination
|
||||
// implementation:
|
||||
//
|
||||
// type yourDestination struct {
|
||||
// stubs.ImplementsPutBlobPartial
|
||||
// …
|
||||
// }
|
||||
// - Stubs with a constructor, like NoPutBlobPartialInitialize. The Initialize marker
|
||||
// means that a constructor must be called:
|
||||
// type yourDestination struct {
|
||||
// stubs.NoPutBlobPartialInitialize
|
||||
// …
|
||||
// }
|
||||
//
|
||||
// dest := &yourDestination{
|
||||
// …
|
||||
// NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
||||
// }
|
||||
//
|
||||
package stubs
|
||||
52
vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go
generated
vendored
52
vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go
generated
vendored
|
|
@ -2,13 +2,23 @@ package imagedestination
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/internal/imagedestination/stubs"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// wrapped provides the private.ImageDestination operations
|
||||
// for a destination that only implements types.ImageDestination
|
||||
type wrapped struct {
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
|
||||
types.ImageDestination
|
||||
}
|
||||
|
||||
// FromPublic(dest) returns an object that provides the private.ImageDestination API
|
||||
//
|
||||
// Eventually, we might want to expose this function, and methods of the returned object,
|
||||
|
|
@ -23,18 +33,11 @@ func FromPublic(dest types.ImageDestination) private.ImageDestination {
|
|||
if dest2, ok := dest.(private.ImageDestination); ok {
|
||||
return dest2
|
||||
}
|
||||
return &wrapped{ImageDestination: dest}
|
||||
}
|
||||
return &wrapped{
|
||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(dest.Reference()),
|
||||
|
||||
// wrapped provides the private.ImageDestination operations
|
||||
// for a destination that only implements types.ImageDestination
|
||||
type wrapped struct {
|
||||
types.ImageDestination
|
||||
}
|
||||
|
||||
// SupportsPutBlobPartial returns true if PutBlobPartial is supported.
|
||||
func (w *wrapped) SupportsPutBlobPartial() bool {
|
||||
return false
|
||||
ImageDestination: dest,
|
||||
}
|
||||
}
|
||||
|
||||
// PutBlobWithOptions writes contents of stream and returns data representing the result.
|
||||
|
|
@ -48,15 +51,6 @@ func (w *wrapped) PutBlobWithOptions(ctx context.Context, stream io.Reader, inpu
|
|||
return w.PutBlob(ctx, stream, inputInfo, options.Cache, options.IsConfig)
|
||||
}
|
||||
|
||||
// PutBlobPartial attempts to create a blob using the data that is already present
|
||||
// at the destination. chunkAccessor is accessed in a non-sequential way to retrieve the missing chunks.
|
||||
// It is available only if SupportsPutBlobPartial().
|
||||
// Even if SupportsPutBlobPartial() returns true, the call can fail, in which case the caller
|
||||
// should fall back to PutBlobWithOptions.
|
||||
func (w *wrapped) PutBlobPartial(ctx context.Context, chunkAccessor private.BlobChunkAccessor, srcInfo types.BlobInfo, cache types.BlobInfoCache) (types.BlobInfo, error) {
|
||||
return types.BlobInfo{}, fmt.Errorf("internal error: PutBlobPartial is not supported by the %q transport", w.Reference().Transport().Name())
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
@ -67,3 +61,19 @@ func (w *wrapped) PutBlobPartial(ctx context.Context, chunkAccessor private.Blob
|
|||
func (w *wrapped) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, types.BlobInfo, error) {
|
||||
return w.TryReusingBlob(ctx, info, options.Cache, options.CanSubstitute)
|
||||
}
|
||||
|
||||
// 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 (w *wrapped) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||
simpleSigs := [][]byte{}
|
||||
for _, sig := range signatures {
|
||||
simpleSig, ok := sig.(signature.SimpleSigning)
|
||||
if !ok {
|
||||
return signature.UnsupportedFormatError(sig)
|
||||
}
|
||||
simpleSigs = append(simpleSigs, simpleSig.UntrustedSignature())
|
||||
}
|
||||
return w.PutSignatures(ctx, simpleSigs, instanceDigest)
|
||||
}
|
||||
|
|
|
|||
54
vendor/github.com/containers/image/v5/internal/imagesource/impl/compat.go
generated
vendored
Normal file
54
vendor/github.com/containers/image/v5/internal/imagesource/impl/compat.go
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Compat implements the obsolete parts of types.ImageSource
|
||||
// for implementations of private.ImageSource.
|
||||
// See AddCompat below.
|
||||
type Compat struct {
|
||||
src private.ImageSourceInternalOnly
|
||||
}
|
||||
|
||||
// AddCompat initializes Compat to implement the obsolete parts of types.ImageSource
|
||||
// for implementations of private.ImageSource.
|
||||
//
|
||||
// Use it like this:
|
||||
// type yourSource struct {
|
||||
// impl.Compat
|
||||
// …
|
||||
// }
|
||||
// src := &yourSource{…}
|
||||
// src.Compat = impl.AddCompat(src)
|
||||
//
|
||||
func AddCompat(src private.ImageSourceInternalOnly) Compat {
|
||||
return Compat{src}
|
||||
}
|
||||
|
||||
// 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 (c *Compat) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
// Silently ignore signatures with other formats; the caller can’t handle them.
|
||||
// Admittedly callers that want to sync all of the image might want to fail instead; this
|
||||
// way an upgrade of c/image neither breaks them nor adds new functionality.
|
||||
// Alternatively, we could possibly define the old GetSignatures to use the multi-format
|
||||
// signature.Blob representation now, in general, but that could silently break them as well.
|
||||
sigs, err := c.src.GetSignaturesWithFormat(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
simpleSigs := [][]byte{}
|
||||
for _, sig := range sigs {
|
||||
if sig, ok := sig.(signature.SimpleSigning); ok {
|
||||
simpleSigs = append(simpleSigs, sig.UntrustedSignature())
|
||||
}
|
||||
}
|
||||
return simpleSigs, nil
|
||||
}
|
||||
23
vendor/github.com/containers/image/v5/internal/imagesource/impl/layer_infos.go
generated
vendored
Normal file
23
vendor/github.com/containers/image/v5/internal/imagesource/impl/layer_infos.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// DoesNotAffectLayerInfosForCopy implements LayerInfosForCopy() that returns nothing.
|
||||
type DoesNotAffectLayerInfosForCopy struct{}
|
||||
|
||||
// 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 (stub DoesNotAffectLayerInfosForCopy) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
27
vendor/github.com/containers/image/v5/internal/imagesource/impl/properties.go
generated
vendored
Normal file
27
vendor/github.com/containers/image/v5/internal/imagesource/impl/properties.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package impl
|
||||
|
||||
// Properties collects properties of an ImageSource that are constant throughout its lifetime
|
||||
// (but might differ across instances).
|
||||
type Properties struct {
|
||||
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
|
||||
HasThreadSafeGetBlob bool
|
||||
}
|
||||
|
||||
// PropertyMethodsInitialize implements parts of private.ImageSource corresponding to Properties.
|
||||
type PropertyMethodsInitialize struct {
|
||||
// We need two separate structs, PropertyMethodsInitialize and Properties, because Go prohibits fields and methods with the same name.
|
||||
|
||||
vals Properties
|
||||
}
|
||||
|
||||
// PropertyMethods creates an PropertyMethodsInitialize for vals.
|
||||
func PropertyMethods(vals Properties) PropertyMethodsInitialize {
|
||||
return PropertyMethodsInitialize{
|
||||
vals: vals,
|
||||
}
|
||||
}
|
||||
|
||||
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
|
||||
func (o PropertyMethodsInitialize) HasThreadSafeGetBlob() bool {
|
||||
return o.vals.HasThreadSafeGetBlob
|
||||
}
|
||||
19
vendor/github.com/containers/image/v5/internal/imagesource/impl/signatures.go
generated
vendored
Normal file
19
vendor/github.com/containers/image/v5/internal/imagesource/impl/signatures.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// NoSignatures implements GetSignatures() that returns nothing.
|
||||
type NoSignatures struct{}
|
||||
|
||||
// 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 (stub NoSignatures) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
return nil, nil
|
||||
}
|
||||
52
vendor/github.com/containers/image/v5/internal/imagesource/stubs/get_blob_at.go
generated
vendored
Normal file
52
vendor/github.com/containers/image/v5/internal/imagesource/stubs/get_blob_at.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package stubs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// NoGetBlobAtInitialize implements parts of private.ImageSource
|
||||
// for transports that don’t support GetBlobAt().
|
||||
// See NoGetBlobAt() below.
|
||||
type NoGetBlobAtInitialize struct {
|
||||
transportName string
|
||||
}
|
||||
|
||||
// NoGetBlobAt() creates a NoGetBlobAtInitialize for ref.
|
||||
func NoGetBlobAt(ref types.ImageReference) NoGetBlobAtInitialize {
|
||||
return NoGetBlobAtRaw(ref.Transport().Name())
|
||||
}
|
||||
|
||||
// NoGetBlobAtRaw is the same thing as NoGetBlobAt, but it can be used
|
||||
// in situations where no ImageReference is available.
|
||||
func NoGetBlobAtRaw(transportName string) NoGetBlobAtInitialize {
|
||||
return NoGetBlobAtInitialize{
|
||||
transportName: transportName,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsGetBlobAt() returns true if GetBlobAt (BlobChunkAccessor) is supported.
|
||||
func (stub NoGetBlobAtInitialize) SupportsGetBlobAt() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBlobAt returns a sequential channel of readers that contain data for the requested
|
||||
// blob chunks, and a channel that might get a single error value.
|
||||
// The specified chunks must be not overlapping and sorted by their offset.
|
||||
// The readers must be fully consumed, in the order they are returned, before blocking
|
||||
// to read the next chunk.
|
||||
func (stub NoGetBlobAtInitialize) GetBlobAt(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk) (chan io.ReadCloser, chan error, error) {
|
||||
return nil, nil, fmt.Errorf("internal error: GetBlobAt is not supported by the %q transport", stub.transportName)
|
||||
}
|
||||
|
||||
// ImplementsGetBlobAt implements SupportsGetBlobAt() that returns true.
|
||||
type ImplementsGetBlobAt struct{}
|
||||
|
||||
// SupportsGetBlobAt() returns true if GetBlobAt (BlobChunkAccessor) is supported.
|
||||
func (stub ImplementsGetBlobAt) SupportsGetBlobAt() bool {
|
||||
return true
|
||||
}
|
||||
25
vendor/github.com/containers/image/v5/internal/imagesource/stubs/stubs.go
generated
vendored
Normal file
25
vendor/github.com/containers/image/v5/internal/imagesource/stubs/stubs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Package stubs contains trivial stubs for parts of private.ImageSource.
|
||||
// It can be used from internal/wrapper, so it should not drag in any extra dependencies.
|
||||
// Compare with imagesource/impl, which might require non-trivial implementation work.
|
||||
//
|
||||
// There are two kinds of stubs:
|
||||
// - Pure stubs, like ImplementsGetBlobAt. Those can just be included in an ImageSource
|
||||
// implementation:
|
||||
//
|
||||
// type yourSource struct {
|
||||
// stubs.ImplementsGetBlobAt
|
||||
// …
|
||||
// }
|
||||
// - Stubs with a constructor, like NoGetBlobAtInitialize. The Initialize marker
|
||||
// means that a constructor must be called:
|
||||
// type yourSource struct {
|
||||
// stubs.NoGetBlobAtInitialize
|
||||
// …
|
||||
// }
|
||||
//
|
||||
// dest := &yourSource{
|
||||
// …
|
||||
// NoGetBlobAtInitialize: stubs.NoGetBlobAt(ref),
|
||||
// }
|
||||
//
|
||||
package stubs
|
||||
51
vendor/github.com/containers/image/v5/internal/imagesource/wrapper.go
generated
vendored
51
vendor/github.com/containers/image/v5/internal/imagesource/wrapper.go
generated
vendored
|
|
@ -2,13 +2,22 @@ package imagesource
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"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/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// wrapped provides the private.ImageSource operations
|
||||
// for a source that only implements types.ImageSource
|
||||
type wrapped struct {
|
||||
stubs.NoGetBlobAtInitialize
|
||||
|
||||
types.ImageSource
|
||||
}
|
||||
|
||||
// FromPublic(src) returns an object that provides the private.ImageSource API
|
||||
//
|
||||
// Eventually, we might want to expose this function, and methods of the returned object,
|
||||
|
|
@ -23,25 +32,25 @@ func FromPublic(src types.ImageSource) private.ImageSource {
|
|||
if src2, ok := src.(private.ImageSource); ok {
|
||||
return src2
|
||||
}
|
||||
return &wrapped{ImageSource: src}
|
||||
return &wrapped{
|
||||
NoGetBlobAtInitialize: stubs.NoGetBlobAt(src.Reference()),
|
||||
|
||||
ImageSource: src,
|
||||
}
|
||||
}
|
||||
|
||||
// wrapped provides the private.ImageSource operations
|
||||
// for a source that only implements types.ImageSource
|
||||
type wrapped struct {
|
||||
types.ImageSource
|
||||
}
|
||||
|
||||
// SupportsGetBlobAt() returns true if GetBlobAt (BlobChunkAccessor) is supported.
|
||||
func (w *wrapped) SupportsGetBlobAt() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBlobAt returns a sequential channel of readers that contain data for the requested
|
||||
// blob chunks, and a channel that might get a single error value.
|
||||
// The specified chunks must be not overlapping and sorted by their offset.
|
||||
// The readers must be fully consumed, in the order they are returned, before blocking
|
||||
// to read the next chunk.
|
||||
func (w *wrapped) GetBlobAt(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk) (chan io.ReadCloser, chan error, error) {
|
||||
return nil, nil, fmt.Errorf("internal error: GetBlobAt is not supported by the %q transport", w.Reference().Transport().Name())
|
||||
// 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 (w *wrapped) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
sigs, err := w.GetSignatures(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := []signature.Signature{}
|
||||
for _, sig := range sigs {
|
||||
res = append(res, signature.SimpleSigningFromBlob(sig))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
|||
5
vendor/github.com/containers/image/v5/internal/iolimits/iolimits.go
generated
vendored
5
vendor/github.com/containers/image/v5/internal/iolimits/iolimits.go
generated
vendored
|
|
@ -1,9 +1,8 @@
|
|||
package iolimits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// All constants below are intended to be used as limits for `ReadAtMost`. The
|
||||
|
|
@ -52,7 +51,7 @@ func ReadAtMost(reader io.Reader, limit int) ([]byte, error) {
|
|||
}
|
||||
|
||||
if len(res) > limit {
|
||||
return nil, errors.Errorf("exceeded maximum allowed size of %d bytes", limit)
|
||||
return nil, fmt.Errorf("exceeded maximum allowed size of %d bytes", limit)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
|
|
|||
32
vendor/github.com/containers/image/v5/internal/manifest/errors.go
generated
vendored
Normal file
32
vendor/github.com/containers/image/v5/internal/manifest/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package manifest
|
||||
|
||||
import "fmt"
|
||||
|
||||
// NonImageArtifactError (detected via errors.As) is used when asking for an image-specific operation
|
||||
// on an object which is not a “container image” in the standard sense (e.g. an OCI artifact)
|
||||
//
|
||||
// This is publicly visible as c/image/manifest.NonImageArtifactError (but we don’t provide a public constructor)
|
||||
type NonImageArtifactError struct {
|
||||
// Callers should not be blindly calling image-specific operations and only checking MIME types
|
||||
// on failure; if they care about the artifact type, they should check before using it.
|
||||
// If they blindly assume an image, they don’t really need this value; just a type check
|
||||
// is sufficient for basic "we can only pull images" UI.
|
||||
//
|
||||
// Also, there are fairly widespread “artifacts” which nevertheless use imgspecv1.MediaTypeImageConfig,
|
||||
// e.g. https://github.com/sigstore/cosign/blob/main/specs/SIGNATURE_SPEC.md , which could cause the callers
|
||||
// to complain about a non-image artifact with the correct MIME type; we should probably add some other kind of
|
||||
// type discrimination, _and_ somehow make it available in the API, if we expect API callers to make decisions
|
||||
// based on that kind of data.
|
||||
//
|
||||
// So, let’s not expose this until a specific need is identified.
|
||||
mimeType string
|
||||
}
|
||||
|
||||
// NewNonImageArtifactError returns a NonImageArtifactError about an artifact with mimeType.
|
||||
func NewNonImageArtifactError(mimeType string) error {
|
||||
return NonImageArtifactError{mimeType: mimeType}
|
||||
}
|
||||
|
||||
func (e NonImageArtifactError) Error() string {
|
||||
return fmt.Sprintf("unsupported image-specific operation on artifact with type %q", e.mimeType)
|
||||
}
|
||||
64
vendor/github.com/containers/image/v5/internal/private/private.go
generated
vendored
64
vendor/github.com/containers/image/v5/internal/private/private.go
generated
vendored
|
|
@ -5,26 +5,40 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/blobinfocache"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// ImageSource is an internal extension to the types.ImageSource interface.
|
||||
type ImageSource interface {
|
||||
types.ImageSource
|
||||
|
||||
// ImageSourceInternalOnly is the part of private.ImageSource that is not
|
||||
// a part of types.ImageSource.
|
||||
type ImageSourceInternalOnly interface {
|
||||
// SupportsGetBlobAt() returns true if GetBlobAt (BlobChunkAccessor) is supported.
|
||||
SupportsGetBlobAt() bool
|
||||
// BlobChunkAccessor.GetBlobAt is available only if SupportsGetBlobAt().
|
||||
BlobChunkAccessor
|
||||
|
||||
// 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).
|
||||
GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error)
|
||||
}
|
||||
|
||||
// ImageDestination is an internal extension to the types.ImageDestination
|
||||
// interface.
|
||||
type ImageDestination interface {
|
||||
types.ImageDestination
|
||||
// ImageSource is an internal extension to the types.ImageSource interface.
|
||||
type ImageSource interface {
|
||||
types.ImageSource
|
||||
ImageSourceInternalOnly
|
||||
}
|
||||
|
||||
// ImageDestinationInternalOnly is the part of private.ImageDestination that is not
|
||||
// a part of types.ImageDestination.
|
||||
type ImageDestinationInternalOnly interface {
|
||||
// SupportsPutBlobPartial returns true if PutBlobPartial is supported.
|
||||
SupportsPutBlobPartial() bool
|
||||
// FIXME: Add SupportsSignaturesWithFormat or something like that, to allow early failures
|
||||
// on unsupported formats.
|
||||
|
||||
// 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.
|
||||
|
|
@ -40,7 +54,7 @@ type ImageDestination interface {
|
|||
// It is available only if SupportsPutBlobPartial().
|
||||
// Even if SupportsPutBlobPartial() returns true, the call can fail, in which case the caller
|
||||
// should fall back to PutBlobWithOptions.
|
||||
PutBlobPartial(ctx context.Context, chunkAccessor BlobChunkAccessor, srcInfo types.BlobInfo, cache types.BlobInfoCache) (types.BlobInfo, error)
|
||||
PutBlobPartial(ctx context.Context, chunkAccessor BlobChunkAccessor, srcInfo types.BlobInfo, cache blobinfocache.BlobInfoCache2) (types.BlobInfo, error)
|
||||
|
||||
// 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).
|
||||
|
|
@ -50,15 +64,31 @@ type ImageDestination interface {
|
|||
// 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.
|
||||
TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options TryReusingBlobOptions) (bool, types.BlobInfo, error)
|
||||
|
||||
// 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).
|
||||
PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error
|
||||
}
|
||||
|
||||
// ImageDestination is an internal extension to the types.ImageDestination
|
||||
// interface.
|
||||
type ImageDestination interface {
|
||||
types.ImageDestination
|
||||
ImageDestinationInternalOnly
|
||||
}
|
||||
|
||||
// PutBlobOptions are used in PutBlobWithOptions.
|
||||
type PutBlobOptions struct {
|
||||
Cache types.BlobInfoCache // Cache to optionally update with the uploaded bloblook up blob infos.
|
||||
IsConfig bool // True if the blob is a config
|
||||
Cache blobinfocache.BlobInfoCache2 // Cache to optionally update with the uploaded bloblook up blob infos.
|
||||
IsConfig bool // True if the blob is a config
|
||||
|
||||
// The following fields are new to internal/private. Users of internal/private MUST fill them in,
|
||||
// but they also must expect that they will be ignored by types.ImageDestination transports.
|
||||
// Transports, OTOH, MUST support these fields being zero-valued for types.ImageDestination callers
|
||||
// if they use internal/imagedestination/impl.Compat;
|
||||
// in that case, they will all be consistently zero-valued.
|
||||
|
||||
EmptyLayer bool // True if the blob is an "empty"/"throwaway" layer, and may not necessarily be physically represented.
|
||||
LayerIndex *int // If the blob is a layer, a zero-based index of the layer within the image; nil otherwise.
|
||||
|
|
@ -66,13 +96,16 @@ type PutBlobOptions struct {
|
|||
|
||||
// TryReusingBlobOptions are used in TryReusingBlobWithOptions.
|
||||
type TryReusingBlobOptions struct {
|
||||
Cache types.BlobInfoCache // Cache to use and/or update.
|
||||
Cache blobinfocache.BlobInfoCache2 // Cache to use and/or update.
|
||||
// If true, it is allowed to use an equivalent of the desired blob;
|
||||
// in that case the returned info may not match the input.
|
||||
CanSubstitute bool
|
||||
|
||||
// The following fields are new to internal/private. Users of internal/private MUST fill them in,
|
||||
// but they also must expect that they will be ignored by types.ImageDestination transports.
|
||||
// Transports, OTOH, MUST support these fields being zero-valued for types.ImageDestination callers
|
||||
// if they use internal/imagedestination/impl.Compat;
|
||||
// in that case, they will all be consistently zero-valued.
|
||||
|
||||
EmptyLayer bool // True if the blob is an "empty"/"throwaway" layer, and may not necessarily be physically represented.
|
||||
LayerIndex *int // If the blob is a layer, a zero-based index of the layer within the image; nil otherwise.
|
||||
|
|
@ -104,3 +137,10 @@ type BadPartialRequestError struct {
|
|||
func (e BadPartialRequestError) Error() string {
|
||||
return e.Status
|
||||
}
|
||||
|
||||
// UnparsedImage is an internal extension to the types.UnparsedImage interface.
|
||||
type UnparsedImage interface {
|
||||
types.UnparsedImage
|
||||
// UntrustedSignatures is like ImageSource.GetSignaturesWithFormat, but the result is cached; it is OK to call this however often you need.
|
||||
UntrustedSignatures(ctx context.Context) ([]signature.Signature, error)
|
||||
}
|
||||
|
|
|
|||
111
vendor/github.com/containers/image/v5/internal/signature/signature.go
generated
vendored
Normal file
111
vendor/github.com/containers/image/v5/internal/signature/signature.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// FIXME FIXME: MIME type? Int? String?
|
||||
// An interface with a name, parse methods?
|
||||
type FormatID string
|
||||
|
||||
const (
|
||||
SimpleSigningFormat FormatID = "simple-signing"
|
||||
SigstoreFormat FormatID = "sigstore-json"
|
||||
// Update also UnsupportedFormatError below
|
||||
)
|
||||
|
||||
// Signature is an image signature of some kind.
|
||||
type Signature interface {
|
||||
FormatID() FormatID
|
||||
// blobChunk returns a representation of signature as a []byte, suitable for long-term storage.
|
||||
// Almost everyone should use signature.Blob() instead.
|
||||
blobChunk() ([]byte, error)
|
||||
}
|
||||
|
||||
// BlobChunk returns a representation of sig as a []byte, suitable for long-term storage.
|
||||
func Blob(sig Signature) ([]byte, error) {
|
||||
chunk, err := sig.blobChunk()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format := sig.FormatID()
|
||||
switch format {
|
||||
case SimpleSigningFormat:
|
||||
// For compatibility with old dir formats:
|
||||
return chunk, nil
|
||||
default:
|
||||
res := []byte{0} // Start with a zero byte to clearly mark this is a binary format, and disambiguate from random text.
|
||||
res = append(res, []byte(format)...)
|
||||
res = append(res, '\n')
|
||||
res = append(res, chunk...)
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
// FromBlob returns a signature from parsing a blob created by signature.Blob.
|
||||
func FromBlob(blob []byte) (Signature, error) {
|
||||
if len(blob) == 0 {
|
||||
return nil, errors.New("empty signature blob")
|
||||
}
|
||||
// Historically we’ve just been using GPG with no identification; try to auto-detect that.
|
||||
switch blob[0] {
|
||||
// OpenPGP "compressed data" wrapping the message
|
||||
case 0xA0, 0xA1, 0xA2, 0xA3, // bit 7 = 1; bit 6 = 0 (old packet format); bits 5…2 = 8 (tag: compressed data packet); bits 1…0 = length-type (any)
|
||||
0xC8, // bit 7 = 1; bit 6 = 1 (new packet format); bits 5…0 = 8 (tag: compressed data packet)
|
||||
// OpenPGP “one-pass signature” starting a signature
|
||||
0x90, 0x91, 0x92, 0x3d, // bit 7 = 1; bit 6 = 0 (old packet format); bits 5…2 = 4 (tag: one-pass signature packet); bits 1…0 = length-type (any)
|
||||
0xC4, // bit 7 = 1; bit 6 = 1 (new packet format); bits 5…0 = 4 (tag: one-pass signature packet)
|
||||
// OpenPGP signature packet signing the following data
|
||||
0x88, 0x89, 0x8A, 0x8B, // bit 7 = 1; bit 6 = 0 (old packet format); bits 5…2 = 2 (tag: signature packet); bits 1…0 = length-type (any)
|
||||
0xC2: // bit 7 = 1; bit 6 = 1 (new packet format); bits 5…0 = 2 (tag: signature packet)
|
||||
return SimpleSigningFromBlob(blob), nil
|
||||
|
||||
// The newer format: binary 0, format name, newline, data
|
||||
case 0x00:
|
||||
blob = blob[1:]
|
||||
newline := bytes.IndexByte(blob, '\n')
|
||||
if newline == -1 {
|
||||
return nil, fmt.Errorf("invalid signature format, missing newline")
|
||||
}
|
||||
formatBytes := blob[:newline]
|
||||
for _, b := range formatBytes {
|
||||
if b < 32 || b >= 0x7F {
|
||||
return nil, fmt.Errorf("invalid signature format, non-ASCII byte %#x", b)
|
||||
}
|
||||
}
|
||||
blobChunk := blob[newline+1:]
|
||||
switch {
|
||||
case bytes.Equal(formatBytes, []byte(SimpleSigningFormat)):
|
||||
return SimpleSigningFromBlob(blobChunk), nil
|
||||
case bytes.Equal(formatBytes, []byte(SigstoreFormat)):
|
||||
return SigstoreFromBlobChunk(blobChunk)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized signature format %q", string(formatBytes))
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized signature format, starting with binary %#x", blob[0])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// UnsupportedFormatError returns an error complaining about sig having an unsupported format.
|
||||
func UnsupportedFormatError(sig Signature) error {
|
||||
formatID := sig.FormatID()
|
||||
switch formatID {
|
||||
case SimpleSigningFormat, SigstoreFormat:
|
||||
return fmt.Errorf("unsupported signature format %s", string(formatID))
|
||||
default:
|
||||
return fmt.Errorf("unsupported, and unrecognized, signature format %q", string(formatID))
|
||||
}
|
||||
}
|
||||
|
||||
// copyByteSlice returns a guaranteed-fresh copy of a byte slice
|
||||
// Use this to make sure the underlying data is not shared and can’t be unexpectedly modified.
|
||||
func copyByteSlice(s []byte) []byte {
|
||||
res := []byte{}
|
||||
return append(res, s...)
|
||||
}
|
||||
84
vendor/github.com/containers/image/v5/internal/signature/sigstore.go
generated
vendored
Normal file
84
vendor/github.com/containers/image/v5/internal/signature/sigstore.go
generated
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package signature
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
// from sigstore/cosign/pkg/types.SimpleSigningMediaType
|
||||
SigstoreSignatureMIMEType = "application/vnd.dev.cosign.simplesigning.v1+json"
|
||||
// from sigstore/cosign/pkg/oci/static.SignatureAnnotationKey
|
||||
SigstoreSignatureAnnotationKey = "dev.cosignproject.cosign/signature"
|
||||
)
|
||||
|
||||
// Sigstore is a github.com/cosign/cosign signature.
|
||||
// For the persistent-storage format used for blobChunk(), we want
|
||||
// a degree of forward compatibility against unexpected field changes
|
||||
// (as has happened before), which is why this data type
|
||||
// contains just a payload + annotations (including annotations
|
||||
// that we don’t recognize or support), instead of individual fields
|
||||
// for the known annotations.
|
||||
type Sigstore struct {
|
||||
untrustedMIMEType string
|
||||
untrustedPayload []byte
|
||||
untrustedAnnotations map[string]string
|
||||
}
|
||||
|
||||
// sigstoreJSONRepresentation needs the files to be public, which we don’t want for
|
||||
// the main Sigstore type.
|
||||
type sigstoreJSONRepresentation struct {
|
||||
UntrustedMIMEType string `json:"mimeType"`
|
||||
UntrustedPayload []byte `json:"payload"`
|
||||
UntrustedAnnotations map[string]string `json:"annotations"`
|
||||
}
|
||||
|
||||
// SigstoreFromComponents returns a Sigstore object from its components.
|
||||
func SigstoreFromComponents(untrustedMimeType string, untrustedPayload []byte, untrustedAnnotations map[string]string) Sigstore {
|
||||
return Sigstore{
|
||||
untrustedMIMEType: untrustedMimeType,
|
||||
untrustedPayload: copyByteSlice(untrustedPayload),
|
||||
untrustedAnnotations: copyStringMap(untrustedAnnotations),
|
||||
}
|
||||
}
|
||||
|
||||
// SigstoreFromBlobChunk converts a Sigstore signature, as returned by Sigstore.blobChunk, into a Sigstore object.
|
||||
func SigstoreFromBlobChunk(blobChunk []byte) (Sigstore, error) {
|
||||
var v sigstoreJSONRepresentation
|
||||
if err := json.Unmarshal(blobChunk, &v); err != nil {
|
||||
return Sigstore{}, err
|
||||
}
|
||||
return SigstoreFromComponents(v.UntrustedMIMEType,
|
||||
v.UntrustedPayload,
|
||||
v.UntrustedAnnotations), nil
|
||||
}
|
||||
|
||||
func (s Sigstore) FormatID() FormatID {
|
||||
return SigstoreFormat
|
||||
}
|
||||
|
||||
// blobChunk returns a representation of signature as a []byte, suitable for long-term storage.
|
||||
// Almost everyone should use signature.Blob() instead.
|
||||
func (s Sigstore) blobChunk() ([]byte, error) {
|
||||
return json.Marshal(sigstoreJSONRepresentation{
|
||||
UntrustedMIMEType: s.UntrustedMIMEType(),
|
||||
UntrustedPayload: s.UntrustedPayload(),
|
||||
UntrustedAnnotations: s.UntrustedAnnotations(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s Sigstore) UntrustedMIMEType() string {
|
||||
return s.untrustedMIMEType
|
||||
}
|
||||
func (s Sigstore) UntrustedPayload() []byte {
|
||||
return copyByteSlice(s.untrustedPayload)
|
||||
}
|
||||
|
||||
func (s Sigstore) UntrustedAnnotations() map[string]string {
|
||||
return copyStringMap(s.untrustedAnnotations)
|
||||
}
|
||||
|
||||
func copyStringMap(m map[string]string) map[string]string {
|
||||
res := map[string]string{}
|
||||
for k, v := range m {
|
||||
res[k] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
27
vendor/github.com/containers/image/v5/internal/signature/simple.go
generated
vendored
Normal file
27
vendor/github.com/containers/image/v5/internal/signature/simple.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package signature
|
||||
|
||||
// SimpleSigning is a “simple signing” signature.
|
||||
type SimpleSigning struct {
|
||||
untrustedSignature []byte
|
||||
}
|
||||
|
||||
// SimpleSigningFromBlob converts a “simple signing” signature into a SimpleSigning object.
|
||||
func SimpleSigningFromBlob(blobChunk []byte) SimpleSigning {
|
||||
return SimpleSigning{
|
||||
untrustedSignature: copyByteSlice(blobChunk),
|
||||
}
|
||||
}
|
||||
|
||||
func (s SimpleSigning) FormatID() FormatID {
|
||||
return SimpleSigningFormat
|
||||
}
|
||||
|
||||
// blobChunk returns a representation of signature as a []byte, suitable for long-term storage.
|
||||
// Almost everyone should use signature.Blob() instead.
|
||||
func (s SimpleSigning) blobChunk() ([]byte, error) {
|
||||
return copyByteSlice(s.untrustedSignature), nil
|
||||
}
|
||||
|
||||
func (s SimpleSigning) UntrustedSignature() []byte {
|
||||
return copyByteSlice(s.untrustedSignature)
|
||||
}
|
||||
38
vendor/github.com/containers/image/v5/internal/unparsedimage/wrapper.go
generated
vendored
Normal file
38
vendor/github.com/containers/image/v5/internal/unparsedimage/wrapper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package unparsedimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// wrapped provides the private.UnparsedImage operations
|
||||
// for an object that only implements types.UnparsedImage
|
||||
type wrapped struct {
|
||||
types.UnparsedImage
|
||||
}
|
||||
|
||||
// FromPublic(unparsed) returns an object that provides the private.UnparsedImage API
|
||||
func FromPublic(unparsed types.UnparsedImage) private.UnparsedImage {
|
||||
if unparsed2, ok := unparsed.(private.UnparsedImage); ok {
|
||||
return unparsed2
|
||||
}
|
||||
return &wrapped{
|
||||
UnparsedImage: unparsed,
|
||||
}
|
||||
}
|
||||
|
||||
// UntrustedSignatures is like ImageSource.GetSignaturesWithFormat, but the result is cached; it is OK to call this however often you need.
|
||||
func (w *wrapped) UntrustedSignatures(ctx context.Context) ([]signature.Signature, error) {
|
||||
sigs, err := w.Signatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := []signature.Signature{}
|
||||
for _, sig := range sigs {
|
||||
res = append(res, signature.SimpleSigningFromBlob(sig))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
63
vendor/github.com/containers/image/v5/manifest/common.go
generated
vendored
63
vendor/github.com/containers/image/v5/manifest/common.go
generated
vendored
|
|
@ -118,6 +118,18 @@ type compressionMIMETypeSet map[string]string
|
|||
const mtsUncompressed = "" // A key in compressionMIMETypeSet for the uncompressed variant
|
||||
const mtsUnsupportedMIMEType = "" // A value in compressionMIMETypeSet that means “recognized but unsupported”
|
||||
|
||||
// findCompressionMIMETypeSet returns a pointer to a compressionMIMETypeSet in variantTable that contains a value of mimeType, or nil if not found
|
||||
func findCompressionMIMETypeSet(variantTable []compressionMIMETypeSet, mimeType string) compressionMIMETypeSet {
|
||||
for _, variants := range variantTable {
|
||||
for _, mt := range variants {
|
||||
if mt == mimeType {
|
||||
return variants
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compressionVariantMIMEType returns a variant of mimeType for the specified algorithm (which may be nil
|
||||
// to mean "no compression"), based on variantTable.
|
||||
// The returned error will be a ManifestLayerCompressionIncompatibilityError if mimeType has variants
|
||||
|
|
@ -130,29 +142,26 @@ func compressionVariantMIMEType(variantTable []compressionMIMETypeSet, mimeType
|
|||
if mimeType == mtsUnsupportedMIMEType { // Prevent matching against the {algo:mtsUnsupportedMIMEType} entries
|
||||
return "", fmt.Errorf("cannot update unknown MIME type")
|
||||
}
|
||||
for _, variants := range variantTable {
|
||||
for _, mt := range variants {
|
||||
if mt == mimeType { // Found the variant
|
||||
name := mtsUncompressed
|
||||
if algorithm != nil {
|
||||
name = algorithm.InternalUnstableUndocumentedMIMEQuestionMark()
|
||||
}
|
||||
if res, ok := variants[name]; ok {
|
||||
if res != mtsUnsupportedMIMEType {
|
||||
return res, nil
|
||||
}
|
||||
if name != mtsUncompressed {
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("%s compression is not supported for type %q", name, mt)}
|
||||
}
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("uncompressed variant is not supported for type %q", mt)}
|
||||
}
|
||||
if name != mtsUncompressed {
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("unknown compressed with algorithm %s variant for type %s", name, mt)}
|
||||
}
|
||||
// We can't very well say “the idea of no compression is unknown”
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("uncompressed variant is not supported for type %q", mt)}
|
||||
}
|
||||
variants := findCompressionMIMETypeSet(variantTable, mimeType)
|
||||
if variants != nil {
|
||||
name := mtsUncompressed
|
||||
if algorithm != nil {
|
||||
name = algorithm.InternalUnstableUndocumentedMIMEQuestionMark()
|
||||
}
|
||||
if res, ok := variants[name]; ok {
|
||||
if res != mtsUnsupportedMIMEType {
|
||||
return res, nil
|
||||
}
|
||||
if name != mtsUncompressed {
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("%s compression is not supported for type %q", name, mimeType)}
|
||||
}
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("uncompressed variant is not supported for type %q", mimeType)}
|
||||
}
|
||||
if name != mtsUncompressed {
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("unknown compressed with algorithm %s variant for type %s", name, mimeType)}
|
||||
}
|
||||
// We can't very well say “the idea of no compression is unknown”
|
||||
return "", ManifestLayerCompressionIncompatibilityError{fmt.Sprintf("uncompressed variant is not supported for type %q", mimeType)}
|
||||
}
|
||||
if algorithm != nil {
|
||||
return "", fmt.Errorf("unsupported MIME type for compression: %s", mimeType)
|
||||
|
|
@ -209,3 +218,13 @@ type ManifestLayerCompressionIncompatibilityError struct {
|
|||
func (m ManifestLayerCompressionIncompatibilityError) Error() string {
|
||||
return m.text
|
||||
}
|
||||
|
||||
// compressionVariantsRecognizeMIMEType returns true if variantTable contains data about compressing/decompressing layers with mimeType
|
||||
// Note that the caller still needs to worry about a specific algorithm not being supported.
|
||||
func compressionVariantsRecognizeMIMEType(variantTable []compressionMIMETypeSet, mimeType string) bool {
|
||||
if mimeType == mtsUnsupportedMIMEType { // Prevent matching against the {algo:mtsUnsupportedMIMEType} entries
|
||||
return false
|
||||
}
|
||||
variants := findCompressionMIMETypeSet(variantTable, mimeType)
|
||||
return variants != nil // Alternatively, this could be len(variants) > 1, but really the caller should ask about a specific algorithm.
|
||||
}
|
||||
|
|
|
|||
27
vendor/github.com/containers/image/v5/manifest/docker_schema1.go
generated
vendored
27
vendor/github.com/containers/image/v5/manifest/docker_schema1.go
generated
vendored
|
|
@ -2,6 +2,8 @@ package manifest
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -10,7 +12,6 @@ import (
|
|||
"github.com/containers/image/v5/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Schema1FSLayers is an entry of the "fsLayers" array in docker/distribution schema 1.
|
||||
|
|
@ -58,7 +59,7 @@ func Schema1FromManifest(manifest []byte) (*Schema1, error) {
|
|||
return nil, err
|
||||
}
|
||||
if s1.SchemaVersion != 1 {
|
||||
return nil, errors.Errorf("unsupported schema version %d", s1.SchemaVersion)
|
||||
return nil, fmt.Errorf("unsupported schema version %d", s1.SchemaVersion)
|
||||
}
|
||||
if err := validateUnambiguousManifestFormat(manifest, DockerV2Schema1SignedMediaType,
|
||||
allowedFieldFSLayers|allowedFieldHistory); err != nil {
|
||||
|
|
@ -113,7 +114,7 @@ func (m *Schema1) initialize() error {
|
|||
m.ExtractedV1Compatibility = make([]Schema1V1Compatibility, len(m.History))
|
||||
for i, h := range m.History {
|
||||
if err := json.Unmarshal([]byte(h.V1Compatibility), &m.ExtractedV1Compatibility[i]); err != nil {
|
||||
return errors.Wrapf(err, "parsing v2s1 history entry %d", i)
|
||||
return fmt.Errorf("parsing v2s1 history entry %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -142,7 +143,7 @@ func (m *Schema1) LayerInfos() []LayerInfo {
|
|||
func (m *Schema1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
||||
// Our LayerInfos includes empty layers (where m.ExtractedV1Compatibility[].ThrowAway), so expect them to be included here as well.
|
||||
if len(m.FSLayers) != len(layerInfos) {
|
||||
return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.FSLayers), len(layerInfos))
|
||||
return fmt.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.FSLayers), len(layerInfos))
|
||||
}
|
||||
m.FSLayers = make([]Schema1FSLayers, len(layerInfos))
|
||||
for i, info := range layerInfos {
|
||||
|
|
@ -187,7 +188,7 @@ func (m *Schema1) fixManifestLayers() error {
|
|||
for _, img := range m.ExtractedV1Compatibility {
|
||||
// skip IDs that appear after each other, we handle those later
|
||||
if _, exists := idmap[img.ID]; img.ID != lastID && exists {
|
||||
return errors.Errorf("ID %+v appears multiple times in manifest", img.ID)
|
||||
return fmt.Errorf("ID %+v appears multiple times in manifest", img.ID)
|
||||
}
|
||||
lastID = img.ID
|
||||
idmap[lastID] = struct{}{}
|
||||
|
|
@ -199,7 +200,7 @@ func (m *Schema1) fixManifestLayers() error {
|
|||
m.History = append(m.History[:i], m.History[i+1:]...)
|
||||
m.ExtractedV1Compatibility = append(m.ExtractedV1Compatibility[:i], m.ExtractedV1Compatibility[i+1:]...)
|
||||
} else if m.ExtractedV1Compatibility[i].Parent != m.ExtractedV1Compatibility[i+1].ID {
|
||||
return errors.Errorf("Invalid parent ID. Expected %v, got %v", m.ExtractedV1Compatibility[i+1].ID, m.ExtractedV1Compatibility[i].Parent)
|
||||
return fmt.Errorf("Invalid parent ID. Expected %v, got %v", m.ExtractedV1Compatibility[i+1].ID, m.ExtractedV1Compatibility[i].Parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -209,7 +210,7 @@ var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
|||
|
||||
func validateV1ID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return errors.Errorf("image ID %q is invalid", id)
|
||||
return fmt.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -246,14 +247,14 @@ func (m *Schema1) ToSchema2Config(diffIDs []digest.Digest) ([]byte, error) {
|
|||
config := []byte(m.History[0].V1Compatibility)
|
||||
err := json.Unmarshal(config, &s1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "decoding configuration")
|
||||
return nil, fmt.Errorf("decoding configuration: %w", err)
|
||||
}
|
||||
// Images created with versions prior to 1.8.3 require us to re-encode the encoded object,
|
||||
// adding some fields that aren't "omitempty".
|
||||
if s1.DockerVersion != "" && versions.LessThan(s1.DockerVersion, "1.8.3") {
|
||||
config, err = json.Marshal(&s1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "re-encoding compat image config %#v", s1)
|
||||
return nil, fmt.Errorf("re-encoding compat image config %#v: %w", s1, err)
|
||||
}
|
||||
}
|
||||
// Build the history.
|
||||
|
|
@ -280,7 +281,7 @@ func (m *Schema1) ToSchema2Config(diffIDs []digest.Digest) ([]byte, error) {
|
|||
raw := make(map[string]*json.RawMessage)
|
||||
err = json.Unmarshal(config, &raw)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "re-decoding compat image config %#v", s1)
|
||||
return nil, fmt.Errorf("re-decoding compat image config %#v: %w", s1, err)
|
||||
}
|
||||
// Drop some fields.
|
||||
delete(raw, "id")
|
||||
|
|
@ -292,20 +293,20 @@ func (m *Schema1) ToSchema2Config(diffIDs []digest.Digest) ([]byte, error) {
|
|||
// Add the history and rootfs information.
|
||||
rootfs, err := json.Marshal(rootFS)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error encoding rootfs information %#v: %v", rootFS, err)
|
||||
return nil, fmt.Errorf("error encoding rootfs information %#v: %v", rootFS, err)
|
||||
}
|
||||
rawRootfs := json.RawMessage(rootfs)
|
||||
raw["rootfs"] = &rawRootfs
|
||||
history, err := json.Marshal(convertedHistory)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error encoding history information %#v: %v", convertedHistory, err)
|
||||
return nil, fmt.Errorf("error encoding history information %#v: %v", convertedHistory, err)
|
||||
}
|
||||
rawHistory := json.RawMessage(history)
|
||||
raw["history"] = &rawHistory
|
||||
// Encode the result.
|
||||
config, err = json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error re-encoding compat image config %#v: %v", s1, err)
|
||||
return nil, fmt.Errorf("error re-encoding compat image config %#v: %v", s1, err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
|||
13
vendor/github.com/containers/image/v5/manifest/docker_schema2.go
generated
vendored
13
vendor/github.com/containers/image/v5/manifest/docker_schema2.go
generated
vendored
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/containers/image/v5/pkg/strslice"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Schema2Descriptor is a “descriptor” in docker/distribution schema 2.
|
||||
|
|
@ -234,7 +233,7 @@ var schema2CompressionMIMETypeSets = []compressionMIMETypeSet{
|
|||
// CompressionAlgorithm that would result in anything other than gzip compression.
|
||||
func (m *Schema2) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
||||
if len(m.LayersDescriptors) != len(layerInfos) {
|
||||
return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.LayersDescriptors), len(layerInfos))
|
||||
return fmt.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.LayersDescriptors), len(layerInfos))
|
||||
}
|
||||
original := m.LayersDescriptors
|
||||
m.LayersDescriptors = make([]Schema2Descriptor, len(layerInfos))
|
||||
|
|
@ -246,7 +245,7 @@ func (m *Schema2) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
|||
}
|
||||
mimeType, err := updatedMIMEType(schema2CompressionMIMETypeSets, mimeType, info)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "preparing updated manifest, layer %q", info.Digest)
|
||||
return fmt.Errorf("preparing updated manifest, layer %q: %w", info.Digest, err)
|
||||
}
|
||||
m.LayersDescriptors[i].MediaType = mimeType
|
||||
m.LayersDescriptors[i].Digest = info.Digest
|
||||
|
|
@ -295,3 +294,11 @@ func (m *Schema2) ImageID([]digest.Digest) (string, error) {
|
|||
}
|
||||
return m.ConfigDescriptor.Digest.Hex(), nil
|
||||
}
|
||||
|
||||
// CanChangeLayerCompression returns true if we can compress/decompress layers with mimeType in the current image
|
||||
// (and the code can handle that).
|
||||
// NOTE: Even if this returns true, the relevant format might not accept all compression algorithms; the set of accepted
|
||||
// algorithms depends not on the current format, but possibly on the target of a conversion.
|
||||
func (m *Schema2) CanChangeLayerCompression(mimeType string) bool {
|
||||
return compressionVariantsRecognizeMIMEType(schema2CompressionMIMETypeSets, mimeType)
|
||||
}
|
||||
|
|
|
|||
17
vendor/github.com/containers/image/v5/manifest/docker_schema2_list.go
generated
vendored
17
vendor/github.com/containers/image/v5/manifest/docker_schema2_list.go
generated
vendored
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Schema2PlatformSpec describes the platform which a particular manifest is
|
||||
|
|
@ -60,26 +59,26 @@ func (list *Schema2List) Instance(instanceDigest digest.Digest) (ListUpdate, err
|
|||
}, nil
|
||||
}
|
||||
}
|
||||
return ListUpdate{}, errors.Errorf("unable to find instance %s passed to Schema2List.Instances", instanceDigest)
|
||||
return ListUpdate{}, fmt.Errorf("unable to find instance %s passed to Schema2List.Instances", instanceDigest)
|
||||
}
|
||||
|
||||
// UpdateInstances updates the sizes, digests, and media types of the manifests
|
||||
// which the list catalogs.
|
||||
func (list *Schema2List) UpdateInstances(updates []ListUpdate) error {
|
||||
if len(updates) != len(list.Manifests) {
|
||||
return errors.Errorf("incorrect number of update entries passed to Schema2List.UpdateInstances: expected %d, got %d", len(list.Manifests), len(updates))
|
||||
return fmt.Errorf("incorrect number of update entries passed to Schema2List.UpdateInstances: expected %d, got %d", len(list.Manifests), len(updates))
|
||||
}
|
||||
for i := range updates {
|
||||
if err := updates[i].Digest.Validate(); err != nil {
|
||||
return errors.Wrapf(err, "update %d of %d passed to Schema2List.UpdateInstances contained an invalid digest", i+1, len(updates))
|
||||
return fmt.Errorf("update %d of %d passed to Schema2List.UpdateInstances contained an invalid digest: %w", i+1, len(updates), err)
|
||||
}
|
||||
list.Manifests[i].Digest = updates[i].Digest
|
||||
if updates[i].Size < 0 {
|
||||
return errors.Errorf("update %d of %d passed to Schema2List.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
|
||||
return fmt.Errorf("update %d of %d passed to Schema2List.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
|
||||
}
|
||||
list.Manifests[i].Size = updates[i].Size
|
||||
if updates[i].MediaType == "" {
|
||||
return errors.Errorf("update %d of %d passed to Schema2List.UpdateInstances had no media type (was %q)", i+1, len(updates), list.Manifests[i].MediaType)
|
||||
return fmt.Errorf("update %d of %d passed to Schema2List.UpdateInstances had no media type (was %q)", i+1, len(updates), list.Manifests[i].MediaType)
|
||||
}
|
||||
list.Manifests[i].MediaType = updates[i].MediaType
|
||||
}
|
||||
|
|
@ -91,7 +90,7 @@ func (list *Schema2List) UpdateInstances(updates []ListUpdate) error {
|
|||
func (list *Schema2List) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) {
|
||||
wantedPlatforms, err := platform.WantedPlatforms(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "getting platform information %#v", ctx)
|
||||
return "", fmt.Errorf("getting platform information %#v: %w", ctx, err)
|
||||
}
|
||||
for _, wantedPlatform := range wantedPlatforms {
|
||||
for _, d := range list.Manifests {
|
||||
|
|
@ -115,7 +114,7 @@ func (list *Schema2List) ChooseInstance(ctx *types.SystemContext) (digest.Digest
|
|||
func (list *Schema2List) Serialize() ([]byte, error) {
|
||||
buf, err := json.Marshal(list)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "marshaling Schema2List %#v", list)
|
||||
return nil, fmt.Errorf("marshaling Schema2List %#v: %w", list, err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
|
@ -190,7 +189,7 @@ func Schema2ListFromManifest(manifest []byte) (*Schema2List, error) {
|
|||
Manifests: []Schema2ManifestDescriptor{},
|
||||
}
|
||||
if err := json.Unmarshal(manifest, &list); err != nil {
|
||||
return nil, errors.Wrapf(err, "unmarshaling Schema2List %q", string(manifest))
|
||||
return nil, fmt.Errorf("unmarshaling Schema2List %q: %w", string(manifest), err)
|
||||
}
|
||||
if err := validateUnambiguousManifestFormat(manifest, DockerV2ListMediaType,
|
||||
allowedFieldManifests); err != nil {
|
||||
|
|
|
|||
5
vendor/github.com/containers/image/v5/manifest/manifest.go
generated
vendored
5
vendor/github.com/containers/image/v5/manifest/manifest.go
generated
vendored
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
internalManifest "github.com/containers/image/v5/internal/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/libtrust"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
|
|
@ -34,6 +35,10 @@ const (
|
|||
DockerV2Schema2ForeignLayerMediaTypeGzip = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
|
||||
)
|
||||
|
||||
// NonImageArtifactError (detected via errors.As) is used when asking for an image-specific operation
|
||||
// on an object which is not a “container image” in the standard sense (e.g. an OCI artifact)
|
||||
type NonImageArtifactError = internalManifest.NonImageArtifactError
|
||||
|
||||
// SupportedSchema2MediaType checks if the specified string is a supported Docker v2s2 media type.
|
||||
func SupportedSchema2MediaType(m string) error {
|
||||
switch m {
|
||||
|
|
|
|||
99
vendor/github.com/containers/image/v5/manifest/oci.go
generated
vendored
99
vendor/github.com/containers/image/v5/manifest/oci.go
generated
vendored
|
|
@ -5,13 +5,13 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
internalManifest "github.com/containers/image/v5/internal/manifest"
|
||||
compressiontypes "github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/image/v5/types"
|
||||
ociencspec "github.com/containers/ocicrypt/spec"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BlobInfoFromOCI1Descriptor returns a types.BlobInfo based on the input OCI1 descriptor.
|
||||
|
|
@ -115,9 +115,15 @@ var oci1CompressionMIMETypeSets = []compressionMIMETypeSet{
|
|||
// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls+mediatype), in order (the root layer first, and then successive layered layers)
|
||||
// The returned error will be a manifest.ManifestLayerCompressionIncompatibilityError if any of the layerInfos includes a combination of CompressionOperation and
|
||||
// CompressionAlgorithm that isn't supported by OCI.
|
||||
//
|
||||
// It’s generally the caller’s responsibility to determine whether a particular edit is acceptable, rather than relying on
|
||||
// failures of this function, because the layer is typically created _before_ UpdateLayerInfos is called, because UpdateLayerInfos needs
|
||||
// to know the final digest). See OCI1.CanChangeLayerCompression for some help in determining this; other aspects like compression
|
||||
// algorithms that might not be supported by a format, or the limited set of MIME types accepted for encryption, are not currently
|
||||
// handled — that logic should eventually also be provided as OCI1 methods, not hard-coded in callers.
|
||||
func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
||||
if len(m.Layers) != len(layerInfos) {
|
||||
return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.Layers), len(layerInfos))
|
||||
return fmt.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.Layers), len(layerInfos))
|
||||
}
|
||||
original := m.Layers
|
||||
m.Layers = make([]imgspecv1.Descriptor, len(layerInfos))
|
||||
|
|
@ -132,7 +138,7 @@ func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
|||
}
|
||||
mimeType, err := updatedMIMEType(oci1CompressionMIMETypeSets, mimeType, info)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "preparing updated manifest, layer %q", info.Digest)
|
||||
return fmt.Errorf("preparing updated manifest, layer %q: %w", info.Digest, err)
|
||||
}
|
||||
if info.CryptoOperation == types.Encrypt {
|
||||
encMediaType, err := getEncryptedMediaType(mimeType)
|
||||
|
|
@ -151,6 +157,33 @@ func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getEncryptedMediaType will return the mediatype to its encrypted counterpart and return
|
||||
// an error if the mediatype does not support encryption
|
||||
func getEncryptedMediaType(mediatype string) (string, error) {
|
||||
for _, s := range strings.Split(mediatype, "+")[1:] {
|
||||
if s == "encrypted" {
|
||||
return "", fmt.Errorf("unsupported mediaType: %v already encrypted", mediatype)
|
||||
}
|
||||
}
|
||||
unsuffixedMediatype := strings.Split(mediatype, "+")[0]
|
||||
switch unsuffixedMediatype {
|
||||
case DockerV2Schema2LayerMediaType, imgspecv1.MediaTypeImageLayer, imgspecv1.MediaTypeImageLayerNonDistributable:
|
||||
return mediatype + "+encrypted", nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unsupported mediaType to encrypt: %v", mediatype)
|
||||
}
|
||||
|
||||
// getEncryptedMediaType will return the mediatype to its encrypted counterpart and return
|
||||
// an error if the mediatype does not support decryption
|
||||
func getDecryptedMediaType(mediatype string) (string, error) {
|
||||
if !strings.HasSuffix(mediatype, "+encrypted") {
|
||||
return "", fmt.Errorf("unsupported mediaType to decrypt %v:", mediatype)
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(mediatype, "+encrypted"), nil
|
||||
}
|
||||
|
||||
// Serialize returns the manifest in a blob format.
|
||||
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
|
||||
func (m *OCI1) Serialize() ([]byte, error) {
|
||||
|
|
@ -159,6 +192,14 @@ func (m *OCI1) Serialize() ([]byte, error) {
|
|||
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error) {
|
||||
if m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
|
||||
// We could return at least the layers, but that’s already available in a better format via types.Image.LayerInfos.
|
||||
// Most software calling this without human intervention is going to expect the values to be realistic and relevant,
|
||||
// and is probably better served by failing; we can always re-visit that later if we fail now, but
|
||||
// if we started returning some data for OCI artifacts now, we couldn’t start failing in this function later.
|
||||
return nil, internalManifest.NewNonImageArtifactError(m.Config.MediaType)
|
||||
}
|
||||
|
||||
config, err := configGetter(m.ConfigInfo())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -186,35 +227,39 @@ func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*type
|
|||
|
||||
// ImageID computes an ID which can uniquely identify this image by its contents.
|
||||
func (m *OCI1) ImageID([]digest.Digest) (string, error) {
|
||||
// The way m.Config.Digest “uniquely identifies” an image is
|
||||
// by containing RootFS.DiffIDs, which identify the layers of the image.
|
||||
// For non-image artifacts, the we can’t expect the config to change
|
||||
// any time the other layers (semantically) change, so this approach of
|
||||
// distinguishing objects only by m.Config.Digest doesn’t work in general.
|
||||
//
|
||||
// Any caller of this method presumably wants to disambiguate the same
|
||||
// images with a different representation, but doesn’t want to disambiguate
|
||||
// representations (by using a manifest digest). So, submitting a non-image
|
||||
// artifact to such a caller indicates an expectation mismatch.
|
||||
// So, we just fail here instead of inventing some other ID value (e.g.
|
||||
// by combining the config and blob layer digests). That still
|
||||
// gives us the option to not fail, and return some value, in the future,
|
||||
// without committing to that approach now.
|
||||
// (The only known caller of ImageID is storage/storageImageDestination.computeID,
|
||||
// which can’t work with non-image artifacts.)
|
||||
if m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
|
||||
return "", internalManifest.NewNonImageArtifactError(m.Config.MediaType)
|
||||
}
|
||||
|
||||
if err := m.Config.Digest.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return m.Config.Digest.Hex(), nil
|
||||
}
|
||||
|
||||
// getEncryptedMediaType will return the mediatype to its encrypted counterpart and return
|
||||
// an error if the mediatype does not support encryption
|
||||
func getEncryptedMediaType(mediatype string) (string, error) {
|
||||
for _, s := range strings.Split(mediatype, "+")[1:] {
|
||||
if s == "encrypted" {
|
||||
return "", errors.Errorf("unsupportedmediatype: %v already encrypted", mediatype)
|
||||
}
|
||||
// CanChangeLayerCompression returns true if we can compress/decompress layers with mimeType in the current image
|
||||
// (and the code can handle that).
|
||||
// NOTE: Even if this returns true, the relevant format might not accept all compression algorithms; the set of accepted
|
||||
// algorithms depends not on the current format, but possibly on the target of a conversion.
|
||||
func (m *OCI1) CanChangeLayerCompression(mimeType string) bool {
|
||||
if m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
|
||||
return false
|
||||
}
|
||||
unsuffixedMediatype := strings.Split(mediatype, "+")[0]
|
||||
switch unsuffixedMediatype {
|
||||
case DockerV2Schema2LayerMediaType, imgspecv1.MediaTypeImageLayer, imgspecv1.MediaTypeImageLayerNonDistributable:
|
||||
return mediatype + "+encrypted", nil
|
||||
}
|
||||
|
||||
return "", errors.Errorf("unsupported mediatype to encrypt: %v", mediatype)
|
||||
}
|
||||
|
||||
// getEncryptedMediaType will return the mediatype to its encrypted counterpart and return
|
||||
// an error if the mediatype does not support decryption
|
||||
func getDecryptedMediaType(mediatype string) (string, error) {
|
||||
if !strings.HasSuffix(mediatype, "+encrypted") {
|
||||
return "", errors.Errorf("unsupported mediatype to decrypt %v:", mediatype)
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(mediatype, "+encrypted"), nil
|
||||
return compressionVariantsRecognizeMIMEType(oci1CompressionMIMETypeSets, mimeType)
|
||||
}
|
||||
|
|
|
|||
17
vendor/github.com/containers/image/v5/manifest/oci_index.go
generated
vendored
17
vendor/github.com/containers/image/v5/manifest/oci_index.go
generated
vendored
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// OCI1Index is just an alias for the OCI index type, but one which we can
|
||||
|
|
@ -44,26 +43,26 @@ func (index *OCI1Index) Instance(instanceDigest digest.Digest) (ListUpdate, erro
|
|||
}, nil
|
||||
}
|
||||
}
|
||||
return ListUpdate{}, errors.Errorf("unable to find instance %s in OCI1Index", instanceDigest)
|
||||
return ListUpdate{}, fmt.Errorf("unable to find instance %s in OCI1Index", instanceDigest)
|
||||
}
|
||||
|
||||
// UpdateInstances updates the sizes, digests, and media types of the manifests
|
||||
// which the list catalogs.
|
||||
func (index *OCI1Index) UpdateInstances(updates []ListUpdate) error {
|
||||
if len(updates) != len(index.Manifests) {
|
||||
return errors.Errorf("incorrect number of update entries passed to OCI1Index.UpdateInstances: expected %d, got %d", len(index.Manifests), len(updates))
|
||||
return fmt.Errorf("incorrect number of update entries passed to OCI1Index.UpdateInstances: expected %d, got %d", len(index.Manifests), len(updates))
|
||||
}
|
||||
for i := range updates {
|
||||
if err := updates[i].Digest.Validate(); err != nil {
|
||||
return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances contained an invalid digest", i+1, len(updates))
|
||||
return fmt.Errorf("update %d of %d passed to OCI1Index.UpdateInstances contained an invalid digest: %w", i+1, len(updates), err)
|
||||
}
|
||||
index.Manifests[i].Digest = updates[i].Digest
|
||||
if updates[i].Size < 0 {
|
||||
return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
|
||||
return fmt.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
|
||||
}
|
||||
index.Manifests[i].Size = updates[i].Size
|
||||
if updates[i].MediaType == "" {
|
||||
return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had no media type (was %q)", i+1, len(updates), index.Manifests[i].MediaType)
|
||||
return fmt.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had no media type (was %q)", i+1, len(updates), index.Manifests[i].MediaType)
|
||||
}
|
||||
index.Manifests[i].MediaType = updates[i].MediaType
|
||||
}
|
||||
|
|
@ -75,7 +74,7 @@ func (index *OCI1Index) UpdateInstances(updates []ListUpdate) error {
|
|||
func (index *OCI1Index) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) {
|
||||
wantedPlatforms, err := platform.WantedPlatforms(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "getting platform information %#v", ctx)
|
||||
return "", fmt.Errorf("getting platform information %#v: %w", ctx, err)
|
||||
}
|
||||
for _, wantedPlatform := range wantedPlatforms {
|
||||
for _, d := range index.Manifests {
|
||||
|
|
@ -108,7 +107,7 @@ func (index *OCI1Index) ChooseInstance(ctx *types.SystemContext) (digest.Digest,
|
|||
func (index *OCI1Index) Serialize() ([]byte, error) {
|
||||
buf, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "marshaling OCI1Index %#v", index)
|
||||
return nil, fmt.Errorf("marshaling OCI1Index %#v: %w", index, err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
|
@ -202,7 +201,7 @@ func OCI1IndexFromManifest(manifest []byte) (*OCI1Index, error) {
|
|||
},
|
||||
}
|
||||
if err := json.Unmarshal(manifest, &index); err != nil {
|
||||
return nil, errors.Wrapf(err, "unmarshaling OCI1Index %q", string(manifest))
|
||||
return nil, fmt.Errorf("unmarshaling OCI1Index %q: %w", string(manifest), err)
|
||||
}
|
||||
if err := validateUnambiguousManifestFormat(manifest, imgspecv1.MediaTypeImageIndex,
|
||||
allowedFieldManifests); err != nil {
|
||||
|
|
|
|||
69
vendor/github.com/containers/image/v5/oci/archive/oci_dest.go
generated
vendored
69
vendor/github.com/containers/image/v5/oci/archive/oci_dest.go
generated
vendored
|
|
@ -2,38 +2,49 @@ package archive
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/internal/blobinfocache"
|
||||
"github.com/containers/image/v5/internal/imagedestination"
|
||||
"github.com/containers/image/v5/internal/imagedestination/impl"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ociArchiveImageDestination struct {
|
||||
impl.Compat
|
||||
|
||||
ref ociArchiveReference
|
||||
unpackedDest types.ImageDestination
|
||||
unpackedDest private.ImageDestination
|
||||
tempDirRef tempDirOCIRef
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (private.ImageDestination, error) {
|
||||
tempDirRef, err := createOCIRef(sys, ref.image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "creating oci reference")
|
||||
return nil, fmt.Errorf("creating oci reference: %w", err)
|
||||
}
|
||||
unpackedDest, err := tempDirRef.ociRefExtracted.NewImageDestination(ctx, sys)
|
||||
if err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return nil, errors.Wrapf(err, "deleting temp directory %q", tempDirRef.tempDirectory)
|
||||
return nil, fmt.Errorf("deleting temp directory %q: %w", tempDirRef.tempDirectory, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &ociArchiveImageDestination{ref: ref,
|
||||
unpackedDest: unpackedDest,
|
||||
tempDirRef: tempDirRef}, nil
|
||||
d := &ociArchiveImageDestination{
|
||||
ref: ref,
|
||||
unpackedDest: imagedestination.FromPublic(unpackedDest),
|
||||
tempDirRef: tempDirRef,
|
||||
}
|
||||
d.Compat = impl.AddCompat(d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination.
|
||||
|
|
@ -87,29 +98,40 @@ func (d *ociArchiveImageDestination) HasThreadSafePutBlob() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result.
|
||||
// SupportsPutBlobPartial returns true if PutBlobPartial is supported.
|
||||
func (d *ociArchiveImageDestination) SupportsPutBlobPartial() bool {
|
||||
return d.unpackedDest.SupportsPutBlobPartial()
|
||||
}
|
||||
|
||||
// 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.
|
||||
// May update cache.
|
||||
// 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 *ociArchiveImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.PutBlob(ctx, stream, inputInfo, cache, isConfig)
|
||||
func (d *ociArchiveImageDestination) PutBlobWithOptions(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.PutBlobWithOptions(ctx, stream, inputInfo, options)
|
||||
}
|
||||
|
||||
// TryReusingBlob checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
|
||||
// PutBlobPartial attempts to create a blob using the data that is already present
|
||||
// at the destination. chunkAccessor is accessed in a non-sequential way to retrieve the missing chunks.
|
||||
// It is available only if SupportsPutBlobPartial().
|
||||
// Even if SupportsPutBlobPartial() returns true, the call can fail, in which case the caller
|
||||
// should fall back to PutBlobWithOptions.
|
||||
func (d *ociArchiveImageDestination) PutBlobPartial(ctx context.Context, chunkAccessor private.BlobChunkAccessor, srcInfo types.BlobInfo, cache blobinfocache.BlobInfoCache2) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.PutBlobPartial(ctx, chunkAccessor, srcInfo, cache)
|
||||
}
|
||||
|
||||
// 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 canSubstitute, TryReusingBlob can use an equivalent equivalent of the desired blob; in that case the returned info may not match the input.
|
||||
// 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.
|
||||
// May use and/or update cache.
|
||||
func (d *ociArchiveImageDestination) TryReusingBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache, canSubstitute bool) (bool, types.BlobInfo, error) {
|
||||
return d.unpackedDest.TryReusingBlob(ctx, info, cache, canSubstitute)
|
||||
func (d *ociArchiveImageDestination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, types.BlobInfo, error) {
|
||||
return d.unpackedDest.TryReusingBlobWithOptions(ctx, info, options)
|
||||
}
|
||||
|
||||
// PutManifest writes the manifest to the destination.
|
||||
|
|
@ -121,11 +143,12 @@ func (d *ociArchiveImageDestination) PutManifest(ctx context.Context, m []byte,
|
|||
return d.unpackedDest.PutManifest(ctx, m, instanceDigest)
|
||||
}
|
||||
|
||||
// PutSignatures writes a set of signatures to the destination.
|
||||
// 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.
|
||||
func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
|
||||
return d.unpackedDest.PutSignatures(ctx, signatures, instanceDigest)
|
||||
// MUST be called after PutManifest (signatures may reference manifest contents).
|
||||
func (d *ociArchiveImageDestination) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||
return d.unpackedDest.PutSignaturesWithFormat(ctx, signatures, instanceDigest)
|
||||
}
|
||||
|
||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted
|
||||
|
|
@ -135,7 +158,7 @@ func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatur
|
|||
// after the directory is made, it is tarred up into a file and the directory is deleted
|
||||
func (d *ociArchiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
|
||||
if err := d.unpackedDest.Commit(ctx, unparsedToplevel); err != nil {
|
||||
return errors.Wrapf(err, "storing image %q", d.ref.image)
|
||||
return fmt.Errorf("storing image %q: %w", d.ref.image, err)
|
||||
}
|
||||
|
||||
// path of directory to tar up
|
||||
|
|
@ -150,13 +173,13 @@ func tarDirectory(src, dst string) error {
|
|||
// input is a stream of bytes from the archive of the directory at path
|
||||
input, err := archive.Tar(src, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "retrieving stream of bytes from %q", src)
|
||||
return fmt.Errorf("retrieving stream of bytes from %q: %w", src, err)
|
||||
}
|
||||
|
||||
// creates the tar file
|
||||
outFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating tar file %q", dst)
|
||||
return fmt.Errorf("creating tar file %q: %w", dst, err)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
|
|
|
|||
53
vendor/github.com/containers/image/v5/oci/archive/oci_src.go
generated
vendored
53
vendor/github.com/containers/image/v5/oci/archive/oci_src.go
generated
vendored
|
|
@ -2,40 +2,51 @@ package archive
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/internal/imagesource"
|
||||
"github.com/containers/image/v5/internal/imagesource/impl"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
ocilayout "github.com/containers/image/v5/oci/layout"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ociArchiveImageSource struct {
|
||||
impl.Compat
|
||||
|
||||
ref ociArchiveReference
|
||||
unpackedSrc types.ImageSource
|
||||
unpackedSrc private.ImageSource
|
||||
tempDirRef tempDirOCIRef
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
// newImageSource untars the file and saves it in a temp directory
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (types.ImageSource, error) {
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (private.ImageSource, error) {
|
||||
tempDirRef, err := createUntarTempDir(sys, ref)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating temp directory")
|
||||
return nil, fmt.Errorf("creating temp directory: %w", err)
|
||||
}
|
||||
|
||||
unpackedSrc, err := tempDirRef.ociRefExtracted.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return nil, errors.Wrapf(err, "deleting temp directory %q", tempDirRef.tempDirectory)
|
||||
return nil, fmt.Errorf("deleting temp directory %q: %w", tempDirRef.tempDirectory, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &ociArchiveImageSource{ref: ref,
|
||||
unpackedSrc: unpackedSrc,
|
||||
tempDirRef: tempDirRef}, nil
|
||||
s := &ociArchiveImageSource{
|
||||
ref: ref,
|
||||
unpackedSrc: imagesource.FromPublic(unpackedSrc),
|
||||
tempDirRef: tempDirRef,
|
||||
}
|
||||
s.Compat = impl.AddCompat(s)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// LoadManifestDescriptor loads the manifest
|
||||
|
|
@ -48,11 +59,11 @@ func LoadManifestDescriptor(imgRef types.ImageReference) (imgspecv1.Descriptor,
|
|||
func LoadManifestDescriptorWithContext(sys *types.SystemContext, imgRef types.ImageReference) (imgspecv1.Descriptor, error) {
|
||||
ociArchRef, ok := imgRef.(ociArchiveReference)
|
||||
if !ok {
|
||||
return imgspecv1.Descriptor{}, errors.Errorf("error typecasting, need type ociArchiveReference")
|
||||
return imgspecv1.Descriptor{}, errors.New("error typecasting, need type ociArchiveReference")
|
||||
}
|
||||
tempDirRef, err := createUntarTempDir(sys, ociArchRef)
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, errors.Wrap(err, "creating temp directory")
|
||||
return imgspecv1.Descriptor{}, fmt.Errorf("creating temp directory: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
err := tempDirRef.deleteTempDir()
|
||||
|
|
@ -61,7 +72,7 @@ func LoadManifestDescriptorWithContext(sys *types.SystemContext, imgRef types.Im
|
|||
|
||||
descriptor, err := ocilayout.LoadManifestDescriptor(tempDirRef.ociRefExtracted)
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, errors.Wrap(err, "loading index")
|
||||
return imgspecv1.Descriptor{}, fmt.Errorf("loading index: %w", err)
|
||||
}
|
||||
return descriptor, nil
|
||||
}
|
||||
|
|
@ -101,12 +112,26 @@ func (s *ociArchiveImageSource) GetBlob(ctx context.Context, info types.BlobInfo
|
|||
return s.unpackedSrc.GetBlob(ctx, info, cache)
|
||||
}
|
||||
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
// SupportsGetBlobAt() returns true if GetBlobAt (BlobChunkAccessor) is supported.
|
||||
func (s *ociArchiveImageSource) SupportsGetBlobAt() bool {
|
||||
return s.unpackedSrc.SupportsGetBlobAt()
|
||||
}
|
||||
|
||||
// GetBlobAt returns a sequential channel of readers that contain data for the requested
|
||||
// blob chunks, and a channel that might get a single error value.
|
||||
// The specified chunks must be not overlapping and sorted by their offset.
|
||||
// The readers must be fully consumed, in the order they are returned, before blocking
|
||||
// to read the next chunk.
|
||||
func (s *ociArchiveImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk) (chan io.ReadCloser, chan error, error) {
|
||||
return s.unpackedSrc.GetBlobAt(ctx, info, chunks)
|
||||
}
|
||||
|
||||
// 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 *ociArchiveImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
return s.unpackedSrc.GetSignatures(ctx, instanceDigest)
|
||||
func (s *ociArchiveImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
return s.unpackedSrc.GetSignaturesWithFormat(ctx, instanceDigest)
|
||||
}
|
||||
|
||||
// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
|
||||
|
|
|
|||
20
vendor/github.com/containers/image/v5/oci/archive/oci_transport.go
generated
vendored
20
vendor/github.com/containers/image/v5/oci/archive/oci_transport.go
generated
vendored
|
|
@ -2,20 +2,20 @@ package archive
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"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/internal/image"
|
||||
"github.com/containers/image/v5/internal/tmpdir"
|
||||
"github.com/containers/image/v5/oci/internal"
|
||||
ocilayout "github.com/containers/image/v5/oci/layout"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -122,11 +122,7 @@ func (ref ociArchiveReference) PolicyConfigurationNamespaces() []string {
|
|||
// 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 ociArchiveReference) 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)
|
||||
return image.FromReference(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
|
|
@ -143,7 +139,7 @@ func (ref ociArchiveReference) NewImageDestination(ctx context.Context, sys *typ
|
|||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ociArchiveReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for oci: images")
|
||||
return errors.New("Deleting images not implemented for oci: images")
|
||||
}
|
||||
|
||||
// struct to store the ociReference and temporary directory returned by createOCIRef
|
||||
|
|
@ -162,7 +158,7 @@ func (t *tempDirOCIRef) deleteTempDir() error {
|
|||
func createOCIRef(sys *types.SystemContext, image string) (tempDirOCIRef, error) {
|
||||
dir, err := os.MkdirTemp(tmpdir.TemporaryDirectoryForBigFiles(sys), "oci")
|
||||
if err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "creating temp directory")
|
||||
return tempDirOCIRef{}, fmt.Errorf("creating temp directory: %w", err)
|
||||
}
|
||||
ociRef, err := ocilayout.NewReference(dir, image)
|
||||
if err != nil {
|
||||
|
|
@ -177,7 +173,7 @@ func createOCIRef(sys *types.SystemContext, image string) (tempDirOCIRef, error)
|
|||
func createUntarTempDir(sys *types.SystemContext, ref ociArchiveReference) (tempDirOCIRef, error) {
|
||||
tempDirRef, err := createOCIRef(sys, ref.image)
|
||||
if err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrap(err, "creating oci reference")
|
||||
return tempDirOCIRef{}, fmt.Errorf("creating oci reference: %w", err)
|
||||
}
|
||||
src := ref.resolvedFile
|
||||
dst := tempDirRef.tempDirectory
|
||||
|
|
@ -189,9 +185,9 @@ func createUntarTempDir(sys *types.SystemContext, ref ociArchiveReference) (temp
|
|||
defer arch.Close()
|
||||
if err := archive.NewDefaultArchiver().Untar(arch, dst, &archive.TarOptions{NoLchown: true}); err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "deleting temp directory %q", tempDirRef.tempDirectory)
|
||||
return tempDirOCIRef{}, fmt.Errorf("deleting temp directory %q: %w", tempDirRef.tempDirectory, err)
|
||||
}
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "untarring file %q", tempDirRef.tempDirectory)
|
||||
return tempDirOCIRef{}, fmt.Errorf("untarring file %q: %w", tempDirRef.tempDirectory, err)
|
||||
}
|
||||
return tempDirRef, nil
|
||||
}
|
||||
|
|
|
|||
15
vendor/github.com/containers/image/v5/oci/internal/oci_util.go
generated
vendored
15
vendor/github.com/containers/image/v5/oci/internal/oci_util.go
generated
vendored
|
|
@ -1,7 +1,8 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
|
@ -27,7 +28,7 @@ func ValidateImageName(image string) error {
|
|||
|
||||
var err error
|
||||
if !refRegexp.MatchString(image) {
|
||||
err = errors.Errorf("Invalid image %s", image)
|
||||
err = fmt.Errorf("Invalid image %s", image)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -72,11 +73,11 @@ func ValidateOCIPath(path string) error {
|
|||
if runtime.GOOS == "windows" {
|
||||
// On Windows we must allow for a ':' as part of the path
|
||||
if strings.Count(path, ":") > 1 {
|
||||
return errors.Errorf("Invalid OCI reference: path %s contains more than one colon", path)
|
||||
return fmt.Errorf("Invalid OCI reference: path %s contains more than one colon", path)
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(path, ":") {
|
||||
return errors.Errorf("Invalid OCI reference: path %s contains a colon", path)
|
||||
return fmt.Errorf("Invalid OCI reference: path %s contains a colon", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -96,7 +97,7 @@ func ValidateScope(scope string) error {
|
|||
|
||||
cleaned := filepath.Clean(scope)
|
||||
if cleaned != scope {
|
||||
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
return fmt.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -105,7 +106,7 @@ func ValidateScope(scope string) error {
|
|||
func validateScopeWindows(scope string) error {
|
||||
matched, _ := regexp.Match(`^[a-zA-Z]:\\`, []byte(scope))
|
||||
if !matched {
|
||||
return errors.Errorf("Invalid scope '%s'. Must be an absolute path", scope)
|
||||
return fmt.Errorf("Invalid scope '%s'. Must be an absolute path", scope)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -113,7 +114,7 @@ func validateScopeWindows(scope string) error {
|
|||
|
||||
func validateScopeNonWindows(scope string) error {
|
||||
if !strings.HasPrefix(scope, "/") {
|
||||
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
|
||||
return fmt.Errorf("Invalid scope %s: must be an absolute path", scope)
|
||||
}
|
||||
|
||||
// Refuse also "/", otherwise "/" and "" would have the same semantics,
|
||||
|
|
|
|||
114
vendor/github.com/containers/image/v5/oci/layout/oci_dest.go
generated
vendored
114
vendor/github.com/containers/image/v5/oci/layout/oci_dest.go
generated
vendored
|
|
@ -3,29 +3,37 @@ package layout
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"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/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociImageDestination struct {
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
acceptUncompressedLayers bool
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
stubs.NoSignaturesInitialize
|
||||
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(sys *types.SystemContext, ref ociReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(sys *types.SystemContext, ref ociReference) (private.ImageDestination, error) {
|
||||
var index *imgspecv1.Index
|
||||
if indexExists(ref) {
|
||||
var err error
|
||||
|
|
@ -42,10 +50,32 @@ func newImageDestination(sys *types.SystemContext, ref ociReference) (types.Imag
|
|||
}
|
||||
}
|
||||
|
||||
d := &ociImageDestination{ref: ref, index: *index}
|
||||
desiredLayerCompression := types.Compress
|
||||
if sys != nil && sys.OCIAcceptUncompressedLayers {
|
||||
desiredLayerCompression = types.PreserveOriginal
|
||||
}
|
||||
|
||||
d := &ociImageDestination{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
SupportedManifestMIMETypes: []string{
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
imgspecv1.MediaTypeImageIndex,
|
||||
},
|
||||
DesiredLayerCompression: desiredLayerCompression,
|
||||
AcceptsForeignLayerURLs: true,
|
||||
MustMatchRuntimeOS: false,
|
||||
IgnoresEmbeddedDockerReference: false, // N/A, DockerReference() returns nil.
|
||||
HasThreadSafePutBlob: true,
|
||||
}),
|
||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
||||
NoSignaturesInitialize: stubs.NoSignatures("Pushing signatures for OCI images is not supported"),
|
||||
|
||||
ref: ref,
|
||||
index: *index,
|
||||
}
|
||||
d.Compat = impl.AddCompat(d)
|
||||
if sys != nil {
|
||||
d.sharedBlobDir = sys.OCISharedBlobDirPath
|
||||
d.acceptUncompressedLayers = sys.OCIAcceptUncompressedLayers
|
||||
}
|
||||
|
||||
if err := ensureDirectoryExists(d.ref.dir); err != nil {
|
||||
|
|
@ -71,58 +101,14 @@ func (d *ociImageDestination) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) SupportedManifestMIMETypes() []string {
|
||||
return []string{
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
imgspecv1.MediaTypeImageIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *ociImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
return errors.Errorf("Pushing signatures for OCI images is not supported")
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
if d.acceptUncompressedLayers {
|
||||
return types.PreserveOriginal
|
||||
}
|
||||
return types.Compress
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (d *ociImageDestination) AcceptsForeignLayerURLs() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime architecture and OS. False otherwise.
|
||||
func (d *ociImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *ociImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // N/A, DockerReference() returns nil.
|
||||
}
|
||||
|
||||
// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently.
|
||||
func (d *ociImageDestination) HasThreadSafePutBlob() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result.
|
||||
// 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.
|
||||
// May update cache.
|
||||
// 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 *ociImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) {
|
||||
func (d *ociImageDestination) PutBlobWithOptions(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions) (types.BlobInfo, error) {
|
||||
blobFile, err := os.CreateTemp(d.ref.dir, "oci-put-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
|
|
@ -146,7 +132,7 @@ func (d *ociImageDestination) PutBlob(ctx context.Context, stream io.Reader, inp
|
|||
}
|
||||
blobDigest := digester.Digest()
|
||||
if inputInfo.Size != -1 && size != inputInfo.Size {
|
||||
return types.BlobInfo{}, errors.Errorf("Size mismatch when copying %s, expected %d, got %d", blobDigest, inputInfo.Size, size)
|
||||
return types.BlobInfo{}, fmt.Errorf("Size mismatch when copying %s, expected %d, got %d", blobDigest, inputInfo.Size, size)
|
||||
}
|
||||
if err := blobFile.Sync(); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
|
|
@ -180,18 +166,16 @@ func (d *ociImageDestination) PutBlob(ctx context.Context, stream io.Reader, inp
|
|||
return types.BlobInfo{Digest: blobDigest, Size: size}, nil
|
||||
}
|
||||
|
||||
// TryReusingBlob checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
|
||||
// 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 canSubstitute, TryReusingBlob can use an equivalent equivalent of the desired blob; in that case the returned info may not match the input.
|
||||
// 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.
|
||||
// May use and/or update cache.
|
||||
func (d *ociImageDestination) TryReusingBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache, canSubstitute bool) (bool, types.BlobInfo, error) {
|
||||
func (d *ociImageDestination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, types.BlobInfo, error) {
|
||||
if info.Digest == "" {
|
||||
return false, types.BlobInfo{}, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
return false, types.BlobInfo{}, errors.New("Can not check for a blob with unknown digest")
|
||||
}
|
||||
blobPath, err := d.ref.blobPath(info.Digest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
|
|
@ -290,16 +274,6 @@ func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) {
|
|||
d.index.Manifests = append(d.index.Manifests, *desc)
|
||||
}
|
||||
|
||||
// PutSignatures would add the given signatures to the oci layout (currently not supported).
|
||||
// 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.
|
||||
func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
|
||||
if len(signatures) != 0 {
|
||||
return errors.Errorf("Pushing signatures for OCI images is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
|
||||
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
|
||||
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
|
||||
|
|
|
|||
62
vendor/github.com/containers/image/v5/oci/layout/oci_src.go
generated
vendored
62
vendor/github.com/containers/image/v5/oci/layout/oci_src.go
generated
vendored
|
|
@ -2,22 +2,32 @@ package layout
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"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/manifest"
|
||||
"github.com/containers/image/v5/pkg/tlsclientconfig"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociImageSource struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
impl.NoSignatures
|
||||
impl.DoesNotAffectLayerInfosForCopy
|
||||
stubs.NoGetBlobAtInitialize
|
||||
|
||||
ref ociReference
|
||||
index *imgspecv1.Index
|
||||
descriptor imgspecv1.Descriptor
|
||||
|
|
@ -26,7 +36,7 @@ type ociImageSource struct {
|
|||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
func newImageSource(sys *types.SystemContext, ref ociReference) (types.ImageSource, error) {
|
||||
func newImageSource(sys *types.SystemContext, ref ociReference) (private.ImageSource, error) {
|
||||
tr := tlsclientconfig.NewTransport()
|
||||
tr.TLSClientConfig = tlsconfig.ServerDefault()
|
||||
|
||||
|
|
@ -47,12 +57,23 @@ func newImageSource(sys *types.SystemContext, ref ociReference) (types.ImageSour
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &ociImageSource{ref: ref, index: index, descriptor: descriptor, client: client}
|
||||
s := &ociImageSource{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
HasThreadSafeGetBlob: false,
|
||||
}),
|
||||
NoGetBlobAtInitialize: stubs.NoGetBlobAt(ref),
|
||||
|
||||
ref: ref,
|
||||
index: index,
|
||||
descriptor: descriptor,
|
||||
client: client,
|
||||
}
|
||||
if sys != nil {
|
||||
// TODO(jonboulle): check dir existence?
|
||||
d.sharedBlobDir = sys.OCISharedBlobDirPath
|
||||
s.sharedBlobDir = sys.OCISharedBlobDirPath
|
||||
}
|
||||
return d, nil
|
||||
s.Compat = impl.AddCompat(s)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source.
|
||||
|
|
@ -103,11 +124,6 @@ func (s *ociImageSource) GetManifest(ctx context.Context, instanceDigest *digest
|
|||
return m, mimeType, nil
|
||||
}
|
||||
|
||||
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
|
||||
func (s *ociImageSource) 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.
|
||||
|
|
@ -137,14 +153,6 @@ func (s *ociImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache
|
|||
return r, fi.Size(), 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 *ociImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
|
||||
// getExternalBlob returns the reader of the first available blob URL from urls, which must not be empty.
|
||||
// This function can return nil reader when no url is supported by this function. In this case, the caller
|
||||
// should fallback to fetch the non-external blob (i.e. pull from the registry).
|
||||
|
|
@ -162,19 +170,19 @@ func (s *ociImageSource) getExternalBlob(ctx context.Context, urls []string) (io
|
|||
hasSupportedURL = true
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", u, err.Error())
|
||||
errWrap = fmt.Errorf("fetching %s failed %s: %w", u, err.Error(), errWrap)
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", u, err.Error())
|
||||
errWrap = fmt.Errorf("fetching %s failed %s: %w", u, err.Error(), errWrap)
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed, response code not 200", u)
|
||||
errWrap = fmt.Errorf("fetching %s failed, response code not 200: %w", u, errWrap)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -187,18 +195,6 @@ func (s *ociImageSource) getExternalBlob(ctx context.Context, urls []string) (io
|
|||
return nil, 0, errWrap
|
||||
}
|
||||
|
||||
// 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 *ociImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBlobSize(resp *http.Response) int64 {
|
||||
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
|
|
|
|||
16
vendor/github.com/containers/image/v5/oci/layout/oci_transport.go
generated
vendored
16
vendor/github.com/containers/image/v5/oci/layout/oci_transport.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package layout
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -10,13 +11,12 @@ import (
|
|||
|
||||
"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/internal/image"
|
||||
"github.com/containers/image/v5/oci/internal"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -154,11 +154,7 @@ func (ref ociReference) PolicyConfigurationNamespaces() []string {
|
|||
// 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 ociReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, sys, src)
|
||||
return image.FromReference(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// getIndex returns a pointer to the index references by this ociReference. If an error occurs opening an index nil is returned together
|
||||
|
|
@ -219,7 +215,7 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
|
|||
func LoadManifestDescriptor(imgRef types.ImageReference) (imgspecv1.Descriptor, error) {
|
||||
ociRef, ok := imgRef.(ociReference)
|
||||
if !ok {
|
||||
return imgspecv1.Descriptor{}, errors.Errorf("error typecasting, need type ociRef")
|
||||
return imgspecv1.Descriptor{}, errors.New("error typecasting, need type ociRef")
|
||||
}
|
||||
return ociRef.getManifestDescriptor()
|
||||
}
|
||||
|
|
@ -238,7 +234,7 @@ func (ref ociReference) NewImageDestination(ctx context.Context, sys *types.Syst
|
|||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ociReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for oci: images")
|
||||
return errors.New("Deleting images not implemented for oci: images")
|
||||
}
|
||||
|
||||
// ociLayoutPath returns a path for the oci-layout within a directory using OCI conventions.
|
||||
|
|
@ -254,7 +250,7 @@ func (ref ociReference) indexPath() string {
|
|||
// blobPath returns a path for a blob within a directory using OCI image-layout conventions.
|
||||
func (ref ociReference) blobPath(digest digest.Digest, sharedBlobDir string) (string, error) {
|
||||
if err := digest.Validate(); err != nil {
|
||||
return "", errors.Wrapf(err, "unexpected digest reference %s", digest)
|
||||
return "", fmt.Errorf("unexpected digest reference %s: %w", digest, err)
|
||||
}
|
||||
blobDir := filepath.Join(ref.dir, "blobs")
|
||||
if sharedBlobDir != "" {
|
||||
|
|
|
|||
5
vendor/github.com/containers/image/v5/pkg/compression/compression.go
generated
vendored
5
vendor/github.com/containers/image/v5/pkg/compression/compression.go
generated
vendored
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/storage/pkg/chunked/compressor"
|
||||
"github.com/klauspost/pgzip"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
|
@ -151,13 +150,13 @@ func DetectCompression(input io.Reader) (DecompressorFunc, io.Reader, error) {
|
|||
func AutoDecompress(stream io.Reader) (io.ReadCloser, bool, error) {
|
||||
decompressor, stream, err := DetectCompression(stream)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "detecting compression")
|
||||
return nil, false, fmt.Errorf("detecting compression: %w", err)
|
||||
}
|
||||
var res io.ReadCloser
|
||||
if decompressor != nil {
|
||||
res, err = decompressor(stream)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "initializing decompression")
|
||||
return nil, false, fmt.Errorf("initializing decompression: %w", err)
|
||||
}
|
||||
} else {
|
||||
res = io.NopCloser(stream)
|
||||
|
|
|
|||
68
vendor/github.com/containers/image/v5/pkg/docker/config/config.go
generated
vendored
68
vendor/github.com/containers/image/v5/pkg/docker/config/config.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
|
@ -18,7 +19,6 @@ import (
|
|||
helperclient "github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ func SetCredentials(sys *types.SystemContext, key, username, password string) (s
|
|||
}
|
||||
|
||||
func unsupportedNamespaceErr(helper string) error {
|
||||
return errors.Errorf("namespaced key is not supported for credential helper %s", helper)
|
||||
return fmt.Errorf("namespaced key is not supported for credential helper %s", helper)
|
||||
}
|
||||
|
||||
// SetAuthentication stores the username and password in the credential helper or file
|
||||
|
|
@ -149,7 +149,7 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon
|
|||
// readJSONFile returns an empty map in case the path doesn't exist.
|
||||
auths, err := readJSONFile(path.path, path.legacyFormat)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading JSON file %q", path.path)
|
||||
return nil, fmt.Errorf("reading JSON file %q: %w", path.path, err)
|
||||
}
|
||||
// Credential helpers in the auth file have a
|
||||
// direct mapping to a registry, so we can just
|
||||
|
|
@ -170,16 +170,14 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon
|
|||
creds, err := listAuthsFromCredHelper(helper)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error listing credentials stored in credential helper %s: %v", helper, err)
|
||||
}
|
||||
switch errors.Cause(err) {
|
||||
case nil:
|
||||
for registry := range creds {
|
||||
addKey(registry)
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
creds = nil // It's okay if the helper doesn't exist.
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
case exec.ErrNotFound:
|
||||
// It's okay if the helper doesn't exist.
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
for registry := range creds {
|
||||
addKey(registry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -358,7 +356,7 @@ func getAuthenticationWithHomeDir(sys *types.SystemContext, key, homeDir string)
|
|||
return "", "", err
|
||||
}
|
||||
if auth.IdentityToken != "" {
|
||||
return "", "", errors.Wrap(ErrNotSupported, "non-empty identity token found and this API doesn't support it")
|
||||
return "", "", fmt.Errorf("non-empty identity token found and this API doesn't support it: %w", ErrNotSupported)
|
||||
}
|
||||
return auth.Username, auth.Password, nil
|
||||
}
|
||||
|
|
@ -397,7 +395,7 @@ func RemoveAuthentication(sys *types.SystemContext, key string) error {
|
|||
return
|
||||
}
|
||||
}
|
||||
multiErr = multierror.Append(multiErr, errors.Wrapf(err, "removing credentials for %s from credential helper %s", key, helper))
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("removing credentials for %s from credential helper %s: %w", key, helper, err))
|
||||
}
|
||||
|
||||
for _, helper := range helpers {
|
||||
|
|
@ -465,19 +463,19 @@ func RemoveAllAuthentication(sys *types.SystemContext) error {
|
|||
default:
|
||||
var creds map[string]string
|
||||
creds, err = listAuthsFromCredHelper(helper)
|
||||
switch errors.Cause(err) {
|
||||
case nil:
|
||||
for registry := range creds {
|
||||
err = deleteAuthFromCredHelper(helper, registry)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
// It's okay if the helper doesn't exist.
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
for registry := range creds {
|
||||
err = deleteAuthFromCredHelper(helper, registry)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
case exec.ErrNotFound:
|
||||
// It's okay if the helper doesn't exist.
|
||||
continue
|
||||
default:
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
|
@ -530,7 +528,7 @@ func getPathToAuthWithOS(sys *types.SystemContext, goOS string) (string, bool, e
|
|||
// This means the user set the XDG_RUNTIME_DIR variable and either forgot to create the directory
|
||||
// or made a typo while setting the environment variable,
|
||||
// so return an error referring to $XDG_RUNTIME_DIR instead of xdgRuntimeDirPath inside.
|
||||
return "", false, errors.Wrapf(err, "%q directory set by $XDG_RUNTIME_DIR does not exist. Either create the directory or unset $XDG_RUNTIME_DIR.", runtimeDir)
|
||||
return "", false, fmt.Errorf("%q directory set by $XDG_RUNTIME_DIR does not exist. Either create the directory or unset $XDG_RUNTIME_DIR.: %w", runtimeDir, err)
|
||||
} // else ignore err and let the caller fail accessing xdgRuntimeDirPath.
|
||||
return filepath.Join(runtimeDir, xdgRuntimeDirPath), false, nil
|
||||
}
|
||||
|
|
@ -554,13 +552,13 @@ func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
|
|||
|
||||
if legacyFormat {
|
||||
if err = json.Unmarshal(raw, &auths.AuthConfigs); err != nil {
|
||||
return dockerConfigFile{}, errors.Wrapf(err, "unmarshaling JSON at %q", path)
|
||||
return dockerConfigFile{}, fmt.Errorf("unmarshaling JSON at %q: %w", path, err)
|
||||
}
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(raw, &auths); err != nil {
|
||||
return dockerConfigFile{}, errors.Wrapf(err, "unmarshaling JSON at %q", path)
|
||||
return dockerConfigFile{}, fmt.Errorf("unmarshaling JSON at %q: %w", path, err)
|
||||
}
|
||||
|
||||
if auths.AuthConfigs == nil {
|
||||
|
|
@ -592,21 +590,21 @@ func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) (
|
|||
|
||||
auths, err := readJSONFile(path, false)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "reading JSON file %q", path)
|
||||
return "", fmt.Errorf("reading JSON file %q: %w", path, err)
|
||||
}
|
||||
|
||||
updated, err := editor(&auths)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "updating %q", path)
|
||||
return "", fmt.Errorf("updating %q: %w", path, err)
|
||||
}
|
||||
if updated {
|
||||
newData, err := json.MarshalIndent(auths, "", "\t")
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "marshaling JSON %q", path)
|
||||
return "", fmt.Errorf("marshaling JSON %q: %w", path, err)
|
||||
}
|
||||
|
||||
if err = ioutils.AtomicWriteFile(path, newData, 0600); err != nil {
|
||||
return "", errors.Wrapf(err, "writing to file %q", path)
|
||||
return "", fmt.Errorf("writing to file %q: %w", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -660,7 +658,7 @@ func deleteAuthFromCredHelper(credHelper, registry string) error {
|
|||
func findCredentialsInFile(key, registry, path string, legacyFormat bool) (types.DockerAuthConfig, error) {
|
||||
auths, err := readJSONFile(path, legacyFormat)
|
||||
if err != nil {
|
||||
return types.DockerAuthConfig{}, errors.Wrapf(err, "reading JSON file %q", path)
|
||||
return types.DockerAuthConfig{}, fmt.Errorf("reading JSON file %q: %w", path, err)
|
||||
}
|
||||
|
||||
// First try cred helpers. They should always be normalized.
|
||||
|
|
@ -781,7 +779,7 @@ func normalizeRegistry(registry string) string {
|
|||
// allowed and returns an indicator if the key is namespaced.
|
||||
func validateKey(key string) (bool, error) {
|
||||
if strings.HasPrefix(key, "http://") || strings.HasPrefix(key, "https://") {
|
||||
return false, errors.Errorf("key %s contains http[s]:// prefix", key)
|
||||
return false, fmt.Errorf("key %s contains http[s]:// prefix", key)
|
||||
}
|
||||
|
||||
// Ideally this should only accept explicitly valid keys, compare
|
||||
|
|
|
|||
12
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/paths_common.go
generated
vendored
Normal file
12
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/paths_common.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//go:build !freebsd
|
||||
// +build !freebsd
|
||||
|
||||
package sysregistriesv2
|
||||
|
||||
// builtinRegistriesConfPath is the path to the registry configuration file.
|
||||
// DO NOT change this, instead see systemRegistriesConfPath above.
|
||||
const builtinRegistriesConfPath = "/etc/containers/registries.conf"
|
||||
|
||||
// builtinRegistriesConfDirPath is the path to the registry configuration directory.
|
||||
// DO NOT change this, instead see systemRegistriesConfDirectoryPath above.
|
||||
const builtinRegistriesConfDirPath = "/etc/containers/registries.conf.d"
|
||||
12
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/paths_freebsd.go
generated
vendored
Normal file
12
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/paths_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package sysregistriesv2
|
||||
|
||||
// builtinRegistriesConfPath is the path to the registry configuration file.
|
||||
// DO NOT change this, instead see systemRegistriesConfPath above.
|
||||
const builtinRegistriesConfPath = "/usr/local/etc/containers/registries.conf"
|
||||
|
||||
// builtinRegistriesConfDirPath is the path to the registry configuration directory.
|
||||
// DO NOT change this, instead see systemRegistriesConfDirectoryPath above.
|
||||
const builtinRegistriesConfDirPath = "/usr/local/etc/containers/registries.conf.d"
|
||||
30
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go
generated
vendored
30
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go
generated
vendored
|
|
@ -1,6 +1,7 @@
|
|||
package sysregistriesv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
|
@ -12,7 +13,6 @@ import (
|
|||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ func editShortNameAlias(ctx *types.SystemContext, name string, value *string) er
|
|||
} else {
|
||||
// If the name does not exist, throw an error.
|
||||
if _, exists := conf.Aliases[name]; !exists {
|
||||
return errors.Errorf("short-name alias %q not found in %q: please check registries.conf files", name, confPath)
|
||||
return fmt.Errorf("short-name alias %q not found in %q: please check registries.conf files", name, confPath)
|
||||
}
|
||||
|
||||
delete(conf.Aliases, name)
|
||||
|
|
@ -210,25 +210,25 @@ func RemoveShortNameAlias(ctx *types.SystemContext, name string) error {
|
|||
func parseShortNameValue(alias string) (reference.Named, error) {
|
||||
ref, err := reference.Parse(alias)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parsing alias %q", alias)
|
||||
return nil, fmt.Errorf("parsing alias %q: %w", alias, err)
|
||||
}
|
||||
|
||||
if _, ok := ref.(reference.Digested); ok {
|
||||
return nil, errors.Errorf("invalid alias %q: must not contain digest", alias)
|
||||
return nil, fmt.Errorf("invalid alias %q: must not contain digest", alias)
|
||||
}
|
||||
|
||||
if _, ok := ref.(reference.Tagged); ok {
|
||||
return nil, errors.Errorf("invalid alias %q: must not contain tag", alias)
|
||||
return nil, fmt.Errorf("invalid alias %q: must not contain tag", alias)
|
||||
}
|
||||
|
||||
named, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias)
|
||||
return nil, fmt.Errorf("invalid alias %q: must contain registry and repository", alias)
|
||||
}
|
||||
|
||||
registry := reference.Domain(named)
|
||||
if !(strings.ContainsAny(registry, ".:") || registry == "localhost") {
|
||||
return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias)
|
||||
return nil, fmt.Errorf("invalid alias %q: must contain registry and repository", alias)
|
||||
}
|
||||
|
||||
// A final parse to make sure that docker.io references are correctly
|
||||
|
|
@ -242,25 +242,25 @@ func parseShortNameValue(alias string) (reference.Named, error) {
|
|||
func validateShortName(name string) error {
|
||||
repo, err := reference.Parse(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot parse short name: %q", name)
|
||||
return fmt.Errorf("cannot parse short name: %q: %w", name, err)
|
||||
}
|
||||
|
||||
if _, ok := repo.(reference.Digested); ok {
|
||||
return errors.Errorf("invalid short name %q: must not contain digest", name)
|
||||
return fmt.Errorf("invalid short name %q: must not contain digest", name)
|
||||
}
|
||||
|
||||
if _, ok := repo.(reference.Tagged); ok {
|
||||
return errors.Errorf("invalid short name %q: must not contain tag", name)
|
||||
return fmt.Errorf("invalid short name %q: must not contain tag", name)
|
||||
}
|
||||
|
||||
named, ok := repo.(reference.Named)
|
||||
if !ok {
|
||||
return errors.Errorf("invalid short name %q: no name", name)
|
||||
return fmt.Errorf("invalid short name %q: no name", name)
|
||||
}
|
||||
|
||||
registry := reference.Domain(named)
|
||||
if strings.ContainsAny(registry, ".:") || registry == "localhost" {
|
||||
return errors.Errorf("invalid short name %q: must not contain registry", name)
|
||||
return fmt.Errorf("invalid short name %q: must not contain registry", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -298,7 +298,7 @@ func newShortNameAliasCache(path string, conf *shortNameAliasConf) (*shortNameAl
|
|||
if len(errs) > 0 {
|
||||
err := errs[0]
|
||||
for i := 1; i < len(errs); i++ {
|
||||
err = errors.Wrapf(err, "%v\n", errs[i])
|
||||
err = fmt.Errorf("%v\n: %w", errs[i], err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -319,7 +319,7 @@ func loadShortNameAliasConf(confPath string) (*shortNameAliasConf, *shortNameAli
|
|||
meta, err := toml.DecodeFile(confPath, &conf)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
// It's okay if the config doesn't exist. Other errors are not.
|
||||
return nil, nil, errors.Wrapf(err, "loading short-name aliases config file %q", confPath)
|
||||
return nil, nil, fmt.Errorf("loading short-name aliases config file %q: %w", confPath, err)
|
||||
}
|
||||
if keys := meta.Undecoded(); len(keys) > 0 {
|
||||
logrus.Debugf("Failed to decode keys %q from %q", keys, confPath)
|
||||
|
|
@ -329,7 +329,7 @@ func loadShortNameAliasConf(confPath string) (*shortNameAliasConf, *shortNameAli
|
|||
// file could still be corrupted by another process or user.
|
||||
cache, err := newShortNameAliasCache(confPath, &conf)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "loading short-name aliases config file %q", confPath)
|
||||
return nil, nil, fmt.Errorf("loading short-name aliases config file %q: %w", confPath, err)
|
||||
}
|
||||
|
||||
return &conf, cache, nil
|
||||
|
|
|
|||
21
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go
generated
vendored
21
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go
generated
vendored
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -25,20 +24,12 @@ import (
|
|||
// -ldflags '-X github.com/containers/image/v5/sysregistries.systemRegistriesConfPath=$your_path'
|
||||
var systemRegistriesConfPath = builtinRegistriesConfPath
|
||||
|
||||
// builtinRegistriesConfPath is the path to the registry configuration file.
|
||||
// DO NOT change this, instead see systemRegistriesConfPath above.
|
||||
const builtinRegistriesConfPath = "/etc/containers/registries.conf"
|
||||
|
||||
// systemRegistriesConfDirPath is the path to the system-wide registry
|
||||
// configuration directory and is used to add/subtract potential registries for
|
||||
// obtaining images. You can override this at build time with
|
||||
// -ldflags '-X github.com/containers/image/v5/sysregistries.systemRegistriesConfDirectoryPath=$your_path'
|
||||
var systemRegistriesConfDirPath = builtinRegistriesConfDirPath
|
||||
|
||||
// builtinRegistriesConfDirPath is the path to the registry configuration directory.
|
||||
// DO NOT change this, instead see systemRegistriesConfDirectoryPath above.
|
||||
const builtinRegistriesConfDirPath = "/etc/containers/registries.conf.d"
|
||||
|
||||
// AuthenticationFileHelper is a special key for credential helpers indicating
|
||||
// the usage of consulting containers-auth.json files instead of a credential
|
||||
// helper.
|
||||
|
|
@ -111,7 +102,7 @@ func (e *Endpoint) rewriteReference(ref reference.Named, prefix string) (referen
|
|||
newNamedRef = e.Location + refString[prefixLen:]
|
||||
newParsedRef, err := reference.ParseNamed(newNamedRef)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "rewriting reference")
|
||||
return nil, fmt.Errorf("rewriting reference: %w", err)
|
||||
}
|
||||
|
||||
return newParsedRef, nil
|
||||
|
|
@ -674,7 +665,7 @@ func dropInConfigs(wrapper configWrapper) ([]string, error) {
|
|||
if err != nil && !os.IsNotExist(err) {
|
||||
// Ignore IsNotExist errors: most systems won't have a registries.conf.d
|
||||
// directory.
|
||||
return nil, errors.Wrapf(err, "reading registries.conf.d")
|
||||
return nil, fmt.Errorf("reading registries.conf.d: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -716,7 +707,7 @@ func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*parsedC
|
|||
return nil, err // Should never happen
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "loading registries configuration %q", wrapper.configPath)
|
||||
return nil, fmt.Errorf("loading registries configuration %q: %w", wrapper.configPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -729,7 +720,7 @@ func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*parsedC
|
|||
// Enforce v2 format for drop-in-configs.
|
||||
dropIn, err := loadConfigFile(path, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "loading drop-in registries configuration %q", path)
|
||||
return nil, fmt.Errorf("loading drop-in registries configuration %q: %w", path, err)
|
||||
}
|
||||
config.updateWithConfigurationFrom(dropIn)
|
||||
}
|
||||
|
|
@ -790,7 +781,7 @@ func parseShortNameMode(mode string) (types.ShortNameMode, error) {
|
|||
case "permissive":
|
||||
return types.ShortNameModePermissive, nil
|
||||
default:
|
||||
return types.ShortNameModeInvalid, errors.Errorf("invalid short-name mode: %q", mode)
|
||||
return types.ShortNameModeInvalid, fmt.Errorf("invalid short-name mode: %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -983,7 +974,7 @@ func loadConfigFile(path string, forceV2 bool) (*parsedConfig, error) {
|
|||
// Parse and validate short-name aliases.
|
||||
cache, err := newShortNameAliasCache(path, &res.partialV2.shortNameAliasConf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "validating short-name aliases")
|
||||
return nil, fmt.Errorf("validating short-name aliases: %w", err)
|
||||
}
|
||||
res.aliasCache = cache
|
||||
// Clear conf.partialV2.shortNameAliasConf to make it available for garbage collection and
|
||||
|
|
|
|||
8
vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go
generated
vendored
8
vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package tlsclientconfig
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -11,7 +12,6 @@ import (
|
|||
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ func SetupCertificates(dir string, tlsc *tls.Config) error {
|
|||
if tlsc.RootCAs == nil {
|
||||
systemPool, err := tlsconfig.SystemCertPool()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get system cert pool")
|
||||
return fmt.Errorf("unable to get system cert pool: %w", err)
|
||||
}
|
||||
tlsc.RootCAs = systemPool
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ func SetupCertificates(dir string, tlsc *tls.Config) error {
|
|||
keyName := certName[:len(certName)-5] + ".key"
|
||||
logrus.Debugf(" cert: %s", fullPath)
|
||||
if !hasFile(fs, keyName) {
|
||||
return errors.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
|
||||
return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(filepath.Join(dir, certName), filepath.Join(dir, keyName))
|
||||
if err != nil {
|
||||
|
|
@ -73,7 +73,7 @@ func SetupCertificates(dir string, tlsc *tls.Config) error {
|
|||
certName := keyName[:len(keyName)-4] + ".cert"
|
||||
logrus.Debugf(" key: %s", fullPath)
|
||||
if !hasFile(fs, certName) {
|
||||
return errors.Errorf("missing client certificate %s for key %s", certName, keyName)
|
||||
return fmt.Errorf("missing client certificate %s for key %s", certName, keyName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
vendor/github.com/containers/image/v5/signature/docker.go
generated
vendored
11
vendor/github.com/containers/image/v5/signature/docker.go
generated
vendored
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
|
|
@ -56,18 +57,18 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
|
|||
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
|
||||
validateKeyIdentity: func(keyIdentity string) error {
|
||||
if keyIdentity != expectedKeyIdentity {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity)}
|
||||
return internal.NewInvalidSignatureError(fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerReference: func(signedDockerReference string) error {
|
||||
signedRef, err := reference.ParseNormalizedNamed(signedDockerReference)
|
||||
if err != nil {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)}
|
||||
return internal.NewInvalidSignatureError(fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference))
|
||||
}
|
||||
if signedRef.String() != expectedRef.String() {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
|
||||
signedDockerReference, expectedDockerReference)}
|
||||
return internal.NewInvalidSignatureError(fmt.Sprintf("Docker reference %s does not match %s",
|
||||
signedDockerReference, expectedDockerReference))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
@ -77,7 +78,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
|
|||
return err
|
||||
}
|
||||
if !matches {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest)}
|
||||
return internal.NewInvalidSignatureError(fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
|
|||
15
vendor/github.com/containers/image/v5/signature/internal/errors.go
generated
vendored
Normal file
15
vendor/github.com/containers/image/v5/signature/internal/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package internal
|
||||
|
||||
// InvalidSignatureError is returned when parsing an invalid signature.
|
||||
// This is publicly visible as signature.InvalidSignatureError
|
||||
type InvalidSignatureError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err InvalidSignatureError) Error() string {
|
||||
return err.msg
|
||||
}
|
||||
|
||||
func NewInvalidSignatureError(msg string) InvalidSignatureError {
|
||||
return InvalidSignatureError{msg: msg}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package signature
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -7,34 +7,34 @@ import (
|
|||
"io"
|
||||
)
|
||||
|
||||
// jsonFormatError is returned when JSON does not match expected format.
|
||||
type jsonFormatError string
|
||||
// JSONFormatError is returned when JSON does not match expected format.
|
||||
type JSONFormatError string
|
||||
|
||||
func (err jsonFormatError) Error() string {
|
||||
func (err JSONFormatError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
// paranoidUnmarshalJSONObject unmarshals data as a JSON object, but failing on the slightest unexpected aspect
|
||||
// ParanoidUnmarshalJSONObject unmarshals data as a JSON object, but failing on the slightest unexpected aspect
|
||||
// (including duplicated keys, unrecognized keys, and non-matching types). Uses fieldResolver to
|
||||
// determine the destination for a field value, which should return a pointer to the destination if valid, or nil if the key is rejected.
|
||||
//
|
||||
// The fieldResolver approach is useful for decoding the Policy.Transports map; using it for structs is a bit lazy,
|
||||
// we could use reflection to automate this. Later?
|
||||
func paranoidUnmarshalJSONObject(data []byte, fieldResolver func(string) interface{}) error {
|
||||
func ParanoidUnmarshalJSONObject(data []byte, fieldResolver func(string) interface{}) error {
|
||||
seenKeys := map[string]struct{}{}
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
return jsonFormatError(err.Error())
|
||||
return JSONFormatError(err.Error())
|
||||
}
|
||||
if t != json.Delim('{') {
|
||||
return jsonFormatError(fmt.Sprintf("JSON object expected, got \"%s\"", t))
|
||||
return JSONFormatError(fmt.Sprintf("JSON object expected, got \"%s\"", t))
|
||||
}
|
||||
for {
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
return jsonFormatError(err.Error())
|
||||
return JSONFormatError(err.Error())
|
||||
}
|
||||
if t == json.Delim('}') {
|
||||
break
|
||||
|
|
@ -43,34 +43,34 @@ func paranoidUnmarshalJSONObject(data []byte, fieldResolver func(string) interfa
|
|||
key, ok := t.(string)
|
||||
if !ok {
|
||||
// Coverage: This should never happen, dec.Token() rejects non-string-literals in this state.
|
||||
return jsonFormatError(fmt.Sprintf("Key string literal expected, got \"%s\"", t))
|
||||
return JSONFormatError(fmt.Sprintf("Key string literal expected, got \"%s\"", t))
|
||||
}
|
||||
if _, ok := seenKeys[key]; ok {
|
||||
return jsonFormatError(fmt.Sprintf("Duplicate key \"%s\"", key))
|
||||
return JSONFormatError(fmt.Sprintf("Duplicate key \"%s\"", key))
|
||||
}
|
||||
seenKeys[key] = struct{}{}
|
||||
|
||||
valuePtr := fieldResolver(key)
|
||||
if valuePtr == nil {
|
||||
return jsonFormatError(fmt.Sprintf("Unknown key \"%s\"", key))
|
||||
return JSONFormatError(fmt.Sprintf("Unknown key \"%s\"", key))
|
||||
}
|
||||
// This works like json.Unmarshal, in particular it allows us to implement UnmarshalJSON to implement strict parsing of the field value.
|
||||
if err := dec.Decode(valuePtr); err != nil {
|
||||
return jsonFormatError(err.Error())
|
||||
return JSONFormatError(err.Error())
|
||||
}
|
||||
}
|
||||
if _, err := dec.Token(); err != io.EOF {
|
||||
return jsonFormatError("Unexpected data after JSON object")
|
||||
return JSONFormatError("Unexpected data after JSON object")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// paranoidUnmarshalJSONObject unmarshals data as a JSON object, but failing on the slightest unexpected aspect
|
||||
// ParanoidUnmarshalJSONObjectExactFields unmarshals data as a JSON object, but failing on the slightest unexpected aspect
|
||||
// (including duplicated keys, unrecognized keys, and non-matching types). Each of the fields in exactFields
|
||||
// must be present exactly once, and none other fields are accepted.
|
||||
func paranoidUnmarshalJSONObjectExactFields(data []byte, exactFields map[string]interface{}) error {
|
||||
func ParanoidUnmarshalJSONObjectExactFields(data []byte, exactFields map[string]interface{}) error {
|
||||
seenKeys := map[string]struct{}{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
if err := ParanoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
if valuePtr, ok := exactFields[key]; ok {
|
||||
seenKeys[key] = struct{}{}
|
||||
return valuePtr
|
||||
|
|
@ -81,7 +81,7 @@ func paranoidUnmarshalJSONObjectExactFields(data []byte, exactFields map[string]
|
|||
}
|
||||
for key := range exactFields {
|
||||
if _, ok := seenKeys[key]; !ok {
|
||||
return jsonFormatError(fmt.Sprintf(`Key "%s" missing in a JSON object`, key))
|
||||
return JSONFormatError(fmt.Sprintf(`Key "%s" missing in a JSON object`, key))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
201
vendor/github.com/containers/image/v5/signature/internal/sigstore_payload.go
generated
vendored
Normal file
201
vendor/github.com/containers/image/v5/signature/internal/sigstore_payload.go
generated
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/version"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
sigstoreSignature "github.com/sigstore/sigstore/pkg/signature"
|
||||
)
|
||||
|
||||
const (
|
||||
sigstoreSignatureType = "cosign container image signature"
|
||||
sigstoreHarcodedHashAlgorithm = crypto.SHA256
|
||||
)
|
||||
|
||||
// UntrustedSigstorePayload is a parsed content of a sigstore signature payload (not the full signature)
|
||||
type UntrustedSigstorePayload struct {
|
||||
UntrustedDockerManifestDigest digest.Digest
|
||||
UntrustedDockerReference string // FIXME: more precise type?
|
||||
UntrustedCreatorID *string
|
||||
// This is intentionally an int64; the native JSON float64 type would allow to represent _some_ sub-second precision,
|
||||
// but not nearly enough (with current timestamp values, a single unit in the last place is on the order of hundreds of nanoseconds).
|
||||
// So, this is explicitly an int64, and we reject fractional values. If we did need more precise timestamps eventually,
|
||||
// we would add another field, UntrustedTimestampNS int64.
|
||||
UntrustedTimestamp *int64
|
||||
}
|
||||
|
||||
// NewUntrustedSigstorePayload returns an UntrustedSigstorePayload object with
|
||||
// the specified primary contents and appropriate metadata.
|
||||
func NewUntrustedSigstorePayload(dockerManifestDigest digest.Digest, dockerReference string) UntrustedSigstorePayload {
|
||||
// Use intermediate variables for these values so that we can take their addresses.
|
||||
// Golang guarantees that they will have a new address on every execution.
|
||||
creatorID := "containers/image " + version.Version
|
||||
timestamp := time.Now().Unix()
|
||||
return UntrustedSigstorePayload{
|
||||
UntrustedDockerManifestDigest: dockerManifestDigest,
|
||||
UntrustedDockerReference: dockerReference,
|
||||
UntrustedCreatorID: &creatorID,
|
||||
UntrustedTimestamp: ×tamp,
|
||||
}
|
||||
}
|
||||
|
||||
// Compile-time check that UntrustedSigstorePayload implements json.Marshaler
|
||||
var _ json.Marshaler = (*UntrustedSigstorePayload)(nil)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (s UntrustedSigstorePayload) MarshalJSON() ([]byte, error) {
|
||||
if s.UntrustedDockerManifestDigest == "" || s.UntrustedDockerReference == "" {
|
||||
return nil, errors.New("Unexpected empty signature content")
|
||||
}
|
||||
critical := map[string]interface{}{
|
||||
"type": sigstoreSignatureType,
|
||||
"image": map[string]string{"docker-manifest-digest": s.UntrustedDockerManifestDigest.String()},
|
||||
"identity": map[string]string{"docker-reference": s.UntrustedDockerReference},
|
||||
}
|
||||
optional := map[string]interface{}{}
|
||||
if s.UntrustedCreatorID != nil {
|
||||
optional["creator"] = *s.UntrustedCreatorID
|
||||
}
|
||||
if s.UntrustedTimestamp != nil {
|
||||
optional["timestamp"] = *s.UntrustedTimestamp
|
||||
}
|
||||
signature := map[string]interface{}{
|
||||
"critical": critical,
|
||||
"optional": optional,
|
||||
}
|
||||
return json.Marshal(signature)
|
||||
}
|
||||
|
||||
// Compile-time check that UntrustedSigstorePayload implements json.Unmarshaler
|
||||
var _ json.Unmarshaler = (*UntrustedSigstorePayload)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (s *UntrustedSigstorePayload) UnmarshalJSON(data []byte) error {
|
||||
err := s.strictUnmarshalJSON(data)
|
||||
if err != nil {
|
||||
if formatErr, ok := err.(JSONFormatError); ok {
|
||||
err = NewInvalidSignatureError(formatErr.Error())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// strictUnmarshalJSON is UnmarshalJSON, except that it may return the internal JSONFormatError error type.
|
||||
// Splitting it into a separate function allows us to do the JSONFormatError → InvalidSignatureError in a single place, the caller.
|
||||
func (s *UntrustedSigstorePayload) strictUnmarshalJSON(data []byte) error {
|
||||
var critical, optional json.RawMessage
|
||||
if err := ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"critical": &critical,
|
||||
"optional": &optional,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var creatorID string
|
||||
var timestamp float64
|
||||
var gotCreatorID, gotTimestamp = false, false
|
||||
// /usr/bin/cosign generates "optional": null if there are no user-specified annotations.
|
||||
if !bytes.Equal(optional, []byte("null")) {
|
||||
if err := ParanoidUnmarshalJSONObject(optional, func(key string) interface{} {
|
||||
switch key {
|
||||
case "creator":
|
||||
gotCreatorID = true
|
||||
return &creatorID
|
||||
case "timestamp":
|
||||
gotTimestamp = true
|
||||
return ×tamp
|
||||
default:
|
||||
var ignore interface{}
|
||||
return &ignore
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if gotCreatorID {
|
||||
s.UntrustedCreatorID = &creatorID
|
||||
}
|
||||
if gotTimestamp {
|
||||
intTimestamp := int64(timestamp)
|
||||
if float64(intTimestamp) != timestamp {
|
||||
return NewInvalidSignatureError("Field optional.timestamp is not is not an integer")
|
||||
}
|
||||
s.UntrustedTimestamp = &intTimestamp
|
||||
}
|
||||
|
||||
var t string
|
||||
var image, identity json.RawMessage
|
||||
if err := ParanoidUnmarshalJSONObjectExactFields(critical, map[string]interface{}{
|
||||
"type": &t,
|
||||
"image": &image,
|
||||
"identity": &identity,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if t != sigstoreSignatureType {
|
||||
return NewInvalidSignatureError(fmt.Sprintf("Unrecognized signature type %s", t))
|
||||
}
|
||||
|
||||
var digestString string
|
||||
if err := ParanoidUnmarshalJSONObjectExactFields(image, map[string]interface{}{
|
||||
"docker-manifest-digest": &digestString,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
||||
|
||||
return ParanoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
||||
"docker-reference": &s.UntrustedDockerReference,
|
||||
})
|
||||
}
|
||||
|
||||
// SigstorePayloadAcceptanceRules specifies how to decide whether an untrusted payload is acceptable.
|
||||
// We centralize the actual parsing and data extraction in VerifySigstorePayload; this supplies
|
||||
// the policy. We use an object instead of supplying func parameters to verifyAndExtractSignature
|
||||
// because the functions have the same or similar types, so there is a risk of exchanging the functions;
|
||||
// named members of this struct are more explicit.
|
||||
type SigstorePayloadAcceptanceRules struct {
|
||||
ValidateSignedDockerReference func(string) error
|
||||
ValidateSignedDockerManifestDigest func(digest.Digest) error
|
||||
}
|
||||
|
||||
// VerifySigstorePayload verifies unverifiedBase64Signature of unverifiedPayload was correctly created by publicKey, and that its principal components
|
||||
// match expected values, both as specified by rules, and returns it.
|
||||
// We return an *UntrustedSigstorePayload, although nothing actually uses it,
|
||||
// just to double-check against stupid typos.
|
||||
func VerifySigstorePayload(publicKey crypto.PublicKey, unverifiedPayload []byte, unverifiedBase64Signature string, rules SigstorePayloadAcceptanceRules) (*UntrustedSigstorePayload, error) {
|
||||
verifier, err := sigstoreSignature.LoadVerifier(publicKey, sigstoreHarcodedHashAlgorithm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating verifier: %w", err)
|
||||
}
|
||||
|
||||
unverifiedSignature, err := base64.StdEncoding.DecodeString(unverifiedBase64Signature)
|
||||
if err != nil {
|
||||
return nil, NewInvalidSignatureError(fmt.Sprintf("base64 decoding: %v", err))
|
||||
}
|
||||
// github.com/sigstore/cosign/pkg/cosign.verifyOCISignature uses signatureoptions.WithContext(),
|
||||
// which seems to be not used by anything. So we don’t bother.
|
||||
if err := verifier.VerifySignature(bytes.NewReader(unverifiedSignature), bytes.NewReader(unverifiedPayload)); err != nil {
|
||||
return nil, NewInvalidSignatureError(fmt.Sprintf("cryptographic signature verification failed: %v", err))
|
||||
}
|
||||
|
||||
var unmatchedPayload UntrustedSigstorePayload
|
||||
if err := json.Unmarshal(unverifiedPayload, &unmatchedPayload); err != nil {
|
||||
return nil, NewInvalidSignatureError(err.Error())
|
||||
}
|
||||
if err := rules.ValidateSignedDockerManifestDigest(unmatchedPayload.UntrustedDockerManifestDigest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rules.ValidateSignedDockerReference(unmatchedPayload.UntrustedDockerReference); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// SigstorePayloadAcceptanceRules have accepted this value.
|
||||
return &unmatchedPayload, nil
|
||||
}
|
||||
2
vendor/github.com/containers/image/v5/signature/mechanism.go
generated
vendored
2
vendor/github.com/containers/image/v5/signature/mechanism.go
generated
vendored
|
|
@ -65,7 +65,7 @@ func NewGPGSigningMechanism() (SigningMechanism, error) {
|
|||
// of these keys.
|
||||
// The caller must call .Close() on the returned SigningMechanism.
|
||||
func NewEphemeralGPGSigningMechanism(blob []byte) (SigningMechanism, []string, error) {
|
||||
return newEphemeralGPGSigningMechanism(blob)
|
||||
return newEphemeralGPGSigningMechanism([][]byte{blob})
|
||||
}
|
||||
|
||||
// gpgUntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION,
|
||||
|
|
|
|||
19
vendor/github.com/containers/image/v5/signature/mechanism_gpgme.go
generated
vendored
19
vendor/github.com/containers/image/v5/signature/mechanism_gpgme.go
generated
vendored
|
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
"github.com/proglottis/gpgme"
|
||||
)
|
||||
|
||||
|
|
@ -32,10 +33,10 @@ func newGPGSigningMechanismInDirectory(optionalDir string) (signingMechanismWith
|
|||
}
|
||||
|
||||
// newEphemeralGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism which
|
||||
// recognizes _only_ public keys from the supplied blob, and returns the identities
|
||||
// recognizes _only_ public keys from the supplied blobs, and returns the identities
|
||||
// of these keys.
|
||||
// The caller must call .Close() on the returned SigningMechanism.
|
||||
func newEphemeralGPGSigningMechanism(blob []byte) (signingMechanismWithPassphrase, []string, error) {
|
||||
func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
|
||||
dir, err := os.MkdirTemp("", "containers-ephemeral-gpg-")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
@ -54,9 +55,13 @@ func newEphemeralGPGSigningMechanism(blob []byte) (signingMechanismWithPassphras
|
|||
ctx: ctx,
|
||||
ephemeralDir: dir,
|
||||
}
|
||||
keyIdentities, err := mech.importKeysFromBytes(blob)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
keyIdentities := []string{}
|
||||
for _, blob := range blobs {
|
||||
ki, err := mech.importKeysFromBytes(blob)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyIdentities = append(keyIdentities, ki...)
|
||||
}
|
||||
|
||||
removeDir = false
|
||||
|
|
@ -181,13 +186,13 @@ func (m *gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []b
|
|||
return nil, "", err
|
||||
}
|
||||
if len(sigs) != 1 {
|
||||
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Unexpected GPG signature count %d", len(sigs))}
|
||||
return nil, "", internal.NewInvalidSignatureError(fmt.Sprintf("Unexpected GPG signature count %d", len(sigs)))
|
||||
}
|
||||
sig := sigs[0]
|
||||
// This is sig.Summary == gpgme.SigSumValid except for key trust, which we handle ourselves
|
||||
if sig.Status != nil || sig.Validity == gpgme.ValidityNever || sig.ValidityReason != nil || sig.WrongKeyUsage {
|
||||
// FIXME: Better error reporting eventually
|
||||
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Invalid GPG signature: %#v", sig)}
|
||||
return nil, "", internal.NewInvalidSignatureError(fmt.Sprintf("Invalid GPG signature: %#v", sig))
|
||||
}
|
||||
return signedBuffer.Bytes(), sig.Fingerprint, nil
|
||||
}
|
||||
|
|
|
|||
20
vendor/github.com/containers/image/v5/signature/mechanism_openpgp.go
generated
vendored
20
vendor/github.com/containers/image/v5/signature/mechanism_openpgp.go
generated
vendored
|
|
@ -13,6 +13,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
// This is a fallback code; the primary recommendation is to use the gpgme mechanism
|
||||
// implementation, which is out-of-process and more appropriate for handling long-term private key material
|
||||
|
|
@ -62,14 +63,19 @@ func newGPGSigningMechanismInDirectory(optionalDir string) (signingMechanismWith
|
|||
// recognizes _only_ public keys from the supplied blob, and returns the identities
|
||||
// of these keys.
|
||||
// The caller must call .Close() on the returned SigningMechanism.
|
||||
func newEphemeralGPGSigningMechanism(blob []byte) (signingMechanismWithPassphrase, []string, error) {
|
||||
func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
|
||||
m := &openpgpSigningMechanism{
|
||||
keyring: openpgp.EntityList{},
|
||||
}
|
||||
keyIdentities, err := m.importKeysFromBytes(blob)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
keyIdentities := []string{}
|
||||
for _, blob := range blobs {
|
||||
ki, err := m.importKeysFromBytes(blob)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyIdentities = append(keyIdentities, ki...)
|
||||
}
|
||||
|
||||
return m, keyIdentities, nil
|
||||
}
|
||||
|
||||
|
|
@ -144,19 +150,19 @@ func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents [
|
|||
return nil, "", fmt.Errorf("signature error: %v", md.SignatureError)
|
||||
}
|
||||
if md.SignedBy == nil {
|
||||
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Invalid GPG signature: %#v", md.Signature)}
|
||||
return nil, "", internal.NewInvalidSignatureError(fmt.Sprintf("Invalid GPG signature: %#v", md.Signature))
|
||||
}
|
||||
if md.Signature != nil {
|
||||
if md.Signature.SigLifetimeSecs != nil {
|
||||
expiry := md.Signature.CreationTime.Add(time.Duration(*md.Signature.SigLifetimeSecs) * time.Second)
|
||||
if time.Now().After(expiry) {
|
||||
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Signature expired on %s", expiry)}
|
||||
return nil, "", internal.NewInvalidSignatureError(fmt.Sprintf("Signature expired on %s", expiry))
|
||||
}
|
||||
}
|
||||
} else if md.SignatureV3 == nil {
|
||||
// Coverage: If md.SignedBy != nil, the final md.UnverifiedBody.Read() either sets one of md.Signature or md.SignatureV3,
|
||||
// or sets md.SignatureError.
|
||||
return nil, "", InvalidSignatureError{msg: "Unexpected openpgp.MessageDetails: neither Signature nor SignatureV3 is set"}
|
||||
return nil, "", internal.NewInvalidSignatureError("Unexpected openpgp.MessageDetails: neither Signature nor SignatureV3 is set")
|
||||
}
|
||||
|
||||
// Uppercase the fingerprint to be compatible with gpgme
|
||||
|
|
|
|||
194
vendor/github.com/containers/image/v5/signature/policy_config.go
generated
vendored
194
vendor/github.com/containers/image/v5/signature/policy_config.go
generated
vendored
|
|
@ -15,16 +15,17 @@ package signature
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// systemDefaultPolicyPath is the policy path used for DefaultPolicy().
|
||||
|
|
@ -32,10 +33,6 @@ import (
|
|||
// -ldflags '-X github.com/containers/image/v5/signature.systemDefaultPolicyPath=$your_path'
|
||||
var systemDefaultPolicyPath = builtinDefaultPolicyPath
|
||||
|
||||
// builtinDefaultPolicyPath is the policy path used for DefaultPolicy().
|
||||
// DO NOT change this, instead see systemDefaultPolicyPath above.
|
||||
const builtinDefaultPolicyPath = "/etc/containers/policy.json"
|
||||
|
||||
// userPolicyFile is the path to the per user policy path.
|
||||
var userPolicyFile = filepath.FromSlash(".config/containers/policy.json")
|
||||
|
||||
|
|
@ -85,7 +82,7 @@ func NewPolicyFromFile(fileName string) (*Policy, error) {
|
|||
}
|
||||
policy, err := NewPolicyFromBytes(contents)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid policy in %q", fileName)
|
||||
return nil, fmt.Errorf("invalid policy in %q: %w", fileName, err)
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
|
@ -107,7 +104,7 @@ var _ json.Unmarshaler = (*Policy)(nil)
|
|||
func (p *Policy) UnmarshalJSON(data []byte) error {
|
||||
*p = Policy{}
|
||||
transports := policyTransportsMap{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
if err := internal.ParanoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "default":
|
||||
return &p.Default
|
||||
|
|
@ -138,10 +135,10 @@ func (m *policyTransportsMap) UnmarshalJSON(data []byte) error {
|
|||
// We can't unmarshal directly into map values because it is not possible to take an address of a map value.
|
||||
// So, use a temporary map of pointers-to-slices and convert.
|
||||
tmpMap := map[string]*PolicyTransportScopes{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
if err := internal.ParanoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
// transport can be nil
|
||||
transport := transports.Get(key)
|
||||
// paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||
// internal.ParanoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||
if _, ok := tmpMap[key]; ok {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -184,8 +181,8 @@ func (m *policyTransportScopesWithTransport) UnmarshalJSON(data []byte) error {
|
|||
// We can't unmarshal directly into map values because it is not possible to take an address of a map value.
|
||||
// So, use a temporary map of pointers-to-slices and convert.
|
||||
tmpMap := map[string]*PolicyRequirements{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
// paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||
if err := internal.ParanoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
// internal.ParanoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||
if _, ok := tmpMap[key]; ok {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -246,6 +243,8 @@ func newPolicyRequirementFromJSON(data []byte) (PolicyRequirement, error) {
|
|||
res = &prSignedBy{}
|
||||
case prTypeSignedBaseLayer:
|
||||
res = &prSignedBaseLayer{}
|
||||
case prTypeSigstoreSigned:
|
||||
res = &prSigstoreSigned{}
|
||||
default:
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("Unknown policy requirement type \"%s\"", typeField.Type))
|
||||
}
|
||||
|
|
@ -272,7 +271,7 @@ var _ json.Unmarshaler = (*prInsecureAcceptAnything)(nil)
|
|||
func (pr *prInsecureAcceptAnything) UnmarshalJSON(data []byte) error {
|
||||
*pr = prInsecureAcceptAnything{}
|
||||
var tmp prInsecureAcceptAnything
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
|
@ -302,7 +301,7 @@ var _ json.Unmarshaler = (*prReject)(nil)
|
|||
func (pr *prReject) UnmarshalJSON(data []byte) error {
|
||||
*pr = prReject{}
|
||||
var tmp prReject
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
|
@ -316,12 +315,22 @@ func (pr *prReject) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
// newPRSignedBy returns a new prSignedBy if parameters are valid.
|
||||
func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
func newPRSignedBy(keyType sbKeyType, keyPath string, keyPaths []string, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
if !keyType.IsValid() {
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("invalid keyType \"%s\"", keyType))
|
||||
}
|
||||
if len(keyPath) > 0 && len(keyData) > 0 {
|
||||
return nil, InvalidPolicyFormatError("keyType and keyData cannot be used simultaneously")
|
||||
keySources := 0
|
||||
if keyPath != "" {
|
||||
keySources++
|
||||
}
|
||||
if keyPaths != nil {
|
||||
keySources++
|
||||
}
|
||||
if keyData != nil {
|
||||
keySources++
|
||||
}
|
||||
if keySources != 1 {
|
||||
return nil, InvalidPolicyFormatError("exactly one of keyPath, keyPaths and keyData must be specified")
|
||||
}
|
||||
if signedIdentity == nil {
|
||||
return nil, InvalidPolicyFormatError("signedIdentity not specified")
|
||||
|
|
@ -330,6 +339,7 @@ func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIden
|
|||
prCommon: prCommon{Type: prTypeSignedBy},
|
||||
KeyType: keyType,
|
||||
KeyPath: keyPath,
|
||||
KeyPaths: keyPaths,
|
||||
KeyData: keyData,
|
||||
SignedIdentity: signedIdentity,
|
||||
}, nil
|
||||
|
|
@ -337,7 +347,7 @@ func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIden
|
|||
|
||||
// newPRSignedByKeyPath is NewPRSignedByKeyPath, except it returns the private type.
|
||||
func newPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
return newPRSignedBy(keyType, keyPath, nil, signedIdentity)
|
||||
return newPRSignedBy(keyType, keyPath, nil, nil, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSignedByKeyPath returns a new "signedBy" PolicyRequirement using a KeyPath
|
||||
|
|
@ -345,9 +355,19 @@ func NewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity Poli
|
|||
return newPRSignedByKeyPath(keyType, keyPath, signedIdentity)
|
||||
}
|
||||
|
||||
// newPRSignedByKeyPaths is NewPRSignedByKeyPaths, except it returns the private type.
|
||||
func newPRSignedByKeyPaths(keyType sbKeyType, keyPaths []string, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
return newPRSignedBy(keyType, "", keyPaths, nil, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSignedByKeyPaths returns a new "signedBy" PolicyRequirement using KeyPaths
|
||||
func NewPRSignedByKeyPaths(keyType sbKeyType, keyPaths []string, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||
return newPRSignedByKeyPaths(keyType, keyPaths, signedIdentity)
|
||||
}
|
||||
|
||||
// newPRSignedByKeyData is NewPRSignedByKeyData, except it returns the private type.
|
||||
func newPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
return newPRSignedBy(keyType, "", keyData, signedIdentity)
|
||||
return newPRSignedBy(keyType, "", nil, keyData, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSignedByKeyData returns a new "signedBy" PolicyRequirement using a KeyData
|
||||
|
|
@ -362,9 +382,9 @@ var _ json.Unmarshaler = (*prSignedBy)(nil)
|
|||
func (pr *prSignedBy) UnmarshalJSON(data []byte) error {
|
||||
*pr = prSignedBy{}
|
||||
var tmp prSignedBy
|
||||
var gotKeyPath, gotKeyData = false, false
|
||||
var gotKeyPath, gotKeyPaths, gotKeyData = false, false, false
|
||||
var signedIdentity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
if err := internal.ParanoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
|
|
@ -373,6 +393,9 @@ func (pr *prSignedBy) UnmarshalJSON(data []byte) error {
|
|||
case "keyPath":
|
||||
gotKeyPath = true
|
||||
return &tmp.KeyPath
|
||||
case "keyPaths":
|
||||
gotKeyPaths = true
|
||||
return &tmp.KeyPaths
|
||||
case "keyData":
|
||||
gotKeyData = true
|
||||
return &tmp.KeyData
|
||||
|
|
@ -401,16 +424,16 @@ func (pr *prSignedBy) UnmarshalJSON(data []byte) error {
|
|||
var res *prSignedBy
|
||||
var err error
|
||||
switch {
|
||||
case gotKeyPath && gotKeyData:
|
||||
return InvalidPolicyFormatError("keyPath and keyData cannot be used simultaneously")
|
||||
case gotKeyPath && !gotKeyData:
|
||||
case gotKeyPath && !gotKeyPaths && !gotKeyData:
|
||||
res, err = newPRSignedByKeyPath(tmp.KeyType, tmp.KeyPath, tmp.SignedIdentity)
|
||||
case !gotKeyPath && gotKeyData:
|
||||
case !gotKeyPath && gotKeyPaths && !gotKeyData:
|
||||
res, err = newPRSignedByKeyPaths(tmp.KeyType, tmp.KeyPaths, tmp.SignedIdentity)
|
||||
case !gotKeyPath && !gotKeyPaths && gotKeyData:
|
||||
res, err = newPRSignedByKeyData(tmp.KeyType, tmp.KeyData, tmp.SignedIdentity)
|
||||
case !gotKeyPath && !gotKeyData:
|
||||
return InvalidPolicyFormatError("At least one of keyPath and keyData mus be specified")
|
||||
default: // Coverage: This should never happen
|
||||
return errors.Errorf("Impossible keyPath/keyData presence combination!?")
|
||||
case !gotKeyPath && !gotKeyPaths && !gotKeyData:
|
||||
return InvalidPolicyFormatError("Exactly one of keyPath, keyPaths and keyData must be specified, none of them present")
|
||||
default:
|
||||
return fmt.Errorf("Exactly one of keyPath, keyPaths and keyData must be specified, more than one present")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -472,7 +495,7 @@ func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error {
|
|||
*pr = prSignedBaseLayer{}
|
||||
var tmp prSignedBaseLayer
|
||||
var baseLayerIdentity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"baseLayerIdentity": &baseLayerIdentity,
|
||||
}); err != nil {
|
||||
|
|
@ -495,6 +518,107 @@ func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newPRSigstoreSigned returns a new prSigstoreSigned if parameters are valid.
|
||||
func newPRSigstoreSigned(keyPath string, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSigstoreSigned, error) {
|
||||
if len(keyPath) > 0 && len(keyData) > 0 {
|
||||
return nil, InvalidPolicyFormatError("keyType and keyData cannot be used simultaneously")
|
||||
}
|
||||
if signedIdentity == nil {
|
||||
return nil, InvalidPolicyFormatError("signedIdentity not specified")
|
||||
}
|
||||
return &prSigstoreSigned{
|
||||
prCommon: prCommon{Type: prTypeSigstoreSigned},
|
||||
KeyPath: keyPath,
|
||||
KeyData: keyData,
|
||||
SignedIdentity: signedIdentity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newPRSigstoreSignedKeyPath is NewPRSigstoreSignedKeyPath, except it returns the private type.
|
||||
func newPRSigstoreSignedKeyPath(keyPath string, signedIdentity PolicyReferenceMatch) (*prSigstoreSigned, error) {
|
||||
return newPRSigstoreSigned(keyPath, nil, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSigstoreSignedKeyPath returns a new "sigstoreSigned" PolicyRequirement using a KeyPath
|
||||
func NewPRSigstoreSignedKeyPath(keyPath string, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||
return newPRSigstoreSignedKeyPath(keyPath, signedIdentity)
|
||||
}
|
||||
|
||||
// newPRSigstoreSignedKeyData is NewPRSigstoreSignedKeyData, except it returns the private type.
|
||||
func newPRSigstoreSignedKeyData(keyData []byte, signedIdentity PolicyReferenceMatch) (*prSigstoreSigned, error) {
|
||||
return newPRSigstoreSigned("", keyData, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSigstoreSignedKeyData returns a new "sigstoreSigned" PolicyRequirement using a KeyData
|
||||
func NewPRSigstoreSignedKeyData(keyData []byte, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||
return newPRSigstoreSignedKeyData(keyData, signedIdentity)
|
||||
}
|
||||
|
||||
// Compile-time check that prSigstoreSigned implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prSigstoreSigned)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (pr *prSigstoreSigned) UnmarshalJSON(data []byte) error {
|
||||
*pr = prSigstoreSigned{}
|
||||
var tmp prSigstoreSigned
|
||||
var gotKeyPath, gotKeyData = false, false
|
||||
var signedIdentity json.RawMessage
|
||||
if err := internal.ParanoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
case "keyPath":
|
||||
gotKeyPath = true
|
||||
return &tmp.KeyPath
|
||||
case "keyData":
|
||||
gotKeyData = true
|
||||
return &tmp.KeyData
|
||||
case "signedIdentity":
|
||||
return &signedIdentity
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prTypeSigstoreSigned {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
if signedIdentity == nil {
|
||||
tmp.SignedIdentity = NewPRMMatchRepoDigestOrExact()
|
||||
} else {
|
||||
si, err := newPolicyReferenceMatchFromJSON(signedIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp.SignedIdentity = si
|
||||
}
|
||||
|
||||
var res *prSigstoreSigned
|
||||
var err error
|
||||
switch {
|
||||
case gotKeyPath && gotKeyData:
|
||||
return InvalidPolicyFormatError("keyPath and keyData cannot be used simultaneously")
|
||||
case gotKeyPath && !gotKeyData:
|
||||
res, err = newPRSigstoreSignedKeyPath(tmp.KeyPath, tmp.SignedIdentity)
|
||||
case !gotKeyPath && gotKeyData:
|
||||
res, err = newPRSigstoreSignedKeyData(tmp.KeyData, tmp.SignedIdentity)
|
||||
case !gotKeyPath && !gotKeyData:
|
||||
return InvalidPolicyFormatError("At least one of keyPath and keyData must be specified")
|
||||
default: // Coverage: This should never happen
|
||||
return fmt.Errorf("Impossible keyPath/keyData presence combination!?")
|
||||
}
|
||||
if err != nil {
|
||||
// Coverage: This cannot currently happen, creating a prSigstoreSigned only fails
|
||||
// if signedIdentity is nil, which we replace with a default above.
|
||||
return err
|
||||
}
|
||||
*pr = *res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPolicyReferenceMatchFromJSON parses JSON data into a PolicyReferenceMatch implementation.
|
||||
func newPolicyReferenceMatchFromJSON(data []byte) (PolicyReferenceMatch, error) {
|
||||
var typeField prmCommon
|
||||
|
|
@ -541,7 +665,7 @@ var _ json.Unmarshaler = (*prmMatchExact)(nil)
|
|||
func (prm *prmMatchExact) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchExact{}
|
||||
var tmp prmMatchExact
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
|
@ -571,7 +695,7 @@ var _ json.Unmarshaler = (*prmMatchRepoDigestOrExact)(nil)
|
|||
func (prm *prmMatchRepoDigestOrExact) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchRepoDigestOrExact{}
|
||||
var tmp prmMatchRepoDigestOrExact
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
|
@ -601,7 +725,7 @@ var _ json.Unmarshaler = (*prmMatchRepository)(nil)
|
|||
func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchRepository{}
|
||||
var tmp prmMatchRepository
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
|
@ -641,7 +765,7 @@ var _ json.Unmarshaler = (*prmExactReference)(nil)
|
|||
func (prm *prmExactReference) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmExactReference{}
|
||||
var tmp prmExactReference
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"dockerReference": &tmp.DockerReference,
|
||||
}); err != nil {
|
||||
|
|
@ -683,7 +807,7 @@ var _ json.Unmarshaler = (*prmExactRepository)(nil)
|
|||
func (prm *prmExactRepository) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmExactRepository{}
|
||||
var tmp prmExactRepository
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"dockerRepository": &tmp.DockerRepository,
|
||||
}); err != nil {
|
||||
|
|
@ -755,7 +879,7 @@ var _ json.Unmarshaler = (*prmRemapIdentity)(nil)
|
|||
func (prm *prmRemapIdentity) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmRemapIdentity{}
|
||||
var tmp prmRemapIdentity
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"prefix": &tmp.Prefix,
|
||||
"signedPrefix": &tmp.SignedPrefix,
|
||||
|
|
|
|||
23
vendor/github.com/containers/image/v5/signature/policy_eval.go
generated
vendored
23
vendor/github.com/containers/image/v5/signature/policy_eval.go
generated
vendored
|
|
@ -7,9 +7,11 @@ package signature
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/internal/unparsedimage"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -55,14 +57,14 @@ type PolicyRequirement interface {
|
|||
// a container based on this image; use IsRunningImageAllowed instead.
|
||||
// - Just because a signature is accepted does not automatically mean the contents of the
|
||||
// signature are authorized to run code as root, or to affect system or cluster configuration.
|
||||
isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error)
|
||||
isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error)
|
||||
|
||||
// isRunningImageAllowed returns true if the requirement allows running an image.
|
||||
// If it returns false, err must be non-nil, and should be an PolicyRequirementError if evaluation
|
||||
// succeeded but the result was rejection.
|
||||
// WARNING: This validates signatures and the manifest, but does not download or validate the
|
||||
// layers. Users must validate that the layers match their expected digests.
|
||||
isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error)
|
||||
isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error)
|
||||
}
|
||||
|
||||
// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
|
||||
|
|
@ -71,7 +73,7 @@ type PolicyReferenceMatch interface {
|
|||
// matchesDockerReference decides whether a specific image identity is accepted for an image
|
||||
// (or, usually, for the image's Reference().DockerReference()). Note that
|
||||
// image.Reference().DockerReference() may be nil.
|
||||
matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool
|
||||
matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool
|
||||
}
|
||||
|
||||
// PolicyContext encapsulates a policy and possible cached state
|
||||
|
|
@ -95,7 +97,7 @@ const (
|
|||
// changeContextState changes pc.state, or fails if the state is unexpected
|
||||
func (pc *PolicyContext) changeState(expected, new policyContextState) error {
|
||||
if pc.state != expected {
|
||||
return errors.Errorf(`"Invalid PolicyContext state, expected "%s", found "%s"`, expected, pc.state)
|
||||
return fmt.Errorf(`Invalid PolicyContext state, expected "%s", found "%s"`, expected, pc.state)
|
||||
}
|
||||
pc.state = new
|
||||
return nil
|
||||
|
|
@ -174,7 +176,7 @@ func (pc *PolicyContext) requirementsForImageRef(ref types.ImageReference) Polic
|
|||
// a container based on this image; use IsRunningImageAllowed instead.
|
||||
// - Just because a signature is accepted does not automatically mean the contents of the
|
||||
// signature are authorized to run code as root, or to affect system or cluster configuration.
|
||||
func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(ctx context.Context, image types.UnparsedImage) (sigs []*Signature, finalErr error) {
|
||||
func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(ctx context.Context, publicImage types.UnparsedImage) (sigs []*Signature, finalErr error) {
|
||||
if err := pc.changeState(pcReady, pcInUse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -185,11 +187,12 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(ctx context.Context, im
|
|||
}
|
||||
}()
|
||||
|
||||
image := unparsedimage.FromPublic(publicImage)
|
||||
|
||||
logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", policyIdentityLogName(image.Reference()))
|
||||
reqs := pc.requirementsForImageRef(image.Reference())
|
||||
|
||||
// FIXME: rename Signatures to UnverifiedSignatures
|
||||
// FIXME: pass context.Context
|
||||
// FIXME: Use image.UntrustedSignatures, use that to improve error messages (needs tests!)
|
||||
unverifiedSignatures, err := image.Signatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -255,7 +258,7 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(ctx context.Context, im
|
|||
// succeeded but the result was rejection.
|
||||
// WARNING: This validates signatures and the manifest, but does not download or validate the
|
||||
// layers. Users must validate that the layers match their expected digests.
|
||||
func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (res bool, finalErr error) {
|
||||
func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, publicImage types.UnparsedImage) (res bool, finalErr error) {
|
||||
if err := pc.changeState(pcReady, pcInUse); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -266,6 +269,8 @@ func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, image types.
|
|||
}
|
||||
}()
|
||||
|
||||
image := unparsedimage.FromPublic(publicImage)
|
||||
|
||||
logrus.Debugf("IsRunningImageAllowed for image %s", policyIdentityLogName(image.Reference()))
|
||||
reqs := pc.requirementsForImageRef(image.Reference())
|
||||
|
||||
|
|
|
|||
6
vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go
generated
vendored
6
vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go
generated
vendored
|
|
@ -5,15 +5,15 @@ package signature
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (pr *prSignedBaseLayer) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prSignedBaseLayer) isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
return sarUnknown, nil, nil
|
||||
}
|
||||
|
||||
func (pr *prSignedBaseLayer) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prSignedBaseLayer) isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error) {
|
||||
// FIXME? Reject this at policy parsing time already?
|
||||
logrus.Errorf("signedBaseLayer not implemented yet!")
|
||||
return false, PolicyRequirementError("signedBaseLayer not implemented yet!")
|
||||
|
|
|
|||
50
vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go
generated
vendored
50
vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go
generated
vendored
|
|
@ -4,44 +4,59 @@ package signature
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
switch pr.KeyType {
|
||||
case SBKeyTypeGPGKeys:
|
||||
case SBKeyTypeSignedByGPGKeys, SBKeyTypeX509Certificates, SBKeyTypeSignedByX509CAs:
|
||||
// FIXME? Reject this at policy parsing time already?
|
||||
return sarRejected, nil, errors.Errorf(`"Unimplemented "keyType" value "%s"`, string(pr.KeyType))
|
||||
return sarRejected, nil, fmt.Errorf(`Unimplemented "keyType" value "%s"`, string(pr.KeyType))
|
||||
default:
|
||||
// This should never happen, newPRSignedBy ensures KeyType.IsValid()
|
||||
return sarRejected, nil, errors.Errorf(`"Unknown "keyType" value "%s"`, string(pr.KeyType))
|
||||
return sarRejected, nil, fmt.Errorf(`Unknown "keyType" value "%s"`, string(pr.KeyType))
|
||||
}
|
||||
|
||||
if pr.KeyPath != "" && pr.KeyData != nil {
|
||||
return sarRejected, nil, errors.New(`Internal inconsistency: both "keyPath" and "keyData" specified`)
|
||||
}
|
||||
// FIXME: move this to per-context initialization
|
||||
var data []byte
|
||||
if pr.KeyData != nil {
|
||||
data = pr.KeyData
|
||||
} else {
|
||||
var data [][]byte
|
||||
keySources := 0
|
||||
if pr.KeyPath != "" {
|
||||
keySources++
|
||||
d, err := os.ReadFile(pr.KeyPath)
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
data = d
|
||||
data = [][]byte{d}
|
||||
}
|
||||
if pr.KeyPaths != nil {
|
||||
keySources++
|
||||
data = [][]byte{}
|
||||
for _, path := range pr.KeyPaths {
|
||||
d, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
}
|
||||
if pr.KeyData != nil {
|
||||
keySources++
|
||||
data = [][]byte{pr.KeyData}
|
||||
}
|
||||
if keySources != 1 {
|
||||
return sarRejected, nil, errors.New(`Internal inconsistency: not exactly one of "keyPath", "keyPaths" and "keyData" specified`)
|
||||
}
|
||||
|
||||
// FIXME: move this to per-context initialization
|
||||
mech, trustedIdentities, err := NewEphemeralGPGSigningMechanism(data)
|
||||
mech, trustedIdentities, err := newEphemeralGPGSigningMechanism(data)
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
|
|
@ -89,8 +104,9 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image types
|
|||
return sarAccepted, signature, nil
|
||||
}
|
||||
|
||||
func (pr *prSignedBy) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
// FIXME: pass context.Context
|
||||
func (pr *prSignedBy) isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error) {
|
||||
// FIXME: Use image.UntrustedSignatures, use that to improve error messages
|
||||
// (needs tests!)
|
||||
sigs, err := image.Signatures(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -108,7 +124,7 @@ func (pr *prSignedBy) isRunningImageAllowed(ctx context.Context, image types.Unp
|
|||
// Huh?! This should not happen at all; treat it as any other invalid value.
|
||||
fallthrough
|
||||
default:
|
||||
reason = errors.Errorf(`Internal error: Unexpected signature verification result "%s"`, string(res))
|
||||
reason = fmt.Errorf(`Internal error: Unexpected signature verification result "%s"`, string(res))
|
||||
}
|
||||
rejections = append(rejections, reason)
|
||||
}
|
||||
|
|
|
|||
140
vendor/github.com/containers/image/v5/signature/policy_eval_sigstore.go
generated
vendored
Normal file
140
vendor/github.com/containers/image/v5/signature/policy_eval_sigstore.go
generated
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// Policy evaluation for prSigstoreSigned.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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/signature/internal"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sigstore/sigstore/pkg/cryptoutils"
|
||||
)
|
||||
|
||||
func (pr *prSigstoreSigned) isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
// We don’t know of a single user of this API, and we might return unexpected values in Signature.
|
||||
// For now, just punt.
|
||||
return sarRejected, nil, errors.New("isSignatureAuthorAccepted is not implemented for sigstore")
|
||||
}
|
||||
|
||||
func (pr *prSigstoreSigned) isSignatureAccepted(ctx context.Context, image private.UnparsedImage, sig signature.Sigstore) (signatureAcceptanceResult, error) {
|
||||
if pr.KeyPath != "" && pr.KeyData != nil {
|
||||
return sarRejected, errors.New(`Internal inconsistency: both "keyPath" and "keyData" specified`)
|
||||
}
|
||||
// FIXME: move this to per-context initialization
|
||||
var publicKeyPEM []byte
|
||||
if pr.KeyData != nil {
|
||||
publicKeyPEM = pr.KeyData
|
||||
} else {
|
||||
d, err := os.ReadFile(pr.KeyPath)
|
||||
if err != nil {
|
||||
return sarRejected, err
|
||||
}
|
||||
publicKeyPEM = d
|
||||
}
|
||||
|
||||
publicKey, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyPEM)
|
||||
if err != nil {
|
||||
return sarRejected, fmt.Errorf("parsing public key: %w", err)
|
||||
}
|
||||
|
||||
untrustedAnnotations := sig.UntrustedAnnotations()
|
||||
untrustedBase64Signature, ok := untrustedAnnotations[signature.SigstoreSignatureAnnotationKey]
|
||||
if !ok {
|
||||
return sarRejected, fmt.Errorf("missing %s annotation", signature.SigstoreSignatureAnnotationKey)
|
||||
}
|
||||
|
||||
signature, err := internal.VerifySigstorePayload(publicKey, sig.UntrustedPayload(), untrustedBase64Signature, internal.SigstorePayloadAcceptanceRules{
|
||||
ValidateSignedDockerReference: func(ref string) error {
|
||||
if !pr.SignedIdentity.matchesDockerReference(image, ref) {
|
||||
return PolicyRequirementError(fmt.Sprintf("Signature for identity %s is not accepted", ref))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ValidateSignedDockerManifestDigest: func(digest digest.Digest) error {
|
||||
m, _, err := image.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
digestMatches, err := manifest.MatchesDigest(m, digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !digestMatches {
|
||||
return PolicyRequirementError(fmt.Sprintf("Signature for digest %s does not match", digest))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return sarRejected, err
|
||||
}
|
||||
if signature == nil { // A paranoid sanity check that VerifySigstorePayload has returned consistent values
|
||||
return sarRejected, errors.New("internal error: VerifySigstorePayload succeeded but returned no data") // Coverage: This should never happen.
|
||||
}
|
||||
|
||||
return sarAccepted, nil
|
||||
}
|
||||
|
||||
func (pr *prSigstoreSigned) isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error) {
|
||||
sigs, err := image.UntrustedSignatures(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var rejections []error
|
||||
foundNonSigstoreSignatures := 0
|
||||
foundSigstoreNonAttachments := 0
|
||||
for _, s := range sigs {
|
||||
sigstoreSig, ok := s.(signature.Sigstore)
|
||||
if !ok {
|
||||
foundNonSigstoreSignatures++
|
||||
continue
|
||||
}
|
||||
if sigstoreSig.UntrustedMIMEType() != signature.SigstoreSignatureMIMEType {
|
||||
foundSigstoreNonAttachments++
|
||||
continue
|
||||
}
|
||||
|
||||
var reason error
|
||||
switch res, err := pr.isSignatureAccepted(ctx, image, sigstoreSig); res {
|
||||
case sarAccepted:
|
||||
// One accepted signature is enough.
|
||||
return true, nil
|
||||
case sarRejected:
|
||||
reason = err
|
||||
case sarUnknown:
|
||||
// Huh?! This should not happen at all; treat it as any other invalid value.
|
||||
fallthrough
|
||||
default:
|
||||
reason = fmt.Errorf(`Internal error: Unexpected signature verification result "%s"`, string(res))
|
||||
}
|
||||
rejections = append(rejections, reason)
|
||||
}
|
||||
var summary error
|
||||
switch len(rejections) {
|
||||
case 0:
|
||||
if foundNonSigstoreSignatures == 0 && foundSigstoreNonAttachments == 0 {
|
||||
// A nice message for the most common case.
|
||||
summary = PolicyRequirementError("A signature was required, but no signature exists")
|
||||
} else {
|
||||
summary = PolicyRequirementError(fmt.Sprintf("A signature was required, but no signature exists (%d non-sigstore signatures, %d sigstore non-signature attachments)",
|
||||
foundNonSigstoreSignatures, foundSigstoreNonAttachments))
|
||||
}
|
||||
case 1:
|
||||
summary = rejections[0]
|
||||
default:
|
||||
var msgs []string
|
||||
for _, e := range rejections {
|
||||
msgs = append(msgs, e.Error())
|
||||
}
|
||||
summary = PolicyRequirementError(fmt.Sprintf("None of the signatures were accepted, reasons: %s",
|
||||
strings.Join(msgs, "; ")))
|
||||
}
|
||||
return false, summary
|
||||
}
|
||||
10
vendor/github.com/containers/image/v5/signature/policy_eval_simple.go
generated
vendored
10
vendor/github.com/containers/image/v5/signature/policy_eval_simple.go
generated
vendored
|
|
@ -6,24 +6,24 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
// prInsecureAcceptAnything semantics: Every image is allowed to run,
|
||||
// but this does not consider the signature as verified.
|
||||
return sarUnknown, nil, nil
|
||||
}
|
||||
|
||||
func (pr *prInsecureAcceptAnything) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prInsecureAcceptAnything) isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (pr *prReject) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prReject) isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
return sarRejected, nil, PolicyRequirementError(fmt.Sprintf("Any signatures for image %s are rejected by policy.", transports.ImageName(image.Reference())))
|
||||
}
|
||||
|
||||
func (pr *prReject) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prReject) isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error) {
|
||||
return false, PolicyRequirementError(fmt.Sprintf("Running image %s is rejected by policy.", transports.ImageName(image.Reference())))
|
||||
}
|
||||
|
|
|
|||
8
vendor/github.com/containers/image/v5/signature/policy_paths_common.go
generated
vendored
Normal file
8
vendor/github.com/containers/image/v5/signature/policy_paths_common.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build !freebsd
|
||||
// +build !freebsd
|
||||
|
||||
package signature
|
||||
|
||||
// builtinDefaultPolicyPath is the policy path used for DefaultPolicy().
|
||||
// DO NOT change this, instead see systemDefaultPolicyPath above.
|
||||
const builtinDefaultPolicyPath = "/etc/containers/policy.json"
|
||||
8
vendor/github.com/containers/image/v5/signature/policy_paths_freebsd.go
generated
vendored
Normal file
8
vendor/github.com/containers/image/v5/signature/policy_paths_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package signature
|
||||
|
||||
// builtinDefaultPolicyPath is the policy path used for DefaultPolicy().
|
||||
// DO NOT change this, instead see systemDefaultPolicyPath above.
|
||||
const builtinDefaultPolicyPath = "/usr/local/etc/containers/policy.json"
|
||||
16
vendor/github.com/containers/image/v5/signature/policy_reference_match.go
generated
vendored
16
vendor/github.com/containers/image/v5/signature/policy_reference_match.go
generated
vendored
|
|
@ -7,12 +7,12 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images.
|
||||
func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (reference.Named, reference.Named, error) {
|
||||
func parseImageAndDockerReference(image private.UnparsedImage, s2 string) (reference.Named, reference.Named, error) {
|
||||
r1 := image.Reference().DockerReference()
|
||||
if r1 == nil {
|
||||
return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity",
|
||||
|
|
@ -25,7 +25,7 @@ func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (referen
|
|||
return r1, r2, nil
|
||||
}
|
||||
|
||||
func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
func (prm *prmMatchExact) matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -56,7 +56,7 @@ func matchRepoDigestOrExactReferenceValues(intended, signature reference.Named)
|
|||
return false
|
||||
}
|
||||
}
|
||||
func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -64,7 +64,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse
|
|||
return matchRepoDigestOrExactReferenceValues(intended, signature)
|
||||
}
|
||||
|
||||
func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
func (prm *prmMatchRepository) matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -85,7 +85,7 @@ func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, err
|
|||
return r1, r2, nil
|
||||
}
|
||||
|
||||
func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
func (prm *prmExactReference) matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseDockerReferences(prm.DockerReference, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -97,7 +97,7 @@ func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage,
|
|||
return signature.String() == intended.String()
|
||||
}
|
||||
|
||||
func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
func (prm *prmExactRepository) matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseDockerReferences(prm.DockerRepository, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -141,7 +141,7 @@ func (prm *prmRemapIdentity) remapReferencePrefix(ref reference.Named) (referenc
|
|||
return newParsedRef, nil
|
||||
}
|
||||
|
||||
func (prm *prmRemapIdentity) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
func (prm *prmRemapIdentity) matchesDockerReference(image private.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
|
|||
29
vendor/github.com/containers/image/v5/signature/policy_types.go
generated
vendored
29
vendor/github.com/containers/image/v5/signature/policy_types.go
generated
vendored
|
|
@ -46,6 +46,7 @@ const (
|
|||
prTypeReject prTypeIdentifier = "reject"
|
||||
prTypeSignedBy prTypeIdentifier = "signedBy"
|
||||
prTypeSignedBaseLayer prTypeIdentifier = "signedBaseLayer"
|
||||
prTypeSigstoreSigned prTypeIdentifier = "sigstoreSigned"
|
||||
)
|
||||
|
||||
// prInsecureAcceptAnything is a PolicyRequirement with type = prTypeInsecureAcceptAnything:
|
||||
|
|
@ -66,18 +67,20 @@ type prReject struct {
|
|||
type prSignedBy struct {
|
||||
prCommon
|
||||
|
||||
// KeyType specifies what kind of key reference KeyPath/KeyData is.
|
||||
// KeyType specifies what kind of key reference KeyPath/KeyPaths/KeyData is.
|
||||
// Acceptable values are “GPGKeys” | “signedByGPGKeys” “X.509Certificates” | “signedByX.509CAs”
|
||||
// FIXME: eventually also support GPGTOFU, X.509TOFU, with KeyPath only
|
||||
KeyType sbKeyType `json:"keyType"`
|
||||
|
||||
// KeyPath is a pathname to a local file containing the trusted key(s). Exactly one of KeyPath and KeyData must be specified.
|
||||
// KeyPath is a pathname to a local file containing the trusted key(s). Exactly one of KeyPath, KeyPaths and KeyData must be specified.
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
// KeyData contains the trusted key(s), base64-encoded. Exactly one of KeyPath and KeyData must be specified.
|
||||
// KeyPaths if a set of pathnames to local files containing the trusted key(s). Exactly one of KeyPath, KeyPaths and KeyData must be specified.
|
||||
KeyPaths []string `json:"keyPaths,omitempty"`
|
||||
// KeyData contains the trusted key(s), base64-encoded. Exactly one of KeyPath, KeyPaths and KeyData must be specified.
|
||||
KeyData []byte `json:"keyData,omitempty"`
|
||||
|
||||
// SignedIdentity specifies what image identity the signature must be claiming about the image.
|
||||
// Defaults to "match-exact" if not specified.
|
||||
// Defaults to "matchRepoDigestOrExact" if not specified.
|
||||
SignedIdentity PolicyReferenceMatch `json:"signedIdentity"`
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +107,24 @@ type prSignedBaseLayer struct {
|
|||
BaseLayerIdentity PolicyReferenceMatch `json:"baseLayerIdentity"`
|
||||
}
|
||||
|
||||
// prSigstoreSigned is a PolicyRequirement with type = prTypeSigstoreSigned: the image is signed by trusted keys for a specified identity
|
||||
type prSigstoreSigned struct {
|
||||
prCommon
|
||||
|
||||
// KeyPath is a pathname to a local file containing the trusted key. Exactly one of KeyPath and KeyData must be specified.
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
// KeyData contains the trusted key, base64-encoded. Exactly one of KeyPath and KeyData must be specified.
|
||||
KeyData []byte `json:"keyData,omitempty"`
|
||||
// FIXME: Multiple public keys?
|
||||
|
||||
// FIXME: Support fulcio+rekor as an alternative.
|
||||
|
||||
// SignedIdentity specifies what image identity the signature must be claiming about the image.
|
||||
// Defaults to "matchRepoDigestOrExact" if not specified.
|
||||
// Note that /usr/bin/cosign interoperability might require using repo-only matching.
|
||||
SignedIdentity PolicyReferenceMatch `json:"signedIdentity"`
|
||||
}
|
||||
|
||||
// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
|
||||
// The type is public, but its implementation is private.
|
||||
|
||||
|
|
|
|||
70
vendor/github.com/containers/image/v5/signature/sigstore/copied.go
generated
vendored
Normal file
70
vendor/github.com/containers/image/v5/signature/sigstore/copied.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package sigstore
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/sigstore/sigstore/pkg/signature"
|
||||
"github.com/theupdateframework/go-tuf/encrypted"
|
||||
)
|
||||
|
||||
// The following code was copied from github.com/sigstore.
|
||||
// FIXME: Eliminate that duplication.
|
||||
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const (
|
||||
// from sigstore/cosign/pkg/cosign.sigstorePrivateKeyPemType
|
||||
sigstorePrivateKeyPemType = "ENCRYPTED COSIGN PRIVATE KEY"
|
||||
)
|
||||
|
||||
// from sigstore/cosign/pkg/cosign.loadPrivateKey
|
||||
// FIXME: Do we need all of these key formats, and all of those
|
||||
func loadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) {
|
||||
// Decrypt first
|
||||
p, _ := pem.Decode(key)
|
||||
if p == nil {
|
||||
return nil, errors.New("invalid pem block")
|
||||
}
|
||||
if p.Type != sigstorePrivateKeyPemType {
|
||||
return nil, fmt.Errorf("unsupported pem type: %s", p.Type)
|
||||
}
|
||||
|
||||
x509Encoded, err := encrypted.Decrypt(p.Bytes, pass)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
pk, err := x509.ParsePKCS8PrivateKey(x509Encoded)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing private key: %w", err)
|
||||
}
|
||||
switch pk := pk.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return signature.LoadRSAPKCS1v15SignerVerifier(pk, crypto.SHA256)
|
||||
case *ecdsa.PrivateKey:
|
||||
return signature.LoadECDSASignerVerifier(pk, crypto.SHA256)
|
||||
case ed25519.PrivateKey:
|
||||
return signature.LoadED25519SignerVerifier(pk)
|
||||
default:
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
}
|
||||
65
vendor/github.com/containers/image/v5/signature/sigstore/sign.go
generated
vendored
Normal file
65
vendor/github.com/containers/image/v5/signature/sigstore/sign.go
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package sigstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/signature"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
sigstoreSignature "github.com/sigstore/sigstore/pkg/signature"
|
||||
)
|
||||
|
||||
// SignDockerManifestWithPrivateKeyFileUnstable returns a signature for manifest as the specified dockerReference,
|
||||
// using a private key and an optional passphrase.
|
||||
//
|
||||
// Yes, this returns an internal type, and should currently not be used outside of c/image.
|
||||
// There is NO COMITTMENT TO STABLE API.
|
||||
func SignDockerManifestWithPrivateKeyFileUnstable(m []byte, dockerReference reference.Named, privateKeyFile string, passphrase []byte) (signature.Sigstore, error) {
|
||||
privateKeyPEM, err := os.ReadFile(privateKeyFile)
|
||||
if err != nil {
|
||||
return signature.Sigstore{}, fmt.Errorf("reading private key from %s: %w", privateKeyFile, err)
|
||||
}
|
||||
signer, err := loadPrivateKey(privateKeyPEM, passphrase)
|
||||
if err != nil {
|
||||
return signature.Sigstore{}, fmt.Errorf("initializing private key: %w", err)
|
||||
}
|
||||
|
||||
return signDockerManifest(m, dockerReference, signer)
|
||||
}
|
||||
|
||||
func signDockerManifest(m []byte, dockerReference reference.Named, signer sigstoreSignature.Signer) (signature.Sigstore, error) {
|
||||
if reference.IsNameOnly(dockerReference) {
|
||||
return signature.Sigstore{}, fmt.Errorf("reference %s can’t be signed, it has neither a tag nor a digest", dockerReference.String())
|
||||
}
|
||||
manifestDigest, err := manifest.Digest(m)
|
||||
if err != nil {
|
||||
return signature.Sigstore{}, err
|
||||
}
|
||||
// sigstore/cosign completely ignores dockerReference for actual policy decisions.
|
||||
// They record the repo (but NOT THE TAG) in the value; without the tag we can’t detect version rollbacks.
|
||||
// So, just do what simple signing does, and cosign won’t mind.
|
||||
payloadData := internal.NewUntrustedSigstorePayload(manifestDigest, dockerReference.String())
|
||||
payloadBytes, err := json.Marshal(payloadData)
|
||||
if err != nil {
|
||||
return signature.Sigstore{}, err
|
||||
}
|
||||
|
||||
// github.com/sigstore/cosign/internal/pkg/cosign.payloadSigner uses signatureoptions.WithContext(),
|
||||
// which seems to be not used by anything. So we don’t bother.
|
||||
signatureBytes, err := signer.SignMessage(bytes.NewReader(payloadBytes))
|
||||
if err != nil {
|
||||
return signature.Sigstore{}, fmt.Errorf("creating signature: %w", err)
|
||||
}
|
||||
base64Signature := base64.StdEncoding.EncodeToString(signatureBytes)
|
||||
|
||||
return signature.SigstoreFromComponents(signature.SigstoreSignatureMIMEType,
|
||||
payloadBytes,
|
||||
map[string]string{
|
||||
signature.SigstoreSignatureAnnotationKey: base64Signature,
|
||||
}), nil
|
||||
}
|
||||
|
|
@ -6,12 +6,13 @@ package signature
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
"github.com/containers/image/v5/version"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -19,13 +20,7 @@ const (
|
|||
)
|
||||
|
||||
// InvalidSignatureError is returned when parsing an invalid signature.
|
||||
type InvalidSignatureError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err InvalidSignatureError) Error() string {
|
||||
return err.msg
|
||||
}
|
||||
type InvalidSignatureError = internal.InvalidSignatureError
|
||||
|
||||
// Signature is a parsed content of a signature.
|
||||
// The only way to get this structure from a blob should be as a return value from a successful call to verifyAndExtractSignature below.
|
||||
|
|
@ -111,18 +106,18 @@ var _ json.Unmarshaler = (*untrustedSignature)(nil)
|
|||
func (s *untrustedSignature) UnmarshalJSON(data []byte) error {
|
||||
err := s.strictUnmarshalJSON(data)
|
||||
if err != nil {
|
||||
if formatErr, ok := err.(jsonFormatError); ok {
|
||||
err = InvalidSignatureError{msg: formatErr.Error()}
|
||||
if formatErr, ok := err.(internal.JSONFormatError); ok {
|
||||
err = internal.NewInvalidSignatureError(formatErr.Error())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// strictUnmarshalJSON is UnmarshalJSON, except that it may return the internal jsonFormatError error type.
|
||||
// Splitting it into a separate function allows us to do the jsonFormatError → InvalidSignatureError in a single place, the caller.
|
||||
// strictUnmarshalJSON is UnmarshalJSON, except that it may return the internal.JSONFormatError error type.
|
||||
// Splitting it into a separate function allows us to do the internal.JSONFormatError → InvalidSignatureError in a single place, the caller.
|
||||
func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
||||
var critical, optional json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"critical": &critical,
|
||||
"optional": &optional,
|
||||
}); err != nil {
|
||||
|
|
@ -132,7 +127,7 @@ func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
|||
var creatorID string
|
||||
var timestamp float64
|
||||
var gotCreatorID, gotTimestamp = false, false
|
||||
if err := paranoidUnmarshalJSONObject(optional, func(key string) interface{} {
|
||||
if err := internal.ParanoidUnmarshalJSONObject(optional, func(key string) interface{} {
|
||||
switch key {
|
||||
case "creator":
|
||||
gotCreatorID = true
|
||||
|
|
@ -153,14 +148,14 @@ func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
|||
if gotTimestamp {
|
||||
intTimestamp := int64(timestamp)
|
||||
if float64(intTimestamp) != timestamp {
|
||||
return InvalidSignatureError{msg: "Field optional.timestamp is not is not an integer"}
|
||||
return internal.NewInvalidSignatureError("Field optional.timestamp is not is not an integer")
|
||||
}
|
||||
s.UntrustedTimestamp = &intTimestamp
|
||||
}
|
||||
|
||||
var t string
|
||||
var image, identity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(critical, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(critical, map[string]interface{}{
|
||||
"type": &t,
|
||||
"image": &image,
|
||||
"identity": &identity,
|
||||
|
|
@ -168,18 +163,18 @@ func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
|||
return err
|
||||
}
|
||||
if t != signatureType {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Unrecognized signature type %s", t)}
|
||||
return internal.NewInvalidSignatureError(fmt.Sprintf("Unrecognized signature type %s", t))
|
||||
}
|
||||
|
||||
var digestString string
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(image, map[string]interface{}{
|
||||
if err := internal.ParanoidUnmarshalJSONObjectExactFields(image, map[string]interface{}{
|
||||
"docker-manifest-digest": &digestString,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
||||
|
||||
return paranoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
||||
return internal.ParanoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
||||
"docker-reference": &s.UntrustedDockerReference,
|
||||
})
|
||||
}
|
||||
|
|
@ -231,7 +226,7 @@ func verifyAndExtractSignature(mech SigningMechanism, unverifiedSignature []byte
|
|||
|
||||
var unmatchedSignature untrustedSignature
|
||||
if err := json.Unmarshal(signed, &unmatchedSignature); err != nil {
|
||||
return nil, InvalidSignatureError{msg: err.Error()}
|
||||
return nil, internal.NewInvalidSignatureError(err.Error())
|
||||
}
|
||||
if err := rules.validateSignedDockerManifestDigest(unmatchedSignature.UntrustedDockerManifestDigest); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -269,7 +264,7 @@ func GetUntrustedSignatureInformationWithoutVerifying(untrustedSignatureBytes []
|
|||
}
|
||||
var untrustedDecodedContents untrustedSignature
|
||||
if err := json.Unmarshal(untrustedContents, &untrustedDecodedContents); err != nil {
|
||||
return nil, InvalidSignatureError{msg: err.Error()}
|
||||
return nil, internal.NewInvalidSignatureError(err.Error())
|
||||
}
|
||||
|
||||
var timestamp *time.Time // = nil
|
||||
4
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
4
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
|
|
@ -6,9 +6,9 @@ const (
|
|||
// VersionMajor is for an API incompatible changes
|
||||
VersionMajor = 5
|
||||
// VersionMinor is for functionality in a backwards-compatible manner
|
||||
VersionMinor = 21
|
||||
VersionMinor = 22
|
||||
// VersionPatch is for backwards-compatible bug fixes
|
||||
VersionPatch = 1
|
||||
VersionPatch = 0
|
||||
|
||||
// VersionDev indicates development branch. Releases will be empty string.
|
||||
VersionDev = ""
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue