github.com/lalkh/containerd@v1.4.3/remotes/docker/handler.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package docker 18 19 import ( 20 "context" 21 "fmt" 22 "net/url" 23 "strings" 24 25 "github.com/containerd/containerd/content" 26 "github.com/containerd/containerd/images" 27 "github.com/containerd/containerd/labels" 28 "github.com/containerd/containerd/log" 29 "github.com/containerd/containerd/reference" 30 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 31 ) 32 33 var ( 34 // labelDistributionSource describes the source blob comes from. 35 labelDistributionSource = "containerd.io/distribution.source" 36 ) 37 38 // AppendDistributionSourceLabel updates the label of blob with distribution source. 39 func AppendDistributionSourceLabel(manager content.Manager, ref string) (images.HandlerFunc, error) { 40 refspec, err := reference.Parse(ref) 41 if err != nil { 42 return nil, err 43 } 44 45 u, err := url.Parse("dummy://" + refspec.Locator) 46 if err != nil { 47 return nil, err 48 } 49 50 source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/") 51 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 52 info, err := manager.Info(ctx, desc.Digest) 53 if err != nil { 54 return nil, err 55 } 56 57 key := distributionSourceLabelKey(source) 58 59 originLabel := "" 60 if info.Labels != nil { 61 originLabel = info.Labels[key] 62 } 63 value := appendDistributionSourceLabel(originLabel, repo) 64 65 // The repo name has been limited under 256 and the distribution 66 // label might hit the limitation of label size, when blob data 67 // is used as the very, very common layer. 68 if err := labels.Validate(key, value); err != nil { 69 log.G(ctx).Warnf("skip to append distribution label: %s", err) 70 return nil, nil 71 } 72 73 info = content.Info{ 74 Digest: desc.Digest, 75 Labels: map[string]string{ 76 key: value, 77 }, 78 } 79 _, err = manager.Update(ctx, info, fmt.Sprintf("labels.%s", key)) 80 return nil, err 81 }, nil 82 } 83 84 func appendDistributionSourceLabel(originLabel, repo string) string { 85 repos := []string{} 86 if originLabel != "" { 87 repos = strings.Split(originLabel, ",") 88 } 89 repos = append(repos, repo) 90 91 // use empty string to present duplicate items 92 for i := 1; i < len(repos); i++ { 93 tmp, j := repos[i], i-1 94 for ; j >= 0 && repos[j] >= tmp; j-- { 95 if repos[j] == tmp { 96 tmp = "" 97 } 98 repos[j+1] = repos[j] 99 } 100 repos[j+1] = tmp 101 } 102 103 i := 0 104 for ; i < len(repos) && repos[i] == ""; i++ { 105 } 106 107 return strings.Join(repos[i:], ",") 108 } 109 110 func distributionSourceLabelKey(source string) string { 111 return fmt.Sprintf("%s.%s", labelDistributionSource, source) 112 } 113 114 // selectRepositoryMountCandidate will select the repo which has longest 115 // common prefix components as the candidate. 116 func selectRepositoryMountCandidate(refspec reference.Spec, sources map[string]string) string { 117 u, err := url.Parse("dummy://" + refspec.Locator) 118 if err != nil { 119 // NOTE: basically, it won't be error here 120 return "" 121 } 122 123 source, target := u.Hostname(), strings.TrimPrefix(u.Path, "/") 124 repoLabel, ok := sources[distributionSourceLabelKey(source)] 125 if !ok || repoLabel == "" { 126 return "" 127 } 128 129 n, match := 0, "" 130 components := strings.Split(target, "/") 131 for _, repo := range strings.Split(repoLabel, ",") { 132 // the target repo is not a candidate 133 if repo == target { 134 continue 135 } 136 137 if l := commonPrefixComponents(components, repo); l >= n { 138 n, match = l, repo 139 } 140 } 141 return match 142 } 143 144 func commonPrefixComponents(components []string, target string) int { 145 targetComponents := strings.Split(target, "/") 146 147 i := 0 148 for ; i < len(components) && i < len(targetComponents); i++ { 149 if components[i] != targetComponents[i] { 150 break 151 } 152 } 153 return i 154 }