resolver.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /*
  2. Copyright 2023 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 oci
  14. import (
  15. "context"
  16. "io"
  17. "net/url"
  18. "strings"
  19. "github.com/containerd/containerd/v2/core/remotes"
  20. "github.com/containerd/containerd/v2/core/remotes/docker"
  21. "github.com/containerd/containerd/v2/pkg/labels"
  22. "github.com/containerd/errdefs"
  23. "github.com/distribution/reference"
  24. "github.com/docker/cli/cli/config/configfile"
  25. "github.com/docker/compose/v2/internal/registry"
  26. "github.com/moby/buildkit/util/contentutil"
  27. spec "github.com/opencontainers/image-spec/specs-go/v1"
  28. )
  29. // NewResolver setup an OCI Resolver based on docker/cli config to provide registry credentials
  30. func NewResolver(config *configfile.ConfigFile) remotes.Resolver {
  31. return docker.NewResolver(docker.ResolverOptions{
  32. Hosts: docker.ConfigureDefaultRegistries(
  33. docker.WithAuthorizer(docker.NewDockerAuthorizer(
  34. docker.WithAuthCreds(func(host string) (string, string, error) {
  35. host = registry.GetAuthConfigKey(host)
  36. auth, err := config.GetAuthConfig(host)
  37. if err != nil {
  38. return "", "", err
  39. }
  40. if auth.IdentityToken != "" {
  41. return "", auth.IdentityToken, nil
  42. }
  43. return auth.Username, auth.Password, nil
  44. }),
  45. )),
  46. ),
  47. })
  48. }
  49. // Get retrieves a Named OCI resource and returns OCI Descriptor and Manifest
  50. func Get(ctx context.Context, resolver remotes.Resolver, ref reference.Named) (spec.Descriptor, []byte, error) {
  51. _, descriptor, err := resolver.Resolve(ctx, ref.String())
  52. if err != nil {
  53. return spec.Descriptor{}, nil, err
  54. }
  55. fetcher, err := resolver.Fetcher(ctx, ref.String())
  56. if err != nil {
  57. return spec.Descriptor{}, nil, err
  58. }
  59. fetch, err := fetcher.Fetch(ctx, descriptor)
  60. if err != nil {
  61. return spec.Descriptor{}, nil, err
  62. }
  63. content, err := io.ReadAll(fetch)
  64. if err != nil {
  65. return spec.Descriptor{}, nil, err
  66. }
  67. return descriptor, content, nil
  68. }
  69. func Copy(ctx context.Context, resolver remotes.Resolver, image reference.Named, named reference.Named) (spec.Descriptor, error) {
  70. src, desc, err := resolver.Resolve(ctx, image.String())
  71. if err != nil {
  72. return spec.Descriptor{}, err
  73. }
  74. if desc.Annotations == nil {
  75. desc.Annotations = make(map[string]string)
  76. }
  77. // set LabelDistributionSource so push will actually use a registry mount
  78. refspec := reference.TrimNamed(image).String()
  79. u, err := url.Parse("dummy://" + refspec)
  80. if err != nil {
  81. return spec.Descriptor{}, err
  82. }
  83. source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/")
  84. desc.Annotations[labels.LabelDistributionSource+"."+source] = repo
  85. p, err := resolver.Pusher(ctx, named.Name())
  86. if err != nil {
  87. return spec.Descriptor{}, err
  88. }
  89. f, err := resolver.Fetcher(ctx, src)
  90. if err != nil {
  91. return spec.Descriptor{}, err
  92. }
  93. err = contentutil.CopyChain(ctx,
  94. contentutil.FromPusher(p),
  95. contentutil.FromFetcher(f), desc)
  96. return desc, err
  97. }
  98. func Push(ctx context.Context, resolver remotes.Resolver, ref reference.Named, descriptor spec.Descriptor) error {
  99. pusher, err := resolver.Pusher(ctx, ref.String())
  100. if err != nil {
  101. return err
  102. }
  103. ctx = remotes.WithMediaTypeKeyPrefix(ctx, ComposeYAMLMediaType, "artifact-")
  104. ctx = remotes.WithMediaTypeKeyPrefix(ctx, ComposeEnvFileMediaType, "artifact-")
  105. ctx = remotes.WithMediaTypeKeyPrefix(ctx, ComposeEmptyConfigMediaType, "config-")
  106. ctx = remotes.WithMediaTypeKeyPrefix(ctx, spec.MediaTypeEmptyJSON, "config-")
  107. push, err := pusher.Push(ctx, descriptor)
  108. if errdefs.IsAlreadyExists(err) {
  109. return nil
  110. }
  111. if err != nil {
  112. return err
  113. }
  114. _, err = push.Write(descriptor.Data)
  115. if err != nil {
  116. // Close the writer on error since Commit won't be called
  117. _ = push.Close()
  118. return err
  119. }
  120. // Commit will close the writer
  121. return push.Commit(ctx, int64(len(descriptor.Data)), descriptor.Digest)
  122. }