build_buildkit.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "fmt"
  17. "net/url"
  18. "os"
  19. "path/filepath"
  20. "strings"
  21. ctxkube "github.com/docker/buildx/driver/kubernetes/context"
  22. "github.com/docker/buildx/store"
  23. "github.com/docker/buildx/store/storeutil"
  24. "github.com/docker/cli/cli/command"
  25. "github.com/docker/cli/cli/context/docker"
  26. ctxstore "github.com/docker/cli/cli/context/store"
  27. dockerclient "github.com/docker/docker/client"
  28. "github.com/sirupsen/logrus"
  29. "golang.org/x/sync/errgroup"
  30. "k8s.io/client-go/tools/clientcmd"
  31. "github.com/docker/buildx/build"
  32. "github.com/docker/buildx/driver"
  33. _ "github.com/docker/buildx/driver/docker" //nolint:blank-imports
  34. _ "github.com/docker/buildx/driver/docker-container" //nolint:blank-imports
  35. _ "github.com/docker/buildx/driver/kubernetes" //nolint:blank-imports
  36. _ "github.com/docker/buildx/driver/remote" //nolint:blank-imports
  37. xprogress "github.com/docker/buildx/util/progress"
  38. )
  39. func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]build.Options, mode string) (map[string]string, error) {
  40. dis, err := s.getDrivers(ctx)
  41. if err != nil {
  42. return nil, err
  43. }
  44. // Progress needs its own context that lives longer than the
  45. // build one otherwise it won't read all the messages from
  46. // build and will lock
  47. progressCtx, cancel := context.WithCancel(context.Background())
  48. defer cancel()
  49. w := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
  50. response, err := build.Build(ctx, dis, opts, &internalAPI{dockerCli: s.dockerCli}, filepath.Dir(s.configFile().Filename), w)
  51. errW := w.Wait()
  52. if err == nil {
  53. err = errW
  54. }
  55. if err != nil {
  56. return nil, WrapCategorisedComposeError(err, BuildFailure)
  57. }
  58. imagesBuilt := map[string]string{}
  59. for name, img := range response {
  60. if img == nil || len(img.ExporterResponse) == 0 {
  61. continue
  62. }
  63. digest, ok := img.ExporterResponse["containerimage.digest"]
  64. if !ok {
  65. continue
  66. }
  67. imagesBuilt[name] = digest
  68. }
  69. return imagesBuilt, err
  70. }
  71. func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, error) { //nolint:gocyclo
  72. txn, release, err := storeutil.GetStore(s.dockerCli)
  73. if err != nil {
  74. return nil, err
  75. }
  76. defer release()
  77. ng, err := storeutil.GetCurrentInstance(txn, s.dockerCli)
  78. if err != nil {
  79. return nil, err
  80. }
  81. dis := make([]build.DriverInfo, len(ng.Nodes))
  82. var f driver.Factory
  83. if ng.Driver != "" {
  84. factories := driver.GetFactories(true)
  85. for _, fac := range factories {
  86. if fac.Name() == ng.Driver {
  87. f = fac
  88. continue
  89. }
  90. }
  91. if f == nil {
  92. if f, err = driver.GetFactory(ng.Driver, true); f == nil || err != nil {
  93. return nil, fmt.Errorf("failed to find buildx driver %q, error: %w", ng.Driver, err)
  94. }
  95. }
  96. } else {
  97. ep := ng.Nodes[0].Endpoint
  98. dockerapi, err := clientForEndpoint(s.dockerCli, ep)
  99. if err != nil {
  100. return nil, err
  101. }
  102. f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
  103. if err != nil {
  104. return nil, err
  105. }
  106. ng.Driver = f.Name()
  107. }
  108. imageopt, err := storeutil.GetImageConfig(s.dockerCli, ng)
  109. if err != nil {
  110. return nil, err
  111. }
  112. eg, _ := errgroup.WithContext(ctx)
  113. for i, n := range ng.Nodes {
  114. func(i int, n store.Node) {
  115. eg.Go(func() error {
  116. di := build.DriverInfo{
  117. Name: n.Name,
  118. Platform: n.Platforms,
  119. ProxyConfig: storeutil.GetProxyConfig(s.dockerCli),
  120. }
  121. defer func() {
  122. dis[i] = di
  123. }()
  124. dockerapi, err := clientForEndpoint(s.dockerCli, n.Endpoint)
  125. if err != nil {
  126. di.Err = err
  127. return nil
  128. }
  129. // TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
  130. dockerapi.NegotiateAPIVersion(ctx)
  131. contextStore := s.dockerCli.ContextStore()
  132. var kcc driver.KubeClientConfig
  133. kcc, err = configFromContext(n.Endpoint, contextStore)
  134. if err != nil {
  135. // err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
  136. // try again with name="default".
  137. // FIXME: n should retain real context name.
  138. kcc, err = configFromContext("default", contextStore)
  139. if err != nil {
  140. logrus.Error(err)
  141. }
  142. }
  143. tryToUseKubeConfigInCluster := false
  144. if kcc == nil {
  145. tryToUseKubeConfigInCluster = true
  146. } else {
  147. if _, err := kcc.ClientConfig(); err != nil {
  148. tryToUseKubeConfigInCluster = true
  149. }
  150. }
  151. if tryToUseKubeConfigInCluster {
  152. kccInCluster := driver.KubeClientConfigInCluster{}
  153. if _, err := kccInCluster.ClientConfig(); err == nil {
  154. logrus.Debug("using kube config in cluster")
  155. kcc = kccInCluster
  156. }
  157. }
  158. d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
  159. if err != nil {
  160. di.Err = err
  161. return nil
  162. }
  163. di.Driver = d
  164. di.ImageOpt = imageopt
  165. return nil
  166. })
  167. }(i, n)
  168. }
  169. if err := eg.Wait(); err != nil {
  170. return nil, err
  171. }
  172. return dis, nil
  173. }
  174. func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
  175. list, err := dockerCli.ContextStore().List()
  176. if err != nil {
  177. return nil, err
  178. }
  179. for _, l := range list {
  180. if l.Name != name {
  181. continue
  182. }
  183. dep, ok := l.Endpoints["docker"]
  184. if !ok {
  185. return nil, fmt.Errorf("context %q does not have a Docker endpoint", name)
  186. }
  187. epm, ok := dep.(docker.EndpointMeta)
  188. if !ok {
  189. return nil, fmt.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
  190. }
  191. ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
  192. if err != nil {
  193. return nil, err
  194. }
  195. clientOpts, err := ep.ClientOpts()
  196. if err != nil {
  197. return nil, err
  198. }
  199. return dockerclient.NewClientWithOpts(clientOpts...)
  200. }
  201. ep := docker.Endpoint{
  202. EndpointMeta: docker.EndpointMeta{
  203. Host: name,
  204. },
  205. }
  206. clientOpts, err := ep.ClientOpts()
  207. if err != nil {
  208. return nil, err
  209. }
  210. return dockerclient.NewClientWithOpts(clientOpts...)
  211. }
  212. func configFromContext(endpointName string, s ctxstore.Reader) (clientcmd.ClientConfig, error) {
  213. if strings.HasPrefix(endpointName, "kubernetes://") {
  214. u, _ := url.Parse(endpointName)
  215. if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
  216. _ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
  217. }
  218. rules := clientcmd.NewDefaultClientConfigLoadingRules()
  219. apiConfig, err := rules.Load()
  220. if err != nil {
  221. return nil, err
  222. }
  223. return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
  224. }
  225. return ctxkube.ConfigFromContext(endpointName, s)
  226. }
  227. type internalAPI struct {
  228. dockerCli command.Cli
  229. }
  230. func (a *internalAPI) DockerAPI(name string) (dockerclient.APIClient, error) {
  231. if name == "" {
  232. name = a.dockerCli.CurrentContext()
  233. }
  234. return clientForEndpoint(a.dockerCli, name)
  235. }