github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/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 }