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

feat: implement reproducible build and get cached image #213

Merged
merged 15 commits into from
Jun 12, 2024
Merged
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,6 @@ On MacOS or Windows systems, we recommend either using a VM or the provided `.de
| `--coder-agent-url` | `CODER_AGENT_URL` | | URL of the Coder deployment. If CODER_AGENT_TOKEN is also set, logs from envbuilder will be forwarded here and will be visible in the workspace build logs. |
| `--coder-agent-token` | `CODER_AGENT_TOKEN` | | Authentication token for a Coder agent. If this is set, then CODER_AGENT_URL must also be set. |
| `--coder-agent-subsystem` | `CODER_AGENT_SUBSYSTEM` | | Coder agent subsystems to report when forwarding logs. The envbuilder subsystem is always included. |
| `--push-image` | `ENVBUILDER_PUSH_IMAGE` | | Flag to determine if the image should be pushed to the container registry. This option implies reproducible builds. |
| `--get-cached-image` | `ENVBUILDER_GET_CACHED_IMAGE` | | Flag to determine if the cached image is available, and if it is, to return it. |
<!--- END docsgen --->
51 changes: 43 additions & 8 deletions envbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ func Run(ctx context.Context, options Options) error {
options.WorkspaceFolder = f
}

stageNumber := 1
stageNumber := 0
startStage := func(format string, args ...any) func(format string, args ...any) {
now := time.Now()
stageNum := stageNumber
stageNumber++
stageNum := stageNumber
options.Logger(notcodersdk.LogLevelInfo, "#%d: %s", stageNum, fmt.Sprintf(format, args...))

return func(format string, args ...any) {
Expand Down Expand Up @@ -338,7 +338,7 @@ func Run(ctx context.Context, options Options) error {

HijackLogrus(func(entry *logrus.Entry) {
for _, line := range strings.Split(entry.Message, "\r") {
options.Logger(notcodersdk.LogLevelInfo, "#2: %s", color.HiBlackString(line))
options.Logger(notcodersdk.LogLevelInfo, "#%d: %s", stageNumber, color.HiBlackString(line))
}
})

Expand Down Expand Up @@ -474,20 +474,24 @@ func Run(ctx context.Context, options Options) error {
cacheTTL = time.Hour * 24 * time.Duration(options.CacheTTLDays)
}

endStage := startStage("πŸ—οΈ Building image...")
// At this point we have all the context, we can now build!
registryMirror := []string{}
if val, ok := os.LookupEnv("KANIKO_REGISTRY_MIRROR"); ok {
registryMirror = strings.Split(val, ";")
}
image, err := executor.DoBuild(&config.KanikoOptions{
var destinations []string
if options.CacheRepo != "" {
destinations = append(destinations, options.CacheRepo)
}
opts := &config.KanikoOptions{
// Boilerplate!
CustomPlatform: platforms.Format(platforms.Normalize(platforms.DefaultSpec())),
SnapshotMode: "redo",
RunV2: true,
RunStdout: stdoutWriter,
RunStderr: stderrWriter,
Destinations: []string{"local"},
Destinations: destinations,
NoPush: !options.PushImage || len(destinations) == 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming the reason for the logical disjuction here is that Kaniko will push all intermediate cache layers to destinations, while specifying options.PushImage signifies to push only the final result?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep πŸ‘πŸ»

CacheRunLayers: true,
CacheCopyLayers: true,
CompressedCaching: true,
Expand Down Expand Up @@ -518,11 +522,42 @@ func Run(ctx context.Context, options Options) error {
RegistryMirrors: registryMirror,
},
SrcContext: buildParams.BuildContext,
})

// For cached image utilization, produce reproducible builds.
Reproducible: options.PushImage,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this getting used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

if options.GetCachedImage {
endStage := startStage("πŸ—οΈ Building fake image...")
image, err := executor.DoFakeBuild(opts)
if err != nil {
logrus.Infof("unable to build fake image: %s", err)
johnstcn marked this conversation as resolved.
Show resolved Hide resolved
os.Exit(1)
}
endStage("πŸ—οΈ Built fake image!")
digest, err := image.Digest()
if err != nil {
return nil, xerrors.Errorf("image digest: %w", err)
}

_, _ = fmt.Fprintf(os.Stdout, "%s@%s\n", options.CacheRepo, digest.String())
os.Exit(0)
}

endStage := startStage("πŸ—οΈ Building image...")
image, err := executor.DoBuild(opts)
if err != nil {
return nil, err
return nil, xerrors.Errorf("do build: %w", err)
}
endStage("πŸ—οΈ Built image!")
if options.PushImage {
endStage = startStage("πŸ—οΈ Pushing image...")
if err := executor.DoPush(image, opts); err != nil {
return nil, xerrors.Errorf("do push: %w", err)
}
endStage("πŸ—οΈ Pushed image!")
}

return image, err
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ toolchain go1.22.3

// There are a few options we need added to Kaniko!
// See: https://github.com/GoogleContainerTools/kaniko/compare/main...coder:kaniko:main
replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240524082248-9d0d55902c34
replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240530173157-696ae8dd977a

require (
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/coder/kaniko v0.0.0-20240524082248-9d0d55902c34 h1:Wm7sMNc1aTN5l0NerYHb3LZdQJVQp4QrW4v83N21sfc=
github.com/coder/kaniko v0.0.0-20240524082248-9d0d55902c34/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM=
github.com/coder/kaniko v0.0.0-20240530173157-696ae8dd977a h1:6BYdPtclD76/AfADhH19JAFGvorhx7xmXf+Wp6d9TEk=
github.com/coder/kaniko v0.0.0-20240530173157-696ae8dd977a/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM=
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc=
Expand Down
21 changes: 21 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ type Options struct {
// CoderAgentSubsystem is the Coder agent subsystems to report when forwarding
// logs. The envbuilder subsystem is always included.
CoderAgentSubsystem []string

// PushImage is a flag to determine if the image should be pushed to the
// container registry. This option implies reproducible builds.
PushImage bool
// GetCachedImage is a flag to determine if the cached image is available,
// and if it is, to return it.
GetCachedImage bool
}

const envPrefix = "ENVBUILDER_"
Expand Down Expand Up @@ -395,6 +402,20 @@ func (o *Options) CLI() serpent.OptionSet {
Description: "Coder agent subsystems to report when forwarding logs. " +
"The envbuilder subsystem is always included.",
},
{
Flag: "push-image",
Env: WithEnvPrefix("PUSH_IMAGE"),
Value: serpent.BoolOf(&o.PushImage),
Description: "Flag to determine if the image should be pushed to " +
"the container registry. This option implies reproducible builds.",
johnstcn marked this conversation as resolved.
Show resolved Hide resolved
},
{
Flag: "get-cached-image",
Env: WithEnvPrefix("GET_CACHED_IMAGE"),
Value: serpent.BoolOf(&o.GetCachedImage),
Description: "Flag to determine if the cached image is available, " +
"and if it is, to return it.",
johnstcn marked this conversation as resolved.
Show resolved Hide resolved
},
}

// Add options without the prefix for backward compatibility. These options
Expand Down
1 change: 1 addition & 0 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func runCLI() envbuilder.Options {
}

i := cmd.Invoke().WithOS()
i.Args = []string{"--help"}
fakeIO(i)
err := i.Run()
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions testdata/options.golden
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ OPTIONS:
your system! This is used in cases where bypass is needed to unblock
customers.

--get-cached-image bool, $ENVBUILDER_GET_CACHED_IMAGE
Flag to determine if the cached image is available, and if it is, to
return it.

--git-clone-depth int, $ENVBUILDER_GIT_CLONE_DEPTH
The depth to use when cloning the Git repository.

Expand Down Expand Up @@ -129,6 +133,10 @@ OPTIONS:
should check for the presence of this script and execute it after
successful startup.

--push-image bool, $ENVBUILDER_PUSH_IMAGE
Flag to determine if the image should be pushed to the container
registry. This option implies reproducible builds.

--setup-script string, $ENVBUILDER_SETUP_SCRIPT
The script to run before the init script. It runs as the root user
regardless of the user specified in the devcontainer.json file.
Expand Down