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:
parent
ee6973ecfe
commit
b715fb86ee
|
@ -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 = ×tamp
|
||||
}
|
||||
|
||||
if iopts.Quiet {
|
||||
options.ReportWriter = ioutil.Discard
|
||||
|
|
|
@ -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: ×tamp,
|
||||
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 = ×tamp
|
||||
}
|
||||
if c.Flag("timestamp").Changed {
|
||||
exclusiveFlags++
|
||||
timestamp := time.Unix(iopts.timestamp, 0).UTC()
|
||||
options.HistoryTimestamp = ×tamp
|
||||
}
|
||||
if iopts.omitTimestamp {
|
||||
exclusiveFlags++
|
||||
timestamp := time.Unix(0, 0).UTC()
|
||||
options.HistoryTimestamp = ×tamp
|
||||
}
|
||||
|
||||
if exclusiveFlags > 1 {
|
||||
return errors.Errorf("can not use more then one timestamp option at at time")
|
||||
}
|
||||
|
||||
if !iopts.quiet {
|
||||
options.ReportWriter = os.Stderr
|
||||
}
|
||||
|
|
|
@ -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 = ×tamp
|
||||
}
|
||||
nameToRemove := ""
|
||||
if dest == nil {
|
||||
nameToRemove = stringid.GenerateRandomID() + "-tmp"
|
||||
|
|
|
@ -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 .
|
||||
|
|
|
@ -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**
|
||||
|
|
29
image.go
29
image.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue