diff options
Diffstat (limited to 'registry-scanner/pkg/image/image.go')
| -rw-r--r-- | registry-scanner/pkg/image/image.go | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/registry-scanner/pkg/image/image.go b/registry-scanner/pkg/image/image.go new file mode 100644 index 0000000..01261be --- /dev/null +++ b/registry-scanner/pkg/image/image.go @@ -0,0 +1,275 @@ +package image + +import ( + "strings" + "time" + + "github.com/distribution/distribution/v3/reference" + + "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log" + "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag" +) + +type ContainerImage struct { + RegistryURL string + ImageName string + ImageTag *tag.ImageTag + ImageAlias string + HelmParamImageName string + HelmParamImageVersion string + KustomizeImage *ContainerImage + original string +} + +type ContainerImageList []*ContainerImage + +// NewFromIdentifier parses an image identifier and returns a populated ContainerImage +func NewFromIdentifier(identifier string) *ContainerImage { + imgRef := identifier + alias := "" + if strings.Contains(identifier, "=") { + n := strings.SplitN(identifier, "=", 2) + imgRef = n[1] + alias = n[0] + } + if parsed, err := reference.ParseNormalizedNamed(imgRef); err == nil { + img := ContainerImage{} + img.RegistryURL = reference.Domain(parsed) + // remove default registry for backwards-compatibility + if img.RegistryURL == "docker.io" && !strings.HasPrefix(imgRef, "docker.io") { + img.RegistryURL = "" + } + img.ImageAlias = alias + img.ImageName = reference.Path(parsed) + // if library/ was added to the image name, remove it + if !strings.HasPrefix(imgRef, "library/") { + img.ImageName = strings.TrimPrefix(img.ImageName, "library/") + } + if digested, ok := parsed.(reference.Digested); ok { + img.ImageTag = &tag.ImageTag{ + TagDigest: string(digested.Digest()), + } + } else if tagged, ok := parsed.(reference.Tagged); ok { + img.ImageTag = &tag.ImageTag{ + TagName: tagged.Tag(), + } + } + img.original = identifier + return &img + } + + // if distribution couldn't parse it, fall back to the legacy parsing logic + img := ContainerImage{} + img.RegistryURL = getRegistryFromIdentifier(identifier) + img.ImageAlias, img.ImageName, img.ImageTag = getImageTagFromIdentifier(identifier) + img.original = identifier + return &img +} + +// String returns the string representation of given ContainerImage +func (img *ContainerImage) String() string { + str := "" + if img.ImageAlias != "" { + str += img.ImageAlias + str += "=" + } + str += img.GetFullNameWithTag() + return str +} + +func (img *ContainerImage) GetFullNameWithoutTag() string { + str := "" + if img.RegistryURL != "" { + str += img.RegistryURL + "/" + } + str += img.ImageName + return str +} + +// GetFullNameWithTag returns the complete image slug, including the registry +// and any tag digest or tag name set for the image. +func (img *ContainerImage) GetFullNameWithTag() string { + str := "" + if img.RegistryURL != "" { + str += img.RegistryURL + "/" + } + str += img.ImageName + if img.ImageTag != nil { + if img.ImageTag.TagName != "" { + str += ":" + str += img.ImageTag.TagName + } + if img.ImageTag.TagDigest != "" { + str += "@" + str += img.ImageTag.TagDigest + } + } + return str +} + +// GetTagWithDigest returns tag name along with any tag digest set for the image +func (img *ContainerImage) GetTagWithDigest() string { + str := "" + if img.ImageTag != nil { + if img.ImageTag.TagName != "" { + str += img.ImageTag.TagName + } + if img.ImageTag.TagDigest != "" { + if str == "" { + str += "latest" + } + str += "@" + str += img.ImageTag.TagDigest + } + } + return str +} + +func (img *ContainerImage) Original() string { + return img.original +} + +// IsUpdatable checks whether the given image can be updated with newTag while +// taking tagSpec into account. tagSpec must be given as a semver compatible +// version spec, i.e. ^1.0 or ~2.1 +func (img *ContainerImage) IsUpdatable(newTag, tagSpec string) bool { + return false +} + +// WithTag returns a copy of img with new tag information set +func (img *ContainerImage) WithTag(newTag *tag.ImageTag) *ContainerImage { + nimg := &ContainerImage{} + nimg.RegistryURL = img.RegistryURL + nimg.ImageName = img.ImageName + nimg.ImageTag = newTag + nimg.ImageAlias = img.ImageAlias + nimg.HelmParamImageName = img.HelmParamImageName + nimg.HelmParamImageVersion = img.HelmParamImageVersion + return nimg +} + +func (img *ContainerImage) DiffersFrom(other *ContainerImage, checkVersion bool) bool { + return img.RegistryURL != other.RegistryURL || img.ImageName != other.ImageName || (checkVersion && img.ImageTag.TagName != other.ImageTag.TagName) +} + +// ContainsImage checks whether img is contained in a list of images +func (list *ContainerImageList) ContainsImage(img *ContainerImage, checkVersion bool) *ContainerImage { + // if there is a KustomizeImage override, check it for a match first + if img.KustomizeImage != nil { + if kustomizeMatch := list.ContainsImage(img.KustomizeImage, checkVersion); kustomizeMatch != nil { + return kustomizeMatch + } + } + for _, image := range *list { + if img.ImageName == image.ImageName && image.RegistryURL == img.RegistryURL { + if !checkVersion || image.ImageTag.TagName == img.ImageTag.TagName { + return image + } + } + } + return nil +} + +func (list *ContainerImageList) Originals() []string { + results := make([]string, len(*list)) + for i, img := range *list { + results[i] = img.Original() + } + return results +} + +// String Returns the name of all images as a string, separated using comma +func (list *ContainerImageList) String() string { + imgNameList := make([]string, 0) + for _, image := range *list { + imgNameList = append(imgNameList, image.String()) + } + return strings.Join(imgNameList, ",") +} + +// Gets the registry URL from an image identifier +func getRegistryFromIdentifier(identifier string) string { + var imageString string + comp := strings.Split(identifier, "=") + if len(comp) > 1 { + imageString = comp[1] + } else { + imageString = identifier + } + comp = strings.Split(imageString, "/") + if len(comp) > 1 && strings.Contains(comp[0], ".") { + return comp[0] + } else { + return "" + } +} + +// Gets the image name and tag from an image identifier +func getImageTagFromIdentifier(identifier string) (string, string, *tag.ImageTag) { + var imageString string + var sourceName string + + // The original name is prepended to the image name, separated by = + comp := strings.SplitN(identifier, "=", 2) + if len(comp) == 2 { + sourceName = comp[0] + imageString = comp[1] + } else { + imageString = identifier + } + + // Strip any repository identifier from the string + comp = strings.Split(imageString, "/") + if len(comp) > 1 && strings.Contains(comp[0], ".") { + imageString = strings.Join(comp[1:], "/") + } + + // We can either have a tag name or a digest reference, or both + // jannfis/test-image:0.1 + // gcr.io/jannfis/test-image:0.1 + // gcr.io/jannfis/test-image@sha256:abcde + // gcr.io/jannfis/test-image:test-tag@sha256:abcde + if strings.Contains(imageString, "@") { + comp = strings.SplitN(imageString, "@", 2) + colonPos := strings.LastIndex(comp[0], ":") + slashPos := strings.LastIndex(comp[0], "/") + if colonPos > slashPos { + // first half (before @) contains image and tag name + return sourceName, comp[0][:colonPos], tag.NewImageTag(comp[0][colonPos+1:], time.Unix(0, 0), comp[1]) + } else { + // first half contains image name without tag name + return sourceName, comp[0], tag.NewImageTag("", time.Unix(0, 0), comp[1]) + } + } else { + comp = strings.SplitN(imageString, ":", 2) + if len(comp) != 2 { + return sourceName, imageString, nil + } else { + tagName, tagDigest := getImageDigestFromTag(comp[1]) + return sourceName, comp[0], tag.NewImageTag(tagName, time.Unix(0, 0), tagDigest) + } + } +} + +func getImageDigestFromTag(tagStr string) (string, string) { + a := strings.Split(tagStr, "@") + if len(a) != 2 { + return tagStr, "" + } else { + return a[0], a[1] + } +} + +// LogContext returns a log context for the given image, with required fields +// set to the image's information. +func (img *ContainerImage) LogContext() *log.LogContext { + logCtx := log.WithContext() + logCtx.AddField("image_name", img.GetFullNameWithoutTag()) + logCtx.AddField("image_alias", img.ImageAlias) + logCtx.AddField("registry_url", img.RegistryURL) + if img.ImageTag != nil { + logCtx.AddField("image_tag", img.ImageTag.TagName) + logCtx.AddField("image_digest", img.ImageTag.TagDigest) + } + return logCtx +} |
