github.com/mcuadros/ascode@v1.3.1/starlark/module/docker/image.go (about) 1 package docker 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "sort" 10 "sync" 11 12 "github.com/Masterminds/semver/v3" 13 "github.com/containers/image/v5/docker" 14 "github.com/containers/image/v5/docker/reference" 15 "github.com/containers/image/v5/types" 16 "go.starlark.net/starlark" 17 "go.starlark.net/starlarkstruct" 18 ) 19 20 const ( 21 // ModuleName defines the expected name for this Module when used 22 // in starlark's load() function, eg: load('docker', 'docker') 23 ModuleName = "docker" 24 25 imageFuncName = "image" 26 latestTag = "lastest" 27 ) 28 29 var ( 30 once sync.Once 31 dockerModule starlark.StringDict 32 ) 33 34 // LoadModule loads the os module. 35 // It is concurrency-safe and idempotent. 36 // 37 // outline: docker 38 // The docker modules allow you to manipulate docker image names. 39 // path: docker 40 func LoadModule() (starlark.StringDict, error) { 41 once.Do(func() { 42 dockerModule = starlark.StringDict{ 43 "docker": &starlarkstruct.Module{ 44 Name: "docker", 45 Members: starlark.StringDict{ 46 imageFuncName: starlark.NewBuiltin(imageFuncName, Image), 47 }, 48 }, 49 } 50 }) 51 52 return dockerModule, nil 53 } 54 55 type sString = starlark.String 56 57 // image represents a docker container image. 58 // 59 // outline: docker 60 // types: 61 // Image 62 // Represents a docker container image. 63 // 64 // fields: 65 // name string 66 // Image name. Eg.: `docker.io/library/fedora` 67 // domain string 68 // Registry domain. Eg.: `docker.io`. 69 // path string 70 // Repository path. Eg.: `library/fedora` 71 // 72 // methods: 73 // tags() list 74 // List of all the tags for this container image. 75 // version() string 76 // Return the highest tag matching the image constraint. 77 // params: 78 // full bool 79 // If `true` returns the image name plus the tag. Eg.: `docker.io/library/fedora:29` 80 type image struct { 81 tags []string 82 ref types.ImageReference 83 constraint string 84 sString 85 } 86 87 // Image returns a starlak.Builtin function capable of instantiate 88 // new Image instances. 89 // 90 // outline: docker 91 // functions: 92 // image(image, constraint) Image 93 // Returns a new `Image` based on a given image and constraint. 94 // 95 // params: 96 // image string 97 // Container image name. Eg.: `ubuntu` or `quay.io/prometheus/prometheus`. 98 // constraint string 99 // [Semver](https://github.com/Masterminds/semver/#checking-version-constraints) contraint. Eg.: `1.2.*` 100 // 101 func Image( 102 thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple, 103 ) (starlark.Value, error) { 104 105 var image, constraint string 106 err := starlark.UnpackArgs(imageFuncName, args, kwargs, "image", &image, "constraint", &constraint) 107 if err != nil { 108 return nil, err 109 } 110 111 return newImage(image, constraint) 112 } 113 114 func newImage(name, constraint string) (*image, error) { 115 ref, err := reference.ParseNormalizedNamed(name) 116 if err != nil { 117 return nil, err 118 } 119 120 if !reference.IsNameOnly(ref) { 121 return nil, errors.New("no tag or digest allowed in reference") 122 } 123 124 dref, err := docker.NewReference(reference.TagNameOnly(ref)) 125 if err != nil { 126 return nil, err 127 } 128 129 return &image{ 130 ref: dref, 131 constraint: constraint, 132 sString: starlark.String(ref.Name()), 133 }, nil 134 } 135 136 func (i *image) Attr(name string) (starlark.Value, error) { 137 switch name { 138 case "name": 139 return starlark.String(i.ref.DockerReference().Name()), nil 140 case "domain": 141 name := i.ref.DockerReference() 142 return starlark.String(reference.Domain(name)), nil 143 case "path": 144 name := i.ref.DockerReference() 145 return starlark.String(reference.Path(name)), nil 146 case "tags": 147 return starlark.NewBuiltin("tags", i.builtinVersionFunc), nil 148 case "version": 149 return starlark.NewBuiltin("version", i.builtinVersionFunc), nil 150 } 151 152 return nil, nil 153 } 154 155 func (i *image) AttrNames() []string { 156 return []string{"name", "domain", "path", "tags", "version"} 157 } 158 159 func (i *image) builtinVersionFunc( 160 _ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple, 161 ) (starlark.Value, error) { 162 163 var full bool 164 starlark.UnpackArgs("version", args, kwargs, "full", &full) 165 166 v, err := i.getVersion() 167 if err != nil { 168 return starlark.None, err 169 } 170 171 if full { 172 v = fmt.Sprintf("%s:%s", i.ref.DockerReference().Name(), v) 173 } 174 175 return starlark.String(v), nil 176 } 177 178 func (i *image) builtinTagsFunc( 179 _ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple, 180 ) (starlark.Value, error) { 181 return i.getTags() 182 } 183 184 func (i *image) getTags() (*starlark.List, error) { 185 if len(i.tags) != 0 { 186 return listToStarlark(i.tags), nil 187 } 188 189 var err error 190 i.tags, err = docker.GetRepositoryTags(context.TODO(), imageSystemContext(), i.ref) 191 if err != nil { 192 return nil, fmt.Errorf("error listing repository tags: %v", err) 193 } 194 195 i.tags = sortTags(i.tags) 196 return listToStarlark(i.tags), nil 197 } 198 199 func (i *image) getVersion() (string, error) { 200 if i.constraint == latestTag { 201 return latestTag, nil 202 } 203 204 _, err := i.getTags() 205 if err != nil { 206 return "", err 207 } 208 209 if len(i.tags) == 0 { 210 return "", fmt.Errorf("no tags form this image") 211 } 212 213 c, err := semver.NewConstraint(i.constraint) 214 if err != nil { 215 return i.doGetVersionExactTag(i.constraint) 216 } 217 218 return i.doGetVersionWithConstraint(c) 219 } 220 221 func (i *image) doGetVersionWithConstraint(c *semver.Constraints) (string, error) { 222 // it assumes tags are always sorted from higher to lower 223 for _, tag := range i.tags { 224 v, err := semver.NewVersion(tag) 225 if err == nil { 226 if c.Check(v) { 227 return tag, nil 228 } 229 } 230 } 231 232 return "", nil 233 } 234 235 func (i *image) doGetVersionExactTag(expected string) (string, error) { 236 for _, tag := range i.tags { 237 if tag == expected { 238 return tag, nil 239 } 240 } 241 242 return "", fmt.Errorf("tag %q not found in repository", expected) 243 } 244 245 func sortTags(tags []string) []string { 246 versions, others := listToVersion(tags) 247 sort.Sort(sort.Reverse(semver.Collection(versions))) 248 return versionToList(versions, others) 249 } 250 251 func listToStarlark(input []string) *starlark.List { 252 output := make([]starlark.Value, len(input)) 253 for i, v := range input { 254 output[i] = starlark.String(v) 255 } 256 257 return starlark.NewList(output) 258 } 259 260 func listToVersion(input []string) ([]*semver.Version, []string) { 261 versions := make([]*semver.Version, 0) 262 other := make([]string, 0) 263 264 for _, text := range input { 265 v, err := semver.NewVersion(text) 266 if err == nil && v.Prerelease() == "" { 267 versions = append(versions, v) 268 continue 269 } 270 271 other = append(other, text) 272 } 273 274 return versions, other 275 } 276 277 func versionToList(versions []*semver.Version, other []string) []string { 278 output := make([]string, 0) 279 for _, v := range versions { 280 output = append(output, v.Original()) 281 } 282 283 return append(output, other...) 284 } 285 286 func imageSystemContext() *types.SystemContext { 287 cfgFile := os.Getenv("DOCKER_CONFIG_FILE") 288 if cfgFile == "" { 289 if cfgPath := os.Getenv("DOCKER_CONFIG"); cfgPath != "" { 290 cfgFile = filepath.Join(cfgPath, "config.json") 291 } 292 } 293 294 return &types.SystemContext{ 295 AuthFilePath: cfgFile, 296 } 297 }