summaryrefslogtreecommitdiff
path: root/libpod/image/parts.go
blob: 62cca2757f13bbdd3462c75e7ecbce6c034612c2 (plain)
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
package image

import (
	"strings"

	"github.com/containers/image/docker/reference"
	"github.com/pkg/errors"
)

// imageParts describes the parts of an image's name
type imageParts struct {
	unnormalizedRef reference.Named // WARNING: Did not go through docker.io[/library] normalization
	registry        string
	name            string
	tag             string
	isTagged        bool
	hasRegistry     bool
}

// Registries must contain a ":" or a "." or be localhost; this helper exists for users of reference.Parse.
// For inputs that should use the docker.io[/library] normalization, use reference.ParseNormalizedNamed instead.
func isRegistry(name string) bool {
	return strings.ContainsAny(name, ".:") || name == "localhost"
}

// GetImageBaseName uses decompose and string splits to obtain the base
// name of an image.  Doing this here because it beats changing the
// imageParts struct names to be exported as well.
func GetImageBaseName(input string) (string, error) {
	decomposedImage, err := decompose(input)
	if err != nil {
		return "", err
	}
	splitImageName := strings.Split(decomposedImage.unnormalizedRef.Name(), "/")
	return splitImageName[len(splitImageName)-1], nil
}

// decompose breaks an input name into an imageParts description
func decompose(input string) (imageParts, error) {
	var (
		parts       imageParts
		hasRegistry bool
		tag         string
	)
	imgRef, err := reference.Parse(input)
	if err != nil {
		return parts, err
	}
	unnormalizedNamed := imgRef.(reference.Named)
	ntag, isTagged := imgRef.(reference.NamedTagged)
	if !isTagged {
		tag = "latest"
		if _, hasDigest := imgRef.(reference.Digested); hasDigest {
			tag = "none"
		}
	} else {
		tag = ntag.Tag()
	}
	registry := reference.Domain(unnormalizedNamed)
	imageName := reference.Path(unnormalizedNamed)
	// ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed,
	// does not use the standard heuristics for domains vs. namespaces/repos, so we need to check
	// explicitly.
	if isRegistry(registry) {
		hasRegistry = true
	} else {
		if registry != "" {
			imageName = registry + "/" + imageName
			registry = ""
		}
	}
	return imageParts{
		unnormalizedRef: unnormalizedNamed,
		registry:        registry,
		hasRegistry:     hasRegistry,
		name:            imageName,
		tag:             tag,
		isTagged:        isTagged,
	}, nil
}

// suspiciousRefNameTagValuesForSearch returns a "tag" value used in a previous implementation.
// This exists only to preserve existing behavior in heuristic code; it’s dubious that that behavior is correct,
// gespecially for the tag value.
func (ip *imageParts) suspiciousRefNameTagValuesForSearch() (string, string, string) {
	registry := reference.Domain(ip.unnormalizedRef)
	imageName := reference.Path(ip.unnormalizedRef)
	// ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed,
	// does not use the standard heuristics for domains vs. namespaces/repos.
	if registry != "" && !isRegistry(registry) {
		imageName = registry + "/" + imageName
		registry = ""
	}

	var tag string
	if tagged, isTagged := ip.unnormalizedRef.(reference.NamedTagged); isTagged {
		tag = tagged.Tag()
	} else if _, hasDigest := ip.unnormalizedRef.(reference.Digested); hasDigest {
		tag = "none"
	} else {
		tag = "latest"
	}
	return registry, imageName, tag
}

// referenceWithRegistry returns a (normalized) reference.Named composed of ip (with !ip.hasRegistry)
// qualified with registry.
func (ip *imageParts) referenceWithRegistry(registry string) (reference.Named, error) {
	if ip.hasRegistry {
		return nil, errors.Errorf("internal error: referenceWithRegistry called on imageParts with a registry (%#v)", *ip)
	}
	// We could build a reference.WithName+WithTag/WithDigest here, but we need to round-trip via a string
	// and a ParseNormalizedNamed anyway to get the right normalization of docker.io/library, so
	// just use a string directly.
	qualified := registry + "/" + ip.unnormalizedRef.String()
	ref, err := reference.ParseNormalizedNamed(qualified)
	if err != nil {
		return nil, errors.Wrapf(err, "error normalizing registry+unqualified reference %#v", qualified)
	}
	return ref, nil
}

// normalizedReference returns a (normalized) reference for ip (with ip.hasRegistry)
func (ip *imageParts) normalizedReference() (reference.Named, error) {
	if !ip.hasRegistry {
		return nil, errors.Errorf("internal error: normalizedReference called on imageParts without a registry (%#v)", *ip)
	}
	// We need to round-trip via a string to get the right normalization of docker.io/library
	s := ip.unnormalizedRef.String()
	ref, err := reference.ParseNormalizedNamed(s)
	if err != nil { // Should never happen
		return nil, errors.Wrapf(err, "error normalizing qualified reference %#v", s)
	}
	return ref, nil
}