Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add KO_DATA_DATE_EPOCH env var to set the modification time for files in kodata #372

Merged
merged 2 commits into from
Jun 15, 2021
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
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