Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Support initContainers and {image, tag} and {image: {repository, tag}} Helm values #1258

Merged
merged 9 commits into from
Aug 2, 2018
2 changes: 1 addition & 1 deletion bin/kubeyaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/sh
docker run --rm -i quay.io/squaremo/kubeyaml:0.3.3 "$@"
docker run --rm -i quay.io/squaremo/kubeyaml:0.4.2 "$@"
160 changes: 113 additions & 47 deletions cluster/kubernetes/resource/fluxhelmrelease.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,61 +54,127 @@ func sorted_keys(values map[string]interface{}) []string {
// it cannot interpret the values as specifying images, or if the
// `visit` function itself returns an error.
func FindFluxHelmReleaseContainers(values map[string]interface{}, visit func(string, image.Ref, ImageSetter) error) error {
// Try the simplest format first:
// ```
// values:
// image: 'repo/image:tag'
// ```
if imgInfo, ok := values["image"]; ok {
if imgInfoStr, ok := imgInfo.(string); ok {
imageRef, err := image.ParseRef(imgInfoStr)
if err == nil {
return visit(ReleaseContainerName, imageRef, func(ref image.Ref) {
values["image"] = ref.String()
})
}
}
// an image defined at the top-level is given a standard container name:
if image, setter, ok := interpretAsContainer(stringMap(values)); ok {
visit(ReleaseContainerName, image, setter)
}
// Second most simple format:
// ```
// values:
// foo:
// image: repo/foo:tag
// bar:
// image: repo/bar:tag
// ```

// an image as part of a field is treated as a "container" spec
// named for the field:
for _, k := range sorted_keys(values) {
var imgInfo interface{}
var ok bool
var setter ImageSetter
// From a YAML (i.e., a file), it's a
// `map[interface{}]interface{}`, and from JSON (i.e.,
// Kubernetes API) it's a `map[string]interface{}`.
switch m := values[k].(type) {
case map[string]interface{}:
imgInfo, ok = m["image"]
setter = func(ref image.Ref) {
m["image"] = ref.String()
}
case map[interface{}]interface{}:
imgInfo, ok = m["image"]
setter = func(ref image.Ref) {
m["image"] = ref.String()
}
if image, setter, ok := interpret(values[k]); ok {
visit(k, image, setter)
}
if ok {
if imgInfoStr, ok := imgInfo.(string); ok {
imageRef, err := image.ParseRef(imgInfoStr)
if err == nil {
err = visit(k, imageRef, setter)
}
return nil
}

// The following is some machinery for interpreting a
// FluxHelmRelease's `values` field as defining images to be
// interpolated into the chart templates.
//
// The top-level value is a map[string]interface{}, but beneath that,
// we get maps in two varieties: from a YAML (i.e., a file), they are
// `map[interface{}]interface{}`, and from JSON (i.e., Kubernetes API)
// they are a `map[string]interface{}`. To conflate them, here's an
// interface for maps:

type mapper interface {
get(string) (interface{}, bool)
set(string, interface{})
}

type stringMap map[string]interface{}
type anyMap map[interface{}]interface{}

func (m stringMap) get(k string) (interface{}, bool) { v, ok := m[k]; return v, ok }
func (m stringMap) set(k string, v interface{}) { m[k] = v }

func (m anyMap) get(k string) (interface{}, bool) { v, ok := m[k]; return v, ok }
func (m anyMap) set(k string, v interface{}) { m[k] = v }

// interpret gets a value which may contain a description of an image.
func interpret(values interface{}) (image.Ref, ImageSetter, bool) {
switch m := values.(type) {
case map[string]interface{}:
return interpretAsContainer(stringMap(m))
case map[interface{}]interface{}:
return interpretAsContainer(anyMap(m))
}
return image.Ref{}, nil, false
}

// interpretAsContainer takes a `mapper` value that may _contain_ an
// image, and attempts to interpret it.
func interpretAsContainer(m mapper) (image.Ref, ImageSetter, bool) {
imageValue, ok := m.get("image")
if !ok {
return image.Ref{}, nil, false
}
switch img := imageValue.(type) {
case string:
// ```
// container:
// image: 'repo/image:tag'
// ```
imageRef, err := image.ParseRef(img)
if err == nil {
var taggy bool
if tag, ok := m.get("tag"); ok {
// conatainer:
// image: repo/foo
// tag: v1
if tagStr, ok := tag.(string); ok {
taggy = true
imageRef.Tag = tagStr
}
if err != nil {
return err
}
return imageRef, func(ref image.Ref) {
if taggy {
m.set("image", ref.Name.String())
m.set("tag", ref.Tag)
return
}
m.set("image", ref.String())
}, true
}
case map[string]interface{}:
return interpretAsImage(stringMap(img))
case map[interface{}]interface{}:
return interpretAsImage(anyMap(img))
}
return image.Ref{}, nil, false
}

// interpretAsImage takes a `mapper` value that may represent an
// image, and attempts to interpret it.
func interpretAsImage(m mapper) (image.Ref, ImageSetter, bool) {
var imgRepo, imgTag interface{}
var ok bool
if imgRepo, ok = m.get("repository"); !ok {
return image.Ref{}, nil, false
}

if imgTag, ok = m.get("tag"); !ok {
return image.Ref{}, nil, false
}

if imgStr, ok := imgRepo.(string); ok {
if tagStr, ok := imgTag.(string); ok {
// container:
// image:
// repository: repo/bar
// tag: v1
imgRef, err := image.ParseRef(imgStr + ":" + tagStr)
if err == nil {
return imgRef, func(ref image.Ref) {
m.set("repository", ref.Name.String())
m.set("tag", ref.Tag)
}, true
}
}
}
return nil
return image.Ref{}, nil, false
}

// Containers returns the containers that are defined in the
Expand Down
Loading