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)
4144func (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
180183func 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 ()
370+
371+ return resp .StatusCode == http .StatusOK , nil
372+ }
0 commit comments