github.com/tilt-dev/tilt@v0.36.0/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, 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  
    72  	contextFS, err := fsutil.NewFS(contextDir)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	contextFS, err = fsutil.NewFilterFS(contextFS, &fsutil.FilterOpt{
    78  		Map: fileMap,
    79  	})
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	dockerfileFS, err := fsutil.NewFS(dir)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	session.Allow(filesync.NewFSSyncProvider(filesync.StaticDirSource{
    90  		"context":    contextFS,
    91  		"dockerfile": dockerfileFS,
    92  	}))
    93  
    94  	go func() {
    95  		defer func() {
    96  			_ = session.Close()
    97  		}()
    98  
    99  		// Start the server
   100  		dialSession := func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
   101  			return d.DialHijack(ctx, "/session", proto, meta)
   102  		}
   103  		_ = session.Run(ctx, dialSession)
   104  	}()
   105  
   106  	opts := types.ImageBuildOptions{}
   107  	opts.Version = types.BuilderBuildKit
   108  	opts.Dockerfile = "Dockerfile"
   109  	opts.RemoteContext = "client-session"
   110  	opts.SessionID = session.ID()
   111  	if !useCache {
   112  		opts.NoCache = true
   113  	}
   114  	defer session.Close()
   115  
   116  	response, err := d.ImageBuild(ctx, nil, opts)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	defer func() {
   121  		_ = response.Body.Close()
   122  	}()
   123  
   124  	return readDockerOutput(ctx, response.Body)
   125  }
   126  
   127  func readDockerOutput(ctx context.Context, reader io.Reader) error {
   128  	decoder := json.NewDecoder(reader)
   129  
   130  	for decoder.More() {
   131  		message := jsonmessage.JSONMessage{}
   132  		err := decoder.Decode(&message)
   133  		if err != nil {
   134  			return errors.Wrap(err, "decoding docker output")
   135  		}
   136  
   137  		isFromBuildkit := messageIsFromBuildkit(message)
   138  		if isFromBuildkit && !useLegacyAPI {
   139  			err := writeBuildkitStatus(message.Aux)
   140  			if err != nil {
   141  				return err
   142  			}
   143  		} else if !isFromBuildkit && useLegacyAPI {
   144  			err := json.NewEncoder(os.Stdout).Encode(message)
   145  			if err != nil {
   146  				return err
   147  			}
   148  		}
   149  	}
   150  	return nil
   151  }
   152  
   153  func writeBuildkitStatus(aux *json.RawMessage) error {
   154  	var resp controlapi.StatusResponse
   155  	var dt []byte
   156  	if err := json.Unmarshal(*aux, &dt); err != nil {
   157  		return err
   158  	}
   159  	if err := (&resp).UnmarshalVT(dt); err != nil {
   160  		return err
   161  	}
   162  
   163  	return json.NewEncoder(os.Stdout).Encode(&resp)
   164  }
   165  
   166  func messageIsFromBuildkit(msg jsonmessage.JSONMessage) bool {
   167  	return msg.ID == "moby.buildkit.trace"
   168  }