Skip to content

Commit

Permalink
feat: implement SOURCE_DATE_EPOCH build argument
Browse files Browse the repository at this point in the history
In general, buildkit ignores timestamps when caching intermediate build
results, but still timestamps leak into the final container image.

So if the package is rebuilt with cache, output image stays same (even
though build step might produce different timestamps, but same content,
final output will still be cached).

This new feature enforces timestamp from `SOURCE_DATE_EPOCH` on the
output in the `finalize` step so that build is reproducible.

The downside is that if the build step is used in other steps, and
`SOURCE_DATE_EPOCH` is used, it might cause cascading rebuilds.

Signed-off-by: Andrey Smirnov <[email protected]>
  • Loading branch information
smira committed Mar 3, 2022
1 parent 7a0ad18 commit 376fe2b
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 16 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ Step `finalize` performs final copying of the build artifacts into scratch image

Finalize instruction `{"from": "/", "to": "/"}` copies full build contents as output image, but usually it doesn't make sense to include build temporary files and build dependencies into the package output. Usual trick to install build result under designated initially empty prefix (e.g. `/rootfs`) and set only contents of that prefix as build output.

If `SOURCE_DATE_EPOCH` build argument is set, `bldr` will update timestamps of all files copied in the `finalize` step to the value of `SOURCE_DATE_EPOCH`.

### Built-in variables

Variables are made available to the templating engine when processing `pkg.yaml` contents and also pushed into the build as environment variables.
Expand Down
40 changes: 30 additions & 10 deletions internal/pkg/convert/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/moby/buildkit/client/llb"
"github.com/opencontainers/go-digest"
"github.com/talos-systems/bldr/internal/pkg/constants"
"github.com/talos-systems/bldr/internal/pkg/environment"
"github.com/talos-systems/bldr/internal/pkg/solver"
"github.com/talos-systems/bldr/internal/pkg/types/v1alpha2"
)
Expand All @@ -22,10 +23,29 @@ const (
pkgDir = "/pkg"
)

var defaultCopyOptions = &llb.CopyInfo{
CopyDirContentsOnly: true,
CreateDestPath: true,
FollowSymlinks: true,
func defaultCopyOptions(options *environment.Options, reproducible bool) *llb.CopyInfo {
copyOptions := &llb.CopyInfo{
CopyDirContentsOnly: true,
CreateDestPath: true,
FollowSymlinks: true,
}

if reproducible {
copyOptions.ChownOpt = &llb.ChownOpt{
User: &llb.UserOpt{
UID: 0,
},
Group: &llb.UserOpt{
UID: 0,
},
}

if !options.SourceDateEpoch.IsZero() {
copyOptions.CreatedTime = &options.SourceDateEpoch
}
}

return copyOptions
}

// NodeLLB wraps PackageNode to provide LLB conversion.
Expand Down Expand Up @@ -80,7 +100,7 @@ func (node *NodeLLB) context(root llb.State) llb.State {
relPath := node.Pkg.BaseDir

return root.File(
llb.Copy(node.Graph.LocalContext, filepath.Join("/", relPath), pkgDir, defaultCopyOptions),
llb.Copy(node.Graph.LocalContext, filepath.Join("/", relPath), pkgDir, defaultCopyOptions(node.Graph.Options, false)),
llb.WithCustomNamef(node.Prefix+"context %s -> %s", relPath, pkgDir),
)
}
Expand Down Expand Up @@ -138,7 +158,7 @@ func (node *NodeLLB) dependencies(root llb.State) (llb.State, error) {
}

root = root.File(
llb.Copy(depState, dep.Src(), dep.Dest(), defaultCopyOptions),
llb.Copy(depState, dep.Src(), dep.Dest(), defaultCopyOptions(node.Graph.Options, false)),
llb.WithCustomNamef("copy --from %s %s -> %s", srcName, dep.Src(), dep.Dest()))
}

Expand Down Expand Up @@ -167,7 +187,7 @@ func (node *NodeLLB) stepDownload(root llb.State, step v1alpha2.Step) llb.State

checksummer := node.Graph.Checksummer.File(
llb.Mkfile("/checksums", 0644, source.ToSHA512Sum()).
Copy(download, "/", "/", defaultCopyOptions).
Copy(download, "/", "/", defaultCopyOptions(node.Graph.Options, false)).
Mkdir("/empty", constants.DefaultDirMode),
llb.WithCustomName(node.Prefix+"cksum-prepare"),
).Run(
Expand All @@ -178,8 +198,8 @@ func (node *NodeLLB) stepDownload(root llb.State, step v1alpha2.Step) llb.State
).Root()

root = root.File(
llb.Copy(download, "/", step.TmpDir, defaultCopyOptions).
Copy(checksummer, "/empty", "/", defaultCopyOptions), // TODO: this is "fake" dependency on checksummer
llb.Copy(download, "/", step.TmpDir, defaultCopyOptions(node.Graph.Options, false)).
Copy(checksummer, "/empty", "/", defaultCopyOptions(node.Graph.Options, false)), // TODO: this is "fake" dependency on checksummer
llb.WithCustomName(node.Prefix+"download finalize"),
)
}
Expand Down Expand Up @@ -253,7 +273,7 @@ func (node *NodeLLB) finalize(root llb.State) llb.State {

for _, fin := range node.Pkg.Finalize {
newroot = newroot.File(
llb.Copy(root, fin.From, fin.To, defaultCopyOptions),
llb.Copy(root, fin.From, fin.To, defaultCopyOptions(node.Graph.Options, true)),
llb.WithCustomNamef(node.Prefix+"finalize %s -> %s", fin.From, fin.To),
)
}
Expand Down
13 changes: 8 additions & 5 deletions internal/pkg/environment/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
package environment

import (
"time"

"github.com/moby/buildkit/client/llb"

"github.com/talos-systems/bldr/internal/pkg/types"
)

// Options for bldr.
type Options struct {
BuildPlatform Platform
TargetPlatform Platform
Target string
CommonPrefix string
ProxyEnv *llb.ProxyEnv
BuildPlatform Platform
TargetPlatform Platform
Target string
CommonPrefix string
ProxyEnv *llb.ProxyEnv
SourceDateEpoch time.Time
}

// GetVariables returns set of variables set for options.
Expand Down
13 changes: 12 additions & 1 deletion internal/pkg/pkgfile/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"strconv"
"strings"
"time"

ctrplatforms "github.com/containerd/containerd/platforms"
"github.com/moby/buildkit/client/llb"
Expand All @@ -30,7 +31,8 @@ const (
keyTargetPlatform = "platform"
keyMultiPlatform = "multi-platform"

buildArgPrefix = "build-arg:"
buildArgPrefix = "build-arg:"
buildArgSourceDateEpoch = buildArgPrefix + "SOURCE_DATE_EPOCH"

localNameDockerfile = "dockerfile"
sharedKeyHint = constants.PkgYaml
Expand All @@ -45,6 +47,15 @@ func Build(ctx context.Context, c client.Client, options *environment.Options) (
options.Target = opts[keyTarget]
options.ProxyEnv = proxyEnvFromBuildArgs(filter(opts, buildArgPrefix))

if sourceDateEpoch, ok := opts[buildArgSourceDateEpoch]; ok {
timestamp, err := strconv.ParseInt(sourceDateEpoch, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing %q: %w", buildArgSourceDateEpoch, err)
}

options.SourceDateEpoch = time.Unix(timestamp, 0)
}

platforms := []environment.Platform{options.TargetPlatform}

if opts[keyTargetPlatform] != "" {
Expand Down

0 comments on commit 376fe2b

Please sign in to comment.