Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ type Options struct {
// so that storage.ResolveReference returns exactly the created image.
// WARNING: It is unspecified whether the reference also contains a reference.Named element.
ReportResolvedReference *types.ImageReference

// DestinationTimestamp, if set, will force timestamps of content created in the destination to this value.
// Most transports don't support this.
//
// In oci-archive: destinations, this will set the create/mod/access timestamps in each tar entry
// (but not a timestamp of the created archive file).
DestinationTimestamp *time.Time
}

// OptionCompressionVariant allows to supply information about
Expand Down Expand Up @@ -354,6 +361,7 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
if err := c.dest.CommitWithOptions(ctx, private.CommitOptions{
UnparsedToplevel: c.unparsedToplevel,
ReportResolvedReference: options.ReportResolvedReference,
Timestamp: options.DestinationTimestamp,
}); err != nil {
return nil, fmt.Errorf("committing the finished image: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/BurntSushi/toml v1.4.0
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01
github.com/containers/ocicrypt v1.2.1
github.com/containers/storage v1.57.2-0.20250211190637-7aa96daee0a3
github.com/containers/storage v1.57.2-0.20250220203011-3a013da40ef1
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f
github.com/distribution/reference v0.6.0
github.com/docker/cli v28.0.1+incompatible
Expand Down Expand Up @@ -67,7 +67,7 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/coreos/go-oidc/v3 v3.12.0 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYgle
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
github.com/containers/storage v1.57.2-0.20250211190637-7aa96daee0a3 h1:YLjd5aplmRP98Jlrqz5+kNmbVZvpZwrZygkF96KR2Fs=
github.com/containers/storage v1.57.2-0.20250211190637-7aa96daee0a3/go.mod h1:zsh6czcxcdqKIz//cVU6waEJ+2Ui8OEnrwCvM/DE3iU=
github.com/containers/storage v1.57.2-0.20250220203011-3a013da40ef1 h1:Gsx/Ad+axho5kmTCshG82Ghlvle8sMDGC74tukRE9aU=
github.com/containers/storage v1.57.2-0.20250220203011-3a013da40ef1/go.mod h1:egC90qMy0fTpGjkaHj667syy1Cbr3XPZEVX/qkUPrdM=
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM=
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down
7 changes: 7 additions & 0 deletions internal/private/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package private
import (
"context"
"io"
"time"

"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/blobinfocache"
Expand Down Expand Up @@ -170,6 +171,12 @@ type CommitOptions struct {
// What “resolved” means is transport-specific.
// Transports which don’t support reporting resolved references can ignore the field; the generic copy code writes "nil" into the value.
ReportResolvedReference *types.ImageReference
// Timestamp, if set, will force timestamps of content created in the destination to this value.
// Most transports don't support this.
//
// In oci-archive: destinations, this will set the create/mod/access timestamps in each tar entry
// (but not a timestamp of the created archive file).
Timestamp *time.Time
}

// ImageSourceChunk is a portion of a blob.
Expand Down
8 changes: 6 additions & 2 deletions oci/archive/oci_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"time"

"github.com/containers/image/v5/internal/imagedestination"
"github.com/containers/image/v5/internal/imagedestination/impl"
Expand Down Expand Up @@ -172,16 +173,19 @@ func (d *ociArchiveImageDestination) CommitWithOptions(ctx context.Context, opti
src := d.tempDirRef.tempDirectory
// path to save tarred up file
dst := d.ref.resolvedFile
return tarDirectory(src, dst)
return tarDirectory(src, dst, options.Timestamp)
}

// tar converts the directory at src and saves it to dst
func tarDirectory(src, dst string) error {
// if contentModTimes is non-nil, tar header entries times are set to this
func tarDirectory(src, dst string, contentModTimes *time.Time) error {
// input is a stream of bytes from the archive of the directory at path
input, err := archive.TarWithOptions(src, &archive.TarOptions{
Compression: archive.Uncompressed,
// Don’t include the data about the user account this code is running under.
ChownOpts: &idtools.IDPair{UID: 0, GID: 0},
// override tar header timestamps
Timestamp: contentModTimes,
})
if err != nil {
return fmt.Errorf("retrieving stream of bytes from %q: %w", src, err)
Expand Down
2 changes: 1 addition & 1 deletion oci/archive/oci_dest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestTarDirectory(t *testing.T) {
require.NoError(t, err)

dest := filepath.Join(t.TempDir(), "file.tar")
err = tarDirectory(srcDir, dest)
err = tarDirectory(srcDir, dest, nil)
require.NoError(t, err)

f, err := os.Open(dest)
Expand Down
34 changes: 31 additions & 3 deletions oci/archive/oci_transport_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package archive

import (
"archive/tar"
"context"
"io"
"os"
"path/filepath"
"testing"
"time"

_ "github.com/containers/image/v5/internal/testing/explicitfilepath-tmpdir"
"github.com/containers/image/v5/types"
Expand Down Expand Up @@ -139,7 +142,7 @@ func refToTempOCI(t *testing.T) (types.ImageReference, string) {

// refToTempOCIArchive creates a temporary directory, copies the contents of that directory
// to a temporary tar file and returns a reference to the temporary tar file
func refToTempOCIArchive(t *testing.T) (ref types.ImageReference, tmpTarFile string) {
func refToTempOCIArchive(t *testing.T, tarEntryTimestamp *time.Time) (ref types.ImageReference, tmpTarFile string) {
tmpDir := t.TempDir()
m := `{
"schemaVersion": 2,
Expand All @@ -163,7 +166,7 @@ func refToTempOCIArchive(t *testing.T) (ref types.ImageReference, tmpTarFile str
require.NoError(t, err)
tarFile, err := os.CreateTemp("", "oci-transport-test.tar")
require.NoError(t, err)
err = tarDirectory(tmpDir, tarFile.Name())
err = tarDirectory(tmpDir, tarFile.Name(), tarEntryTimestamp)
require.NoError(t, err)
ref, err = NewReference(tarFile.Name(), "")
require.NoError(t, err)
Expand Down Expand Up @@ -253,13 +256,38 @@ func TestReferenceNewImage(t *testing.T) {
}

func TestReferenceNewImageSource(t *testing.T) {
ref, tmpTarFile := refToTempOCIArchive(t)
ref, tmpTarFile := refToTempOCIArchive(t, nil)
defer os.RemoveAll(tmpTarFile)
src, err := ref.NewImageSource(context.Background(), nil)
assert.NoError(t, err)
defer src.Close()
}

func TestTimestampEntriesPassedThrough(t *testing.T) {
// set target time to a bit in the future, but rounded
targetTime := time.Now().Add(time.Hour).Truncate(time.Second)

_, tmpTarFile := refToTempOCIArchive(t, &targetTime)
defer os.RemoveAll(tmpTarFile)

f, err := os.Open(tmpTarFile)
assert.NoError(t, err)
defer f.Close()

numEntries := 0
tr := tar.NewReader(f)
for {
th, err := tr.Next()
if err == io.EOF {
break
}
assert.NoError(t, err)
assert.Equal(t, targetTime, th.ModTime) // access time and change time are ignored by Go's tar.Writer unless the creator explicitly sets a non-default header format, so just check mod time
numEntries++
}
assert.NotEqual(t, 0, numEntries)
}

func TestReferenceNewImageDestination(t *testing.T) {
ref, _ := refToTempOCI(t)
dest, err := ref.NewImageDestination(context.Background(), nil)
Expand Down