github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/cmd/buildkitapi/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"flag"
     7  	"io"
     8  	"log"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/client"
    15  	"github.com/docker/docker/pkg/jsonmessage"
    16  	controlapi "github.com/moby/buildkit/api/services/control"
    17  	"github.com/moby/buildkit/identity"
    18  	"github.com/moby/buildkit/session"
    19  	"github.com/moby/buildkit/session/filesync"
    20  	"github.com/pkg/errors"
    21  	"github.com/tonistiigi/fsutil"
    22  	fsutiltypes "github.com/tonistiigi/fsutil/types"
    23  )
    24  
    25  var useCache bool
    26  var useLegacyAPI bool
    27  var contextDir string
    28  
    29  // A small utility for running Buildkit on the dockerfile
    30  // in the current directory printing out all the buildkit api
    31  // response protobufs.
    32  func main() {
    33  	flag.BoolVar(&useCache, "cache", false, "Enable docker caching")
    34  	flag.BoolVar(&useLegacyAPI, "legacy", false, "Print legacy build events")
    35  	flag.StringVar(&contextDir, "context", "", "Context directory")
    36  	flag.Parse()
    37  
    38  	err := run()
    39  	if err != nil {
    40  		log.Fatal(err)
    41  	}
    42  }
    43  
    44  func run() error {
    45  	ctx := context.Background()
    46  	d, err := client.NewEnvClient()
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	d.NegotiateAPIVersion(ctx)
    52  
    53  	session, err := session.NewSession(ctx, "tilt", identity.NewID())
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	fileMap := func(path string, s *fsutiltypes.Stat) fsutil.MapResult {
    59  		s.Uid = 0
    60  		s.Gid = 0
    61  		return fsutil.MapResultKeep
    62  	}
    63  
    64  	dir, _ := os.Getwd()
    65  	if contextDir == "" {
    66  		contextDir = dir
    67  	}
    68  	if !filepath.IsAbs(contextDir) {
    69  		contextDir = filepath.Join(dir, contextDir)
    70  	}
    71  	session.Allow(filesync.NewFSSyncProvider(filesync.StaticDirSource{
    72  		"context": filesync.SyncedDir{
    73  			Dir: contextDir,
    74  			Map: fileMap,
    75  		},
    76  		"dockerfile": filesync.SyncedDir{
    77  			Dir: dir,
    78  		},
    79  	}))
    80  
    81  	go func() {
    82  		defer func() {
    83  			_ = session.Close()
    84  		}()
    85  
    86  		// Start the server
    87  		dialSession := func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
    88  			return d.DialHijack(ctx, "/session", proto, meta)
    89  		}
    90  		_ = session.Run(ctx, dialSession)
    91  	}()
    92  
    93  	opts := types.ImageBuildOptions{}
    94  	opts.Version = types.BuilderBuildKit
    95  	opts.Dockerfile = "Dockerfile"
    96  	opts.RemoteContext = "client-session"
    97  	opts.SessionID = session.ID()
    98  	if !useCache {
    99  		opts.NoCache = true
   100  	}
   101  	defer session.Close()
   102  
   103  	response, err := d.ImageBuild(ctx, nil, opts)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer func() {
   108  		_ = response.Body.Close()
   109  	}()
   110  
   111  	return readDockerOutput(ctx, response.Body)
   112  }
   113  
   114  func readDockerOutput(ctx context.Context, reader io.Reader) error {
   115  	decoder := json.NewDecoder(reader)
   116  
   117  	for decoder.More() {
   118  		message := jsonmessage.JSONMessage{}
   119  		err := decoder.Decode(&message)
   120  		if err != nil {
   121  			return errors.Wrap(err, "decoding docker output")
   122  		}
   123  
   124  		isFromBuildkit := messageIsFromBuildkit(message)
   125  		if isFromBuildkit && !useLegacyAPI {
   126  			err := writeBuildkitStatus(message.Aux)
   127  			if err != nil {
   128  				return err
   129  			}
   130  		} else if !isFromBuildkit && useLegacyAPI {
   131  			err := json.NewEncoder(os.Stdout).Encode(message)
   132  			if err != nil {
   133  				return err
   134  			}
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  func writeBuildkitStatus(aux *json.RawMessage) error {
   141  	var resp controlapi.StatusResponse
   142  	var dt []byte
   143  	if err := json.Unmarshal(*aux, &dt); err != nil {
   144  		return err
   145  	}
   146  	if err := (&resp).Unmarshal(dt); err != nil {
   147  		return err
   148  	}
   149  
   150  	return json.NewEncoder(os.Stdout).Encode(resp)
   151  }
   152  
   153  func messageIsFromBuildkit(msg jsonmessage.JSONMessage) bool {
   154  	return msg.ID == "moby.buildkit.trace"
   155  }