2020-03-16 23:53:28 +01:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-03-19 12:04:45 +01:00
|
|
|
"os"
|
2020-03-19 12:56:28 +01:00
|
|
|
"path/filepath"
|
2020-03-16 23:53:28 +01:00
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
|
|
"github.com/containers/image/v5/docker"
|
|
|
|
"github.com/containers/image/v5/docker/reference"
|
|
|
|
"github.com/containers/image/v5/types"
|
|
|
|
"go.starlark.net/starlark"
|
|
|
|
"go.starlark.net/starlarkstruct"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ModuleName defines the expected name for this Module when used
|
2020-03-30 00:24:10 +02:00
|
|
|
// in starlark's load() function, eg: load('docker', 'docker')
|
|
|
|
ModuleName = "docker"
|
2020-03-16 23:53:28 +01:00
|
|
|
|
2020-03-30 00:41:24 +02:00
|
|
|
imageFuncName = "image"
|
|
|
|
latestTag = "lastest"
|
2020-03-16 23:53:28 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
once sync.Once
|
|
|
|
dockerModule starlark.StringDict
|
|
|
|
)
|
|
|
|
|
|
|
|
// LoadModule loads the os module.
|
|
|
|
// It is concurrency-safe and idempotent.
|
|
|
|
//
|
|
|
|
// outline: docker
|
2020-03-30 00:24:10 +02:00
|
|
|
// The docker modules allow you to manipulate docker image names.
|
|
|
|
// path: docker
|
2020-03-16 23:53:28 +01:00
|
|
|
func LoadModule() (starlark.StringDict, error) {
|
|
|
|
once.Do(func() {
|
|
|
|
dockerModule = starlark.StringDict{
|
|
|
|
"docker": &starlarkstruct.Module{
|
|
|
|
Name: "docker",
|
|
|
|
Members: starlark.StringDict{
|
2020-03-30 00:41:24 +02:00
|
|
|
imageFuncName: starlark.NewBuiltin(imageFuncName, Image),
|
2020-03-16 23:53:28 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return dockerModule, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type sString = starlark.String
|
2020-03-30 00:24:10 +02:00
|
|
|
|
|
|
|
// image represents a docker container image.
|
|
|
|
//
|
|
|
|
// outline: docker
|
|
|
|
// types:
|
|
|
|
// Image
|
|
|
|
// Represents a docker container image.
|
|
|
|
//
|
|
|
|
// fields:
|
|
|
|
// name string
|
|
|
|
// Image name. Eg.: `docker.io/library/fedora`
|
|
|
|
// domain string
|
|
|
|
// Registry domain. Eg.: `docker.io`.
|
|
|
|
// path string
|
|
|
|
// Repository path. Eg.: `library/fedora`
|
|
|
|
//
|
|
|
|
// methods:
|
|
|
|
// tags() list
|
|
|
|
// List of all the tags for this container image.
|
|
|
|
// version() string
|
|
|
|
// Return the highest tag matching the image constraint.
|
|
|
|
// params:
|
|
|
|
// full bool
|
|
|
|
// If `true` returns the image name plus the tag. Eg.: `docker.io/library/fedora:29`
|
2020-03-16 23:53:28 +01:00
|
|
|
type image struct {
|
|
|
|
tags []string
|
|
|
|
ref types.ImageReference
|
|
|
|
constraint string
|
|
|
|
sString
|
|
|
|
}
|
|
|
|
|
2020-03-30 00:24:10 +02:00
|
|
|
// Image returns a starlak.Builtin function capable of instantiate
|
|
|
|
// new Image instances.
|
|
|
|
//
|
|
|
|
// outline: docker
|
|
|
|
// functions:
|
|
|
|
// image(image, constraint) Image
|
|
|
|
// Returns a new `Image` based on a given image and constraint.
|
|
|
|
//
|
|
|
|
// params:
|
|
|
|
// image string
|
|
|
|
// Container image name. Eg.: `ubuntu` or `quay.io/prometheus/prometheus`.
|
|
|
|
// constraint string
|
|
|
|
// [Semver](https://github.com/Masterminds/semver/#checking-version-constraints) contraint. Eg.: `1.2.*`
|
|
|
|
//
|
2020-03-16 23:53:28 +01:00
|
|
|
func Image(
|
|
|
|
thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple,
|
|
|
|
) (starlark.Value, error) {
|
|
|
|
|
|
|
|
var image, constraint string
|
2020-03-30 00:41:24 +02:00
|
|
|
err := starlark.UnpackArgs(imageFuncName, args, kwargs, "image", &image, "constraint", &constraint)
|
2020-03-16 23:53:28 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return newImage(image, constraint)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newImage(name, constraint string) (*image, error) {
|
|
|
|
ref, err := reference.ParseNormalizedNamed(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reference.IsNameOnly(ref) {
|
|
|
|
return nil, errors.New("no tag or digest allowed in reference")
|
|
|
|
}
|
|
|
|
|
|
|
|
dref, err := docker.NewReference(reference.TagNameOnly(ref))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &image{
|
|
|
|
ref: dref,
|
|
|
|
constraint: constraint,
|
|
|
|
sString: starlark.String(ref.Name()),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *image) Attr(name string) (starlark.Value, error) {
|
|
|
|
switch name {
|
|
|
|
case "name":
|
|
|
|
return starlark.String(i.ref.DockerReference().Name()), nil
|
2020-03-19 12:15:57 +01:00
|
|
|
case "domain":
|
|
|
|
name := i.ref.DockerReference()
|
|
|
|
return starlark.String(reference.Domain(name)), nil
|
|
|
|
case "path":
|
|
|
|
name := i.ref.DockerReference()
|
|
|
|
return starlark.String(reference.Path(name)), nil
|
2020-03-16 23:53:28 +01:00
|
|
|
case "tags":
|
2024-10-16 18:03:55 +02:00
|
|
|
return starlark.NewBuiltin("tags", i.builtinTagsFunc), nil
|
2020-03-16 23:53:28 +01:00
|
|
|
case "version":
|
|
|
|
return starlark.NewBuiltin("version", i.builtinVersionFunc), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-03-19 12:15:57 +01:00
|
|
|
func (i *image) AttrNames() []string {
|
|
|
|
return []string{"name", "domain", "path", "tags", "version"}
|
|
|
|
}
|
|
|
|
|
2020-03-16 23:53:28 +01:00
|
|
|
func (i *image) builtinVersionFunc(
|
|
|
|
_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple,
|
|
|
|
) (starlark.Value, error) {
|
|
|
|
|
|
|
|
var full bool
|
2020-03-30 00:24:10 +02:00
|
|
|
starlark.UnpackArgs("version", args, kwargs, "full", &full)
|
2020-03-16 23:53:28 +01:00
|
|
|
|
|
|
|
v, err := i.getVersion()
|
|
|
|
if err != nil {
|
|
|
|
return starlark.None, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if full {
|
|
|
|
v = fmt.Sprintf("%s:%s", i.ref.DockerReference().Name(), v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return starlark.String(v), nil
|
|
|
|
}
|
|
|
|
|
2020-03-30 00:24:10 +02:00
|
|
|
func (i *image) builtinTagsFunc(
|
|
|
|
_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple,
|
|
|
|
) (starlark.Value, error) {
|
|
|
|
return i.getTags()
|
|
|
|
}
|
|
|
|
|
2020-03-16 23:53:28 +01:00
|
|
|
func (i *image) getTags() (*starlark.List, error) {
|
|
|
|
if len(i.tags) != 0 {
|
|
|
|
return listToStarlark(i.tags), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
2020-03-19 12:04:45 +01:00
|
|
|
i.tags, err = docker.GetRepositoryTags(context.TODO(), imageSystemContext(), i.ref)
|
2020-03-16 23:53:28 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error listing repository tags: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
i.tags = sortTags(i.tags)
|
|
|
|
return listToStarlark(i.tags), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *image) getVersion() (string, error) {
|
|
|
|
if i.constraint == latestTag {
|
|
|
|
return latestTag, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := i.getTags()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(i.tags) == 0 {
|
|
|
|
return "", fmt.Errorf("no tags form this image")
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := semver.NewConstraint(i.constraint)
|
|
|
|
if err != nil {
|
|
|
|
return i.doGetVersionExactTag(i.constraint)
|
|
|
|
}
|
|
|
|
|
|
|
|
return i.doGetVersionWithConstraint(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *image) doGetVersionWithConstraint(c *semver.Constraints) (string, error) {
|
|
|
|
// it assumes tags are always sorted from higher to lower
|
|
|
|
for _, tag := range i.tags {
|
|
|
|
v, err := semver.NewVersion(tag)
|
|
|
|
if err == nil {
|
|
|
|
if c.Check(v) {
|
|
|
|
return tag, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *image) doGetVersionExactTag(expected string) (string, error) {
|
|
|
|
for _, tag := range i.tags {
|
|
|
|
if tag == expected {
|
|
|
|
return tag, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("tag %q not found in repository", expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortTags(tags []string) []string {
|
|
|
|
versions, others := listToVersion(tags)
|
|
|
|
sort.Sort(sort.Reverse(semver.Collection(versions)))
|
|
|
|
return versionToList(versions, others)
|
|
|
|
}
|
|
|
|
|
|
|
|
func listToStarlark(input []string) *starlark.List {
|
|
|
|
output := make([]starlark.Value, len(input))
|
|
|
|
for i, v := range input {
|
|
|
|
output[i] = starlark.String(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return starlark.NewList(output)
|
|
|
|
}
|
|
|
|
|
|
|
|
func listToVersion(input []string) ([]*semver.Version, []string) {
|
|
|
|
versions := make([]*semver.Version, 0)
|
|
|
|
other := make([]string, 0)
|
|
|
|
|
|
|
|
for _, text := range input {
|
|
|
|
v, err := semver.NewVersion(text)
|
|
|
|
if err == nil && v.Prerelease() == "" {
|
|
|
|
versions = append(versions, v)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
other = append(other, text)
|
|
|
|
}
|
|
|
|
|
|
|
|
return versions, other
|
|
|
|
}
|
|
|
|
|
|
|
|
func versionToList(versions []*semver.Version, other []string) []string {
|
|
|
|
output := make([]string, 0)
|
|
|
|
for _, v := range versions {
|
|
|
|
output = append(output, v.Original())
|
|
|
|
}
|
|
|
|
|
|
|
|
return append(output, other...)
|
|
|
|
}
|
2020-03-19 12:04:45 +01:00
|
|
|
|
|
|
|
func imageSystemContext() *types.SystemContext {
|
2020-03-19 12:56:28 +01:00
|
|
|
cfgFile := os.Getenv("DOCKER_CONFIG_FILE")
|
|
|
|
if cfgFile == "" {
|
|
|
|
if cfgPath := os.Getenv("DOCKER_CONFIG"); cfgPath != "" {
|
|
|
|
cfgFile = filepath.Join(cfgPath, "config.json")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-19 12:04:45 +01:00
|
|
|
return &types.SystemContext{
|
2020-03-19 12:56:28 +01:00
|
|
|
AuthFilePath: cfgFile,
|
2020-03-19 12:04:45 +01:00
|
|
|
}
|
|
|
|
}
|