Skip to content

Commit

Permalink
Support loading templates from network (HTTP)
Browse files Browse the repository at this point in the history
  • Loading branch information
rycus86 committed Jun 10, 2018
1 parent 035f68e commit cd6fa07
Show file tree
Hide file tree
Showing 8 changed files with 695 additions and 11 deletions.
519 changes: 519 additions & 0 deletions examples/modernized/stack-templated.yml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions pkg/template/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package template

const (
XPodlikeExtension = "x-podlike"
ArgsProperty = "args"
ServicesProperty = "services"

TypeInline = "inline"
TypeHttp = "http"
)
2 changes: 1 addition & 1 deletion pkg/template/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pod:
func getTagForPod() string {
v := version.Parse()

if v.Tag == version.DEFAULT_VERSION {
if v.Tag == version.DEFAULT_VERSION || v.Tag == "master" {
return "latest"
} else {
return v.Tag
Expand Down
14 changes: 13 additions & 1 deletion pkg/template/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func podTemplateHookFunc() mapstructure.DecodeHookFunc {

item := reflect.ValueOf(data).Interface()
if m, ok := item.(map[string]interface{}); ok {
if inline, ok := m["inline"]; ok {
if inline, ok := m[TypeInline]; ok {

if t == reflect.TypeOf(podTemplate{}) {
return podTemplate{Template: inline.(string), Inline: true}, nil
Expand All @@ -41,6 +41,18 @@ func podTemplateHookFunc() mapstructure.DecodeHookFunc {
{Template: inline.(string), Inline: true},
}, nil
}

} else if httpSource, ok := m[TypeHttp]; ok {

if t == reflect.TypeOf(podTemplate{}) {
return podTemplate{Template: httpSource.(string), Http: true}, nil

} else if t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(podTemplate{}) {
return []podTemplate{
{Template: httpSource.(string), Http: true},
}, nil
}

}
}

Expand Down
12 changes: 6 additions & 6 deletions pkg/template/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ func (ts *transformSession) prepareConfiguration() {
}

func (ts *transformSession) collectTopLevelConfigurations(configFile types.ConfigFile) {
if configSection, ok := configFile.Config["x-podlike"]; ok {
if configSection, ok := configFile.Config[XPodlikeExtension]; ok {
globalConfig, ok := configSection.(map[string]interface{})
if !ok {
panic("top level x-podlike config is not a mapping")
}

// extract the top level global arguments first
if args, ok := globalConfig["args"]; ok {
if args, ok := globalConfig[ArgsProperty]; ok {
if mArgs, ok := args.(map[string]interface{}); ok {
mergeRecursively(ts.Args, mArgs)
delete(globalConfig, "args")
delete(globalConfig, ArgsProperty)
} else {
panic("template args is not a mapping")
}
Expand Down Expand Up @@ -56,7 +56,7 @@ func (ts *transformSession) collectTopLevelConfigurations(configFile types.Confi
}

func (ts *transformSession) collectServiceLevelConfigurations(configFile types.ConfigFile) {
services, ok := configFile.Config["services"]
services, ok := configFile.Config[ServicesProperty]
if !ok {
return // ok, some YAMLs might only define volumes and such
}
Expand All @@ -72,14 +72,14 @@ func (ts *transformSession) collectServiceLevelConfigurations(configFile types.C
panic(fmt.Sprintf("service definition is not a mapping for %s", serviceName))
}

configSection, ok := mDefinition["x-podlike"]
configSection, ok := mDefinition[XPodlikeExtension]
if !ok {
continue
}

if v := schema.Version(configFile.Config); versions.LessThan(v, "3.7") {
// we have to delete the extension key below schema version 3.7
delete(mDefinition, "x-podlike")
delete(mDefinition, XPodlikeExtension)
}

var config transformConfiguration
Expand Down
20 changes: 17 additions & 3 deletions pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package template
import (
"bytes"
"gopkg.in/yaml.v2"
"io/ioutil"
"net/http"
"path"
"text/template"
)
Expand Down Expand Up @@ -38,8 +40,8 @@ func (t *podTemplate) render(tc *transformConfiguration) map[string]interface{}
}

func (t *podTemplate) prepareTemplate(workingDir string) *template.Template {
name := "inline"
if !t.Inline {
name := TypeInline
if !t.Inline && !t.Http {
name = path.Base(t.Template)
}

Expand All @@ -52,11 +54,23 @@ func (t *podTemplate) prepareTemplate(workingDir string) *template.Template {

var err error

if t.Inline {
if t.Http {
if resp, err := http.Get(t.Template); err == nil && resp.StatusCode == 200 {
defer resp.Body.Close()

if content, err := ioutil.ReadAll(resp.Body); err == nil {
tmpl, err = tmpl.Parse(string(content))
}
}

} else if t.Inline {
tmpl, err = tmpl.Parse(t.Template)

} else {
tmpl, err = tmpl.ParseFiles(path.Join(workingDir, t.Template))

}

if err != nil {
panic(err)
}
Expand Down
128 changes: 128 additions & 0 deletions pkg/template/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package template

import (
"github.com/docker/cli/cli/compose/types"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
)

func TestRender_File(t *testing.T) {
tmplFile, err := ioutil.TempFile("", "template")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmplFile.Name())

if _, err := tmplFile.WriteString(`
component:
image: sample/{{ .Service.Labels.CompName }}:{{ .Args.TargetTag }}
`); err != nil {
t.Fatal(err)
}
if err := tmplFile.Close(); err != nil {
t.Fatal(err)
}

tmpl := podTemplate{
Template: tmplFile.Name(),
}

rendered := tmpl.render(&transformConfiguration{
Service: &types.ServiceConfig{
Labels: types.Labels{
"CompName": "testing",
},
},
Args: map[string]interface{}{
"TargetTag": "0.1.2",
},
Session: &transformSession{},
})

if comp, ok := rendered["component"]; !ok {
t.Error("Root key not found")
} else if mComp, ok := comp.(map[string]interface{}); !ok {
t.Error("Invalid root key")
} else if image, ok := mComp["image"]; !ok {
t.Error("Image key not found")
} else if image != "sample/testing:0.1.2" {
t.Error("Invalid image value found")
}
}

func TestRender_Inline(t *testing.T) {
tmpl := podTemplate{
Template: `
component:
image: sample/{{ .Service.Labels.CompName }}:{{ .Args.TargetTag }}
`,
Inline: true,
}

rendered := tmpl.render(&transformConfiguration{
Service: &types.ServiceConfig{
Labels: types.Labels{
"CompName": "testing",
},
},
Args: map[string]interface{}{
"TargetTag": "0.1.2",
},
Session: &transformSession{},
})

if comp, ok := rendered["component"]; !ok {
t.Error("Root key not found")
} else if mComp, ok := comp.(map[string]interface{}); !ok {
t.Error("Invalid root key")
} else if image, ok := mComp["image"]; !ok {
t.Error("Image key not found")
} else if image != "sample/testing:0.1.2" {
t.Error("Invalid image value found")
}
}

func TestRender_Http(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI != "/template/testing" {
t.Fatal("Invalid template request")
}

w.WriteHeader(200)
w.Write([]byte(`
component:
image: sample/{{ .Service.Labels.CompName }}:{{ .Args.TargetTag }}
`))
}))
defer server.Close()

tmpl := podTemplate{
Template: server.URL + "/template/testing",
Http: true,
}

rendered := tmpl.render(&transformConfiguration{
Service: &types.ServiceConfig{
Labels: types.Labels{
"CompName": "testing",
},
},
Args: map[string]interface{}{
"TargetTag": "0.1.2",
},
Session: &transformSession{},
})

if comp, ok := rendered["component"]; !ok {
t.Error("Root key not found")
} else if mComp, ok := comp.(map[string]interface{}); !ok {
t.Error("Invalid root key")
} else if image, ok := mComp["image"]; !ok {
t.Error("Image key not found")
} else if image != "sample/testing:0.1.2" {
t.Error("Invalid image value found")
}
}
1 change: 1 addition & 0 deletions pkg/template/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type transformConfiguration struct {
type podTemplate struct {
Template string
Inline bool
Http bool
}

type templateVars struct {
Expand Down

0 comments on commit cd6fa07

Please sign in to comment.