Skip to content

Commit fb11958

Browse files
feat: use gha cache exporter when building via buildkit
Buildkit builds expose custom cache exporters which can be used to locally speed up builds on Github Actions. The gha cacher will use the Github Actions cache api instead of the remote registry for caching. To enable this, set the following two environment variables. ``` DOCKER_BUILDKIT=1 BUILDKIT_CACHE_EXPORTER=gha ``` Note that the cache mode is set to min by default (no value == min) due to a potential timeout issue within the exporter. See moby/buildkit#2276 for details. To switch the cache mode, set the following environment variable: ``` BUILDKIT_CACHE_MODE=max ```
1 parent 429effd commit fb11958

File tree

1 file changed

+59
-2
lines changed

1 file changed

+59
-2
lines changed

cli/cmd/docker/builder.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"io/ioutil"
11+
"net/http"
1112
"os"
1213
"os/exec"
1314
"path/filepath"
@@ -37,7 +38,9 @@ type BuildOpts struct {
3738
LogFile *os.File
3839
}
3940

40-
// BuildLocal
41+
// BuildLocal builds the image via docker
42+
// If the DOCKER_BUILDKIT environment variable is set, builds will switch to
43+
// using the docker binary directly (with buildkit enabled)
4144
func (a *Agent) BuildLocal(ctx context.Context, opts *BuildOpts) (err error) {
4245
if os.Getenv("DOCKER_BUILDKIT") == "1" {
4346
return buildLocalWithBuildkit(ctx, *opts)
@@ -178,6 +181,7 @@ func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCl
178181
}
179182

180183
func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error {
184+
fmt.Println("Triggering build via buildkit")
181185
if _, err := exec.LookPath("docker"); err != nil {
182186
return fmt.Errorf("unable to find docker binary in PATH for buildkit build: %w", err)
183187
}
@@ -203,12 +207,35 @@ func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error {
203207
extraDockerArgs = parsedFields
204208
}
205209

210+
cacheFrom := fmt.Sprintf("%s:%s", opts.ImageRepo, opts.CurrentTag)
211+
cacheTo := ""
212+
if ok, _ := isRunningInGithubActions(); ok && os.Getenv("BUILDKIT_CACHE_EXPORTER") == "gha" {
213+
fmt.Println("Github Actions environment detected, switching to the GitHub Actions cache exporter")
214+
cacheFrom = "type=gha"
215+
cacheTo = "type=gha"
216+
217+
// CacheMode is set separately to avoid cases where builds may timeout for
218+
// dockerfiles with many layers.
219+
// See https://github.com/moby/buildkit/issues/2276 for details.
220+
cacheMode := os.Getenv("BUILDKIT_CACHE_MODE")
221+
if cacheMode == "min" || cacheMode == "max" {
222+
fmt.Printf("Setting GHA cache mode to %s\n", cacheMode)
223+
cacheTo = fmt.Sprintf("type=gha,mode=%s", cacheMode)
224+
} else if cacheMode != "" {
225+
return errors.New("error while parsing buildkit environment variables: BUILDKIT_CACHE_MODE set to invalid value, valid values: min, max")
226+
}
227+
}
228+
206229
commandArgs := []string{
207230
"build",
208231
"-f", dockerfileName,
209232
"--tag", fmt.Sprintf("%s:%s", opts.ImageRepo, opts.Tag),
210-
"--cache-from", fmt.Sprintf("%s:%s", opts.ImageRepo, opts.CurrentTag),
233+
"--cache-from", cacheFrom,
211234
}
235+
if cacheTo != "" {
236+
commandArgs = append(commandArgs, "--cache-to", cacheTo)
237+
}
238+
212239
for key, val := range opts.Env {
213240
commandArgs = append(commandArgs, "--build-arg", fmt.Sprintf("%s=%s", key, val))
214241
}
@@ -313,3 +340,33 @@ func sliceContainsString(haystack []string, needle string) bool {
313340

314341
return false
315342
}
343+
344+
// isRunningInGithubActions detects if the environment is a github actions
345+
// runner environment by validating certain environment variables and then
346+
// making a call to the Github api to verify the run itself.
347+
func isRunningInGithubActions() (bool, error) {
348+
for _, key := range []string{"CI", "GITHUB_RUN_ID", "GITHUB_TOKEN", "GITHUB_REPOSITORY"} {
349+
if key == "" {
350+
return false, nil
351+
}
352+
}
353+
354+
url := fmt.Sprintf("https://api.github.com/repos/%s/actions/runs/%s", os.Getenv("GITHUB_REPOSITORY"), os.Getenv("GITHUB_RUN_ID"))
355+
356+
req, err := http.NewRequest("GET", url, nil)
357+
if err == nil {
358+
return false, err
359+
}
360+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("GITHUB_TOKEN")))
361+
362+
client := http.Client{
363+
Timeout: 5 * time.Second,
364+
}
365+
resp, err := client.Do(req)
366+
if err != nil {
367+
return false, err
368+
}
369+
defer resp.Body.Close() //nolint:errcheck
370+
371+
return resp.StatusCode == http.StatusOK, nil
372+
}

0 commit comments

Comments
 (0)