Skip to content

Commit

Permalink
Add KO_DATA_DATE_EPOCH env var to set the modification time for files…
Browse files Browse the repository at this point in the history
… in `kodata` (#372)

* Add KO_DATA_DATE_EPOCH env var

* Add documentation for KO_DATA_DATE_EPOCH env var
  • Loading branch information
skirsten authored Jun 15, 2021
1 parent 2ba8bb2 commit ee23538
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 22 deletions.
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ you can include Git commit information in your image with:
ln -s -r .git/HEAD ./cmd/app/kodata/
```

Also note that `http.FileServer` will not serve the `Last-Modified` header
(or validate `If-Modified-Since` request headers) because `ko` does not embed
timestamps by default.

This can be supported by manually setting the `KO_DATA_DATE_EPOCH` environment
variable during build ([See below](#Why-are-my-images-all-created-in-1970)).

# Kubernetes Integration

You could stop at just building and pushing images.
Expand Down Expand Up @@ -335,21 +342,27 @@ GOFLAGS="-ldflags=-X=main.version=1.2.3" ko publish .
## Why are my images all created in 1970?

In order to support [reproducible builds](https://reproducible-builds.org), `ko`
doesn't embed timestamps in the images it produces by default; however, `ko`
does respect the
[`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/)
environment variable.
doesn't embed timestamps in the images it produces by default.

However, `ko` does respect the [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/)
environment variable, which will set the container image's timestamp
accordingly.

Similarly, the `KO_DATA_DATE_EPOCH` environment variable can be used to set
the _modtime_ timestamp of the files in `KO_DATA_PATH`.

For example, you can set this to the current timestamp by executing:
For example, you can set the container image's timestamp to the current
timestamp by executing:

```
export SOURCE_DATE_EPOCH=$(date +%s)
```

or to the latest git commit's timestamp with:
or set the timestamp of the files in `KO_DATA_PATH` to the latest git commit's
timestamp with:

```
export SOURCE_DATE_EPOCH=$(git log -1 --format='%ct')
export KO_DATA_DATE_EPOCH=$(git log -1 --format='%ct')
```

## Can I optimize images for [eStargz support](https://github.com/containerd/stargz-snapshotter/blob/v0.2.0/docs/stargz-estargz.md)?
Expand Down
33 changes: 21 additions & 12 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type platformMatcher struct {
type gobuild struct {
getBase GetBase
creationTime v1.Time
kodataCreationTime v1.Time
build builder
disableOptimizations bool
mod *modules
Expand All @@ -96,6 +97,7 @@ type Option func(*gobuildOpener) error
type gobuildOpener struct {
getBase GetBase
creationTime v1.Time
kodataCreationTime v1.Time
build builder
disableOptimizations bool
mod *modules
Expand All @@ -116,6 +118,7 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
return &gobuild{
getBase: gbo.getBase,
creationTime: gbo.creationTime,
kodataCreationTime: gbo.kodataCreationTime,
build: gbo.build,
disableOptimizations: gbo.disableOptimizations,
mod: gbo.mod,
Expand Down Expand Up @@ -414,13 +417,13 @@ func appFilename(importpath string) string {
return base
}

func tarAddDirectories(tw *tar.Writer, dir string) error {
func tarAddDirectories(tw *tar.Writer, dir string, creationTime v1.Time) error {
if dir == "." || dir == string(filepath.Separator) {
return nil
}

// Write parent directories first
if err := tarAddDirectories(tw, filepath.Dir(dir)); err != nil {
if err := tarAddDirectories(tw, filepath.Dir(dir), creationTime); err != nil {
return err
}

Expand All @@ -431,21 +434,22 @@ func tarAddDirectories(tw *tar.Writer, dir string) error {
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
// under which it was created. Additionally, windows can only set 0222,
// 0444, or 0666, none of which are executable.
Mode: 0555,
Mode: 0555,
ModTime: creationTime.Time,
}); err != nil {
return err
}

return nil
}

func tarBinary(name, binary string) (*bytes.Buffer, error) {
func tarBinary(name, binary string, creationTime v1.Time) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
tw := tar.NewWriter(buf)
defer tw.Close()

// write the parent directories to the tarball archive
if err := tarAddDirectories(tw, path.Dir(name)); err != nil {
if err := tarAddDirectories(tw, path.Dir(name), creationTime); err != nil {
return nil, err
}

Expand All @@ -465,7 +469,8 @@ func tarBinary(name, binary string) (*bytes.Buffer, error) {
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
// under which it was created. Additionally, windows can only set 0222,
// 0444, or 0666, none of which are executable.
Mode: 0555,
Mode: 0555,
ModTime: creationTime.Time,
}
// write the header to the tarball archive
if err := tw.WriteHeader(header); err != nil {
Expand Down Expand Up @@ -493,7 +498,7 @@ const kodataRoot = "/var/run/ko"
// walkRecursive performs a filepath.Walk of the given root directory adding it
// to the provided tar.Writer with root -> chroot. All symlinks are dereferenced,
// which is what leads to recursion when we encounter a directory symlink.
func walkRecursive(tw *tar.Writer, root, chroot string) error {
func walkRecursive(tw *tar.Writer, root, chroot string, creationTime v1.Time) error {
return filepath.Walk(root, func(hostPath string, info os.FileInfo, err error) error {
if hostPath == root {
// Add an entry for the root directory of our walk.
Expand All @@ -503,7 +508,8 @@ func walkRecursive(tw *tar.Writer, root, chroot string) error {
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
// under which it was created. Additionally, windows can only set 0222,
// 0444, or 0666, none of which are executable.
Mode: 0555,
Mode: 0555,
ModTime: creationTime.Time,
})
}
if err != nil {
Expand All @@ -527,7 +533,7 @@ func walkRecursive(tw *tar.Writer, root, chroot string) error {
}
// Skip other directories.
if info.Mode().IsDir() {
return walkRecursive(tw, evalPath, newPath)
return walkRecursive(tw, evalPath, newPath, creationTime)
}

// Open the file to copy it into the tarball.
Expand All @@ -545,7 +551,8 @@ func walkRecursive(tw *tar.Writer, root, chroot string) error {
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
// under which it was created. Additionally, windows can only set 0222,
// 0444, or 0666, none of which are executable.
Mode: 0555,
Mode: 0555,
ModTime: creationTime.Time,
}); err != nil {
return fmt.Errorf("tar.Writer.WriteHeader(%q): %w", newPath, err)
}
Expand All @@ -566,7 +573,9 @@ func (g *gobuild) tarKoData(ref reference) (*bytes.Buffer, error) {
return nil, err
}

return buf, walkRecursive(tw, root, kodataRoot)
creationTime := g.kodataCreationTime

return buf, walkRecursive(tw, root, kodataRoot, creationTime)
}

func (g *gobuild) buildOne(ctx context.Context, s string, base v1.Image, platform *v1.Platform) (v1.Image, error) {
Expand Down Expand Up @@ -616,7 +625,7 @@ func (g *gobuild) buildOne(ctx context.Context, s string, base v1.Image, platfor
appPath := path.Join(appDir, appFilename(ref.Path()))

// Construct a tarball with the binary and produce a layer.
binaryLayerBuf, err := tarBinary(appPath, file)
binaryLayerBuf, err := tarBinary(appPath, file, v1.Time{})
if err != nil {
return nil, err
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/build/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ func WithCreationTime(t v1.Time) Option {
}
}

// WithKoDataCreationTime is a functional option for overriding the creation
// time given to the files in the kodata directory.
func WithKoDataCreationTime(t v1.Time) Option {
return func(gbo *gobuildOpener) error {
gbo.kodataCreationTime = t
return nil
}
}

// WithDisabledOptimizations is a functional option for disabling optimizations
// when compiling.
func WithDisabledOptimizations() Option {
Expand Down
14 changes: 11 additions & 3 deletions pkg/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,27 @@ func getBaseImage(platform string, bo *options.BuildOptions) build.GetBase {
}
}

func getCreationTime() (*v1.Time, error) {
epoch := os.Getenv("SOURCE_DATE_EPOCH")
func getTimeFromEnv(env string) (*v1.Time, error) {
epoch := os.Getenv(env)
if epoch == "" {
return nil, nil
}

seconds, err := strconv.ParseInt(epoch, 10, 64)
if err != nil {
return nil, fmt.Errorf("the environment variable SOURCE_DATE_EPOCH should be the number of seconds since January 1st 1970, 00:00 UTC, got: %v", err)
return nil, fmt.Errorf("the environment variable %s should be the number of seconds since January 1st 1970, 00:00 UTC, got: %v", env, err)
}
return &v1.Time{Time: time.Unix(seconds, 0)}, nil
}

func getCreationTime() (*v1.Time, error) {
return getTimeFromEnv("SOURCE_DATE_EPOCH")
}

func getKoDataCreationTime() (*v1.Time, error) {
return getTimeFromEnv("KO_DATA_DATE_EPOCH")
}

func createCancellableContext() context.Context {
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
Expand Down
8 changes: 8 additions & 0 deletions pkg/commands/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) {
return nil, err
}

kodataCreationTime, err := getKoDataCreationTime()
if err != nil {
return nil, err
}

platform := bo.Platform
if platform == "" {
platform = "linux/amd64"
Expand Down Expand Up @@ -86,6 +91,9 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) {
if creationTime != nil {
opts = append(opts, build.WithCreationTime(*creationTime))
}
if kodataCreationTime != nil {
opts = append(opts, build.WithKoDataCreationTime(*kodataCreationTime))
}
if bo.DisableOptimizations {
opts = append(opts, build.WithDisabledOptimizations())
}
Expand Down

0 comments on commit ee23538

Please sign in to comment.