deps: update osbuild/images to 9548bf0d0140
Update osbuild/images dependency to osbuild/images@9548bf0d01
This commit is contained in:
parent
139bf4dec2
commit
fb3761d602
98 changed files with 1883 additions and 629 deletions
22
vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go
generated
vendored
22
vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go
generated
vendored
|
|
@ -23162,6 +23162,9 @@ var awsPartition = partition{
|
|||
endpointKey{
|
||||
Region: "eu-west-3",
|
||||
}: endpoint{},
|
||||
endpointKey{
|
||||
Region: "il-central-1",
|
||||
}: endpoint{},
|
||||
endpointKey{
|
||||
Region: "me-central-1",
|
||||
}: endpoint{},
|
||||
|
|
@ -28656,6 +28659,25 @@ var awsPartition = partition{
|
|||
},
|
||||
},
|
||||
},
|
||||
"tnb": service{
|
||||
Endpoints: serviceEndpoints{
|
||||
endpointKey{
|
||||
Region: "ap-southeast-2",
|
||||
}: endpoint{},
|
||||
endpointKey{
|
||||
Region: "eu-central-1",
|
||||
}: endpoint{},
|
||||
endpointKey{
|
||||
Region: "eu-west-3",
|
||||
}: endpoint{},
|
||||
endpointKey{
|
||||
Region: "us-east-1",
|
||||
}: endpoint{},
|
||||
endpointKey{
|
||||
Region: "us-west-2",
|
||||
}: endpoint{},
|
||||
},
|
||||
},
|
||||
"transcribe": service{
|
||||
Defaults: endpointDefaults{
|
||||
defaultKey{}: endpoint{
|
||||
|
|
|
|||
2
vendor/github.com/aws/aws-sdk-go/aws/version.go
generated
vendored
2
vendor/github.com/aws/aws-sdk-go/aws/version.go
generated
vendored
|
|
@ -5,4 +5,4 @@ package aws
|
|||
const SDKName = "aws-sdk-go"
|
||||
|
||||
// SDKVersion is the version of this SDK
|
||||
const SDKVersion = "1.44.316"
|
||||
const SDKVersion = "1.44.318"
|
||||
|
|
|
|||
8
vendor/github.com/containers/image/v5/copy/blob.go
generated
vendored
8
vendor/github.com/containers/image/v5/copy/blob.go
generated
vendored
|
|
@ -83,12 +83,12 @@ func (ic *imageCopier) copyBlobFromStream(ctx context.Context, srcReader io.Read
|
|||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// === Report progress using the ic.c.progress channel, if required.
|
||||
if ic.c.progress != nil && ic.c.progressInterval > 0 {
|
||||
// === Report progress using the ic.c.options.Progress channel, if required.
|
||||
if ic.c.options.Progress != nil && ic.c.options.ProgressInterval > 0 {
|
||||
progressReader := newProgressReader(
|
||||
stream.reader,
|
||||
ic.c.progress,
|
||||
ic.c.progressInterval,
|
||||
ic.c.options.Progress,
|
||||
ic.c.options.ProgressInterval,
|
||||
srcInfo,
|
||||
)
|
||||
defer progressReader.reportDone()
|
||||
|
|
|
|||
107
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
107
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/containers/image/v5/internal/private"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/blobinfocache"
|
||||
compression "github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/signature/signer"
|
||||
"github.com/containers/image/v5/transports"
|
||||
|
|
@ -126,36 +127,46 @@ type Options struct {
|
|||
// Download layer contents with "nondistributable" media types ("foreign" layers) and translate the layer media type
|
||||
// to not indicate "nondistributable".
|
||||
DownloadForeignLayers bool
|
||||
|
||||
// Contains slice of OptionCompressionVariant, where copy will ensure that for each platform
|
||||
// in the manifest list, a variant with the requested compression will exist.
|
||||
// Invalid when copying a non-multi-architecture image. That will probably
|
||||
// change in the future.
|
||||
EnsureCompressionVariantsExist []OptionCompressionVariant
|
||||
}
|
||||
|
||||
// OptionCompressionVariant allows to supply information about
|
||||
// selected compression algorithm and compression level by the
|
||||
// end-user. Refer to EnsureCompressionVariantsExist to know
|
||||
// more about its usage.
|
||||
type OptionCompressionVariant struct {
|
||||
Algorithm compression.Algorithm
|
||||
Level *int // Only used when we are creating a new image instance using the specified algorithm, not when the image already contains such an instance
|
||||
}
|
||||
|
||||
// copier allows us to keep track of diffID values for blobs, and other
|
||||
// data shared across one or more images in a possible manifest list.
|
||||
// The owner must call close() when done.
|
||||
type copier struct {
|
||||
dest private.ImageDestination
|
||||
rawSource private.ImageSource
|
||||
reportWriter io.Writer
|
||||
progressOutput io.Writer
|
||||
progressInterval time.Duration
|
||||
progress chan types.ProgressProperties
|
||||
policyContext *signature.PolicyContext
|
||||
dest private.ImageDestination
|
||||
rawSource private.ImageSource
|
||||
options *Options // never nil
|
||||
|
||||
reportWriter io.Writer
|
||||
progressOutput io.Writer
|
||||
|
||||
unparsedToplevel *image.UnparsedImage // for rawSource
|
||||
blobInfoCache internalblobinfocache.BlobInfoCache2
|
||||
ociDecryptConfig *encconfig.DecryptConfig
|
||||
ociEncryptConfig *encconfig.EncryptConfig
|
||||
concurrentBlobCopiesSemaphore *semaphore.Weighted // Limits the amount of concurrently copied blobs
|
||||
downloadForeignLayers bool
|
||||
signers []*signer.Signer // Signers to use to create new signatures for the image
|
||||
signersToClose []*signer.Signer // Signers that should be closed when this copier is destroyed.
|
||||
signers []*signer.Signer // Signers to use to create new signatures for the image
|
||||
signersToClose []*signer.Signer // Signers that should be closed when this copier is destroyed.
|
||||
}
|
||||
|
||||
// Image copies image from srcRef to destRef, using policyContext to validate
|
||||
// source image admissibility. It returns the manifest which was written to
|
||||
// the new copy of the image.
|
||||
func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (copiedManifest []byte, retErr error) {
|
||||
// NOTE this function uses an output parameter for the error return value.
|
||||
// Setting this and returning is the ideal way to return an error.
|
||||
//
|
||||
// the defers in this routine will wrap the error return with its own errors
|
||||
// which can be valuable context in the middle of a multi-streamed copy.
|
||||
if options == nil {
|
||||
options = &Options{}
|
||||
}
|
||||
|
|
@ -209,27 +220,27 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
|
|||
}
|
||||
|
||||
c := &copier{
|
||||
dest: dest,
|
||||
rawSource: rawSource,
|
||||
reportWriter: reportWriter,
|
||||
progressOutput: progressOutput,
|
||||
progressInterval: options.ProgressInterval,
|
||||
progress: options.Progress,
|
||||
policyContext: policyContext,
|
||||
dest: dest,
|
||||
rawSource: rawSource,
|
||||
options: options,
|
||||
|
||||
reportWriter: reportWriter,
|
||||
progressOutput: progressOutput,
|
||||
|
||||
unparsedToplevel: image.UnparsedInstance(rawSource, nil),
|
||||
// FIXME? The cache is used for sources and destinations equally, but we only have a SourceCtx and DestinationCtx.
|
||||
// For now, use DestinationCtx (because blob reuse changes the behavior of the destination side more); eventually
|
||||
// we might want to add a separate CommonCtx — or would that be too confusing?
|
||||
blobInfoCache: internalblobinfocache.FromBlobInfoCache(blobinfocache.DefaultCache(options.DestinationCtx)),
|
||||
ociDecryptConfig: options.OciDecryptConfig,
|
||||
ociEncryptConfig: options.OciEncryptConfig,
|
||||
downloadForeignLayers: options.DownloadForeignLayers,
|
||||
blobInfoCache: internalblobinfocache.FromBlobInfoCache(blobinfocache.DefaultCache(options.DestinationCtx)),
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
// Set the concurrentBlobCopiesSemaphore if we can copy layers in parallel.
|
||||
if dest.HasThreadSafePutBlob() && rawSource.HasThreadSafeGetBlob() {
|
||||
c.concurrentBlobCopiesSemaphore = options.ConcurrentBlobCopiesSemaphore
|
||||
c.concurrentBlobCopiesSemaphore = c.options.ConcurrentBlobCopiesSemaphore
|
||||
if c.concurrentBlobCopiesSemaphore == nil {
|
||||
max := options.MaxParallelDownloads
|
||||
max := c.options.MaxParallelDownloads
|
||||
if max == 0 {
|
||||
max = maxParallelDownloads
|
||||
}
|
||||
|
|
@ -237,33 +248,40 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
|
|||
}
|
||||
} else {
|
||||
c.concurrentBlobCopiesSemaphore = semaphore.NewWeighted(int64(1))
|
||||
if options.ConcurrentBlobCopiesSemaphore != nil {
|
||||
if err := options.ConcurrentBlobCopiesSemaphore.Acquire(ctx, 1); err != nil {
|
||||
if c.options.ConcurrentBlobCopiesSemaphore != nil {
|
||||
if err := c.options.ConcurrentBlobCopiesSemaphore.Acquire(ctx, 1); err != nil {
|
||||
return nil, fmt.Errorf("acquiring semaphore for concurrent blob copies: %w", err)
|
||||
}
|
||||
defer options.ConcurrentBlobCopiesSemaphore.Release(1)
|
||||
defer c.options.ConcurrentBlobCopiesSemaphore.Release(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.setupSigners(options); err != nil {
|
||||
if err := c.setupSigners(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unparsedToplevel := image.UnparsedInstance(rawSource, nil)
|
||||
multiImage, err := isMultiImage(ctx, unparsedToplevel)
|
||||
multiImage, err := isMultiImage(ctx, c.unparsedToplevel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("determining manifest MIME type for %s: %w", transports.ImageName(srcRef), err)
|
||||
}
|
||||
|
||||
if !multiImage {
|
||||
if len(options.EnsureCompressionVariantsExist) > 0 {
|
||||
return nil, fmt.Errorf("EnsureCompressionVariantsExist is not implemented when not creating a multi-architecture image")
|
||||
}
|
||||
// The simple case: just copy a single image.
|
||||
if copiedManifest, _, _, err = c.copySingleImage(ctx, policyContext, options, unparsedToplevel, unparsedToplevel, nil); err != nil {
|
||||
single, err := c.copySingleImage(ctx, c.unparsedToplevel, nil, copySingleImageOptions{requireCompressionFormatMatch: false})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if options.ImageListSelection == CopySystemImage {
|
||||
copiedManifest = single.manifest
|
||||
} else if c.options.ImageListSelection == CopySystemImage {
|
||||
if len(options.EnsureCompressionVariantsExist) > 0 {
|
||||
return nil, fmt.Errorf("EnsureCompressionVariantsExist is not implemented when not creating a multi-architecture image")
|
||||
}
|
||||
// This is a manifest list, and we weren't asked to copy multiple images. Choose a single image that
|
||||
// matches the current system to copy, and copy it.
|
||||
mfest, manifestType, err := unparsedToplevel.Manifest(ctx)
|
||||
mfest, manifestType, err := c.unparsedToplevel.Manifest(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading manifest for %s: %w", transports.ImageName(srcRef), err)
|
||||
}
|
||||
|
|
@ -271,34 +289,35 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing primary manifest as list for %s: %w", transports.ImageName(srcRef), err)
|
||||
}
|
||||
instanceDigest, err := manifestList.ChooseInstanceByCompression(options.SourceCtx, options.PreferGzipInstances) // try to pick one that matches options.SourceCtx
|
||||
instanceDigest, err := manifestList.ChooseInstanceByCompression(c.options.SourceCtx, c.options.PreferGzipInstances) // try to pick one that matches c.options.SourceCtx
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("choosing an image from manifest list %s: %w", transports.ImageName(srcRef), err)
|
||||
}
|
||||
logrus.Debugf("Source is a manifest list; copying (only) instance %s for current system", instanceDigest)
|
||||
unparsedInstance := image.UnparsedInstance(rawSource, &instanceDigest)
|
||||
|
||||
if copiedManifest, _, _, err = c.copySingleImage(ctx, policyContext, options, unparsedToplevel, unparsedInstance, nil); err != nil {
|
||||
single, err := c.copySingleImage(ctx, unparsedInstance, nil, copySingleImageOptions{requireCompressionFormatMatch: false})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copying system image from manifest list: %w", err)
|
||||
}
|
||||
} else { /* options.ImageListSelection == CopyAllImages or options.ImageListSelection == CopySpecificImages, */
|
||||
copiedManifest = single.manifest
|
||||
} else { /* c.options.ImageListSelection == CopyAllImages or c.options.ImageListSelection == CopySpecificImages, */
|
||||
// If we were asked to copy multiple images and can't, that's an error.
|
||||
if !supportsMultipleImages(c.dest) {
|
||||
return nil, fmt.Errorf("copying multiple images: destination transport %q does not support copying multiple images as a group", destRef.Transport().Name())
|
||||
}
|
||||
// Copy some or all of the images.
|
||||
switch options.ImageListSelection {
|
||||
switch c.options.ImageListSelection {
|
||||
case CopyAllImages:
|
||||
logrus.Debugf("Source is a manifest list; copying all instances")
|
||||
case CopySpecificImages:
|
||||
logrus.Debugf("Source is a manifest list; copying some instances")
|
||||
}
|
||||
if copiedManifest, err = c.copyMultipleImages(ctx, policyContext, options, unparsedToplevel); err != nil {
|
||||
if copiedManifest, err = c.copyMultipleImages(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.dest.Commit(ctx, unparsedToplevel); err != nil {
|
||||
if err := c.dest.Commit(ctx, c.unparsedToplevel); err != nil {
|
||||
return nil, fmt.Errorf("committing the finished image: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
8
vendor/github.com/containers/image/v5/copy/encryption.go
generated
vendored
8
vendor/github.com/containers/image/v5/copy/encryption.go
generated
vendored
|
|
@ -34,7 +34,7 @@ type bpDecryptionStepData struct {
|
|||
// srcInfo is only used for error messages.
|
||||
// Returns data for other steps; the caller should eventually use updateCryptoOperation.
|
||||
func (ic *imageCopier) blobPipelineDecryptionStep(stream *sourceStream, srcInfo types.BlobInfo) (*bpDecryptionStepData, error) {
|
||||
if !isOciEncrypted(stream.info.MediaType) || ic.c.ociDecryptConfig == nil {
|
||||
if !isOciEncrypted(stream.info.MediaType) || ic.c.options.OciDecryptConfig == nil {
|
||||
return &bpDecryptionStepData{
|
||||
decrypting: false,
|
||||
}, nil
|
||||
|
|
@ -47,7 +47,7 @@ func (ic *imageCopier) blobPipelineDecryptionStep(stream *sourceStream, srcInfo
|
|||
desc := imgspecv1.Descriptor{
|
||||
Annotations: stream.info.Annotations,
|
||||
}
|
||||
reader, decryptedDigest, err := ocicrypt.DecryptLayer(ic.c.ociDecryptConfig, stream.reader, desc, false)
|
||||
reader, decryptedDigest, err := ocicrypt.DecryptLayer(ic.c.options.OciDecryptConfig, stream.reader, desc, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypting layer %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ type bpEncryptionStepData struct {
|
|||
// Returns data for other steps; the caller should eventually call updateCryptoOperationAndAnnotations.
|
||||
func (ic *imageCopier) blobPipelineEncryptionStep(stream *sourceStream, toEncrypt bool, srcInfo types.BlobInfo,
|
||||
decryptionStep *bpDecryptionStepData) (*bpEncryptionStepData, error) {
|
||||
if !toEncrypt || isOciEncrypted(srcInfo.MediaType) || ic.c.ociEncryptConfig == nil {
|
||||
if !toEncrypt || isOciEncrypted(srcInfo.MediaType) || ic.c.options.OciEncryptConfig == nil {
|
||||
return &bpEncryptionStepData{
|
||||
encrypting: false,
|
||||
}, nil
|
||||
|
|
@ -101,7 +101,7 @@ func (ic *imageCopier) blobPipelineEncryptionStep(stream *sourceStream, toEncryp
|
|||
Size: srcInfo.Size,
|
||||
Annotations: annotations,
|
||||
}
|
||||
reader, finalizer, err := ocicrypt.EncryptLayer(ic.c.ociEncryptConfig, stream.reader, desc)
|
||||
reader, finalizer, err := ocicrypt.EncryptLayer(ic.c.options.OciEncryptConfig, stream.reader, desc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypting blob %s: %w", srcInfo.Digest, err)
|
||||
}
|
||||
|
|
|
|||
159
vendor/github.com/containers/image/v5/copy/multiple.go
generated
vendored
159
vendor/github.com/containers/image/v5/copy/multiple.go
generated
vendored
|
|
@ -5,16 +5,19 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/internal/image"
|
||||
internalManifest "github.com/containers/image/v5/internal/manifest"
|
||||
"github.com/containers/image/v5/internal/set"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
|
|
@ -28,30 +31,125 @@ const (
|
|||
type instanceCopy struct {
|
||||
op instanceCopyKind
|
||||
sourceDigest digest.Digest
|
||||
|
||||
// Fields which can be used by callers when operation
|
||||
// is `instanceCopyClone`
|
||||
cloneCompressionVariant OptionCompressionVariant
|
||||
clonePlatform *imgspecv1.Platform
|
||||
cloneAnnotations map[string]string
|
||||
}
|
||||
|
||||
// internal type only to make imgspecv1.Platform comparable
|
||||
type platformComparable struct {
|
||||
architecture string
|
||||
os string
|
||||
osVersion string
|
||||
osFeatures string
|
||||
variant string
|
||||
}
|
||||
|
||||
// Converts imgspecv1.Platform to a comparable format.
|
||||
func platformV1ToPlatformComparable(platform *imgspecv1.Platform) platformComparable {
|
||||
if platform == nil {
|
||||
return platformComparable{}
|
||||
}
|
||||
osFeatures := slices.Clone(platform.OSFeatures)
|
||||
sort.Strings(osFeatures)
|
||||
return platformComparable{architecture: platform.Architecture,
|
||||
os: platform.OS,
|
||||
// This is strictly speaking ambiguous, fields of OSFeatures can contain a ','. Probably good enough for now.
|
||||
osFeatures: strings.Join(osFeatures, ","),
|
||||
osVersion: platform.OSVersion,
|
||||
variant: platform.Variant,
|
||||
}
|
||||
}
|
||||
|
||||
// platformCompressionMap prepares a mapping of platformComparable -> CompressionAlgorithmNames for given digests
|
||||
func platformCompressionMap(list internalManifest.List, instanceDigests []digest.Digest) (map[platformComparable]*set.Set[string], error) {
|
||||
res := make(map[platformComparable]*set.Set[string])
|
||||
for _, instanceDigest := range instanceDigests {
|
||||
instanceDetails, err := list.Instance(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting details for instance %s: %w", instanceDigest, err)
|
||||
}
|
||||
platform := platformV1ToPlatformComparable(instanceDetails.ReadOnly.Platform)
|
||||
platformSet, ok := res[platform]
|
||||
if !ok {
|
||||
platformSet = set.New[string]()
|
||||
res[platform] = platformSet
|
||||
}
|
||||
platformSet.AddSlice(instanceDetails.ReadOnly.CompressionAlgorithmNames)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func validateCompressionVariantExists(input []OptionCompressionVariant) error {
|
||||
for _, option := range input {
|
||||
_, err := compression.AlgorithmByName(option.Algorithm.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid algorithm %q in option.EnsureCompressionVariantsExist: %w", option.Algorithm.Name(), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareInstanceCopies prepares a list of instances which needs to copied to the manifest list.
|
||||
func prepareInstanceCopies(instanceDigests []digest.Digest, options *Options) []instanceCopy {
|
||||
func prepareInstanceCopies(list internalManifest.List, instanceDigests []digest.Digest, options *Options) ([]instanceCopy, error) {
|
||||
res := []instanceCopy{}
|
||||
if options.ImageListSelection == CopySpecificImages && len(options.EnsureCompressionVariantsExist) > 0 {
|
||||
// List can already contain compressed instance for a compression selected in `EnsureCompressionVariantsExist`
|
||||
// It’s unclear what it means when `CopySpecificImages` includes an instance in options.Instances,
|
||||
// EnsureCompressionVariantsExist asks for an instance with some compression,
|
||||
// an instance with that compression already exists, but is not included in options.Instances.
|
||||
// We might define the semantics and implement this in the future.
|
||||
return res, fmt.Errorf("EnsureCompressionVariantsExist is not implemented for CopySpecificImages")
|
||||
}
|
||||
err := validateCompressionVariantExists(options.EnsureCompressionVariantsExist)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
compressionsByPlatform, err := platformCompressionMap(list, instanceDigests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, instanceDigest := range instanceDigests {
|
||||
if options.ImageListSelection == CopySpecificImages &&
|
||||
!slices.Contains(options.Instances, instanceDigest) {
|
||||
logrus.Debugf("Skipping instance %s (%d/%d)", instanceDigest, i+1, len(instanceDigests))
|
||||
continue
|
||||
}
|
||||
instanceDetails, err := list.Instance(instanceDigest)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("getting details for instance %s: %w", instanceDigest, err)
|
||||
}
|
||||
res = append(res, instanceCopy{
|
||||
op: instanceCopyCopy,
|
||||
sourceDigest: instanceDigest,
|
||||
})
|
||||
platform := platformV1ToPlatformComparable(instanceDetails.ReadOnly.Platform)
|
||||
compressionList := compressionsByPlatform[platform]
|
||||
for _, compressionVariant := range options.EnsureCompressionVariantsExist {
|
||||
if !compressionList.Contains(compressionVariant.Algorithm.Name()) {
|
||||
res = append(res, instanceCopy{
|
||||
op: instanceCopyClone,
|
||||
sourceDigest: instanceDigest,
|
||||
cloneCompressionVariant: compressionVariant,
|
||||
clonePlatform: instanceDetails.ReadOnly.Platform,
|
||||
cloneAnnotations: maps.Clone(instanceDetails.ReadOnly.Annotations),
|
||||
})
|
||||
// add current compression to the list so that we don’t create duplicate clones
|
||||
compressionList.Add(compressionVariant.Algorithm.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// copyMultipleImages copies some or all of an image list's instances, using
|
||||
// policyContext to validate source image admissibility.
|
||||
func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedToplevel *image.UnparsedImage) (copiedManifest []byte, retErr error) {
|
||||
// c.policyContext to validate source image admissibility.
|
||||
func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte, retErr error) {
|
||||
// Parse the list and get a copy of the original value after it's re-encoded.
|
||||
manifestList, manifestType, err := unparsedToplevel.Manifest(ctx)
|
||||
manifestList, manifestType, err := c.unparsedToplevel.Manifest(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading manifest list: %w", err)
|
||||
}
|
||||
|
|
@ -61,7 +159,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
|
|||
}
|
||||
updatedList := originalList.CloneInternal()
|
||||
|
||||
sigs, err := c.sourceSignatures(ctx, unparsedToplevel, options,
|
||||
sigs, err := c.sourceSignatures(ctx, c.unparsedToplevel,
|
||||
"Getting image list signatures",
|
||||
"Checking if image list destination supports signatures")
|
||||
if err != nil {
|
||||
|
|
@ -94,12 +192,12 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
|
|||
if destIsDigestedReference {
|
||||
cannotModifyManifestListReason = "Destination specifies a digest"
|
||||
}
|
||||
if options.PreserveDigests {
|
||||
if c.options.PreserveDigests {
|
||||
cannotModifyManifestListReason = "Instructed to preserve digests"
|
||||
}
|
||||
|
||||
// Determine if we'll need to convert the manifest list to a different format.
|
||||
forceListMIMEType := options.ForceManifestMIMEType
|
||||
forceListMIMEType := c.options.ForceManifestMIMEType
|
||||
switch forceListMIMEType {
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema2MediaType:
|
||||
forceListMIMEType = manifest.DockerV2ListMediaType
|
||||
|
|
@ -119,8 +217,11 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
|
|||
// Copy each image, or just the ones we want to copy, in turn.
|
||||
instanceDigests := updatedList.Instances()
|
||||
instanceEdits := []internalManifest.ListEdit{}
|
||||
instanceCopyList := prepareInstanceCopies(instanceDigests, options)
|
||||
c.Printf("Copying %d of %d images in list\n", len(instanceCopyList), len(instanceDigests))
|
||||
instanceCopyList, err := prepareInstanceCopies(updatedList, instanceDigests, c.options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing instances for copy: %w", err)
|
||||
}
|
||||
c.Printf("Copying %d images generated from %d images in list\n", len(instanceCopyList), len(instanceDigests))
|
||||
for i, instance := range instanceCopyList {
|
||||
// Update instances to be edited by their `ListOperation` and
|
||||
// populate necessary fields.
|
||||
|
|
@ -129,17 +230,39 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
|
|||
logrus.Debugf("Copying instance %s (%d/%d)", instance.sourceDigest, i+1, len(instanceCopyList))
|
||||
c.Printf("Copying image %s (%d/%d)\n", instance.sourceDigest, i+1, len(instanceCopyList))
|
||||
unparsedInstance := image.UnparsedInstance(c.rawSource, &instanceCopyList[i].sourceDigest)
|
||||
updatedManifest, updatedManifestType, updatedManifestDigest, err := c.copySingleImage(ctx, policyContext, options, unparsedToplevel, unparsedInstance, &instanceCopyList[i].sourceDigest)
|
||||
updated, err := c.copySingleImage(ctx, unparsedInstance, &instanceCopyList[i].sourceDigest, copySingleImageOptions{requireCompressionFormatMatch: false})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copying image %d/%d from manifest list: %w", i+1, len(instanceCopyList), err)
|
||||
}
|
||||
// Record the result of a possible conversion here.
|
||||
instanceEdits = append(instanceEdits, internalManifest.ListEdit{
|
||||
ListOperation: internalManifest.ListOpUpdate,
|
||||
UpdateOldDigest: instance.sourceDigest,
|
||||
UpdateDigest: updatedManifestDigest,
|
||||
UpdateSize: int64(len(updatedManifest)),
|
||||
UpdateMediaType: updatedManifestType})
|
||||
ListOperation: internalManifest.ListOpUpdate,
|
||||
UpdateOldDigest: instance.sourceDigest,
|
||||
UpdateDigest: updated.manifestDigest,
|
||||
UpdateSize: int64(len(updated.manifest)),
|
||||
UpdateCompressionAlgorithms: updated.compressionAlgorithms,
|
||||
UpdateMediaType: updated.manifestMIMEType})
|
||||
case instanceCopyClone:
|
||||
logrus.Debugf("Replicating instance %s (%d/%d)", instance.sourceDigest, i+1, len(instanceCopyList))
|
||||
c.Printf("Replicating image %s (%d/%d)\n", instance.sourceDigest, i+1, len(instanceCopyList))
|
||||
unparsedInstance := image.UnparsedInstance(c.rawSource, &instanceCopyList[i].sourceDigest)
|
||||
updated, err := c.copySingleImage(ctx, unparsedInstance, &instanceCopyList[i].sourceDigest, copySingleImageOptions{
|
||||
requireCompressionFormatMatch: true,
|
||||
compressionFormat: &instance.cloneCompressionVariant.Algorithm,
|
||||
compressionLevel: instance.cloneCompressionVariant.Level})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("replicating image %d/%d from manifest list: %w", i+1, len(instanceCopyList), err)
|
||||
}
|
||||
// Record the result of a possible conversion here.
|
||||
instanceEdits = append(instanceEdits, internalManifest.ListEdit{
|
||||
ListOperation: internalManifest.ListOpAdd,
|
||||
AddDigest: updated.manifestDigest,
|
||||
AddSize: int64(len(updated.manifest)),
|
||||
AddMediaType: updated.manifestMIMEType,
|
||||
AddPlatform: instance.clonePlatform,
|
||||
AddAnnotations: instance.cloneAnnotations,
|
||||
AddCompressionAlgorithms: updated.compressionAlgorithms,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("copying image: invalid copy operation %d", instance.op)
|
||||
}
|
||||
|
|
@ -204,7 +327,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
|
|||
}
|
||||
|
||||
// Sign the manifest list.
|
||||
newSigs, err := c.createSignatures(ctx, manifestList, options.SignIdentity)
|
||||
newSigs, err := c.createSignatures(ctx, manifestList, c.options.SignIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
26
vendor/github.com/containers/image/v5/copy/sign.go
generated
vendored
26
vendor/github.com/containers/image/v5/copy/sign.go
generated
vendored
|
|
@ -13,20 +13,20 @@ import (
|
|||
"github.com/containers/image/v5/transports"
|
||||
)
|
||||
|
||||
// setupSigners initializes c.signers based on options.
|
||||
func (c *copier) setupSigners(options *Options) error {
|
||||
c.signers = append(c.signers, options.Signers...)
|
||||
// c.signersToClose is intentionally not updated with options.Signers.
|
||||
// setupSigners initializes c.signers.
|
||||
func (c *copier) setupSigners() error {
|
||||
c.signers = append(c.signers, c.options.Signers...)
|
||||
// c.signersToClose is intentionally not updated with c.options.Signers.
|
||||
|
||||
// We immediately append created signers to c.signers, and we rely on c.close() to clean them up; so we don’t need
|
||||
// to clean up any created signers on failure.
|
||||
|
||||
if options.SignBy != "" {
|
||||
if c.options.SignBy != "" {
|
||||
opts := []simplesigning.Option{
|
||||
simplesigning.WithKeyFingerprint(options.SignBy),
|
||||
simplesigning.WithKeyFingerprint(c.options.SignBy),
|
||||
}
|
||||
if options.SignPassphrase != "" {
|
||||
opts = append(opts, simplesigning.WithPassphrase(options.SignPassphrase))
|
||||
if c.options.SignPassphrase != "" {
|
||||
opts = append(opts, simplesigning.WithPassphrase(c.options.SignPassphrase))
|
||||
}
|
||||
signer, err := simplesigning.NewSigner(opts...)
|
||||
if err != nil {
|
||||
|
|
@ -36,9 +36,9 @@ func (c *copier) setupSigners(options *Options) error {
|
|||
c.signersToClose = append(c.signersToClose, signer)
|
||||
}
|
||||
|
||||
if options.SignBySigstorePrivateKeyFile != "" {
|
||||
if c.options.SignBySigstorePrivateKeyFile != "" {
|
||||
signer, err := sigstore.NewSigner(
|
||||
sigstore.WithPrivateKeyFile(options.SignBySigstorePrivateKeyFile, options.SignSigstorePrivateKeyPassphrase),
|
||||
sigstore.WithPrivateKeyFile(c.options.SignBySigstorePrivateKeyFile, c.options.SignSigstorePrivateKeyPassphrase),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -50,13 +50,13 @@ func (c *copier) setupSigners(options *Options) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// sourceSignatures returns signatures from unparsedSource based on options,
|
||||
// sourceSignatures returns signatures from unparsedSource,
|
||||
// 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,
|
||||
func (c *copier) sourceSignatures(ctx context.Context, unparsed private.UnparsedImage,
|
||||
gettingSignaturesMessage, checkingDestMessage string) ([]internalsig.Signature, error) {
|
||||
var sigs []internalsig.Signature
|
||||
if options.RemoveSignatures {
|
||||
if c.options.RemoveSignatures {
|
||||
sigs = []internalsig.Signature{}
|
||||
} else {
|
||||
c.Printf("%s\n", gettingSignaturesMessage)
|
||||
|
|
|
|||
275
vendor/github.com/containers/image/v5/copy/single.go
generated
vendored
275
vendor/github.com/containers/image/v5/copy/single.go
generated
vendored
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
compressiontypes "github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
|
|
@ -30,40 +29,54 @@ import (
|
|||
|
||||
// imageCopier tracks state specific to a single image (possibly an item of a manifest list)
|
||||
type imageCopier struct {
|
||||
c *copier
|
||||
manifestUpdates *types.ManifestUpdateOptions
|
||||
src *image.SourcedImage
|
||||
diffIDsAreNeeded bool
|
||||
cannotModifyManifestReason string // The reason the manifest cannot be modified, or an empty string if it can
|
||||
canSubstituteBlobs bool
|
||||
compressionFormat *compressiontypes.Algorithm // Compression algorithm to use, if the user explicitly requested one, or nil.
|
||||
compressionLevel *int
|
||||
ociEncryptLayers *[]int
|
||||
c *copier
|
||||
manifestUpdates *types.ManifestUpdateOptions
|
||||
src *image.SourcedImage
|
||||
diffIDsAreNeeded bool
|
||||
cannotModifyManifestReason string // The reason the manifest cannot be modified, or an empty string if it can
|
||||
canSubstituteBlobs bool
|
||||
compressionFormat *compressiontypes.Algorithm // Compression algorithm to use, if the user explicitly requested one, or nil.
|
||||
compressionLevel *int
|
||||
requireCompressionFormatMatch bool
|
||||
}
|
||||
|
||||
// copySingleImage copies a single (non-manifest-list) image unparsedImage, using policyContext to validate
|
||||
type copySingleImageOptions struct {
|
||||
requireCompressionFormatMatch bool
|
||||
compressionFormat *compressiontypes.Algorithm // Compression algorithm to use, if the user explicitly requested one, or nil.
|
||||
compressionLevel *int
|
||||
}
|
||||
|
||||
// copySingleImageResult carries data produced by copySingleImage
|
||||
type copySingleImageResult struct {
|
||||
manifest []byte
|
||||
manifestMIMEType string
|
||||
manifestDigest digest.Digest
|
||||
compressionAlgorithms []compressiontypes.Algorithm
|
||||
}
|
||||
|
||||
// copySingleImage copies a single (non-manifest-list) image unparsedImage, using c.policyContext to validate
|
||||
// source image admissibility.
|
||||
func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedToplevel, unparsedImage *image.UnparsedImage, targetInstance *digest.Digest) (retManifest []byte, retManifestType string, retManifestDigest digest.Digest, retErr error) {
|
||||
func (c *copier) copySingleImage(ctx context.Context, unparsedImage *image.UnparsedImage, targetInstance *digest.Digest, opts copySingleImageOptions) (copySingleImageResult, error) {
|
||||
// The caller is handling manifest lists; this could happen only if a manifest list contains a manifest list.
|
||||
// Make sure we fail cleanly in such cases.
|
||||
multiImage, err := isMultiImage(ctx, unparsedImage)
|
||||
if err != nil {
|
||||
// FIXME FIXME: How to name a reference for the sub-image?
|
||||
return nil, "", "", fmt.Errorf("determining manifest MIME type for %s: %w", transports.ImageName(unparsedImage.Reference()), err)
|
||||
return copySingleImageResult{}, fmt.Errorf("determining manifest MIME type for %s: %w", transports.ImageName(unparsedImage.Reference()), err)
|
||||
}
|
||||
if multiImage {
|
||||
return nil, "", "", fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image")
|
||||
return copySingleImageResult{}, fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image")
|
||||
}
|
||||
|
||||
// Please keep this policy check BEFORE reading any other information about the image.
|
||||
// (The multiImage check above only matches the MIME type, which we have received anyway.
|
||||
// Actual parsing of anything should be deferred.)
|
||||
if allowed, err := policyContext.IsRunningImageAllowed(ctx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
|
||||
return nil, "", "", fmt.Errorf("Source image rejected: %w", err)
|
||||
if allowed, err := c.policyContext.IsRunningImageAllowed(ctx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
|
||||
return copySingleImageResult{}, fmt.Errorf("Source image rejected: %w", err)
|
||||
}
|
||||
src, err := image.FromUnparsedImage(ctx, options.SourceCtx, unparsedImage)
|
||||
src, err := image.FromUnparsedImage(ctx, c.options.SourceCtx, unparsedImage)
|
||||
if err != nil {
|
||||
return nil, "", "", fmt.Errorf("initializing image from source %s: %w", transports.ImageName(c.rawSource.Reference()), err)
|
||||
return copySingleImageResult{}, fmt.Errorf("initializing image from source %s: %w", transports.ImageName(c.rawSource.Reference()), err)
|
||||
}
|
||||
|
||||
// If the destination is a digested reference, make a note of that, determine what digest value we're
|
||||
|
|
@ -75,33 +88,33 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
destIsDigestedReference = true
|
||||
matches, err := manifest.MatchesDigest(src.ManifestBlob, digested.Digest())
|
||||
if err != nil {
|
||||
return nil, "", "", fmt.Errorf("computing digest of source image's manifest: %w", err)
|
||||
return copySingleImageResult{}, fmt.Errorf("computing digest of source image's manifest: %w", err)
|
||||
}
|
||||
if !matches {
|
||||
manifestList, _, err := unparsedToplevel.Manifest(ctx)
|
||||
manifestList, _, err := c.unparsedToplevel.Manifest(ctx)
|
||||
if err != nil {
|
||||
return nil, "", "", fmt.Errorf("reading manifest from source image: %w", err)
|
||||
return copySingleImageResult{}, fmt.Errorf("reading manifest from source image: %w", err)
|
||||
}
|
||||
matches, err = manifest.MatchesDigest(manifestList, digested.Digest())
|
||||
if err != nil {
|
||||
return nil, "", "", fmt.Errorf("computing digest of source image's manifest: %w", err)
|
||||
return copySingleImageResult{}, fmt.Errorf("computing digest of source image's manifest: %w", err)
|
||||
}
|
||||
if !matches {
|
||||
return nil, "", "", errors.New("Digest of source image's manifest would not match destination reference")
|
||||
return copySingleImageResult{}, errors.New("Digest of source image's manifest would not match destination reference")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkImageDestinationForCurrentRuntime(ctx, options.DestinationCtx, src, c.dest); err != nil {
|
||||
return nil, "", "", err
|
||||
if err := checkImageDestinationForCurrentRuntime(ctx, c.options.DestinationCtx, src, c.dest); err != nil {
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
|
||||
sigs, err := c.sourceSignatures(ctx, src, options,
|
||||
sigs, err := c.sourceSignatures(ctx, src,
|
||||
"Getting image source signatures",
|
||||
"Checking if image destination supports signatures")
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
|
||||
// Determine if we're allowed to modify the manifest.
|
||||
|
|
@ -114,7 +127,7 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
if destIsDigestedReference {
|
||||
cannotModifyManifestReason = "Destination specifies a digest"
|
||||
}
|
||||
if options.PreserveDigests {
|
||||
if c.options.PreserveDigests {
|
||||
cannotModifyManifestReason = "Instructed to preserve digests"
|
||||
}
|
||||
|
||||
|
|
@ -123,13 +136,16 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}},
|
||||
src: src,
|
||||
// diffIDsAreNeeded is computed later
|
||||
cannotModifyManifestReason: cannotModifyManifestReason,
|
||||
ociEncryptLayers: options.OciEncryptLayers,
|
||||
cannotModifyManifestReason: cannotModifyManifestReason,
|
||||
requireCompressionFormatMatch: opts.requireCompressionFormatMatch,
|
||||
}
|
||||
if options.DestinationCtx != nil {
|
||||
if opts.compressionFormat != nil {
|
||||
ic.compressionFormat = opts.compressionFormat
|
||||
ic.compressionLevel = opts.compressionLevel
|
||||
} else if c.options.DestinationCtx != nil {
|
||||
// Note that compressionFormat and compressionLevel can be nil.
|
||||
ic.compressionFormat = options.DestinationCtx.CompressionFormat
|
||||
ic.compressionLevel = options.DestinationCtx.CompressionLevel
|
||||
ic.compressionFormat = c.options.DestinationCtx.CompressionFormat
|
||||
ic.compressionLevel = c.options.DestinationCtx.CompressionLevel
|
||||
}
|
||||
// Decide whether we can substitute blobs with semantic equivalents:
|
||||
// - Don’t do that if we can’t modify the manifest at all
|
||||
|
|
@ -142,20 +158,20 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
ic.canSubstituteBlobs = ic.cannotModifyManifestReason == "" && len(c.signers) == 0
|
||||
|
||||
if err := ic.updateEmbeddedDockerReference(); err != nil {
|
||||
return nil, "", "", err
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
|
||||
destRequiresOciEncryption := (isEncrypted(src) && ic.c.ociDecryptConfig != nil) || options.OciEncryptLayers != nil
|
||||
destRequiresOciEncryption := (isEncrypted(src) && ic.c.options.OciDecryptConfig != nil) || c.options.OciEncryptLayers != nil
|
||||
|
||||
manifestConversionPlan, err := determineManifestConversion(determineManifestConversionInputs{
|
||||
srcMIMEType: ic.src.ManifestMIMEType,
|
||||
destSupportedManifestMIMETypes: ic.c.dest.SupportedManifestMIMETypes(),
|
||||
forceManifestMIMEType: options.ForceManifestMIMEType,
|
||||
forceManifestMIMEType: c.options.ForceManifestMIMEType,
|
||||
requiresOCIEncryption: destRequiresOciEncryption,
|
||||
cannotModifyManifestReason: ic.cannotModifyManifestReason,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
// We set up this part of ic.manifestUpdates quite early, not just around the
|
||||
// code that calls copyUpdatedConfigAndManifest, so that other parts of the copy code
|
||||
|
|
@ -169,27 +185,28 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
ic.diffIDsAreNeeded = src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates)
|
||||
|
||||
// If enabled, fetch and compare the destination's manifest. And as an optimization skip updating the destination iff equal
|
||||
if options.OptimizeDestinationImageAlreadyExists {
|
||||
if c.options.OptimizeDestinationImageAlreadyExists {
|
||||
shouldUpdateSigs := len(sigs) > 0 || len(c.signers) != 0 // TODO: Consider allowing signatures updates only and skipping the image's layers/manifest copy if possible
|
||||
noPendingManifestUpdates := ic.noPendingManifestUpdates()
|
||||
|
||||
logrus.Debugf("Checking if we can skip copying: has signatures=%t, OCI encryption=%t, no manifest updates=%t", shouldUpdateSigs, destRequiresOciEncryption, noPendingManifestUpdates)
|
||||
if !shouldUpdateSigs && !destRequiresOciEncryption && noPendingManifestUpdates {
|
||||
isSrcDestManifestEqual, retManifest, retManifestType, retManifestDigest, err := compareImageDestinationManifestEqual(ctx, options, src, targetInstance, c.dest)
|
||||
logrus.Debugf("Checking if we can skip copying: has signatures=%t, OCI encryption=%t, no manifest updates=%t, compression match required for resuing blobs=%t", shouldUpdateSigs, destRequiresOciEncryption, noPendingManifestUpdates, opts.requireCompressionFormatMatch)
|
||||
if !shouldUpdateSigs && !destRequiresOciEncryption && noPendingManifestUpdates && !ic.requireCompressionFormatMatch {
|
||||
matchedResult, err := ic.compareImageDestinationManifestEqual(ctx, targetInstance)
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to compare destination image manifest: %v", err)
|
||||
return nil, "", "", err
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
|
||||
if isSrcDestManifestEqual {
|
||||
if matchedResult != nil {
|
||||
c.Printf("Skipping: image already present at destination\n")
|
||||
return retManifest, retManifestType, retManifestDigest, nil
|
||||
return *matchedResult, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ic.copyLayers(ctx); err != nil {
|
||||
return nil, "", "", err
|
||||
compressionAlgos, err := ic.copyLayers(ctx)
|
||||
if err != nil {
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
|
||||
// With docker/distribution registries we do not know whether the registry accepts schema2 or schema1 only;
|
||||
|
|
@ -197,8 +214,12 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
// without actually trying to upload something and getting a types.ManifestTypeRejectedError.
|
||||
// So, try the preferred manifest MIME type with possibly-updated blob digests, media types, and sizes if
|
||||
// we're altering how they're compressed. If the process succeeds, fine…
|
||||
manifestBytes, retManifestDigest, err := ic.copyUpdatedConfigAndManifest(ctx, targetInstance)
|
||||
retManifestType = manifestConversionPlan.preferredMIMEType
|
||||
manifestBytes, manifestDigest, err := ic.copyUpdatedConfigAndManifest(ctx, targetInstance)
|
||||
wipResult := copySingleImageResult{
|
||||
manifest: manifestBytes,
|
||||
manifestMIMEType: manifestConversionPlan.preferredMIMEType,
|
||||
manifestDigest: manifestDigest,
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Debugf("Writing manifest using preferred type %s failed: %v", manifestConversionPlan.preferredMIMEType, err)
|
||||
// … if it fails, and the failure is either because the manifest is rejected by the registry, or
|
||||
|
|
@ -213,14 +234,14 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
// We don’t have other options.
|
||||
// In principle the code below would handle this as well, but the resulting error message is fairly ugly.
|
||||
// Don’t bother the user with MIME types if we have no choice.
|
||||
return nil, "", "", err
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
// If the original MIME type is acceptable, determineManifestConversion always uses it as manifestConversionPlan.preferredMIMEType.
|
||||
// So if we are here, we will definitely be trying to convert the manifest.
|
||||
// With ic.cannotModifyManifestReason != "", that would just be a string of repeated failures for the same reason,
|
||||
// so let’s bail out early and with a better error message.
|
||||
if ic.cannotModifyManifestReason != "" {
|
||||
return nil, "", "", fmt.Errorf("writing manifest failed and we cannot try conversions: %q: %w", cannotModifyManifestReason, err)
|
||||
return copySingleImageResult{}, fmt.Errorf("writing manifest failed and we cannot try conversions: %q: %w", cannotModifyManifestReason, err)
|
||||
}
|
||||
|
||||
// errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil.
|
||||
|
|
@ -236,34 +257,37 @@ func (c *copier) copySingleImage(ctx context.Context, policyContext *signature.P
|
|||
}
|
||||
|
||||
// We have successfully uploaded a manifest.
|
||||
manifestBytes = attemptedManifest
|
||||
retManifestDigest = attemptedManifestDigest
|
||||
retManifestType = manifestMIMEType
|
||||
wipResult = copySingleImageResult{
|
||||
manifest: attemptedManifest,
|
||||
manifestMIMEType: manifestMIMEType,
|
||||
manifestDigest: attemptedManifestDigest,
|
||||
}
|
||||
errs = nil // Mark this as a success so that we don't abort below.
|
||||
break
|
||||
}
|
||||
if errs != nil {
|
||||
return nil, "", "", fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
|
||||
return copySingleImageResult{}, fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
|
||||
}
|
||||
}
|
||||
if targetInstance != nil {
|
||||
targetInstance = &retManifestDigest
|
||||
targetInstance = &wipResult.manifestDigest
|
||||
}
|
||||
|
||||
newSigs, err := c.createSignatures(ctx, manifestBytes, options.SignIdentity)
|
||||
newSigs, err := c.createSignatures(ctx, wipResult.manifest, c.options.SignIdentity)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return copySingleImageResult{}, err
|
||||
}
|
||||
sigs = append(sigs, newSigs...)
|
||||
|
||||
if len(sigs) > 0 {
|
||||
c.Printf("Storing signatures\n")
|
||||
if err := c.dest.PutSignaturesWithFormat(ctx, sigs, targetInstance); err != nil {
|
||||
return nil, "", "", fmt.Errorf("writing signatures: %w", err)
|
||||
return copySingleImageResult{}, fmt.Errorf("writing signatures: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return manifestBytes, retManifestType, retManifestDigest, nil
|
||||
wipResult.compressionAlgorithms = compressionAlgos
|
||||
res := wipResult // We are done
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// checkImageDestinationForCurrentRuntime enforces dest.MustMatchRuntimeOS, if necessary.
|
||||
|
|
@ -323,52 +347,68 @@ func (ic *imageCopier) noPendingManifestUpdates() bool {
|
|||
return reflect.DeepEqual(*ic.manifestUpdates, types.ManifestUpdateOptions{InformationOnly: ic.manifestUpdates.InformationOnly})
|
||||
}
|
||||
|
||||
// compareImageDestinationManifestEqual compares the `src` and `dest` image manifests (reading the manifest from the
|
||||
// (possibly remote) destination). Returning true and the destination's manifest, type and digest if they compare equal.
|
||||
func compareImageDestinationManifestEqual(ctx context.Context, options *Options, src *image.SourcedImage, targetInstance *digest.Digest, dest types.ImageDestination) (bool, []byte, string, digest.Digest, error) {
|
||||
srcManifestDigest, err := manifest.Digest(src.ManifestBlob)
|
||||
// compareImageDestinationManifestEqual compares the source and destination image manifests (reading the manifest from the
|
||||
// (possibly remote) destination). If they are equal, it returns a full copySingleImageResult, nil otherwise.
|
||||
func (ic *imageCopier) compareImageDestinationManifestEqual(ctx context.Context, targetInstance *digest.Digest) (*copySingleImageResult, error) {
|
||||
srcManifestDigest, err := manifest.Digest(ic.src.ManifestBlob)
|
||||
if err != nil {
|
||||
return false, nil, "", "", fmt.Errorf("calculating manifest digest: %w", err)
|
||||
return nil, fmt.Errorf("calculating manifest digest: %w", err)
|
||||
}
|
||||
|
||||
destImageSource, err := dest.Reference().NewImageSource(ctx, options.DestinationCtx)
|
||||
destImageSource, err := ic.c.dest.Reference().NewImageSource(ctx, ic.c.options.DestinationCtx)
|
||||
if err != nil {
|
||||
logrus.Debugf("Unable to create destination image %s source: %v", dest.Reference(), err)
|
||||
return false, nil, "", "", nil
|
||||
logrus.Debugf("Unable to create destination image %s source: %v", ic.c.dest.Reference(), err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
destManifest, destManifestType, err := destImageSource.GetManifest(ctx, targetInstance)
|
||||
if err != nil {
|
||||
logrus.Debugf("Unable to get destination image %s/%s manifest: %v", destImageSource, targetInstance, err)
|
||||
return false, nil, "", "", nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
destManifestDigest, err := manifest.Digest(destManifest)
|
||||
if err != nil {
|
||||
return false, nil, "", "", fmt.Errorf("calculating manifest digest: %w", err)
|
||||
return nil, fmt.Errorf("calculating manifest digest: %w", err)
|
||||
}
|
||||
|
||||
logrus.Debugf("Comparing source and destination manifest digests: %v vs. %v", srcManifestDigest, destManifestDigest)
|
||||
if srcManifestDigest != destManifestDigest {
|
||||
return false, nil, "", "", nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
compressionAlgos := set.New[string]()
|
||||
for _, srcInfo := range ic.src.LayerInfos() {
|
||||
compression := compressionAlgorithmFromMIMEType(srcInfo)
|
||||
compressionAlgos.Add(compression.Name())
|
||||
}
|
||||
|
||||
algos, err := algorithmsByNames(compressionAlgos.Values())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Destination and source manifests, types and digests should all be equivalent
|
||||
return true, destManifest, destManifestType, destManifestDigest, nil
|
||||
return ©SingleImageResult{
|
||||
manifest: destManifest,
|
||||
manifestMIMEType: destManifestType,
|
||||
manifestDigest: srcManifestDigest,
|
||||
compressionAlgorithms: algos,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.cannotModifyManifestReason == "".
|
||||
func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
||||
func (ic *imageCopier) copyLayers(ctx context.Context) ([]compressiontypes.Algorithm, error) {
|
||||
srcInfos := ic.src.LayerInfos()
|
||||
numLayers := len(srcInfos)
|
||||
updatedSrcInfos, err := ic.src.LayerInfosForCopy(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
srcInfosUpdated := false
|
||||
if updatedSrcInfos != nil && !reflect.DeepEqual(srcInfos, updatedSrcInfos) {
|
||||
if ic.cannotModifyManifestReason != "" {
|
||||
return fmt.Errorf("Copying this image would require changing layer representation, which we cannot do: %q", ic.cannotModifyManifestReason)
|
||||
return nil, fmt.Errorf("Copying this image would require changing layer representation, which we cannot do: %q", ic.cannotModifyManifestReason)
|
||||
}
|
||||
srcInfos = updatedSrcInfos
|
||||
srcInfosUpdated = true
|
||||
|
|
@ -384,7 +424,7 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
|||
// layer is empty.
|
||||
man, err := manifest.FromBlob(ic.src.ManifestBlob, ic.src.ManifestMIMEType)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
manifestLayerInfos := man.LayerInfos()
|
||||
|
||||
|
|
@ -396,7 +436,7 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
|||
defer ic.c.concurrentBlobCopiesSemaphore.Release(1)
|
||||
defer copyGroup.Done()
|
||||
cld := copyLayerData{}
|
||||
if !ic.c.downloadForeignLayers && ic.c.dest.AcceptsForeignLayerURLs() && len(srcLayer.URLs) != 0 {
|
||||
if !ic.c.options.DownloadForeignLayers && ic.c.dest.AcceptsForeignLayerURLs() && len(srcLayer.URLs) != 0 {
|
||||
// DiffIDs are, currently, needed only when converting from schema1.
|
||||
// In which case src.LayerInfos will not have URLs because schema1
|
||||
// does not support them.
|
||||
|
|
@ -415,10 +455,10 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
|||
// Decide which layers to encrypt
|
||||
layersToEncrypt := set.New[int]()
|
||||
var encryptAll bool
|
||||
if ic.ociEncryptLayers != nil {
|
||||
encryptAll = len(*ic.ociEncryptLayers) == 0
|
||||
if ic.c.options.OciEncryptLayers != nil {
|
||||
encryptAll = len(*ic.c.options.OciEncryptLayers) == 0
|
||||
totalLayers := len(srcInfos)
|
||||
for _, l := range *ic.ociEncryptLayers {
|
||||
for _, l := range *ic.c.options.OciEncryptLayers {
|
||||
// if layer is negative, it is reverse indexed.
|
||||
layersToEncrypt.Add((totalLayers + l) % totalLayers)
|
||||
}
|
||||
|
|
@ -450,14 +490,18 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
|||
// A call to copyGroup.Wait() is done at this point by the defer above.
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compressionAlgos := set.New[string]()
|
||||
destInfos := make([]types.BlobInfo, numLayers)
|
||||
diffIDs := make([]digest.Digest, numLayers)
|
||||
for i, cld := range data {
|
||||
if cld.err != nil {
|
||||
return cld.err
|
||||
return nil, cld.err
|
||||
}
|
||||
if cld.destInfo.CompressionAlgorithm != nil {
|
||||
compressionAlgos.Add(cld.destInfo.CompressionAlgorithm.Name())
|
||||
}
|
||||
destInfos[i] = cld.destInfo
|
||||
diffIDs[i] = cld.diffID
|
||||
|
|
@ -472,7 +516,11 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
|||
if srcInfosUpdated || layerDigestsDiffer(srcInfos, destInfos) {
|
||||
ic.manifestUpdates.LayerInfos = destInfos
|
||||
}
|
||||
return nil
|
||||
algos, err := algorithmsByNames(compressionAlgos.Values())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return algos, nil
|
||||
}
|
||||
|
||||
// layerDigestsDiffer returns true iff the digests in a and b differ (ignoring sizes and possible other fields)
|
||||
|
|
@ -577,6 +625,19 @@ type diffIDResult struct {
|
|||
err error
|
||||
}
|
||||
|
||||
func compressionAlgorithmFromMIMEType(srcInfo types.BlobInfo) *compressiontypes.Algorithm {
|
||||
// This MIME type → compression mapping belongs in manifest-specific code in our manifest
|
||||
// package (but we should preferably replace/change UpdatedImage instead of productizing
|
||||
// this workaround).
|
||||
switch srcInfo.MediaType {
|
||||
case manifest.DockerV2Schema2LayerMediaType, imgspecv1.MediaTypeImageLayerGzip:
|
||||
return &compression.Gzip
|
||||
case imgspecv1.MediaTypeImageLayerZstd:
|
||||
return &compression.Zstd
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyLayer copies a layer with srcInfo (with known Digest and Annotations and possibly known Size) in src to dest, perhaps (de/re/)compressing it,
|
||||
// and returns a complete blobInfo of the copied layer, and a value for LayerDiffIDs if diffIDIsNeeded
|
||||
// srcRef can be used as an additional hint to the destination during checking whether a layer can be reused but srcRef can be nil.
|
||||
|
|
@ -588,17 +649,8 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
|
|||
// which uses the compression information to compute the updated MediaType values.
|
||||
// (Sadly UpdatedImage() is documented to not update MediaTypes from
|
||||
// ManifestUpdateOptions.LayerInfos[].MediaType, so we are doing it indirectly.)
|
||||
//
|
||||
// This MIME type → compression mapping belongs in manifest-specific code in our manifest
|
||||
// package (but we should preferably replace/change UpdatedImage instead of productizing
|
||||
// this workaround).
|
||||
if srcInfo.CompressionAlgorithm == nil {
|
||||
switch srcInfo.MediaType {
|
||||
case manifest.DockerV2Schema2LayerMediaType, imgspecv1.MediaTypeImageLayerGzip:
|
||||
srcInfo.CompressionAlgorithm = &compression.Gzip
|
||||
case imgspecv1.MediaTypeImageLayerZstd:
|
||||
srcInfo.CompressionAlgorithm = &compression.Zstd
|
||||
}
|
||||
srcInfo.CompressionAlgorithm = compressionAlgorithmFromMIMEType(srcInfo)
|
||||
}
|
||||
|
||||
ic.c.printCopyInfo("blob", srcInfo)
|
||||
|
|
@ -608,7 +660,7 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
|
|||
// When encrypting to decrypting, only use the simple code path. We might be able to optimize more
|
||||
// (e.g. if we know the DiffID of an encrypted compressed layer, it might not be necessary to pull, decrypt and decompress again),
|
||||
// but it’s not trivially safe to do such things, so until someone takes the effort to make a comprehensive argument, let’s not.
|
||||
encryptingOrDecrypting := toEncrypt || (isOciEncrypted(srcInfo.MediaType) && ic.c.ociDecryptConfig != nil)
|
||||
encryptingOrDecrypting := toEncrypt || (isOciEncrypted(srcInfo.MediaType) && ic.c.options.OciDecryptConfig != nil)
|
||||
canAvoidProcessingCompleteLayer := !diffIDIsNeeded && !encryptingOrDecrypting
|
||||
|
||||
// Don’t read the layer from the source if we already have the blob, and optimizations are acceptable.
|
||||
|
|
@ -623,12 +675,20 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
|
|||
// a failure when we eventually try to update the manifest with the digest and MIME type of the reused blob.
|
||||
// Fixing that will probably require passing more information to TryReusingBlob() than the current version of
|
||||
// the ImageDestination interface lets us pass in.
|
||||
var requiredCompression *compressiontypes.Algorithm
|
||||
var originalCompression *compressiontypes.Algorithm
|
||||
if ic.requireCompressionFormatMatch {
|
||||
requiredCompression = ic.compressionFormat
|
||||
originalCompression = srcInfo.CompressionAlgorithm
|
||||
}
|
||||
reused, reusedBlob, err := ic.c.dest.TryReusingBlobWithOptions(ctx, srcInfo, private.TryReusingBlobOptions{
|
||||
Cache: ic.c.blobInfoCache,
|
||||
CanSubstitute: canSubstitute,
|
||||
EmptyLayer: emptyLayer,
|
||||
LayerIndex: &layerIndex,
|
||||
SrcRef: srcRef,
|
||||
Cache: ic.c.blobInfoCache,
|
||||
CanSubstitute: canSubstitute,
|
||||
EmptyLayer: emptyLayer,
|
||||
LayerIndex: &layerIndex,
|
||||
SrcRef: srcRef,
|
||||
RequiredCompression: requiredCompression,
|
||||
OriginalCompression: originalCompression,
|
||||
})
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, "", fmt.Errorf("trying to reuse blob %s at destination: %w", srcInfo.Digest, err)
|
||||
|
|
@ -642,8 +702,8 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
|
|||
}()
|
||||
|
||||
// Throw an event that the layer has been skipped
|
||||
if ic.c.progress != nil && ic.c.progressInterval > 0 {
|
||||
ic.c.progress <- types.ProgressProperties{
|
||||
if ic.c.options.Progress != nil && ic.c.options.ProgressInterval > 0 {
|
||||
ic.c.options.Progress <- types.ProgressProperties{
|
||||
Event: types.ProgressEventSkipped,
|
||||
Artifact: srcInfo,
|
||||
}
|
||||
|
|
@ -818,3 +878,16 @@ func computeDiffID(stream io.Reader, decompressor compressiontypes.DecompressorF
|
|||
|
||||
return digest.Canonical.FromReader(stream)
|
||||
}
|
||||
|
||||
// algorithmsByNames returns slice of Algorithms from slice of Algorithm Names
|
||||
func algorithmsByNames(names []string) ([]compressiontypes.Algorithm, error) {
|
||||
result := []compressiontypes.Algorithm{}
|
||||
for _, name := range names {
|
||||
algo, err := compression.AlgorithmByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, algo)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
41
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
41
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
|
|
@ -321,13 +321,21 @@ func (d *dockerImageDestination) TryReusingBlobWithOptions(ctx context.Context,
|
|||
return false, private.ReusedBlob{}, 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, options.Cache)
|
||||
if err != nil {
|
||||
return false, private.ReusedBlob{}, err
|
||||
}
|
||||
if haveBlob {
|
||||
return true, reusedInfo, nil
|
||||
if impl.OriginalBlobMatchesRequiredCompression(options) {
|
||||
// First, check whether the blob happens to already exist at the destination.
|
||||
haveBlob, reusedInfo, err := d.tryReusingExactBlob(ctx, info, options.Cache)
|
||||
if err != nil {
|
||||
return false, private.ReusedBlob{}, err
|
||||
}
|
||||
if haveBlob {
|
||||
return true, reusedInfo, nil
|
||||
}
|
||||
} else {
|
||||
requiredCompression := "nil"
|
||||
if options.OriginalCompression != nil {
|
||||
requiredCompression = options.OriginalCompression.Name()
|
||||
}
|
||||
logrus.Debugf("Ignoring exact blob match case due to compression mismatch ( %s vs %s )", options.RequiredCompression.Name(), requiredCompression)
|
||||
}
|
||||
|
||||
// Then try reusing blobs from other locations.
|
||||
|
|
@ -338,6 +346,19 @@ func (d *dockerImageDestination) TryReusingBlobWithOptions(ctx context.Context,
|
|||
logrus.Debugf("Error parsing BlobInfoCache location reference: %s", err)
|
||||
continue
|
||||
}
|
||||
compressionOperation, compressionAlgorithm, err := blobinfocache.OperationAndAlgorithmForCompressor(candidate.CompressorName)
|
||||
if err != nil {
|
||||
logrus.Debugf("OperationAndAlgorithmForCompressor Failed: %v", err)
|
||||
continue
|
||||
}
|
||||
if !impl.BlobMatchesRequiredCompression(options, compressionAlgorithm) {
|
||||
requiredCompression := "nil"
|
||||
if compressionAlgorithm != nil {
|
||||
requiredCompression = compressionAlgorithm.Name()
|
||||
}
|
||||
logrus.Debugf("Ignoring candidate blob %s as reuse candidate due to compression mismatch ( %s vs %s ) in %s", candidate.Digest.String(), options.RequiredCompression.Name(), requiredCompression, candidateRepo.Name())
|
||||
continue
|
||||
}
|
||||
if candidate.CompressorName != blobinfocache.Uncompressed {
|
||||
logrus.Debugf("Trying to reuse cached location %s compressed with %s in %s", candidate.Digest.String(), candidate.CompressorName, candidateRepo.Name())
|
||||
} else {
|
||||
|
|
@ -388,12 +409,6 @@ func (d *dockerImageDestination) TryReusingBlobWithOptions(ctx context.Context,
|
|||
|
||||
options.Cache.RecordKnownLocation(d.ref.Transport(), bicTransportScope(d.ref), candidate.Digest, newBICLocationReference(d.ref))
|
||||
|
||||
compressionOperation, compressionAlgorithm, err := blobinfocache.OperationAndAlgorithmForCompressor(candidate.CompressorName)
|
||||
if err != nil {
|
||||
logrus.Debugf("... Failed: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
return true, private.ReusedBlob{
|
||||
Digest: candidate.Digest,
|
||||
Size: size,
|
||||
|
|
|
|||
3
vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go
generated
vendored
3
vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go
generated
vendored
|
|
@ -129,6 +129,9 @@ func (d *Destination) PutBlobWithOptions(ctx context.Context, stream io.Reader,
|
|||
// If the blob has been successfully reused, returns (true, info, nil).
|
||||
// If the transport can not reuse the requested blob, TryReusingBlob returns (false, {}, nil); it returns a non-nil error only on an unexpected failure.
|
||||
func (d *Destination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, private.ReusedBlob, error) {
|
||||
if !impl.OriginalBlobMatchesRequiredCompression(options) {
|
||||
return false, private.ReusedBlob{}, nil
|
||||
}
|
||||
if err := d.archive.lock(); err != nil {
|
||||
return false, private.ReusedBlob{}, err
|
||||
}
|
||||
|
|
|
|||
20
vendor/github.com/containers/image/v5/internal/imagedestination/impl/helpers.go
generated
vendored
Normal file
20
vendor/github.com/containers/image/v5/internal/imagedestination/impl/helpers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package impl
|
||||
|
||||
import (
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
compression "github.com/containers/image/v5/pkg/compression/types"
|
||||
)
|
||||
|
||||
// BlobMatchesRequiredCompression validates if compression is required by the caller while selecting a blob, if it is required
|
||||
// then function performs a match against the compression requested by the caller and compression of existing blob
|
||||
// (which can be nil to represent uncompressed or unknown)
|
||||
func BlobMatchesRequiredCompression(options private.TryReusingBlobOptions, candidateCompression *compression.Algorithm) bool {
|
||||
if options.RequiredCompression == nil {
|
||||
return true // no requirement imposed
|
||||
}
|
||||
return candidateCompression != nil && (options.RequiredCompression.Name() == candidateCompression.Name())
|
||||
}
|
||||
|
||||
func OriginalBlobMatchesRequiredCompression(opts private.TryReusingBlobOptions) bool {
|
||||
return BlobMatchesRequiredCompression(opts, opts.OriginalCompression)
|
||||
}
|
||||
3
vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go
generated
vendored
3
vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go
generated
vendored
|
|
@ -64,6 +64,9 @@ func (w *wrapped) PutBlobWithOptions(ctx context.Context, stream io.Reader, inpu
|
|||
// If the blob has been successfully reused, returns (true, info, nil).
|
||||
// If the transport can not reuse the requested blob, TryReusingBlob returns (false, {}, nil); it returns a non-nil error only on an unexpected failure.
|
||||
func (w *wrapped) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, private.ReusedBlob, error) {
|
||||
if options.RequiredCompression != nil {
|
||||
return false, private.ReusedBlob{}, nil
|
||||
}
|
||||
reused, blob, err := w.TryReusingBlob(ctx, info, options.Cache, options.CanSubstitute)
|
||||
if !reused || err != nil {
|
||||
return reused, private.ReusedBlob{}, err
|
||||
|
|
|
|||
14
vendor/github.com/containers/image/v5/internal/manifest/docker_schema2_list.go
generated
vendored
14
vendor/github.com/containers/image/v5/internal/manifest/docker_schema2_list.go
generated
vendored
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
platform "github.com/containers/image/v5/internal/pkg/platform"
|
||||
compression "github.com/containers/image/v5/pkg/compression/types"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
|
@ -57,11 +58,20 @@ func (list *Schema2ListPublic) Instances() []digest.Digest {
|
|||
func (list *Schema2ListPublic) Instance(instanceDigest digest.Digest) (ListUpdate, error) {
|
||||
for _, manifest := range list.Manifests {
|
||||
if manifest.Digest == instanceDigest {
|
||||
return ListUpdate{
|
||||
ret := ListUpdate{
|
||||
Digest: manifest.Digest,
|
||||
Size: manifest.Size,
|
||||
MediaType: manifest.MediaType,
|
||||
}, nil
|
||||
}
|
||||
ret.ReadOnly.CompressionAlgorithmNames = []string{compression.GzipAlgorithmName}
|
||||
ret.ReadOnly.Platform = &imgspecv1.Platform{
|
||||
OS: manifest.Platform.OS,
|
||||
Architecture: manifest.Platform.Architecture,
|
||||
OSVersion: manifest.Platform.OSVersion,
|
||||
OSFeatures: manifest.Platform.OSFeatures,
|
||||
Variant: manifest.Platform.Variant,
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
return ListUpdate{}, fmt.Errorf("unable to find instance %s passed to Schema2List.Instances", instanceDigest)
|
||||
|
|
|
|||
6
vendor/github.com/containers/image/v5/internal/manifest/list.go
generated
vendored
6
vendor/github.com/containers/image/v5/internal/manifest/list.go
generated
vendored
|
|
@ -68,6 +68,12 @@ type ListUpdate struct {
|
|||
Digest digest.Digest
|
||||
Size int64
|
||||
MediaType string
|
||||
// ReadOnly fields: may be set by Instance(), ignored by UpdateInstance()
|
||||
ReadOnly struct {
|
||||
Platform *imgspecv1.Platform
|
||||
Annotations map[string]string
|
||||
CompressionAlgorithmNames []string
|
||||
}
|
||||
}
|
||||
|
||||
type ListOp int
|
||||
|
|
|
|||
31
vendor/github.com/containers/image/v5/internal/manifest/oci_index.go
generated
vendored
31
vendor/github.com/containers/image/v5/internal/manifest/oci_index.go
generated
vendored
|
|
@ -53,11 +53,15 @@ func (index *OCI1IndexPublic) Instances() []digest.Digest {
|
|||
func (index *OCI1IndexPublic) Instance(instanceDigest digest.Digest) (ListUpdate, error) {
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.Digest == instanceDigest {
|
||||
return ListUpdate{
|
||||
ret := ListUpdate{
|
||||
Digest: manifest.Digest,
|
||||
Size: manifest.Size,
|
||||
MediaType: manifest.MediaType,
|
||||
}, nil
|
||||
}
|
||||
ret.ReadOnly.Platform = manifest.Platform
|
||||
ret.ReadOnly.Annotations = manifest.Annotations
|
||||
ret.ReadOnly.CompressionAlgorithmNames = annotationsToCompressionAlgorithmNames(manifest.Annotations)
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
return ListUpdate{}, fmt.Errorf("unable to find instance %s in OCI1Index", instanceDigest)
|
||||
|
|
@ -78,14 +82,29 @@ func (index *OCI1IndexPublic) UpdateInstances(updates []ListUpdate) error {
|
|||
return index.editInstances(editInstances)
|
||||
}
|
||||
|
||||
func addCompressionAnnotations(compressionAlgorithms []compression.Algorithm, annotationsMap map[string]string) {
|
||||
func annotationsToCompressionAlgorithmNames(annotations map[string]string) []string {
|
||||
result := make([]string, 0, 1)
|
||||
if annotations[OCI1InstanceAnnotationCompressionZSTD] == OCI1InstanceAnnotationCompressionZSTDValue {
|
||||
result = append(result, compression.ZstdAlgorithmName)
|
||||
}
|
||||
// No compression was detected, hence assume instance has default compression `Gzip`
|
||||
if len(result) == 0 {
|
||||
result = append(result, compression.GzipAlgorithmName)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func addCompressionAnnotations(compressionAlgorithms []compression.Algorithm, annotationsMap *map[string]string) {
|
||||
// TODO: This should also delete the algorithm if map already contains an algorithm and compressionAlgorithm
|
||||
// list has a different algorithm. To do that, we would need to modify the callers to always provide a reliable
|
||||
// and full compressionAlghorithms list.
|
||||
if *annotationsMap == nil && len(compressionAlgorithms) > 0 {
|
||||
*annotationsMap = map[string]string{}
|
||||
}
|
||||
for _, algo := range compressionAlgorithms {
|
||||
switch algo.Name() {
|
||||
case compression.ZstdAlgorithmName:
|
||||
annotationsMap[OCI1InstanceAnnotationCompressionZSTD] = OCI1InstanceAnnotationCompressionZSTDValue
|
||||
(*annotationsMap)[OCI1InstanceAnnotationCompressionZSTD] = OCI1InstanceAnnotationCompressionZSTDValue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
|
@ -130,13 +149,13 @@ func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
|
|||
maps.Copy(index.Manifests[targetIndex].Annotations, editInstance.UpdateAnnotations)
|
||||
}
|
||||
}
|
||||
addCompressionAnnotations(editInstance.UpdateCompressionAlgorithms, index.Manifests[targetIndex].Annotations)
|
||||
addCompressionAnnotations(editInstance.UpdateCompressionAlgorithms, &index.Manifests[targetIndex].Annotations)
|
||||
case ListOpAdd:
|
||||
annotations := map[string]string{}
|
||||
if editInstance.AddAnnotations != nil {
|
||||
annotations = maps.Clone(editInstance.AddAnnotations)
|
||||
}
|
||||
addCompressionAnnotations(editInstance.AddCompressionAlgorithms, annotations)
|
||||
addCompressionAnnotations(editInstance.AddCompressionAlgorithms, &annotations)
|
||||
addedEntries = append(addedEntries, imgspecv1.Descriptor{
|
||||
MediaType: editInstance.AddMediaType,
|
||||
Size: editInstance.AddSize,
|
||||
|
|
|
|||
9
vendor/github.com/containers/image/v5/internal/private/private.go
generated
vendored
9
vendor/github.com/containers/image/v5/internal/private/private.go
generated
vendored
|
|
@ -112,10 +112,11 @@ type TryReusingBlobOptions struct {
|
|||
// 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.
|
||||
SrcRef reference.Named // A reference to the source image that contains the input blob.
|
||||
RequiredCompression *compression.Algorithm // If set, reuse blobs with a matching algorithm as per implementations in internal/imagedestination/impl.helpers.go
|
||||
OriginalCompression *compression.Algorithm // Must be set if RequiredCompression is set; can be set to nil to indicate “uncompressed” or “unknown”.
|
||||
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.
|
||||
SrcRef reference.Named // A reference to the source image that contains the input blob.
|
||||
}
|
||||
|
||||
// ReusedBlob is information about a blob reused in a destination.
|
||||
|
|
|
|||
6
vendor/github.com/containers/image/v5/internal/set/set.go
generated
vendored
6
vendor/github.com/containers/image/v5/internal/set/set.go
generated
vendored
|
|
@ -28,6 +28,12 @@ func (s *Set[E]) Add(v E) {
|
|||
s.m[v] = struct{}{} // Possibly writing the same struct{}{} presence marker again.
|
||||
}
|
||||
|
||||
func (s *Set[E]) AddSlice(slice []E) {
|
||||
for _, v := range slice {
|
||||
s.Add(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set[E]) Delete(v E) {
|
||||
delete(s.m, v)
|
||||
}
|
||||
|
|
|
|||
3
vendor/github.com/containers/image/v5/oci/layout/oci_dest.go
generated
vendored
3
vendor/github.com/containers/image/v5/oci/layout/oci_dest.go
generated
vendored
|
|
@ -172,6 +172,9 @@ func (d *ociImageDestination) PutBlobWithOptions(ctx context.Context, stream io.
|
|||
// If the blob has been successfully reused, returns (true, info, nil).
|
||||
// If the transport can not reuse the requested blob, TryReusingBlob returns (false, {}, nil); it returns a non-nil error only on an unexpected failure.
|
||||
func (d *ociImageDestination) TryReusingBlobWithOptions(ctx context.Context, info types.BlobInfo, options private.TryReusingBlobOptions) (bool, private.ReusedBlob, error) {
|
||||
if !impl.OriginalBlobMatchesRequiredCompression(options) {
|
||||
return false, private.ReusedBlob{}, nil
|
||||
}
|
||||
if info.Digest == "" {
|
||||
return false, private.ReusedBlob{}, errors.New("Can not check for a blob with unknown digest")
|
||||
}
|
||||
|
|
|
|||
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 = 26
|
||||
VersionMinor = 27
|
||||
// VersionPatch is for backwards-compatible bug fixes
|
||||
VersionPatch = 1
|
||||
VersionPatch = 0
|
||||
|
||||
// VersionDev indicates development branch. Releases will be empty string.
|
||||
VersionDev = ""
|
||||
|
|
|
|||
19
vendor/github.com/osbuild/images/internal/oscap/oscap.go
generated
vendored
19
vendor/github.com/osbuild/images/internal/oscap/oscap.go
generated
vendored
|
|
@ -1,7 +1,11 @@
|
|||
package oscap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/osbuild/images/internal/fsnode"
|
||||
)
|
||||
|
||||
type Profile string
|
||||
|
|
@ -35,6 +39,9 @@ const (
|
|||
defaultCentos9Datastream string = "/usr/share/xml/scap/ssg/content/ssg-cs9-ds.xml"
|
||||
defaultRHEL8Datastream string = "/usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml"
|
||||
defaultRHEL9Datastream string = "/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml"
|
||||
|
||||
// tailoring directory path
|
||||
tailoringDirPath string = "/usr/share/xml/osbuild-openscap-data"
|
||||
)
|
||||
|
||||
func DefaultFedoraDatastream() string {
|
||||
|
|
@ -70,3 +77,15 @@ func IsProfileAllowed(profile string, allowlist []Profile) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetTailoringFile(profile string) (string, string, *fsnode.Directory, error) {
|
||||
newProfile := fmt.Sprintf("%s_osbuild_tailoring", profile)
|
||||
path := filepath.Join(tailoringDirPath, "tailoring.xml")
|
||||
|
||||
tailoringDir, err := fsnode.NewDirectory(tailoringDirPath, nil, nil, nil, true)
|
||||
if err != nil {
|
||||
return "", "", nil, err
|
||||
}
|
||||
|
||||
return newProfile, path, tailoringDir, nil
|
||||
}
|
||||
|
|
|
|||
10
vendor/github.com/osbuild/images/pkg/blueprint/customizations.go
generated
vendored
10
vendor/github.com/osbuild/images/pkg/blueprint/customizations.go
generated
vendored
|
|
@ -107,8 +107,14 @@ type ServicesCustomization struct {
|
|||
}
|
||||
|
||||
type OpenSCAPCustomization struct {
|
||||
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
|
||||
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
|
||||
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
|
||||
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
|
||||
Tailoring *OpenSCAPTailoringCustomizations `json:"tailoring,omitempty" toml:"tailoring,omitempty"`
|
||||
}
|
||||
|
||||
type OpenSCAPTailoringCustomizations struct {
|
||||
Selected []string `json:"selected,omitempty" toml:"selected,omitempty"`
|
||||
Unselected []string `json:"unselected,omitempty" toml:"unselected,omitempty"`
|
||||
}
|
||||
|
||||
type CustomizationError struct {
|
||||
|
|
|
|||
59
vendor/github.com/osbuild/images/pkg/distro/fedora/images.go
generated
vendored
59
vendor/github.com/osbuild/images/pkg/distro/fedora/images.go
generated
vendored
|
|
@ -118,22 +118,6 @@ func osCustomizations(
|
|||
osc.SElinux = "targeted"
|
||||
}
|
||||
|
||||
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
|
||||
if t.rpmOstree {
|
||||
panic("unexpected oscap options for ostree image type")
|
||||
}
|
||||
var datastream = oscapConfig.DataStream
|
||||
if datastream == "" {
|
||||
datastream = oscap.DefaultFedoraDatastream()
|
||||
}
|
||||
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
|
||||
osbuild.OscapConfig{
|
||||
Datastream: datastream,
|
||||
ProfileID: oscapConfig.ProfileID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var err error
|
||||
osc.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
|
||||
if err != nil {
|
||||
|
|
@ -174,6 +158,49 @@ func osCustomizations(
|
|||
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
|
||||
}
|
||||
|
||||
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
|
||||
if t.rpmOstree {
|
||||
panic("unexpected oscap options for ostree image type")
|
||||
}
|
||||
var datastream = oscapConfig.DataStream
|
||||
if datastream == "" {
|
||||
datastream = oscap.DefaultFedoraDatastream()
|
||||
}
|
||||
|
||||
oscapStageOptions := osbuild.OscapConfig{
|
||||
Datastream: datastream,
|
||||
ProfileID: oscapConfig.ProfileID,
|
||||
}
|
||||
|
||||
if oscapConfig.Tailoring != nil {
|
||||
newProfile, tailoringFilepath, tailoringDir, err := oscap.GetTailoringFile(oscapConfig.ProfileID)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error creating tailoring file options: %v", err))
|
||||
}
|
||||
|
||||
tailoringOptions := osbuild.OscapAutotailorConfig{
|
||||
Selected: oscapConfig.Tailoring.Selected,
|
||||
Unselected: oscapConfig.Tailoring.Unselected,
|
||||
NewProfile: newProfile,
|
||||
}
|
||||
|
||||
osc.OpenSCAPTailorConfig = osbuild.NewOscapAutotailorStageOptions(
|
||||
tailoringFilepath,
|
||||
oscapStageOptions,
|
||||
tailoringOptions,
|
||||
)
|
||||
|
||||
// overwrite the profile id with the new tailoring id
|
||||
oscapStageOptions.ProfileID = newProfile
|
||||
oscapStageOptions.Tailoring = tailoringFilepath
|
||||
|
||||
// add the parent directory for the tailoring file
|
||||
osc.Directories = append(osc.Directories, tailoringDir)
|
||||
}
|
||||
|
||||
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(oscapStageOptions)
|
||||
}
|
||||
|
||||
osc.ShellInit = imageConfig.ShellInit
|
||||
|
||||
osc.Grub2Config = imageConfig.Grub2Config
|
||||
|
|
|
|||
10
vendor/github.com/osbuild/images/pkg/distro/fedora/imagetype.go
generated
vendored
10
vendor/github.com/osbuild/images/pkg/distro/fedora/imagetype.go
generated
vendored
|
|
@ -16,7 +16,6 @@ import (
|
|||
"github.com/osbuild/images/pkg/distro"
|
||||
"github.com/osbuild/images/pkg/image"
|
||||
"github.com/osbuild/images/pkg/manifest"
|
||||
"github.com/osbuild/images/pkg/ostree"
|
||||
"github.com/osbuild/images/pkg/platform"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"golang.org/x/exp/slices"
|
||||
|
|
@ -245,18 +244,15 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
return nil, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
|
||||
}
|
||||
|
||||
ostreeURL := ""
|
||||
if options.OSTree != nil {
|
||||
if options.OSTree.ParentRef != "" && options.OSTree.URL == "" {
|
||||
// specifying parent ref also requires URL
|
||||
return nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
|
||||
if err := options.OSTree.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ostreeURL = options.OSTree.URL
|
||||
}
|
||||
|
||||
if t.bootISO && t.rpmOstree {
|
||||
// ostree-based ISOs require a URL from which to pull a payload commit
|
||||
if ostreeURL == "" {
|
||||
if options.OSTree == nil || options.OSTree.URL == "" {
|
||||
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
vendor/github.com/osbuild/images/pkg/distro/rhel8/images.go
generated
vendored
59
vendor/github.com/osbuild/images/pkg/distro/rhel8/images.go
generated
vendored
|
|
@ -133,22 +133,6 @@ func osCustomizations(
|
|||
osc.SElinux = "targeted"
|
||||
}
|
||||
|
||||
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
|
||||
if t.rpmOstree {
|
||||
panic("unexpected oscap options for ostree image type")
|
||||
}
|
||||
var datastream = oscapConfig.DataStream
|
||||
if datastream == "" {
|
||||
datastream = oscap.DefaultRHEL8Datastream(t.arch.distro.isRHEL())
|
||||
}
|
||||
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
|
||||
osbuild.OscapConfig{
|
||||
Datastream: datastream,
|
||||
ProfileID: oscapConfig.ProfileID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if t.arch.distro.isRHEL() && options.Facts != nil {
|
||||
osc.FactAPIType = &options.Facts.APIType
|
||||
}
|
||||
|
|
@ -197,6 +181,49 @@ func osCustomizations(
|
|||
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
|
||||
}
|
||||
|
||||
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
|
||||
if t.rpmOstree {
|
||||
panic("unexpected oscap options for ostree image type")
|
||||
}
|
||||
var datastream = oscapConfig.DataStream
|
||||
if datastream == "" {
|
||||
datastream = oscap.DefaultRHEL8Datastream(t.arch.distro.isRHEL())
|
||||
}
|
||||
|
||||
oscapStageOptions := osbuild.OscapConfig{
|
||||
Datastream: datastream,
|
||||
ProfileID: oscapConfig.ProfileID,
|
||||
}
|
||||
|
||||
if oscapConfig.Tailoring != nil {
|
||||
newProfile, tailoringFilepath, tailoringDir, err := oscap.GetTailoringFile(oscapConfig.ProfileID)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error creating tailoring file options: %v", err))
|
||||
}
|
||||
|
||||
tailoringOptions := osbuild.OscapAutotailorConfig{
|
||||
Selected: oscapConfig.Tailoring.Selected,
|
||||
Unselected: oscapConfig.Tailoring.Unselected,
|
||||
NewProfile: newProfile,
|
||||
}
|
||||
|
||||
osc.OpenSCAPTailorConfig = osbuild.NewOscapAutotailorStageOptions(
|
||||
tailoringFilepath,
|
||||
oscapStageOptions,
|
||||
tailoringOptions,
|
||||
)
|
||||
|
||||
// overwrite the profile id with the new tailoring id
|
||||
oscapStageOptions.ProfileID = newProfile
|
||||
oscapStageOptions.Tailoring = tailoringFilepath
|
||||
|
||||
// add the parent directory for the tailoring file
|
||||
osc.Directories = append(osc.Directories, tailoringDir)
|
||||
}
|
||||
|
||||
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(oscapStageOptions)
|
||||
}
|
||||
|
||||
osc.ShellInit = imageConfig.ShellInit
|
||||
|
||||
osc.Grub2Config = imageConfig.Grub2Config
|
||||
|
|
|
|||
25
vendor/github.com/osbuild/images/pkg/distro/rhel8/imagetype.go
generated
vendored
25
vendor/github.com/osbuild/images/pkg/distro/rhel8/imagetype.go
generated
vendored
|
|
@ -19,7 +19,6 @@ import (
|
|||
"github.com/osbuild/images/pkg/distro"
|
||||
"github.com/osbuild/images/pkg/image"
|
||||
"github.com/osbuild/images/pkg/manifest"
|
||||
"github.com/osbuild/images/pkg/ostree"
|
||||
"github.com/osbuild/images/pkg/platform"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
)
|
||||
|
|
@ -279,19 +278,16 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
|
||||
}
|
||||
|
||||
ostreeURL := ""
|
||||
if options.OSTree != nil {
|
||||
if options.OSTree.ParentRef != "" && options.OSTree.URL == "" {
|
||||
// specifying parent ref also requires URL
|
||||
return nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
|
||||
if err := options.OSTree.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ostreeURL = options.OSTree.URL
|
||||
}
|
||||
|
||||
if t.bootISO && t.rpmOstree {
|
||||
// ostree-based ISOs require a URL from which to pull a payload commit
|
||||
if ostreeURL == "" {
|
||||
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
if options.OSTree == nil || options.OSTree.URL == "" {
|
||||
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
}
|
||||
|
||||
if t.name == "edge-simplified-installer" {
|
||||
|
|
@ -331,8 +327,8 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
|
||||
if t.name == "edge-raw-image" {
|
||||
// ostree-based bootable images require a URL from which to pull a payload commit
|
||||
if ostreeURL == "" {
|
||||
return warnings, fmt.Errorf("edge raw images require specifying a URL from which to retrieve the OSTree commit")
|
||||
if options.OSTree == nil || options.OSTree.URL == "" {
|
||||
return warnings, fmt.Errorf("%q images require specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
}
|
||||
|
||||
allowed := []string{"User", "Group"}
|
||||
|
|
@ -357,7 +353,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
}
|
||||
}
|
||||
|
||||
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree && (!t.bootable || t.bootISO) {
|
||||
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree && t.name != "edge-raw-image" && t.name != "edge-simplified-installer" {
|
||||
return warnings, fmt.Errorf("kernel boot parameter customizations are not supported for ostree types")
|
||||
}
|
||||
|
||||
|
|
@ -373,12 +369,10 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
}
|
||||
|
||||
if osc := customizations.GetOpenSCAP(); osc != nil {
|
||||
// only add support for RHEL 8.7 and above.
|
||||
if common.VersionLessThan(t.arch.distro.osVersion, "8.7") {
|
||||
if t.arch.distro.osVersion == "9.0" {
|
||||
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported os version: %s", t.arch.distro.osVersion))
|
||||
}
|
||||
supported := oscap.IsProfileAllowed(osc.ProfileID, oscapProfileAllowList)
|
||||
if !supported {
|
||||
if !oscap.IsProfileAllowed(osc.ProfileID, oscapProfileAllowList) {
|
||||
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported profile: %s", osc.ProfileID))
|
||||
}
|
||||
if t.rpmOstree {
|
||||
|
|
@ -397,7 +391,6 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
|
|
|
|||
59
vendor/github.com/osbuild/images/pkg/distro/rhel9/images.go
generated
vendored
59
vendor/github.com/osbuild/images/pkg/distro/rhel9/images.go
generated
vendored
|
|
@ -130,22 +130,6 @@ func osCustomizations(
|
|||
osc.SElinux = "targeted"
|
||||
}
|
||||
|
||||
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
|
||||
if t.rpmOstree {
|
||||
panic("unexpected oscap options for ostree image type")
|
||||
}
|
||||
var datastream = oscapConfig.DataStream
|
||||
if datastream == "" {
|
||||
datastream = oscap.DefaultRHEL9Datastream(t.arch.distro.isRHEL())
|
||||
}
|
||||
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
|
||||
osbuild.OscapConfig{
|
||||
Datastream: datastream,
|
||||
ProfileID: oscapConfig.ProfileID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if t.arch.distro.isRHEL() && options.Facts != nil {
|
||||
osc.FactAPIType = &options.Facts.APIType
|
||||
}
|
||||
|
|
@ -194,6 +178,49 @@ func osCustomizations(
|
|||
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
|
||||
}
|
||||
|
||||
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
|
||||
if t.rpmOstree {
|
||||
panic("unexpected oscap options for ostree image type")
|
||||
}
|
||||
var datastream = oscapConfig.DataStream
|
||||
if datastream == "" {
|
||||
datastream = oscap.DefaultRHEL9Datastream(t.arch.distro.isRHEL())
|
||||
}
|
||||
|
||||
oscapStageOptions := osbuild.OscapConfig{
|
||||
Datastream: datastream,
|
||||
ProfileID: oscapConfig.ProfileID,
|
||||
}
|
||||
|
||||
if oscapConfig.Tailoring != nil {
|
||||
newProfile, tailoringFilepath, tailoringDir, err := oscap.GetTailoringFile(oscapConfig.ProfileID)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error creating tailoring file options: %v", err))
|
||||
}
|
||||
|
||||
tailoringOptions := osbuild.OscapAutotailorConfig{
|
||||
Selected: oscapConfig.Tailoring.Selected,
|
||||
Unselected: oscapConfig.Tailoring.Unselected,
|
||||
NewProfile: newProfile,
|
||||
}
|
||||
|
||||
osc.OpenSCAPTailorConfig = osbuild.NewOscapAutotailorStageOptions(
|
||||
tailoringFilepath,
|
||||
oscapStageOptions,
|
||||
tailoringOptions,
|
||||
)
|
||||
|
||||
// overwrite the profile id with the new tailoring id
|
||||
oscapStageOptions.ProfileID = newProfile
|
||||
oscapStageOptions.Tailoring = tailoringFilepath
|
||||
|
||||
// add the parent directory for the tailoring file
|
||||
osc.Directories = append(osc.Directories, tailoringDir)
|
||||
}
|
||||
|
||||
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(oscapStageOptions)
|
||||
}
|
||||
|
||||
osc.ShellInit = imageConfig.ShellInit
|
||||
|
||||
osc.Grub2Config = imageConfig.Grub2Config
|
||||
|
|
|
|||
14
vendor/github.com/osbuild/images/pkg/distro/rhel9/imagetype.go
generated
vendored
14
vendor/github.com/osbuild/images/pkg/distro/rhel9/imagetype.go
generated
vendored
|
|
@ -19,7 +19,6 @@ import (
|
|||
"github.com/osbuild/images/pkg/distro"
|
||||
"github.com/osbuild/images/pkg/image"
|
||||
"github.com/osbuild/images/pkg/manifest"
|
||||
"github.com/osbuild/images/pkg/ostree"
|
||||
"github.com/osbuild/images/pkg/platform"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
)
|
||||
|
|
@ -282,19 +281,16 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
|
||||
}
|
||||
|
||||
ostreeURL := ""
|
||||
if options.OSTree != nil {
|
||||
if options.OSTree.ParentRef != "" && options.OSTree.URL == "" {
|
||||
// specifying parent ref also requires URL
|
||||
return nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
|
||||
if err := options.OSTree.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ostreeURL = options.OSTree.URL
|
||||
}
|
||||
|
||||
if t.bootISO && t.rpmOstree {
|
||||
// ostree-based ISOs require a URL from which to pull a payload commit
|
||||
if ostreeURL == "" {
|
||||
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
if options.OSTree == nil || options.OSTree.URL == "" {
|
||||
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
}
|
||||
|
||||
if t.name == "edge-simplified-installer" {
|
||||
|
|
@ -345,7 +341,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
|
|||
|
||||
if t.name == "edge-raw-image" || t.name == "edge-ami" || t.name == "edge-vsphere" {
|
||||
// ostree-based bootable images require a URL from which to pull a payload commit
|
||||
if ostreeURL == "" {
|
||||
if options.OSTree == nil || options.OSTree.URL == "" {
|
||||
return warnings, fmt.Errorf("%q images require specifying a URL from which to retrieve the OSTree commit", t.name)
|
||||
}
|
||||
|
||||
|
|
|
|||
83
vendor/github.com/osbuild/images/pkg/manifest/os.go
generated
vendored
83
vendor/github.com/osbuild/images/pkg/manifest/os.go
generated
vendored
|
|
@ -93,35 +93,36 @@ type OSCustomizations struct {
|
|||
ShellInit []shell.InitFile
|
||||
|
||||
// TODO: drop osbuild types from the API
|
||||
Firewall *osbuild.FirewallStageOptions
|
||||
Grub2Config *osbuild.GRUB2Config
|
||||
Sysconfig []*osbuild.SysconfigStageOptions
|
||||
SystemdLogind []*osbuild.SystemdLogindStageOptions
|
||||
CloudInit []*osbuild.CloudInitStageOptions
|
||||
Modprobe []*osbuild.ModprobeStageOptions
|
||||
DracutConf []*osbuild.DracutConfStageOptions
|
||||
SystemdUnit []*osbuild.SystemdUnitStageOptions
|
||||
Authselect *osbuild.AuthselectStageOptions
|
||||
SELinuxConfig *osbuild.SELinuxConfigStageOptions
|
||||
Tuned *osbuild.TunedStageOptions
|
||||
Tmpfilesd []*osbuild.TmpfilesdStageOptions
|
||||
PamLimitsConf []*osbuild.PamLimitsConfStageOptions
|
||||
Sysctld []*osbuild.SysctldStageOptions
|
||||
DNFConfig []*osbuild.DNFConfigStageOptions
|
||||
DNFAutomaticConfig *osbuild.DNFAutomaticConfigStageOptions
|
||||
YUMConfig *osbuild.YumConfigStageOptions
|
||||
YUMRepos []*osbuild.YumReposStageOptions
|
||||
SshdConfig *osbuild.SshdConfigStageOptions
|
||||
GCPGuestAgentConfig *osbuild.GcpGuestAgentConfigOptions
|
||||
AuthConfig *osbuild.AuthconfigStageOptions
|
||||
PwQuality *osbuild.PwqualityConfStageOptions
|
||||
OpenSCAPConfig *osbuild.OscapRemediationStageOptions
|
||||
NTPServers []osbuild.ChronyConfigServer
|
||||
WAAgentConfig *osbuild.WAAgentConfStageOptions
|
||||
UdevRules *osbuild.UdevRulesStageOptions
|
||||
WSLConfig *osbuild.WSLConfStageOptions
|
||||
LeapSecTZ *string
|
||||
FactAPIType *facts.APIType
|
||||
Firewall *osbuild.FirewallStageOptions
|
||||
Grub2Config *osbuild.GRUB2Config
|
||||
Sysconfig []*osbuild.SysconfigStageOptions
|
||||
SystemdLogind []*osbuild.SystemdLogindStageOptions
|
||||
CloudInit []*osbuild.CloudInitStageOptions
|
||||
Modprobe []*osbuild.ModprobeStageOptions
|
||||
DracutConf []*osbuild.DracutConfStageOptions
|
||||
SystemdUnit []*osbuild.SystemdUnitStageOptions
|
||||
Authselect *osbuild.AuthselectStageOptions
|
||||
SELinuxConfig *osbuild.SELinuxConfigStageOptions
|
||||
Tuned *osbuild.TunedStageOptions
|
||||
Tmpfilesd []*osbuild.TmpfilesdStageOptions
|
||||
PamLimitsConf []*osbuild.PamLimitsConfStageOptions
|
||||
Sysctld []*osbuild.SysctldStageOptions
|
||||
DNFConfig []*osbuild.DNFConfigStageOptions
|
||||
DNFAutomaticConfig *osbuild.DNFAutomaticConfigStageOptions
|
||||
YUMConfig *osbuild.YumConfigStageOptions
|
||||
YUMRepos []*osbuild.YumReposStageOptions
|
||||
SshdConfig *osbuild.SshdConfigStageOptions
|
||||
GCPGuestAgentConfig *osbuild.GcpGuestAgentConfigOptions
|
||||
AuthConfig *osbuild.AuthconfigStageOptions
|
||||
PwQuality *osbuild.PwqualityConfStageOptions
|
||||
OpenSCAPTailorConfig *osbuild.OscapAutotailorStageOptions
|
||||
OpenSCAPConfig *osbuild.OscapRemediationStageOptions
|
||||
NTPServers []osbuild.ChronyConfigServer
|
||||
WAAgentConfig *osbuild.WAAgentConfStageOptions
|
||||
UdevRules *osbuild.UdevRulesStageOptions
|
||||
WSLConfig *osbuild.WSLConfStageOptions
|
||||
LeapSecTZ *string
|
||||
FactAPIType *facts.APIType
|
||||
|
||||
Subscription *subscription.ImageOptions
|
||||
RHSMConfig map[subscription.RHSMStatus]*osbuild.RHSMStageOptions
|
||||
|
|
@ -293,6 +294,10 @@ func (p *OS) getBuildPackages(distro Distro) []string {
|
|||
packages = append(packages, "skopeo")
|
||||
}
|
||||
|
||||
if p.OpenSCAPTailorConfig != nil {
|
||||
packages = append(packages, "openscap-utils")
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
|
|
@ -655,10 +660,6 @@ func (p *OS) serialize() osbuild.Pipeline {
|
|||
pipeline.AddStage(bootloader)
|
||||
}
|
||||
|
||||
if p.OpenSCAPConfig != nil {
|
||||
pipeline.AddStage(osbuild.NewOscapRemediationStage(p.OpenSCAPConfig))
|
||||
}
|
||||
|
||||
if p.FactAPIType != nil {
|
||||
pipeline.AddStage(osbuild.NewRHSMFactsStage(&osbuild.RHSMFactsStageOptions{
|
||||
Facts: osbuild.RHSMFacts{
|
||||
|
|
@ -715,6 +716,22 @@ func (p *OS) serialize() osbuild.Pipeline {
|
|||
pipeline.AddStage(osbuild.NewWSLConfStage(wslConf))
|
||||
}
|
||||
|
||||
if p.OpenSCAPTailorConfig != nil {
|
||||
if p.OpenSCAPConfig == nil {
|
||||
// This is a programming error, since it doesn't make sense
|
||||
// to have tailoring configs without openscap config.
|
||||
panic(fmt.Errorf("OpenSCAP autotailoring cannot be set if no OpenSCAP config has been provided"))
|
||||
}
|
||||
pipeline.AddStage(osbuild.NewOscapAutotailorStage(p.OpenSCAPTailorConfig))
|
||||
}
|
||||
|
||||
// NOTE: We need to run the OpenSCAP stages as the last stage before SELinux
|
||||
// since the remediation may change file permissions and other aspects of the
|
||||
// hardened image
|
||||
if p.OpenSCAPConfig != nil {
|
||||
pipeline.AddStage(osbuild.NewOscapRemediationStage(p.OpenSCAPConfig))
|
||||
}
|
||||
|
||||
if p.SElinux != "" {
|
||||
pipeline.AddStage(osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
|
||||
FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.SElinux),
|
||||
|
|
|
|||
47
vendor/github.com/osbuild/images/pkg/osbuild/oscap_autotailor_stage.go
generated
vendored
Normal file
47
vendor/github.com/osbuild/images/pkg/osbuild/oscap_autotailor_stage.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package osbuild
|
||||
|
||||
import "fmt"
|
||||
|
||||
type OscapAutotailorStageOptions struct {
|
||||
Filepath string `json:"filepath"`
|
||||
Config OscapAutotailorConfig `json:"config"`
|
||||
}
|
||||
type OscapAutotailorConfig struct {
|
||||
OscapConfig
|
||||
NewProfile string `json:"new_profile"`
|
||||
Selected []string `json:"selected,omitempty"`
|
||||
Unselected []string `json:"unselected,omitempty"`
|
||||
}
|
||||
|
||||
func (OscapAutotailorStageOptions) isStageOptions() {}
|
||||
|
||||
func (c OscapAutotailorConfig) validate() error {
|
||||
if c.NewProfile == "" {
|
||||
return fmt.Errorf("'new_profile' must be specified")
|
||||
}
|
||||
// reuse the oscap validation
|
||||
return c.OscapConfig.validate()
|
||||
}
|
||||
|
||||
func NewOscapAutotailorStage(options *OscapAutotailorStageOptions) *Stage {
|
||||
if err := options.Config.validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Stage{
|
||||
Type: "org.osbuild.oscap.autotailor",
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func NewOscapAutotailorStageOptions(filepath string, oscapOptions OscapConfig, autotailorOptions OscapAutotailorConfig) *OscapAutotailorStageOptions {
|
||||
return &OscapAutotailorStageOptions{
|
||||
Filepath: filepath,
|
||||
Config: OscapAutotailorConfig{
|
||||
OscapConfig: oscapOptions,
|
||||
NewProfile: autotailorOptions.NewProfile,
|
||||
Selected: autotailorOptions.Selected,
|
||||
Unselected: autotailorOptions.Unselected,
|
||||
},
|
||||
}
|
||||
}
|
||||
1
vendor/github.com/osbuild/images/pkg/osbuild/oscap_remediation_stage.go
generated
vendored
1
vendor/github.com/osbuild/images/pkg/osbuild/oscap_remediation_stage.go
generated
vendored
|
|
@ -76,6 +76,7 @@ func NewOscapRemediationStageOptions(options OscapConfig) *OscapRemediationStage
|
|||
ProfileID: options.ProfileID,
|
||||
Datastream: options.Datastream,
|
||||
DatastreamID: options.DatastreamID,
|
||||
Tailoring: options.Tailoring,
|
||||
XCCDFID: options.XCCDFID,
|
||||
BenchmarkID: options.BenchmarkID,
|
||||
ArfResult: options.ArfResult,
|
||||
|
|
|
|||
2
vendor/github.com/osbuild/images/pkg/ostree/errors.go
generated
vendored
2
vendor/github.com/osbuild/images/pkg/ostree/errors.go
generated
vendored
|
|
@ -18,7 +18,7 @@ func NewResolveRefError(msg string, args ...interface{}) ResolveRefError {
|
|||
return ResolveRefError{msg: fmt.Sprintf(msg, args...)}
|
||||
}
|
||||
|
||||
// InvalidParamsError is returned when a parameter is invalid (e.g., malformed
|
||||
// RefError is returned when a parameter is invalid (e.g., malformed
|
||||
// or contains illegal characters).
|
||||
type RefError struct {
|
||||
msg string
|
||||
|
|
|
|||
86
vendor/github.com/osbuild/images/pkg/ostree/ostree.go
generated
vendored
86
vendor/github.com/osbuild/images/pkg/ostree/ostree.go
generated
vendored
|
|
@ -17,7 +17,10 @@ import (
|
|||
"github.com/osbuild/images/pkg/rhsm"
|
||||
)
|
||||
|
||||
var ostreeRefRE = regexp.MustCompile(`^(?:[\w\d][-._\w\d]*\/)*[\w\d][-._\w\d]*$`)
|
||||
var (
|
||||
ostreeRefRE = regexp.MustCompile(`^(?:[\w\d][-._\w\d]*\/)*[\w\d][-._\w\d]*$`)
|
||||
ostreeCommitRE = regexp.MustCompile("^[0-9a-f]{64}$")
|
||||
)
|
||||
|
||||
// SourceSpec serves as input for ResolveParams, and contains all necessary
|
||||
// variables to resolve a ref, which can then be turned into a CommitSpec.
|
||||
|
|
@ -71,6 +74,54 @@ type ImageOptions struct {
|
|||
RHSM bool `json:"rhsm"`
|
||||
}
|
||||
|
||||
// Validate the image options. This doesn't verify the existence of any remote
|
||||
// objects and does not guarantee that refs will be successfully resolved. It
|
||||
// only checks that the values and value combinations are valid.
|
||||
//
|
||||
// The function checks the following:
|
||||
// - The ImageRef, if specified, is a valid ref and does not look like a
|
||||
// checksum.
|
||||
// - The ParentRef, if specified, must be a valid ref or a checksum.
|
||||
// - If the ParentRef is specified, the URL must also be specified.
|
||||
// - URLs must be valid.
|
||||
func (options ImageOptions) Validate() error {
|
||||
if ref := options.ImageRef; ref != "" {
|
||||
// image ref must not look like a checksum
|
||||
if verifyChecksum(ref) {
|
||||
return NewRefError("ostree image ref looks like a checksum %q", ref)
|
||||
}
|
||||
if !verifyRef(ref) {
|
||||
return NewRefError("invalid ostree image ref %q", ref)
|
||||
}
|
||||
}
|
||||
|
||||
if parent := options.ParentRef; parent != "" {
|
||||
if !verifyChecksum(parent) && !verifyRef(parent) {
|
||||
return NewRefError("invalid ostree parent ref or commit %q", parent)
|
||||
}
|
||||
|
||||
// valid URL required
|
||||
if purl := options.URL; purl == "" {
|
||||
return NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
|
||||
}
|
||||
}
|
||||
|
||||
// whether required or not, any URL specified must be valid
|
||||
if purl := options.URL; purl != "" {
|
||||
if _, err := url.ParseRequestURI(purl); err != nil {
|
||||
return fmt.Errorf("ostree URL %q is invalid", purl)
|
||||
}
|
||||
}
|
||||
|
||||
if curl := options.ContentURL; curl != "" {
|
||||
if _, err := url.ParseRequestURI(curl); err != nil {
|
||||
return fmt.Errorf("ostree content URL %q is invalid", curl)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remote defines the options that can be set for an OSTree Remote configuration.
|
||||
type Remote struct {
|
||||
Name string
|
||||
|
|
@ -79,10 +130,14 @@ type Remote struct {
|
|||
GPGKeyPaths []string
|
||||
}
|
||||
|
||||
func VerifyRef(ref string) bool {
|
||||
func verifyRef(ref string) bool {
|
||||
return len(ref) > 0 && ostreeRefRE.MatchString(ref)
|
||||
}
|
||||
|
||||
func verifyChecksum(commit string) bool {
|
||||
return len(commit) > 0 && ostreeCommitRE.MatchString(commit)
|
||||
}
|
||||
|
||||
// ResolveRef resolves the URL path specified by the location and ref
|
||||
// (location+"refs/heads/"+ref) and returns the commit ID for the named ref. If
|
||||
// there is an error, it will be of type ResolveRefError.
|
||||
|
|
@ -109,7 +164,7 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
|
|||
if ca != nil {
|
||||
caCertPEM, err := os.ReadFile(*ca)
|
||||
if err != nil {
|
||||
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
|
||||
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM(caCertPEM)
|
||||
|
|
@ -121,7 +176,7 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
|
|||
|
||||
cert, err := tls.LoadX509KeyPair(subs.Consumer.ConsumerCert, subs.Consumer.ConsumerKey)
|
||||
if err != nil {
|
||||
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
|
||||
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
|
||||
}
|
||||
tlsConf.Certificates = []tls.Certificate{cert}
|
||||
|
||||
|
|
@ -137,7 +192,7 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
|
|||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
|
||||
return "", NewResolveRefError("error preparing ostree resolve request: %s", err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
|
@ -160,26 +215,37 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
|
|||
return checksum, nil
|
||||
}
|
||||
|
||||
// Resolve the ostree source specification into a commit specification.
|
||||
// Resolve the ostree source specification to a commit specification.
|
||||
//
|
||||
// If a URL is defined in the source specification, the checksum of the ref is
|
||||
// resolved, otherwise the checksum is an empty string. Failure to resolve the
|
||||
// checksum results in a ResolveRefError.
|
||||
//
|
||||
// If the ref is already a checksum (64 alphanumeric characters), it is not
|
||||
// resolved or checked against the repository.
|
||||
//
|
||||
// If the ref is malformed, the function returns with a RefError.
|
||||
func Resolve(source SourceSpec) (CommitSpec, error) {
|
||||
if !VerifyRef(source.Ref) {
|
||||
return CommitSpec{}, NewRefError("Invalid ostree ref %q", source.Ref)
|
||||
}
|
||||
|
||||
commit := CommitSpec{
|
||||
Ref: source.Ref,
|
||||
URL: source.URL,
|
||||
}
|
||||
|
||||
if source.RHSM {
|
||||
commit.Secrets = "org.osbuild.rhsm.consumer"
|
||||
}
|
||||
|
||||
if verifyChecksum(source.Ref) {
|
||||
// the ref is a commit: return as is
|
||||
commit.Checksum = source.Ref
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
if !verifyRef(source.Ref) {
|
||||
// the ref is not a commit and it's also an invalid ref
|
||||
return CommitSpec{}, NewRefError("Invalid ostree ref or commit %q", source.Ref)
|
||||
}
|
||||
|
||||
// URL set: Resolve checksum
|
||||
if source.URL != "" {
|
||||
// If a URL is specified, we need to fetch the commit at the URL.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue