2016-06-28 15:24:25 +08:00
package directory
import (
2018-02-23 17:45:56 +08:00
"context"
2022-07-01 04:14:18 +08:00
"errors"
2022-07-01 03:05:20 +08:00
"fmt"
2016-06-28 15:24:25 +08:00
"io"
"os"
2017-11-07 04:58:55 +08:00
"path/filepath"
2020-01-28 21:21:33 +08:00
"runtime"
2016-06-28 15:24:25 +08:00
2022-07-02 03:09:58 +08:00
"github.com/containers/image/v5/internal/imagedestination/impl"
"github.com/containers/image/v5/internal/imagedestination/stubs"
"github.com/containers/image/v5/internal/private"
2021-08-21 12:47:40 +08:00
"github.com/containers/image/v5/internal/putblobdigest"
2022-07-06 00:54:19 +08:00
"github.com/containers/image/v5/internal/signature"
2019-10-26 04:27:45 +08:00
"github.com/containers/image/v5/types"
2024-04-11 15:29:39 +08:00
"github.com/containers/storage/pkg/fileutils"
2017-01-09 18:10:30 +08:00
"github.com/opencontainers/go-digest"
2017-11-07 04:58:55 +08:00
"github.com/sirupsen/logrus"
2016-06-28 15:24:25 +08:00
)
2018-02-20 01:06:10 +08:00
const version = "Directory Transport Version: 1.1\n"
2017-11-07 04:58:55 +08:00
// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
// using the 'dir' transport
2022-07-01 04:14:18 +08:00
var ErrNotContainerImageDir = errors . New ( "not a containers image directory, don't want to overwrite important data" )
2017-11-07 04:58:55 +08:00
2016-06-28 15:24:25 +08:00
type dirImageDestination struct {
2022-07-02 03:09:58 +08:00
impl . Compat
2022-07-02 05:48:25 +08:00
impl . PropertyMethodsInitialize
2024-11-19 04:51:43 +08:00
stubs . IgnoresOriginalOCIConfig
2022-07-02 03:09:58 +08:00
stubs . NoPutBlobPartialInitialize
2022-07-02 05:08:58 +08:00
stubs . AlwaysSupportsSignatures
2022-07-02 03:09:58 +08:00
2022-07-02 06:26:05 +08:00
ref dirReference
2016-06-28 15:24:25 +08:00
}
2017-11-07 04:58:55 +08:00
// newImageDestination returns an ImageDestination for writing to a directory.
2022-07-02 03:09:58 +08:00
func newImageDestination ( sys * types . SystemContext , ref dirReference ) ( private . ImageDestination , error ) {
2021-07-27 19:57:31 +08:00
desiredLayerCompression := types . PreserveOriginal
if sys != nil {
if sys . DirForceCompress {
desiredLayerCompression = types . Compress
2021-07-27 20:20:45 +08:00
if sys . DirForceDecompress {
2022-07-01 03:05:20 +08:00
return nil , fmt . Errorf ( "Cannot compress and decompress at the same time" )
2021-07-27 20:20:45 +08:00
}
}
if sys . DirForceDecompress {
2021-07-27 19:57:31 +08:00
desiredLayerCompression = types . Decompress
}
}
2017-11-07 04:58:55 +08:00
// If directory exists check if it is empty
// if not empty, check whether the contents match that of a container image directory and overwrite the contents
// if the contents don't match throw an error
2022-07-02 02:10:17 +08:00
dirExists , err := pathExists ( ref . resolvedPath )
2017-11-07 04:58:55 +08:00
if err != nil {
2022-07-12 16:48:43 +08:00
return nil , fmt . Errorf ( "checking for path %q: %w" , ref . resolvedPath , err )
2017-11-07 04:58:55 +08:00
}
if dirExists {
2022-07-02 02:10:17 +08:00
isEmpty , err := isDirEmpty ( ref . resolvedPath )
2017-11-07 04:58:55 +08:00
if err != nil {
return nil , err
}
if ! isEmpty {
2022-07-02 02:10:17 +08:00
versionExists , err := pathExists ( ref . versionPath ( ) )
2017-11-07 04:58:55 +08:00
if err != nil {
2022-07-12 16:48:43 +08:00
return nil , fmt . Errorf ( "checking if path exists %q: %w" , ref . versionPath ( ) , err )
2017-11-07 04:58:55 +08:00
}
if versionExists {
2022-07-02 02:10:17 +08:00
contents , err := os . ReadFile ( ref . versionPath ( ) )
2017-11-07 04:58:55 +08:00
if err != nil {
return nil , err
}
// check if contents of version file is what we expect it to be
if string ( contents ) != version {
return nil , ErrNotContainerImageDir
}
} else {
return nil , ErrNotContainerImageDir
}
// delete directory contents so that only one image is in the directory at a time
2022-07-02 02:10:17 +08:00
if err = removeDirContents ( ref . resolvedPath ) ; err != nil {
2022-07-12 16:48:43 +08:00
return nil , fmt . Errorf ( "erasing contents in %q: %w" , ref . resolvedPath , err )
2017-11-07 04:58:55 +08:00
}
2022-07-02 02:10:17 +08:00
logrus . Debugf ( "overwriting existing container image directory %q" , ref . resolvedPath )
2017-11-07 04:58:55 +08:00
}
} else {
// create directory if it doesn't exist
2022-07-02 02:10:17 +08:00
if err := os . MkdirAll ( ref . resolvedPath , 0755 ) ; err != nil {
2022-07-12 16:48:43 +08:00
return nil , fmt . Errorf ( "unable to create directory %q: %w" , ref . resolvedPath , err )
2017-11-07 04:58:55 +08:00
}
}
// create version file
2022-07-02 02:10:17 +08:00
err = os . WriteFile ( ref . versionPath ( ) , [ ] byte ( version ) , 0644 )
2017-11-07 04:58:55 +08:00
if err != nil {
2022-07-12 16:48:43 +08:00
return nil , fmt . Errorf ( "creating version file %q: %w" , ref . versionPath ( ) , err )
2017-11-07 04:58:55 +08:00
}
2022-07-02 02:10:17 +08:00
2022-07-02 03:09:58 +08:00
d := & dirImageDestination {
2022-07-02 05:48:25 +08:00
PropertyMethodsInitialize : impl . PropertyMethods ( impl . Properties {
2022-07-02 06:15:36 +08:00
SupportedManifestMIMETypes : nil ,
2022-07-02 06:26:05 +08:00
DesiredLayerCompression : desiredLayerCompression ,
2022-07-02 06:38:44 +08:00
AcceptsForeignLayerURLs : false ,
2022-07-02 05:48:25 +08:00
MustMatchRuntimeOS : false ,
IgnoresEmbeddedDockerReference : false , // N/A, DockerReference() returns nil.
2022-10-14 03:30:37 +08:00
HasThreadSafePutBlob : true ,
2022-07-02 05:48:25 +08:00
} ) ,
2022-07-02 03:09:58 +08:00
NoPutBlobPartialInitialize : stubs . NoPutBlobPartial ( ref ) ,
2022-07-02 06:26:05 +08:00
ref : ref ,
2022-07-02 03:09:58 +08:00
}
d . Compat = impl . AddCompat ( d )
2017-11-07 04:58:55 +08:00
return d , nil
2016-06-28 15:24:25 +08:00
}
2016-07-03 03:22:20 +08:00
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
func ( d * dirImageDestination ) Reference ( ) types . ImageReference {
return d . ref
2016-06-28 15:24:25 +08:00
}
2016-09-06 04:10:47 +08:00
// Close removes resources associated with an initialized ImageDestination, if any.
2017-02-06 15:13:00 +08:00
func ( d * dirImageDestination ) Close ( ) error {
return nil
2016-09-06 04:10:47 +08:00
}
2022-07-02 03:09:58 +08:00
// PutBlobWithOptions writes contents of stream and returns data representing the result.
2021-08-21 12:27:56 +08:00
// inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents.
2016-09-15 04:48:26 +08:00
// inputInfo.Size is the expected length of stream, if known.
2022-07-02 03:09:58 +08:00
// inputInfo.MediaType describes the blob format, if known.
2016-08-05 01:14:22 +08:00
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
// to any other readers for download using the supplied digest.
2023-03-22 02:26:10 +08:00
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlobWithOptions MUST 1) fail, and 2) delete any data stored so far.
func ( d * dirImageDestination ) PutBlobWithOptions ( ctx context . Context , stream io . Reader , inputInfo types . BlobInfo , options private . PutBlobOptions ) ( private . UploadedBlob , error ) {
2022-04-14 01:33:42 +08:00
blobFile , err := os . CreateTemp ( d . ref . path , "dir-put-blob" )
2016-06-28 15:24:25 +08:00
if err != nil {
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { } , err
2016-06-28 15:24:25 +08:00
}
2016-08-05 01:14:22 +08:00
succeeded := false
2020-01-28 21:21:33 +08:00
explicitClosed := false
2016-08-05 01:14:22 +08:00
defer func ( ) {
2020-01-28 21:21:33 +08:00
if ! explicitClosed {
blobFile . Close ( )
}
2016-08-05 01:14:22 +08:00
if ! succeeded {
os . Remove ( blobFile . Name ( ) )
}
} ( )
2021-08-21 12:47:40 +08:00
digester , stream := putblobdigest . DigestIfCanonicalUnknown ( stream , inputInfo )
2018-04-10 22:25:26 +08:00
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
2021-08-22 02:26:35 +08:00
size , err := io . Copy ( blobFile , stream )
2016-08-27 09:50:27 +08:00
if err != nil {
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { } , err
2016-06-28 15:24:25 +08:00
}
2021-08-21 12:31:51 +08:00
blobDigest := digester . Digest ( )
2016-09-15 04:48:26 +08:00
if inputInfo . Size != - 1 && size != inputInfo . Size {
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { } , fmt . Errorf ( "Size mismatch when copying %s, expected %d, got %d" , blobDigest , inputInfo . Size , size )
2016-08-27 09:50:27 +08:00
}
2016-08-05 01:14:22 +08:00
if err := blobFile . Sync ( ) ; err != nil {
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { } , err
2016-06-28 15:24:25 +08:00
}
2020-01-28 21:21:33 +08:00
2020-01-28 21:49:07 +08:00
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
// On Windows, the “permissions of newly created files” argument to syscall.Open is
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
// always fails on Windows.
2020-01-28 21:21:33 +08:00
if runtime . GOOS != "windows" {
if err := blobFile . Chmod ( 0644 ) ; err != nil {
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { } , err
2020-01-28 21:21:33 +08:00
}
2016-08-05 01:14:22 +08:00
}
2020-01-28 21:21:33 +08:00
2024-04-18 04:26:46 +08:00
blobPath , err := d . ref . layerPath ( blobDigest )
if err != nil {
return private . UploadedBlob { } , err
}
2020-01-28 21:21:33 +08:00
// need to explicitly close the file, since a rename won't otherwise not work on Windows
blobFile . Close ( )
explicitClosed = true
2016-08-05 01:14:22 +08:00
if err := os . Rename ( blobFile . Name ( ) , blobPath ) ; err != nil {
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { } , err
2016-08-05 01:14:22 +08:00
}
succeeded = true
2023-03-22 02:26:10 +08:00
return private . UploadedBlob { Digest : blobDigest , Size : size } , nil
2016-06-28 15:24:25 +08:00
}
2022-07-02 03:09:58 +08:00
// TryReusingBlobWithOptions checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination
2018-08-23 22:13:09 +08:00
// (e.g. if the blob is a filesystem layer, this signifies that the changes it describes need to be applied again when composing a filesystem tree).
// info.Digest must not be empty.
2023-03-22 00:30:05 +08:00
// If the blob has been successfully reused, returns (true, info, nil).
2018-08-23 22:13:09 +08:00
// If the transport can not reuse the requested blob, TryReusingBlob returns (false, {}, nil); it returns a non-nil error only on an unexpected failure.
2023-03-22 00:30:05 +08:00
func ( d * dirImageDestination ) TryReusingBlobWithOptions ( ctx context . Context , info types . BlobInfo , options private . TryReusingBlobOptions ) ( bool , private . ReusedBlob , error ) {
2023-12-07 01:42:51 +08:00
if ! impl . OriginalCandidateMatchesTryReusingBlobOptions ( options ) {
2023-07-02 15:23:12 +08:00
return false , private . ReusedBlob { } , nil
}
Add ImageDestination.{HasBlob,ReapplyBlob}
Add HasBlob() and ReapplyBlob() methods to the ImageDestination
interface, for checking if a blob has already been Put() to a
destination, and allowing the destination implementation to offer the
caller a chance to avoid having to re-Get() the source blob.
This means that copyLayers() no longer keeps track of which blobs it's
copied and skips any -- it handes that down to copyLayer(). The
copyLayer() function is now the lone method of an object that caches the
diffIDs of blobs that it's copied, and skips copying a layer if the
destination blob is already present (HasBlob() says "yes") AND the
blob's diffID is already known.
The "directory" and "oci" destinations implement HasBlob() as a stat()
check for the file they'd create, and make ReapplyBlob() a no-op.
The "docker/daemon" destination now caches the digests of blobs that
it's already added to the output tarball, and checks that in HasBlob(),
making ReapplyBlob() also a no-op.
The "docker"/"openshift" destination HasBlob() checks if the destination
blob is present using an HTTP HEAD request, as PutBlob() does, making
its ReapplyBlob() also a no-op.
Lastly, the "storage" destination HasBlob() checks the image's blobs
list; ReapplyBlob() generates a diff of the most recent layer that
resulted from applying a blob with the specified digest and hands it off
to PutBlob().
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2016-12-02 00:36:52 +08:00
if info . Digest == "" {
2023-03-22 00:30:05 +08:00
return false , private . ReusedBlob { } , fmt . Errorf ( "Can not check for a blob with unknown digest" )
Add ImageDestination.{HasBlob,ReapplyBlob}
Add HasBlob() and ReapplyBlob() methods to the ImageDestination
interface, for checking if a blob has already been Put() to a
destination, and allowing the destination implementation to offer the
caller a chance to avoid having to re-Get() the source blob.
This means that copyLayers() no longer keeps track of which blobs it's
copied and skips any -- it handes that down to copyLayer(). The
copyLayer() function is now the lone method of an object that caches the
diffIDs of blobs that it's copied, and skips copying a layer if the
destination blob is already present (HasBlob() says "yes") AND the
blob's diffID is already known.
The "directory" and "oci" destinations implement HasBlob() as a stat()
check for the file they'd create, and make ReapplyBlob() a no-op.
The "docker/daemon" destination now caches the digests of blobs that
it's already added to the output tarball, and checks that in HasBlob(),
making ReapplyBlob() also a no-op.
The "docker"/"openshift" destination HasBlob() checks if the destination
blob is present using an HTTP HEAD request, as PutBlob() does, making
its ReapplyBlob() also a no-op.
Lastly, the "storage" destination HasBlob() checks the image's blobs
list; ReapplyBlob() generates a diff of the most recent layer that
resulted from applying a blob with the specified digest and hands it off
to PutBlob().
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2016-12-02 00:36:52 +08:00
}
2024-04-18 04:26:46 +08:00
blobPath , err := d . ref . layerPath ( info . Digest )
if err != nil {
return false , private . ReusedBlob { } , err
}
Add ImageDestination.{HasBlob,ReapplyBlob}
Add HasBlob() and ReapplyBlob() methods to the ImageDestination
interface, for checking if a blob has already been Put() to a
destination, and allowing the destination implementation to offer the
caller a chance to avoid having to re-Get() the source blob.
This means that copyLayers() no longer keeps track of which blobs it's
copied and skips any -- it handes that down to copyLayer(). The
copyLayer() function is now the lone method of an object that caches the
diffIDs of blobs that it's copied, and skips copying a layer if the
destination blob is already present (HasBlob() says "yes") AND the
blob's diffID is already known.
The "directory" and "oci" destinations implement HasBlob() as a stat()
check for the file they'd create, and make ReapplyBlob() a no-op.
The "docker/daemon" destination now caches the digests of blobs that
it's already added to the output tarball, and checks that in HasBlob(),
making ReapplyBlob() also a no-op.
The "docker"/"openshift" destination HasBlob() checks if the destination
blob is present using an HTTP HEAD request, as PutBlob() does, making
its ReapplyBlob() also a no-op.
Lastly, the "storage" destination HasBlob() checks the image's blobs
list; ReapplyBlob() generates a diff of the most recent layer that
resulted from applying a blob with the specified digest and hands it off
to PutBlob().
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2016-12-02 00:36:52 +08:00
finfo , err := os . Stat ( blobPath )
if err != nil && os . IsNotExist ( err ) {
2023-03-22 00:30:05 +08:00
return false , private . ReusedBlob { } , nil
Add ImageDestination.{HasBlob,ReapplyBlob}
Add HasBlob() and ReapplyBlob() methods to the ImageDestination
interface, for checking if a blob has already been Put() to a
destination, and allowing the destination implementation to offer the
caller a chance to avoid having to re-Get() the source blob.
This means that copyLayers() no longer keeps track of which blobs it's
copied and skips any -- it handes that down to copyLayer(). The
copyLayer() function is now the lone method of an object that caches the
diffIDs of blobs that it's copied, and skips copying a layer if the
destination blob is already present (HasBlob() says "yes") AND the
blob's diffID is already known.
The "directory" and "oci" destinations implement HasBlob() as a stat()
check for the file they'd create, and make ReapplyBlob() a no-op.
The "docker/daemon" destination now caches the digests of blobs that
it's already added to the output tarball, and checks that in HasBlob(),
making ReapplyBlob() also a no-op.
The "docker"/"openshift" destination HasBlob() checks if the destination
blob is present using an HTTP HEAD request, as PutBlob() does, making
its ReapplyBlob() also a no-op.
Lastly, the "storage" destination HasBlob() checks the image's blobs
list; ReapplyBlob() generates a diff of the most recent layer that
resulted from applying a blob with the specified digest and hands it off
to PutBlob().
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2016-12-02 00:36:52 +08:00
}
if err != nil {
2023-03-22 00:30:05 +08:00
return false , private . ReusedBlob { } , err
Add ImageDestination.{HasBlob,ReapplyBlob}
Add HasBlob() and ReapplyBlob() methods to the ImageDestination
interface, for checking if a blob has already been Put() to a
destination, and allowing the destination implementation to offer the
caller a chance to avoid having to re-Get() the source blob.
This means that copyLayers() no longer keeps track of which blobs it's
copied and skips any -- it handes that down to copyLayer(). The
copyLayer() function is now the lone method of an object that caches the
diffIDs of blobs that it's copied, and skips copying a layer if the
destination blob is already present (HasBlob() says "yes") AND the
blob's diffID is already known.
The "directory" and "oci" destinations implement HasBlob() as a stat()
check for the file they'd create, and make ReapplyBlob() a no-op.
The "docker/daemon" destination now caches the digests of blobs that
it's already added to the output tarball, and checks that in HasBlob(),
making ReapplyBlob() also a no-op.
The "docker"/"openshift" destination HasBlob() checks if the destination
blob is present using an HTTP HEAD request, as PutBlob() does, making
its ReapplyBlob() also a no-op.
Lastly, the "storage" destination HasBlob() checks the image's blobs
list; ReapplyBlob() generates a diff of the most recent layer that
resulted from applying a blob with the specified digest and hands it off
to PutBlob().
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2016-12-02 00:36:52 +08:00
}
2023-03-22 00:30:05 +08:00
return true , private . ReusedBlob { Digest : info . Digest , Size : finfo . Size ( ) } , nil
Add ImageDestination.{HasBlob,ReapplyBlob}
Add HasBlob() and ReapplyBlob() methods to the ImageDestination
interface, for checking if a blob has already been Put() to a
destination, and allowing the destination implementation to offer the
caller a chance to avoid having to re-Get() the source blob.
This means that copyLayers() no longer keeps track of which blobs it's
copied and skips any -- it handes that down to copyLayer(). The
copyLayer() function is now the lone method of an object that caches the
diffIDs of blobs that it's copied, and skips copying a layer if the
destination blob is already present (HasBlob() says "yes") AND the
blob's diffID is already known.
The "directory" and "oci" destinations implement HasBlob() as a stat()
check for the file they'd create, and make ReapplyBlob() a no-op.
The "docker/daemon" destination now caches the digests of blobs that
it's already added to the output tarball, and checks that in HasBlob(),
making ReapplyBlob() also a no-op.
The "docker"/"openshift" destination HasBlob() checks if the destination
blob is present using an HTTP HEAD request, as PutBlob() does, making
its ReapplyBlob() also a no-op.
Lastly, the "storage" destination HasBlob() checks the image's blobs
list; ReapplyBlob() generates a diff of the most recent layer that
resulted from applying a blob with the specified digest and hands it off
to PutBlob().
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2016-12-02 00:36:52 +08:00
}
2017-04-29 05:35:13 +08:00
// PutManifest writes manifest to the destination.
Add manifest list support
Add the manifest.List interface, and implementations for OCIv1 Index and
Docker Schema2List documents.
Add an instanceDigest parameter to PutManifest(), PutSignatures(), and
LayerInfosForCopy, for symmetry with GetManifest() and GetSignatures().
Return an error if the instanceDigest is supplied to destinations which
don't support them, and add stubs that do so even to the transports
which would support it, so that we don't break compilation here.
Add a MultipleImages flag to copy.Options, and if the source for a copy
operation contains multiple images, copy all of the images if we can.
If we can't copy them all, but we were told to, return an error.
Use the generic manifest list API to select a single image to copy from
a list, so that we aren't just limited to the Docker manifest list
format for those cases.
When guessing at the type of a manifest, if the manifest contains a list
of manifests, use its declared MIME type if it included one, else assume
it's an OCI index, because an OCI index doesn't include its MIME type.
When copying, switch from using an encode-then-compare of the original
and updated versions of the list to checking if the instance list was
changed (one of the things we might have changed) or if its type has
changed due to conversion (the other change we might have made). If
neither has changed, then we don't need to change the encoded value of
the manifest.
When copying, when checking for a digest mismatch in a target image
reference, ignore a mismatch between the digest in the reference and the
digest of the main manifest if we're copying one element from a list,
and the digest in the reference matches the digest of the manifest list.
When copying, if conversion of manifests for single images is being
forced, convert manifest lists to the corresponding list types.
When copying, supply the unparsed top level to Commit() by attaching the
value to the context.Context.
Support manifest lists in the directory transport by using the instance
digest as a prefix of the filename used to store a manifest or a piece
of signature data.
Support manifest lists in the oci-layout transport by accepting indexes
as we do images, and stop guessing about Platform values to add to the
top-level index.
Support storing manifest lists to registries in the docker: transport by
using the manifest digest when we're writing one image as part of
pushing a list of them, and by using the instance digest when reading or
writing signature data, when one is specified, or the cached digest of
the non-instanced digest when one is not specified.
Add partial support for manifest lists to the storage transport: when
committing one image from a list into storage, also add a copy of the
manifest list by extracting it from the context.Context. The logic is
already in place to enable locating an image using any of multiple
manifest digests.
When writing an image that has an instanceDigest value (meaning it's a
secondary image), don't try to generate a canonical reference to add to
the image's list of names if the reference for the primary image doesn't
contain a name. That should only happen if we're writing using just an
image ID, which is unlikely, but we still need to handle it.
Avoid computing the digest of the manifest, or retrieving the
either-a-tag-or-a-digest value from the target reference, if we're given
an instanceDigest, which would override them anyway.
Move the check for non-nil instanceDigest values up into the main
PutSignatures() method instead of duplicating it in the per-strategy
helpers.
Add mention of the instanceDigest parameter and its use to various
PutManifest, PutSignatures, and LayerInfosForCopy implementations and
their declarations in interfaces.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2018-01-09 14:03:18 +08:00
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write the manifest for (when
// the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
// It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated
// by `manifest.Digest()`.
2017-04-29 05:35:13 +08:00
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
Add manifest list support
Add the manifest.List interface, and implementations for OCIv1 Index and
Docker Schema2List documents.
Add an instanceDigest parameter to PutManifest(), PutSignatures(), and
LayerInfosForCopy, for symmetry with GetManifest() and GetSignatures().
Return an error if the instanceDigest is supplied to destinations which
don't support them, and add stubs that do so even to the transports
which would support it, so that we don't break compilation here.
Add a MultipleImages flag to copy.Options, and if the source for a copy
operation contains multiple images, copy all of the images if we can.
If we can't copy them all, but we were told to, return an error.
Use the generic manifest list API to select a single image to copy from
a list, so that we aren't just limited to the Docker manifest list
format for those cases.
When guessing at the type of a manifest, if the manifest contains a list
of manifests, use its declared MIME type if it included one, else assume
it's an OCI index, because an OCI index doesn't include its MIME type.
When copying, switch from using an encode-then-compare of the original
and updated versions of the list to checking if the instance list was
changed (one of the things we might have changed) or if its type has
changed due to conversion (the other change we might have made). If
neither has changed, then we don't need to change the encoded value of
the manifest.
When copying, when checking for a digest mismatch in a target image
reference, ignore a mismatch between the digest in the reference and the
digest of the main manifest if we're copying one element from a list,
and the digest in the reference matches the digest of the manifest list.
When copying, if conversion of manifests for single images is being
forced, convert manifest lists to the corresponding list types.
When copying, supply the unparsed top level to Commit() by attaching the
value to the context.Context.
Support manifest lists in the directory transport by using the instance
digest as a prefix of the filename used to store a manifest or a piece
of signature data.
Support manifest lists in the oci-layout transport by accepting indexes
as we do images, and stop guessing about Platform values to add to the
top-level index.
Support storing manifest lists to registries in the docker: transport by
using the manifest digest when we're writing one image as part of
pushing a list of them, and by using the instance digest when reading or
writing signature data, when one is specified, or the cached digest of
the non-instanced digest when one is not specified.
Add partial support for manifest lists to the storage transport: when
committing one image from a list into storage, also add a copy of the
manifest list by extracting it from the context.Context. The logic is
already in place to enable locating an image using any of multiple
manifest digests.
When writing an image that has an instanceDigest value (meaning it's a
secondary image), don't try to generate a canonical reference to add to
the image's list of names if the reference for the primary image doesn't
contain a name. That should only happen if we're writing using just an
image ID, which is unlikely, but we still need to handle it.
Avoid computing the digest of the manifest, or retrieving the
either-a-tag-or-a-digest value from the target reference, if we're given
an instanceDigest, which would override them anyway.
Move the check for non-nil instanceDigest values up into the main
PutSignatures() method instead of duplicating it in the per-strategy
helpers.
Add mention of the instanceDigest parameter and its use to various
PutManifest, PutSignatures, and LayerInfosForCopy implementations and
their declarations in interfaces.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2018-01-09 14:03:18 +08:00
func ( d * dirImageDestination ) PutManifest ( ctx context . Context , manifest [ ] byte , instanceDigest * digest . Digest ) error {
2024-04-18 04:26:46 +08:00
path , err := d . ref . manifestPath ( instanceDigest )
if err != nil {
return err
}
return os . WriteFile ( path , manifest , 0644 )
2016-09-06 01:46:11 +08:00
}
2022-07-06 00:54:19 +08:00
// PutSignaturesWithFormat writes a set of signatures to the destination.
Add manifest list support
Add the manifest.List interface, and implementations for OCIv1 Index and
Docker Schema2List documents.
Add an instanceDigest parameter to PutManifest(), PutSignatures(), and
LayerInfosForCopy, for symmetry with GetManifest() and GetSignatures().
Return an error if the instanceDigest is supplied to destinations which
don't support them, and add stubs that do so even to the transports
which would support it, so that we don't break compilation here.
Add a MultipleImages flag to copy.Options, and if the source for a copy
operation contains multiple images, copy all of the images if we can.
If we can't copy them all, but we were told to, return an error.
Use the generic manifest list API to select a single image to copy from
a list, so that we aren't just limited to the Docker manifest list
format for those cases.
When guessing at the type of a manifest, if the manifest contains a list
of manifests, use its declared MIME type if it included one, else assume
it's an OCI index, because an OCI index doesn't include its MIME type.
When copying, switch from using an encode-then-compare of the original
and updated versions of the list to checking if the instance list was
changed (one of the things we might have changed) or if its type has
changed due to conversion (the other change we might have made). If
neither has changed, then we don't need to change the encoded value of
the manifest.
When copying, when checking for a digest mismatch in a target image
reference, ignore a mismatch between the digest in the reference and the
digest of the main manifest if we're copying one element from a list,
and the digest in the reference matches the digest of the manifest list.
When copying, if conversion of manifests for single images is being
forced, convert manifest lists to the corresponding list types.
When copying, supply the unparsed top level to Commit() by attaching the
value to the context.Context.
Support manifest lists in the directory transport by using the instance
digest as a prefix of the filename used to store a manifest or a piece
of signature data.
Support manifest lists in the oci-layout transport by accepting indexes
as we do images, and stop guessing about Platform values to add to the
top-level index.
Support storing manifest lists to registries in the docker: transport by
using the manifest digest when we're writing one image as part of
pushing a list of them, and by using the instance digest when reading or
writing signature data, when one is specified, or the cached digest of
the non-instanced digest when one is not specified.
Add partial support for manifest lists to the storage transport: when
committing one image from a list into storage, also add a copy of the
manifest list by extracting it from the context.Context. The logic is
already in place to enable locating an image using any of multiple
manifest digests.
When writing an image that has an instanceDigest value (meaning it's a
secondary image), don't try to generate a canonical reference to add to
the image's list of names if the reference for the primary image doesn't
contain a name. That should only happen if we're writing using just an
image ID, which is unlikely, but we still need to handle it.
Avoid computing the digest of the manifest, or retrieving the
either-a-tag-or-a-digest value from the target reference, if we're given
an instanceDigest, which would override them anyway.
Move the check for non-nil instanceDigest values up into the main
PutSignatures() method instead of duplicating it in the per-strategy
helpers.
Add mention of the instanceDigest parameter and its use to various
PutManifest, PutSignatures, and LayerInfosForCopy implementations and
their declarations in interfaces.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2018-01-09 14:03:18 +08:00
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for
// (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
2022-07-06 00:54:19 +08:00
// MUST be called after PutManifest (signatures may reference manifest contents).
func ( d * dirImageDestination ) PutSignaturesWithFormat ( ctx context . Context , signatures [ ] signature . Signature , instanceDigest * digest . Digest ) error {
2016-06-28 15:24:25 +08:00
for i , sig := range signatures {
2022-07-06 00:54:19 +08:00
blob , err := signature . Blob ( sig )
if err != nil {
return err
}
2024-04-18 04:26:46 +08:00
path , err := d . ref . signaturePath ( i , instanceDigest )
if err != nil {
return err
}
if err := os . WriteFile ( path , blob , 0644 ) ; err != nil {
2016-06-28 15:24:25 +08:00
return err
}
}
return nil
}
2016-09-06 04:25:04 +08:00
2024-10-19 07:37:33 +08:00
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
2016-09-06 04:25:04 +08:00
// WARNING: This does not have any transactional semantics:
2024-10-19 07:37:33 +08:00
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func ( d * dirImageDestination ) CommitWithOptions ( ctx context . Context , options private . CommitOptions ) error {
2016-09-06 04:25:04 +08:00
return nil
}
2017-11-07 04:58:55 +08:00
// returns true if path exists
func pathExists ( path string ) ( bool , error ) {
2024-04-11 15:29:39 +08:00
err := fileutils . Exists ( path )
2017-11-07 04:58:55 +08:00
if err == nil {
return true , nil
}
2021-01-20 03:05:47 +08:00
if os . IsNotExist ( err ) {
2017-11-07 04:58:55 +08:00
return false , nil
}
return false , err
}
// returns true if directory is empty
func isDirEmpty ( path string ) ( bool , error ) {
2022-04-14 01:33:42 +08:00
files , err := os . ReadDir ( path )
2017-11-07 04:58:55 +08:00
if err != nil {
return false , err
}
return len ( files ) == 0 , nil
}
// deletes the contents of a directory
func removeDirContents ( path string ) error {
2022-04-14 01:33:42 +08:00
files , err := os . ReadDir ( path )
2017-11-07 04:58:55 +08:00
if err != nil {
return err
}
for _ , file := range files {
if err := os . RemoveAll ( filepath . Join ( path , file . Name ( ) ) ) ; err != nil {
return err
}
}
return nil
}