File: normalize.go

package info (click to toggle)
golang-github-containers-common 0.64.1%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 5,932 kB
  • sloc: makefile: 132; sh: 111
file content (145 lines) | stat: -rw-r--r-- 4,089 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//go:build !remote

package libimage

import (
	"fmt"
	"strings"

	"github.com/containers/image/v5/docker/reference"
	"github.com/sirupsen/logrus"
)

// NormalizeName normalizes the provided name according to the conventions by
// Podman and Buildah.  If tag and digest are missing, the "latest" tag will be
// used.  If it's a short name, it will be prefixed with "localhost/".
//
// References to docker.io are normalized according to the Docker conventions.
// For instance, "docker.io/foo" turns into "docker.io/library/foo".
func NormalizeName(name string) (reference.Named, error) {
	// NOTE: this code is in symmetrie with containers/image/pkg/shortnames.
	ref, err := reference.Parse(name)
	if err != nil {
		return nil, fmt.Errorf("normalizing name %q: %w", name, err)
	}

	named, ok := ref.(reference.Named)
	if !ok {
		return nil, fmt.Errorf("%q is not a named reference", name)
	}

	// Enforce "localhost" if needed.
	registry := reference.Domain(named)
	if !strings.ContainsAny(registry, ".:") && registry != "localhost" {
		name = toLocalImageName(ref.String())
	}

	// Another parse which also makes sure that docker.io references are
	// correctly normalized (e.g., docker.io/alpine to
	// docker.io/library/alpine).
	named, err = reference.ParseNormalizedNamed(name)
	if err != nil {
		return nil, err
	}

	if _, hasTag := named.(reference.NamedTagged); hasTag {
		// Strip off the tag of a tagged and digested reference.
		named, err = normalizeTaggedDigestedNamed(named)
		if err != nil {
			return nil, err
		}
		return named, nil
	}
	if _, hasDigest := named.(reference.Digested); hasDigest {
		return named, nil
	}

	// Make sure to tag "latest".
	return reference.TagNameOnly(named), nil
}

// prefix the specified name with "localhost/".
func toLocalImageName(name string) string {
	return "localhost/" + strings.TrimLeft(name, "/")
}

// NameTagPair represents a RepoTag of an image.
type NameTagPair struct {
	// Name of the RepoTag. Maybe "<none>".
	Name string
	// Tag of the RepoTag. Maybe "<none>".
	Tag string

	// for internal use
	named reference.Named
}

// ToNameTagsPairs splits repoTags into name&tag pairs.
// Guaranteed to return at least one pair.
func ToNameTagPairs(repoTags []reference.Named) ([]NameTagPair, error) {
	none := "<none>"

	pairs := make([]NameTagPair, 0, len(repoTags))
	for i, named := range repoTags {
		pair := NameTagPair{
			Name:  named.Name(),
			Tag:   none,
			named: repoTags[i],
		}

		if tagged, isTagged := named.(reference.NamedTagged); isTagged {
			pair.Tag = tagged.Tag()
		}
		pairs = append(pairs, pair)
	}

	if len(pairs) == 0 {
		pairs = append(pairs, NameTagPair{Name: none, Tag: none})
	}
	return pairs, nil
}

// normalizeTaggedDigestedString strips the tag off the specified string iff it
// is tagged and digested. Note that the tag is entirely ignored to match
// Docker behavior.
func normalizeTaggedDigestedString(s string) (string, reference.Named, error) {
	// Note that the input string is not expected to be parseable, so we
	// return it verbatim in error cases.
	ref, err := reference.Parse(s)
	if err != nil {
		return "", nil, err
	}
	named, ok := ref.(reference.Named)
	if !ok {
		return s, nil, nil
	}
	named, err = normalizeTaggedDigestedNamed(named)
	if err != nil {
		return "", nil, err
	}
	return named.String(), named, nil
}

// normalizeTaggedDigestedNamed strips the tag off the specified named
// reference iff it is tagged and digested. Note that the tag is entirely
// ignored to match Docker behavior.
func normalizeTaggedDigestedNamed(named reference.Named) (reference.Named, error) {
	_, isTagged := named.(reference.NamedTagged)
	if !isTagged {
		return named, nil
	}
	digested, isDigested := named.(reference.Digested)
	if !isDigested {
		return named, nil
	}

	// Now strip off the tag.
	newNamed := reference.TrimNamed(named)
	// And re-add the digest.
	newNamed, err := reference.WithDigest(newNamed, digested.Digest())
	if err != nil {
		return named, err
	}
	logrus.Debugf("Stripped off tag from tagged and digested reference %q", named.String())
	return newNamed, nil
}