Use --timestamp rather then --omit-timestamp

We recieved feedback on the --omit-timestamp that
users would rather specify the timestamp seconds
rather then just use EPOCH.

This PR removes --omit-timestamp from buildah bud
since this has never been released.

We also hide --omit-timestamp from buildah commit
and allow users to continue to use it, but it conflicts
with --timestamp.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2020-08-26 16:56:57 -04:00
parent ee6973ecfe
commit b715fb86ee
No known key found for this signature in database
GPG Key ID: A2DF901DABE2C028
12 changed files with 113 additions and 52 deletions

View File

@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/buildah/imagebuildah"
buildahcli "github.com/containers/buildah/pkg/cli"
@ -336,7 +337,6 @@ func budCmd(c *cobra.Command, inputArgs []string, iopts budOptions) error {
MaxPullPushRetries: maxPullPushRetries,
NamespaceOptions: namespaceOptions,
NoCache: iopts.NoCache,
OmitTimestamp: iopts.OmitTimestamp,
OS: imageOS,
Out: stdout,
Output: output,
@ -358,6 +358,10 @@ func budCmd(c *cobra.Command, inputArgs []string, iopts budOptions) error {
Jobs: &iopts.Jobs,
LogRusage: iopts.LogRusage,
}
if c.Flag("timestamp").Changed {
timestamp := time.Unix(iopts.Timestamp, 0).UTC()
options.Timestamp = &timestamp
}
if iopts.Quiet {
options.ReportWriter = ioutil.Discard

View File

@ -28,6 +28,7 @@ type commitInputOptions struct {
format string
iidfile string
omitTimestamp bool
timestamp int64
quiet bool
referenceTime string
rm bool
@ -74,10 +75,14 @@ func init() {
flags.StringVarP(&opts.format, "format", "f", defaultFormat(), "`format` of the image manifest and metadata")
flags.StringVar(&opts.iidfile, "iidfile", "", "Write the image ID to the file")
flags.BoolVar(&opts.omitTimestamp, "omit-timestamp", false, "set created timestamp to epoch 0 to allow for deterministic builds")
flags.Int64Var(&opts.timestamp, "timestamp", 0, "set created timestamp to epoch seconds to allow for deterministic builds, defaults to current time")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when writing images")
flags.StringVar(&opts.referenceTime, "reference-time", "", "set the timestamp on the image to match the named `file`")
flags.StringVar(&opts.signBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`")
if err := flags.MarkHidden("omit-timestamp"); err != nil {
panic(fmt.Sprintf("error marking omit-timestamp as hidden: %v", err))
}
if err := flags.MarkHidden("reference-time"); err != nil {
panic(fmt.Sprintf("error marking reference-time as hidden: %v", err))
}
@ -121,15 +126,6 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error
if iopts.disableCompression {
compress = imagebuildah.Uncompressed
}
timestamp := time.Now().UTC()
if c.Flag("reference-time").Changed {
referenceFile := iopts.referenceTime
finfo, err := os.Stat(referenceFile)
if err != nil {
return errors.Wrapf(err, "error reading timestamp of file %q", referenceFile)
}
timestamp = finfo.ModTime().UTC()
}
format, err := getFormat(iopts.format)
if err != nil {
@ -181,16 +177,40 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error
PreferredManifestType: format,
Compression: compress,
SignaturePolicyPath: iopts.signaturePolicy,
HistoryTimestamp: &timestamp,
SystemContext: systemContext,
IIDFile: iopts.iidfile,
Squash: iopts.squash,
BlobDirectory: iopts.blobCache,
OmitTimestamp: iopts.omitTimestamp,
SignBy: iopts.signBy,
OciEncryptConfig: encConfig,
OciEncryptLayers: encLayers,
}
exclusiveFlags := 0
if c.Flag("reference-time").Changed {
exclusiveFlags++
referenceFile := iopts.referenceTime
finfo, err := os.Stat(referenceFile)
if err != nil {
return errors.Wrapf(err, "error reading timestamp of file %q", referenceFile)
}
timestamp := finfo.ModTime().UTC()
options.HistoryTimestamp = &timestamp
}
if c.Flag("timestamp").Changed {
exclusiveFlags++
timestamp := time.Unix(iopts.timestamp, 0).UTC()
options.HistoryTimestamp = &timestamp
}
if iopts.omitTimestamp {
exclusiveFlags++
timestamp := time.Unix(0, 0).UTC()
options.HistoryTimestamp = &timestamp
}
if exclusiveFlags > 1 {
return errors.Errorf("can not use more then one timestamp option at at time")
}
if !iopts.quiet {
options.ReportWriter = os.Stderr
}

View File

@ -79,6 +79,7 @@ type CommitOptions struct {
EmptyLayer bool
// OmitTimestamp forces epoch 0 as created timestamp to allow for
// deterministic, content-addressable builds.
// Deprecated use HistoryTimestamp instead.
OmitTimestamp bool
// SignBy is the fingerprint of a GPG key to use for signing the image.
SignBy string
@ -231,6 +232,13 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
// want to compute here because we'll have to do it again when
// cp.Image() instantiates a source image, and we don't want to do the
// work twice.
if options.OmitTimestamp {
if options.HistoryTimestamp != nil {
return imgID, nil, "", errors.Errorf("OmitTimestamp ahd HistoryTimestamp can not be used together")
}
timestamp := time.Unix(0, 0).UTC()
options.HistoryTimestamp = &timestamp
}
nameToRemove := ""
if dest == nil {
nameToRemove = stringid.GenerateRandomID() + "-tmp"

View File

@ -342,14 +342,12 @@ Valid _mode_ values are:
Do not use existing cached images for the container build. Build from the start with a new set of cached layers.
**--omit-timestamp** *bool-value*
**--timestamp** *seconds*
Set the create timestamp to epoch 0 to allow for deterministic builds (defaults to false).
Set the create timestamp to seconds since epoch to allow for deterministic builds (defaults to current time).
By default, the created timestamp is changed and written into the image manifest with every commit,
causing the image's sha256 hash to be different even if the sources are exactly the same otherwise.
When --omit-timestamp is set to true, the created timestamp is always set to the epoch and therefore not
changed, allowing the image's sha256 to remain the same. All files committed to the layers of the image
will get the epoch 0 timestamp.
When --timestamp is set, the created timestamp is always set to the time specified and therefore not changed, allowing the image's sha256 to remain the same. All files committed to the layers of the image will be created with the timestamp.
**--os**="OS"
@ -667,6 +665,8 @@ cat ~/Dockerfile | buildah bud -f - .
buildah bud -f Dockerfile.simple -f Dockerfile.notsosimple .
buildah bud --timestamp=$(date '+%s') -t imageName .
buildah bud -t imageName .
buildah bud --tls-verify=true -t imageName -f Dockerfile.simple .

View File

@ -77,14 +77,12 @@ Squash all of the new image's layers (including those inherited from a base imag
Require HTTPS and verify certificates when talking to container registries (defaults to true)
**--omit-timestamp** *bool-value*
**--timestamp** *secconds*
Set the create timestamp to epoch 0 to allow for deterministic builds (defaults to false).
Set the create timestamp to seconds since epoch to allow for deterministic builds (defaults to current time).
By default, the created timestamp is changed and written into the image manifest with every commit,
causing the image's sha256 hash to be different even if the sources are exactly the same otherwise.
When --omit-timestamp is set to true, the created timestamp is always set to the epoch and therefore not
changed, allowing the image's sha256 to remain the same. All files committed to the layers of the image
will get the epoch 0 timestamp.
When --timestamp is set, the created timestamp is always set to the time specified and therefore not changed, allowing the image's sha256 to remain the same. All files committed to the layers of the image will be created with the timestamp.
## EXAMPLE
@ -112,6 +110,9 @@ This example commits the container to the image on the local registry using cred
This example commits the container to the image on the local registry using credentials from the /tmp/auths/myauths.json file and certificates for authentication.
`buildah commit --authfile /tmp/auths/myauths.json --cert-dir ~/auth --tls-verify=true --creds=username:password containerID docker://localhost:5000/imageName`
This example saves an image based on the container, but stores dates based on epoch time.
`buildah commit --timestamp=0 containerID newImageName`
## ENVIRONMENT
**BUILD\_REGISTRY\_SOURCES**

View File

@ -52,7 +52,7 @@ type containerImageRef struct {
layerID string
oconfig []byte
dconfig []byte
created time.Time
created *time.Time
createdBy string
historyComment string
annotations map[string]string
@ -178,7 +178,10 @@ func (i *containerImageRef) extractRootfs() (io.ReadCloser, error) {
// Build fresh copies of the container configuration structures so that we can edit them
// without making unintended changes to the original Builder.
func (i *containerImageRef) createConfigsAndManifests() (v1.Image, v1.Manifest, docker.V2Image, docker.V2S2Manifest, error) {
created := i.created
created := time.Now()
if i.created != nil {
created = *i.created
}
// Build an empty image, and then decode over it.
oimage := v1.Image{}
@ -296,7 +299,6 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
if err != nil {
return nil, err
}
omitTimestamp := i.created.Equal(time.Unix(0, 0))
// Extract each layer and compute its digests, both compressed (if requested) and uncompressed.
for _, layerID := range layers {
@ -386,9 +388,9 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
return nil, errors.Wrapf(err, "error compressing %s", what)
}
writer := io.MultiWriter(writeCloser, srcHasher.Hash())
// Zero out timestamps in the layer, if we're doing that for
// Use specified timestamps in the layer, if we're doing that for
// history entries.
if omitTimestamp {
if i.created != nil {
nestedWriteCloser := ioutils.NewWriteCloserWrapper(writer, writeCloser.Close)
writeCloser = newTarFilterer(nestedWriteCloser, func(hdr *tar.Header) (bool, bool, io.Reader) {
// Changing a zeroed field to a non-zero field
@ -399,13 +401,13 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
// changing the length) of the header that we
// write.
if !hdr.ModTime.IsZero() {
hdr.ModTime = i.created
hdr.ModTime = *i.created
}
if !hdr.AccessTime.IsZero() {
hdr.AccessTime = i.created
hdr.AccessTime = *i.created
}
if !hdr.ChangeTime.IsZero() {
hdr.ChangeTime = i.created
hdr.ChangeTime = *i.created
}
return false, false, nil
})
@ -481,7 +483,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
}
appendHistory(i.preEmptyLayers)
onews := v1.History{
Created: &i.created,
Created: i.created,
CreatedBy: i.createdBy,
Author: oimage.Author,
Comment: i.historyComment,
@ -489,7 +491,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
}
oimage.History = append(oimage.History, onews)
dnews := docker.V2S2History{
Created: i.created,
Created: *i.created,
CreatedBy: i.createdBy,
Author: dimage.Author,
Comment: i.historyComment,
@ -716,10 +718,6 @@ func (b *Builder) makeImageRef(options CommitOptions, exporting bool) (types.Ima
}
}
if options.OmitTimestamp {
created = time.Unix(0, 0).UTC()
}
parent := ""
if b.FromImageID != "" {
parentDigest := digest.NewDigestFromEncoded(digest.Canonical, b.FromImageID)
@ -738,7 +736,7 @@ func (b *Builder) makeImageRef(options CommitOptions, exporting bool) (types.Ima
layerID: container.LayerID,
oconfig: oconfig,
dconfig: dconfig,
created: created,
created: &created,
createdBy: createdBy,
historyComment: b.HistoryComment(),
annotations: b.Annotations(),
@ -752,6 +750,5 @@ func (b *Builder) makeImageRef(options CommitOptions, exporting bool) (types.Ima
preEmptyLayers: b.PrependedEmptyLayers,
postEmptyLayers: b.AppendedEmptyLayers,
}
return ref, nil
}

View File

@ -168,9 +168,9 @@ type BuildOptions struct {
SignBy string
// Architecture specifies the target architecture of the image to be built.
Architecture string
// OmitTimestamp forces epoch 0 as created timestamp to allow for
// deterministic, content-addressable builds.
OmitTimestamp bool
// Timestamp sets the created timestamp to the specified time, allowing
// for deterministic, content-addressable builds.
Timestamp *time.Time
// OS is the specifies the operating system of the image to be built.
OS string
// MaxPullPushRetries is the maximum number of attempts we'll make to pull or push any one

View File

@ -100,7 +100,7 @@ type Executor struct {
devices []configs.Device
signBy string
architecture string
omitTimestamp bool
timestamp *time.Time
os string
maxPullPushRetries int
retryPullPushDelay time.Duration
@ -207,7 +207,7 @@ func NewExecutor(store storage.Store, options BuildOptions, mainNode *parser.Nod
devices: devices,
signBy: options.SignBy,
architecture: options.Architecture,
omitTimestamp: options.OmitTimestamp,
timestamp: options.Timestamp,
os: options.OS,
maxPullPushRetries: options.MaxPullPushRetries,
retryPullPushDelay: options.PullPushRetryDelay,

View File

@ -1211,7 +1211,7 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer
SignBy: s.executor.signBy,
MaxRetries: s.executor.maxPullPushRetries,
RetryDelay: s.executor.retryPullPushDelay,
OmitTimestamp: s.executor.omitTimestamp,
HistoryTimestamp: s.executor.timestamp,
}
imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options)
if err != nil {

View File

@ -65,7 +65,7 @@ type BudResults struct {
Logfile string
Loglevel int
NoCache bool
OmitTimestamp bool
Timestamp int64
OS string
Platform string
Pull bool
@ -165,7 +165,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet {
fs.BoolVar(&flags.NoCache, "no-cache", false, "Do not use existing cached images for the container build. Build from the start with a new set of cached layers.")
fs.StringVar(&flags.Logfile, "logfile", "", "log to `file` instead of stdout/stderr")
fs.IntVar(&flags.Loglevel, "loglevel", 0, "adjust logging level (range from -2 to 3)")
fs.BoolVar(&flags.OmitTimestamp, "omit-timestamp", false, "set created timestamp to epoch 0 to allow for deterministic builds")
fs.Int64Var(&flags.Timestamp, "timestamp", 0, "set created timestamp to the specified epoch seconds to allow for deterministic builds, defaults to current time")
fs.StringVar(&flags.OS, "os", runtime.GOOS, "set the OS to the provided value instead of the current operating system of the host")
fs.StringVar(&flags.Platform, "platform", parse.DefaultPlatform(), "set the OS/ARCH to the provided value instead of the current operating system and architecture of the host (for example `linux/arm`)")
fs.BoolVar(&flags.Pull, "pull", true, "pull the image from the registry if newer or not present in store, if false, only pull the image if not present")

View File

@ -2180,17 +2180,16 @@ EOM
run_buildah rm -a
}
@test "bud omit-timestamp" {
@test "bud timestamp" {
_prefetch alpine
run_buildah bud --omit-timestamp --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json -t omit -f Dockerfile.1 bud/cache-stages
run_buildah bud --timestamp=0 --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json -t timestamp -f Dockerfile.1 bud/cache-stages
cid=$output
run_buildah inspect --format '{{ .Docker.Created }}' omit
run_buildah inspect --format '{{ .Docker.Created }}' timestamp
expect_output --substring "1970-01-01"
run_buildah inspect --format '{{ .OCIv1.Created }}' omit
run_buildah inspect --format '{{ .OCIv1.Created }}' timestamp
expect_output --substring "1970-01-01"
run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json omit
run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json timestamp
cid=$output
run_buildah run $cid ls -l /tmpfile
expect_output --substring "1970"
@ -2198,6 +2197,18 @@ EOM
rm -rf ${TESTDIR}/tmp
}
@test "bud timestamp compare" {
_prefetch alpine
TIMESTAMP=$(date '+%s')
run_buildah bud --timestamp=${TIMESTAMP} --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json -t timestamp -f Dockerfile.1 bud/cache-stages
cid=$output
run_buildah bud --timestamp=${TIMESTAMP} --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json -t timestamp -f Dockerfile.1 bud/cache-stages
expect_output "$cid"
rm -rf ${TESTDIR}/tmp
}
@test "bud with-rusage" {
_prefetch alpine
run_buildah bud --log-rusage --layers --pull=false --format docker --signature-policy ${TESTSDIR}/policy.json bud/shell

View File

@ -216,6 +216,26 @@ load helpers
expect_output --substring "1970-01-01"
run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json omit
cid=$output
run_buildah run $cid ls -l /test
expect_output --substring "1970"
rm -rf ${TESTDIR}/tmp
}
@test "commit timestamp" {
_prefetch busybox
run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json busybox
cid=$output
run_buildah run $cid touch /test
run_buildah commit --signature-policy ${TESTSDIR}/policy.json --timestamp 0 -q $cid omit
run_buildah inspect --format '{{ .Docker.Created }}' omit
expect_output --substring "1970-01-01"
run_buildah inspect --format '{{ .OCIv1.Created }}' omit
expect_output --substring "1970-01-01"
run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json omit
cid=$output
run_buildah run $cid ls -l /test