This repository has been archived by the owner on Jun 13, 2021. It is now read-only.
/
render.go
150 lines (140 loc) · 4.46 KB
/
render.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package render
import (
"fmt"
"os"
"strings"
"github.com/deislabs/cnab-go/bundle"
"github.com/docker/app/internal/compose"
"github.com/docker/app/internal/renderer"
"github.com/docker/app/internal/slices"
"github.com/docker/app/types"
"github.com/docker/app/types/parameters"
"github.com/docker/cli/cli/compose/loader"
composetemplate "github.com/docker/cli/cli/compose/template"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/pkg/errors"
// Register gotemplate renderer
_ "github.com/docker/app/internal/renderer/gotemplate"
// Register mustache renderer
_ "github.com/docker/app/internal/renderer/mustache"
// Register yatee renderer
_ "github.com/docker/app/internal/renderer/yatee"
// Register json formatter
_ "github.com/docker/app/internal/formatter/json"
// Register yaml formatter
_ "github.com/docker/app/internal/formatter/yaml"
)
// Render renders the Compose file for this app, merging in parameters files, other compose files, and env
// appname string, composeFiles []string, parametersFiles []string
func Render(app *types.App, env map[string]string, imageMap map[string]bundle.Image) (*composetypes.Config, error) {
// prepend the app parameters to the argument parameters
// load the parameters into a struct
fileParameters := app.Parameters()
// inject our metadata
metaPrefixed, err := parameters.Load(app.MetadataRaw(), parameters.WithPrefix("app"))
if err != nil {
return nil, err
}
envParameters, err := parameters.FromFlatten(env)
if err != nil {
return nil, err
}
allParameters, err := parameters.Merge(fileParameters, metaPrefixed, envParameters)
if err != nil {
return nil, errors.Wrap(err, "failed to merge parameters")
}
// prepend our app compose file to the list
renderers := renderer.Drivers()
if r, ok := os.LookupEnv("DOCKERAPP_RENDERERS"); ok {
rl := strings.Split(r, ",")
for _, r := range rl {
if !slices.ContainsString(renderer.Drivers(), r) {
return nil, fmt.Errorf("renderer '%s' not found", r)
}
}
renderers = rl
}
configFiles, _, err := compose.Load(app.Composes(), func(data string) (string, error) {
return renderer.Apply(data, allParameters, renderers...)
})
if err != nil {
return nil, errors.Wrap(err, "failed to load composefiles")
}
return render(configFiles, allParameters.Flatten(), imageMap)
}
func render(configFiles []composetypes.ConfigFile, finalEnv map[string]string, imageMap map[string]bundle.Image) (*composetypes.Config, error) {
rendered, err := loader.Load(composetypes.ConfigDetails{
WorkingDir: ".",
ConfigFiles: configFiles,
Environment: finalEnv,
}, func(opts *loader.Options) {
opts.Interpolate.Substitute = substitute
})
if err != nil {
return nil, errors.Wrap(err, "failed to load Compose file")
}
if err := processEnabled(rendered); err != nil {
return nil, err
}
for ix, service := range rendered.Services {
if img, ok := imageMap[service.Name]; ok {
service.Image = img.Image
rendered.Services[ix] = service
}
}
return rendered, nil
}
func substitute(template string, mapping composetemplate.Mapping) (string, error) {
return composetemplate.SubstituteWith(template, mapping, compose.ExtrapolationPattern, errorIfMissing)
}
func errorIfMissing(substitution string, mapping composetemplate.Mapping) (string, bool, error) {
value, found := mapping(substitution)
if !found {
return "", true, &composetemplate.InvalidTemplateError{
Template: "required variable " + substitution + " is missing a value",
}
}
return value, true, nil
}
func processEnabled(config *composetypes.Config) error {
services := []composetypes.ServiceConfig{}
for _, service := range config.Services {
if service.Extras != nil {
if xEnabled, ok := service.Extras["x-enabled"]; ok {
enabled, err := isEnabled(xEnabled)
if err != nil {
return err
}
if !enabled {
continue
}
}
}
services = append(services, service)
}
config.Services = services
return nil
}
func isEnabled(e interface{}) (bool, error) {
switch v := e.(type) {
case string:
v = strings.ToLower(strings.TrimSpace(v))
switch {
case v == "1", v == "true":
return true, nil
case v == "", v == "0", v == "false":
return false, nil
case strings.HasPrefix(v, "!"):
nv, err := isEnabled(v[1:])
if err != nil {
return false, err
}
return !nv, nil
default:
return false, errors.Errorf("%s is not a valid value for x-enabled", e)
}
case bool:
return v, nil
}
return false, errors.Errorf("invalid type (%T) for x-enabled", e)
}