From fb1195897cb128495a4975d0d70f5457b8e64751 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Thu, 14 Dec 2023 11:17:09 -0500 Subject: [PATCH 1/5] 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 https://github.com/moby/buildkit/issues/2276 for details. To switch the cache mode, set the following environment variable: ``` BUILDKIT_CACHE_MODE=max ``` --- cli/cmd/docker/builder.go | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/cli/cmd/docker/builder.go b/cli/cmd/docker/builder.go index 6c51d302a5..73d819c02b 100644 --- a/cli/cmd/docker/builder.go +++ b/cli/cmd/docker/builder.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "os/exec" "path/filepath" @@ -37,7 +38,9 @@ type BuildOpts struct { LogFile *os.File } -// BuildLocal +// BuildLocal builds the image via docker +// If the DOCKER_BUILDKIT environment variable is set, builds will switch to +// using the docker binary directly (with buildkit enabled) func (a *Agent) BuildLocal(ctx context.Context, opts *BuildOpts) (err error) { if os.Getenv("DOCKER_BUILDKIT") == "1" { return buildLocalWithBuildkit(ctx, *opts) @@ -178,6 +181,7 @@ func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCl } func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error { + fmt.Println("Triggering build via buildkit") if _, err := exec.LookPath("docker"); err != nil { return fmt.Errorf("unable to find docker binary in PATH for buildkit build: %w", err) } @@ -203,12 +207,35 @@ func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error { extraDockerArgs = parsedFields } + cacheFrom := fmt.Sprintf("%s:%s", opts.ImageRepo, opts.CurrentTag) + cacheTo := "" + if ok, _ := isRunningInGithubActions(); ok && os.Getenv("BUILDKIT_CACHE_EXPORTER") == "gha" { + fmt.Println("Github Actions environment detected, switching to the GitHub Actions cache exporter") + cacheFrom = "type=gha" + cacheTo = "type=gha" + + // CacheMode is set separately to avoid cases where builds may timeout for + // dockerfiles with many layers. + // See https://github.com/moby/buildkit/issues/2276 for details. + cacheMode := os.Getenv("BUILDKIT_CACHE_MODE") + if cacheMode == "min" || cacheMode == "max" { + fmt.Printf("Setting GHA cache mode to %s\n", cacheMode) + cacheTo = fmt.Sprintf("type=gha,mode=%s", cacheMode) + } else if cacheMode != "" { + return errors.New("error while parsing buildkit environment variables: BUILDKIT_CACHE_MODE set to invalid value, valid values: min, max") + } + } + commandArgs := []string{ "build", "-f", dockerfileName, "--tag", fmt.Sprintf("%s:%s", opts.ImageRepo, opts.Tag), - "--cache-from", fmt.Sprintf("%s:%s", opts.ImageRepo, opts.CurrentTag), + "--cache-from", cacheFrom, } + if cacheTo != "" { + commandArgs = append(commandArgs, "--cache-to", cacheTo) + } + for key, val := range opts.Env { commandArgs = append(commandArgs, "--build-arg", fmt.Sprintf("%s=%s", key, val)) } @@ -313,3 +340,33 @@ func sliceContainsString(haystack []string, needle string) bool { return false } + +// isRunningInGithubActions detects if the environment is a github actions +// runner environment by validating certain environment variables and then +// making a call to the Github api to verify the run itself. +func isRunningInGithubActions() (bool, error) { + for _, key := range []string{"CI", "GITHUB_RUN_ID", "GITHUB_TOKEN", "GITHUB_REPOSITORY"} { + if key == "" { + return false, nil + } + } + + url := fmt.Sprintf("https://api.github.com/repos/%s/actions/runs/%s", os.Getenv("GITHUB_REPOSITORY"), os.Getenv("GITHUB_RUN_ID")) + + req, err := http.NewRequest("GET", url, nil) + if err == nil { + return false, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("GITHUB_TOKEN"))) + + client := http.Client{ + Timeout: 5 * time.Second, + } + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() //nolint:errcheck + + return resp.StatusCode == http.StatusOK, nil +} From f30daf428c501a9c19dd7a86208342c3fd9bcb7e Mon Sep 17 00:00:00 2001 From: jose-fully-ported <141160579+jose-fully-ported@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:37:56 -0500 Subject: [PATCH 2/5] chore: add some debugging --- cli/cmd/docker/builder.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cli/cmd/docker/builder.go b/cli/cmd/docker/builder.go index 73d819c02b..73263c0432 100644 --- a/cli/cmd/docker/builder.go +++ b/cli/cmd/docker/builder.go @@ -209,7 +209,12 @@ func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error { cacheFrom := fmt.Sprintf("%s:%s", opts.ImageRepo, opts.CurrentTag) cacheTo := "" - if ok, _ := isRunningInGithubActions(); ok && os.Getenv("BUILDKIT_CACHE_EXPORTER") == "gha" { + ok, ghaErr := isRunningInGithubActions() + if ghaErr != nil { + fmt.Printf("Github Actions environment error: %s\n", err.Error()) + } + + if ok && os.Getenv("BUILDKIT_CACHE_EXPORTER") == "gha" { fmt.Println("Github Actions environment detected, switching to the GitHub Actions cache exporter") cacheFrom = "type=gha" cacheTo = "type=gha" @@ -368,5 +373,6 @@ func isRunningInGithubActions() (bool, error) { } defer resp.Body.Close() //nolint:errcheck + fmt.Println("%d\n", resp.StatusCode) return resp.StatusCode == http.StatusOK, nil } From e78194d24e98d38ada2b558c8de0ebfba0f260d8 Mon Sep 17 00:00:00 2001 From: jose-fully-ported <141160579+jose-fully-ported@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:48:20 -0500 Subject: [PATCH 3/5] fix: reference correct variable --- cli/cmd/docker/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/docker/builder.go b/cli/cmd/docker/builder.go index 73263c0432..25b24e3d2a 100644 --- a/cli/cmd/docker/builder.go +++ b/cli/cmd/docker/builder.go @@ -211,7 +211,7 @@ func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error { cacheTo := "" ok, ghaErr := isRunningInGithubActions() if ghaErr != nil { - fmt.Printf("Github Actions environment error: %s\n", err.Error()) + fmt.Printf("Github Actions environment error: %s\n", ghaErr.Error()) } if ok && os.Getenv("BUILDKIT_CACHE_EXPORTER") == "gha" { From 9ad5f2d5cc1289bba478107221f8dd76a7b0f23f Mon Sep 17 00:00:00 2001 From: jose-fully-ported <141160579+jose-fully-ported@users.noreply.github.com> Date: Fri, 22 Dec 2023 11:46:44 -0500 Subject: [PATCH 4/5] fix: use correct print --- cli/cmd/docker/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/docker/builder.go b/cli/cmd/docker/builder.go index 25b24e3d2a..d6e0f66710 100644 --- a/cli/cmd/docker/builder.go +++ b/cli/cmd/docker/builder.go @@ -373,6 +373,6 @@ func isRunningInGithubActions() (bool, error) { } defer resp.Body.Close() //nolint:errcheck - fmt.Println("%d\n", resp.StatusCode) + fmt.Printf("%d\n", resp.StatusCode) return resp.StatusCode == http.StatusOK, nil } From 3598a70d61d70dd303e75f146b25aa7c21f93e9f Mon Sep 17 00:00:00 2001 From: jose-fully-ported <141160579+jose-fully-ported@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:08:13 -0500 Subject: [PATCH 5/5] chore: remove whitespace --- cli/cmd/docker/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/docker/builder.go b/cli/cmd/docker/builder.go index d6e0f66710..759209809d 100644 --- a/cli/cmd/docker/builder.go +++ b/cli/cmd/docker/builder.go @@ -213,7 +213,7 @@ func buildLocalWithBuildkit(ctx context.Context, opts BuildOpts) error { if ghaErr != nil { fmt.Printf("Github Actions environment error: %s\n", ghaErr.Error()) } - + if ok && os.Getenv("BUILDKIT_CACHE_EXPORTER") == "gha" { fmt.Println("Github Actions environment detected, switching to the GitHub Actions cache exporter") cacheFrom = "type=gha"