All checks were successful
continuous-integration/drone/push Build is passing
This implements proper modifier support in the env file using this new fork of the godotenv library. The modifier implementation is quite basic for but can be improved later if needed. See this commit for the actual implementation. Because we are now using proper modifer parsing, it does not affect the parsing of value, so this is possible again: ``` MY_VAR="#foo" ``` Closes coop-cloud/organising#535
150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
package stack // https://github.com/docker/cli/blob/master/cli/command/stack/loader/loader.go
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli/compose/loader"
|
|
"github.com/docker/cli/cli/compose/schema"
|
|
composetypes "github.com/docker/cli/cli/compose/types"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// DontSkipValidation ensures validation is done for compose file loading
|
|
func DontSkipValidation(opts *loader.Options) {
|
|
opts.SkipValidation = false
|
|
}
|
|
|
|
// SkipInterpolation skip interpolating environment variables.
|
|
func SkipInterpolation(opts *loader.Options) {
|
|
opts.SkipInterpolation = true
|
|
}
|
|
|
|
// LoadComposefile parse the composefile specified in the cli and returns its Config and version.
|
|
func LoadComposefile(opts Deploy, appEnv map[string]string, options ...func(*loader.Options)) (*composetypes.Config, error) {
|
|
configDetails, err := getConfigDetails(opts.Composefiles, appEnv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if options == nil {
|
|
options = []func(*loader.Options){DontSkipValidation}
|
|
}
|
|
|
|
dicts := getDictsFrom(configDetails.ConfigFiles)
|
|
config, err := loader.Load(configDetails, options...)
|
|
if err != nil {
|
|
if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
|
|
return nil, fmt.Errorf("compose file contains unsupported options: %s",
|
|
propertyWarnings(fpe.Properties))
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
recipeName, exists := appEnv["RECIPE"]
|
|
if !exists {
|
|
recipeName, _ = appEnv["TYPE"]
|
|
}
|
|
|
|
unsupportedProperties := loader.GetUnsupportedProperties(dicts...)
|
|
if len(unsupportedProperties) > 0 {
|
|
logrus.Warnf("%s: ignoring unsupported options: %s",
|
|
recipeName, strings.Join(unsupportedProperties, ", "))
|
|
}
|
|
|
|
deprecatedProperties := loader.GetDeprecatedProperties(dicts...)
|
|
if len(deprecatedProperties) > 0 {
|
|
logrus.Warnf("%s: ignoring deprecated options: %s",
|
|
recipeName, propertyWarnings(deprecatedProperties))
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]interface{} {
|
|
dicts := []map[string]interface{}{}
|
|
|
|
for _, configFile := range configFiles {
|
|
dicts = append(dicts, configFile.Config)
|
|
}
|
|
|
|
return dicts
|
|
}
|
|
|
|
func propertyWarnings(properties map[string]string) string {
|
|
var msgs []string
|
|
for name, description := range properties {
|
|
msgs = append(msgs, fmt.Sprintf("%s: %s", name, description))
|
|
}
|
|
sort.Strings(msgs)
|
|
return strings.Join(msgs, "\n\n")
|
|
}
|
|
|
|
func getConfigDetails(composefiles []string, appEnv map[string]string) (composetypes.ConfigDetails, error) {
|
|
var details composetypes.ConfigDetails
|
|
|
|
absPath, err := filepath.Abs(composefiles[0])
|
|
if err != nil {
|
|
return details, err
|
|
}
|
|
details.WorkingDir = filepath.Dir(absPath)
|
|
|
|
details.ConfigFiles, err = loadConfigFiles(composefiles)
|
|
if err != nil {
|
|
return details, err
|
|
}
|
|
// Take the first file version (2 files can't have different version)
|
|
details.Version = schema.Version(details.ConfigFiles[0].Config)
|
|
details.Environment = appEnv
|
|
return details, err
|
|
}
|
|
|
|
func buildEnvironment(env []string) (map[string]string, error) {
|
|
result := make(map[string]string, len(env))
|
|
for _, s := range env {
|
|
// if value is empty, s is like "K=", not "K".
|
|
if !strings.Contains(s, "=") {
|
|
return result, fmt.Errorf("unexpected environment %q", s)
|
|
}
|
|
kv := strings.SplitN(s, "=", 2)
|
|
result[kv[0]] = kv[1]
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func loadConfigFiles(filenames []string) ([]composetypes.ConfigFile, error) {
|
|
var configFiles []composetypes.ConfigFile
|
|
|
|
for _, filename := range filenames {
|
|
configFile, err := loadConfigFile(filename)
|
|
if err != nil {
|
|
return configFiles, err
|
|
}
|
|
configFiles = append(configFiles, *configFile)
|
|
}
|
|
|
|
return configFiles, nil
|
|
}
|
|
|
|
func loadConfigFile(filename string) (*composetypes.ConfigFile, error) {
|
|
var bytes []byte
|
|
var err error
|
|
|
|
bytes, err = ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config, err := loader.ParseYAML(bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &composetypes.ConfigFile{
|
|
Filename: filename,
|
|
Config: config,
|
|
}, nil
|
|
}
|