build_buildkit.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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. xprogress "github.com/docker/buildx/util/progress"
  37. )
  38. func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]build.Options, mode string) (map[string]string, error) {
  39. dis, err := s.getDrivers(ctx)
  40. if err != nil {
  41. return nil, err
  42. }
  43. // Progress needs its own context that lives longer than the
  44. // build one otherwise it won't read all the messages from
  45. // build and will lock
  46. progressCtx, cancel := context.WithCancel(context.Background())
  47. defer cancel()
  48. w := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
  49. response, err := build.Build(ctx, dis, opts, &internalAPI{dockerCli: s.dockerCli}, filepath.Dir(s.configFile().Filename), w)
  50. errW := w.Wait()
  51. if err == nil {
  52. err = errW
  53. }
  54. if err != nil {
  55. return nil, WrapCategorisedComposeError(err, BuildFailure)
  56. }
  57. imagesBuilt := map[string]string{}
  58. for name, img := range response {
  59. if img == nil || len(img.ExporterResponse) == 0 {
  60. continue
  61. }
  62. digest, ok := img.ExporterResponse["containerimage.digest"]
  63. if !ok {
  64. continue
  65. }
  66. imagesBuilt[name] = digest
  67. }
  68. return imagesBuilt, err
  69. }
  70. func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, error) { //nolint:gocyclo
  71. txn, release, err := storeutil.GetStore(s.dockerCli)
  72. if err != nil {
  73. return nil, err
  74. }
  75. defer release()
  76. ng, err := storeutil.GetCurrentInstance(txn, s.dockerCli)
  77. if err != nil {
  78. return nil, err
  79. }
  80. dis := make([]build.DriverInfo, len(ng.Nodes))
  81. var f driver.Factory
  82. if ng.Driver != "" {
  83. factories := driver.GetFactories()
  84. for _, fac := range factories {
  85. if fac.Name() == ng.Driver {
  86. f = fac
  87. continue
  88. }
  89. }
  90. if f == nil {
  91. if f = driver.GetFactory(ng.Driver, true); f == nil {
  92. return nil, fmt.Errorf("failed to find buildx driver %q", ng.Driver)
  93. }
  94. }
  95. } else {
  96. ep := ng.Nodes[0].Endpoint
  97. dockerapi, err := clientForEndpoint(s.dockerCli, ep)
  98. if err != nil {
  99. return nil, err
  100. }
  101. f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
  102. if err != nil {
  103. return nil, err
  104. }
  105. ng.Driver = f.Name()
  106. }
  107. imageopt, err := storeutil.GetImageConfig(s.dockerCli, ng)
  108. if err != nil {
  109. return nil, err
  110. }
  111. eg, _ := errgroup.WithContext(ctx)
  112. for i, n := range ng.Nodes {
  113. func(i int, n store.Node) {
  114. eg.Go(func() error {
  115. di := build.DriverInfo{
  116. Name: n.Name,
  117. Platform: n.Platforms,
  118. ProxyConfig: storeutil.GetProxyConfig(s.dockerCli),
  119. }
  120. defer func() {
  121. dis[i] = di
  122. }()
  123. dockerapi, err := clientForEndpoint(s.dockerCli, n.Endpoint)
  124. if err != nil {
  125. di.Err = err
  126. return nil
  127. }
  128. // TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
  129. dockerapi.NegotiateAPIVersion(ctx)
  130. contextStore := s.dockerCli.ContextStore()
  131. var kcc driver.KubeClientConfig
  132. kcc, err = configFromContext(n.Endpoint, contextStore)
  133. if err != nil {
  134. // err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
  135. // try again with name="default".
  136. // FIXME: n should retain real context name.
  137. kcc, err = configFromContext("default", contextStore)
  138. if err != nil {
  139. logrus.Error(err)
  140. }
  141. }
  142. tryToUseKubeConfigInCluster := false
  143. if kcc == nil {
  144. tryToUseKubeConfigInCluster = true
  145. } else {
  146. if _, err := kcc.ClientConfig(); err != nil {
  147. tryToUseKubeConfigInCluster = true
  148. }
  149. }
  150. if tryToUseKubeConfigInCluster {
  151. kccInCluster := driver.KubeClientConfigInCluster{}
  152. if _, err := kccInCluster.ClientConfig(); err == nil {
  153. logrus.Debug("using kube config in cluster")
  154. kcc = kccInCluster
  155. }
  156. }
  157. d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
  158. if err != nil {
  159. di.Err = err
  160. return nil
  161. }
  162. di.Driver = d
  163. di.ImageOpt = imageopt
  164. return nil
  165. })
  166. }(i, n)
  167. }
  168. if err := eg.Wait(); err != nil {
  169. return nil, err
  170. }
  171. return dis, nil
  172. }
  173. func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
  174. list, err := dockerCli.ContextStore().List()
  175. if err != nil {
  176. return nil, err
  177. }
  178. for _, l := range list {
  179. if l.Name != name {
  180. continue
  181. }
  182. dep, ok := l.Endpoints["docker"]
  183. if !ok {
  184. return nil, fmt.Errorf("context %q does not have a Docker endpoint", name)
  185. }
  186. epm, ok := dep.(docker.EndpointMeta)
  187. if !ok {
  188. return nil, fmt.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
  189. }
  190. ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
  191. if err != nil {
  192. return nil, err
  193. }
  194. clientOpts, err := ep.ClientOpts()
  195. if err != nil {
  196. return nil, err
  197. }
  198. return dockerclient.NewClientWithOpts(clientOpts...)
  199. }
  200. ep := docker.Endpoint{
  201. EndpointMeta: docker.EndpointMeta{
  202. Host: name,
  203. },
  204. }
  205. clientOpts, err := ep.ClientOpts()
  206. if err != nil {
  207. return nil, err
  208. }
  209. return dockerclient.NewClientWithOpts(clientOpts...)
  210. }
  211. func configFromContext(endpointName string, s ctxstore.Reader) (clientcmd.ClientConfig, error) {
  212. if strings.HasPrefix(endpointName, "kubernetes://") {
  213. u, _ := url.Parse(endpointName)
  214. if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
  215. _ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
  216. }
  217. rules := clientcmd.NewDefaultClientConfigLoadingRules()
  218. apiConfig, err := rules.Load()
  219. if err != nil {
  220. return nil, err
  221. }
  222. return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
  223. }
  224. return ctxkube.ConfigFromContext(endpointName, s)
  225. }
  226. type internalAPI struct {
  227. dockerCli command.Cli
  228. }
  229. func (a *internalAPI) DockerAPI(name string) (dockerclient.APIClient, error) {
  230. if name == "" {
  231. name = a.dockerCli.CurrentContext()
  232. }
  233. return clientForEndpoint(a.dockerCli, name)
  234. }