github.com/docker/buildx@v0.14.1-0.20240514123050-afcb609966dc/bake/remote.go (about)

     1  package bake
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/docker/buildx/builder"
    11  	controllerapi "github.com/docker/buildx/controller/pb"
    12  	"github.com/docker/buildx/driver"
    13  	"github.com/docker/buildx/util/progress"
    14  	"github.com/moby/buildkit/client"
    15  	"github.com/moby/buildkit/client/llb"
    16  	"github.com/moby/buildkit/frontend/dockerui"
    17  	gwclient "github.com/moby/buildkit/frontend/gateway/client"
    18  	"github.com/moby/buildkit/session"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  type Input struct {
    23  	State *llb.State
    24  	URL   string
    25  }
    26  
    27  func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
    28  	var sessions []session.Attachable
    29  	var filename string
    30  
    31  	st, ok := dockerui.DetectGitContext(url, false)
    32  	if ok {
    33  		if ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{
    34  			ID:    "default",
    35  			Paths: strings.Split(os.Getenv("BUILDX_BAKE_GIT_SSH"), ","),
    36  		}}); err == nil {
    37  			sessions = append(sessions, ssh)
    38  		}
    39  		var gitAuthSecrets []*controllerapi.Secret
    40  		if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_TOKEN"); ok {
    41  			gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
    42  				ID:  llb.GitAuthTokenKey,
    43  				Env: "BUILDX_BAKE_GIT_AUTH_TOKEN",
    44  			})
    45  		}
    46  		if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_HEADER"); ok {
    47  			gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
    48  				ID:  llb.GitAuthHeaderKey,
    49  				Env: "BUILDX_BAKE_GIT_AUTH_HEADER",
    50  			})
    51  		}
    52  		if len(gitAuthSecrets) > 0 {
    53  			if secrets, err := controllerapi.CreateSecrets(gitAuthSecrets); err == nil {
    54  				sessions = append(sessions, secrets)
    55  			}
    56  		}
    57  	} else {
    58  		st, filename, ok = dockerui.DetectHTTPContext(url)
    59  		if !ok {
    60  			return nil, nil, errors.Errorf("not url context")
    61  		}
    62  	}
    63  
    64  	inp := &Input{State: st, URL: url}
    65  	var files []File
    66  
    67  	var node *builder.Node
    68  	for i, n := range nodes {
    69  		if n.Err == nil {
    70  			node = &nodes[i]
    71  			continue
    72  		}
    73  	}
    74  	if node == nil {
    75  		return nil, nil, nil
    76  	}
    77  
    78  	c, err := driver.Boot(ctx, ctx, node.Driver, pw)
    79  	if err != nil {
    80  		return nil, nil, err
    81  	}
    82  
    83  	ch, done := progress.NewChannel(pw)
    84  	defer func() { <-done }()
    85  	_, err = c.Build(ctx, client.SolveOpt{Session: sessions, Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
    86  		def, err := st.Marshal(ctx)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		res, err := c.Solve(ctx, gwclient.SolveRequest{
    91  			Definition: def.ToPB(),
    92  		})
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  
    97  		ref, err := res.SingleRef()
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  
   102  		if filename != "" {
   103  			files, err = filesFromURLRef(ctx, c, ref, inp, filename, names)
   104  		} else {
   105  			files, err = filesFromRef(ctx, ref, names)
   106  		}
   107  		return nil, err
   108  	}, ch)
   109  
   110  	if err != nil {
   111  		return nil, nil, err
   112  	}
   113  
   114  	return files, inp, nil
   115  }
   116  
   117  func isArchive(header []byte) bool {
   118  	for _, m := range [][]byte{
   119  		{0x42, 0x5A, 0x68},                   // bzip2
   120  		{0x1F, 0x8B, 0x08},                   // gzip
   121  		{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
   122  	} {
   123  		if len(header) < len(m) {
   124  			continue
   125  		}
   126  		if bytes.Equal(m, header[:len(m)]) {
   127  			return true
   128  		}
   129  	}
   130  
   131  	r := tar.NewReader(bytes.NewBuffer(header))
   132  	_, err := r.Next()
   133  	return err == nil
   134  }
   135  
   136  func filesFromURLRef(ctx context.Context, c gwclient.Client, ref gwclient.Reference, inp *Input, filename string, names []string) ([]File, error) {
   137  	stat, err := ref.StatFile(ctx, gwclient.StatRequest{Path: filename})
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
   143  		Filename: filename,
   144  		Range: &gwclient.FileRange{
   145  			Length: 1024,
   146  		},
   147  	})
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	if isArchive(dt) {
   153  		bc := llb.Scratch().File(llb.Copy(inp.State, filename, "/", &llb.CopyInfo{
   154  			AttemptUnpack: true,
   155  		}))
   156  		inp.State = &bc
   157  		inp.URL = ""
   158  		def, err := bc.Marshal(ctx)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		res, err := c.Solve(ctx, gwclient.SolveRequest{
   163  			Definition: def.ToPB(),
   164  		})
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  
   169  		ref, err := res.SingleRef()
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  
   174  		return filesFromRef(ctx, ref, names)
   175  	}
   176  
   177  	inp.State = nil
   178  	name := inp.URL
   179  	inp.URL = ""
   180  
   181  	if len(dt) > stat.Size() {
   182  		if stat.Size() > 1024*512 {
   183  			return nil, errors.Errorf("non-archive definition URL bigger than maximum allowed size")
   184  		}
   185  
   186  		dt, err = ref.ReadFile(ctx, gwclient.ReadRequest{
   187  			Filename: filename,
   188  		})
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  	}
   193  
   194  	return []File{{Name: name, Data: dt}}, nil
   195  }
   196  
   197  func filesFromRef(ctx context.Context, ref gwclient.Reference, names []string) ([]File, error) {
   198  	// TODO: auto-remove parent dir in needed
   199  	var files []File
   200  
   201  	isDefault := false
   202  	if len(names) == 0 {
   203  		isDefault = true
   204  		names = defaultFilenames()
   205  	}
   206  
   207  	for _, name := range names {
   208  		_, err := ref.StatFile(ctx, gwclient.StatRequest{Path: name})
   209  		if err != nil {
   210  			if isDefault {
   211  				continue
   212  			}
   213  			return nil, err
   214  		}
   215  		dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{Filename: name})
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		files = append(files, File{Name: name, Data: dt})
   220  	}
   221  
   222  	return files, nil
   223  }