github.com/containerd/Containerd@v1.4.13/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  }