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
17package docker
18
19import (
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
33var (
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.
39func 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
84func 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
110func 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.
116func 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
144func 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}
155