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 } // LoadComposefile parse the composefile specified in the cli and returns its Config and version. func LoadComposefile(opts Deploy, appEnv map[string]string) (*composetypes.Config, error) { configDetails, err := getConfigDetails(opts.Composefiles, appEnv) if err != nil { return nil, err } dicts := getDictsFrom(configDetails.ConfigFiles) config, err := loader.Load(configDetails, DontSkipValidation) 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 } unsupportedProperties := loader.GetUnsupportedProperties(dicts...) if len(unsupportedProperties) > 0 { logrus.Warnf("%s: ignoring unsupported options: %s", appEnv["RECIPE"], strings.Join(unsupportedProperties, ", ")) } deprecatedProperties := loader.GetDeprecatedProperties(dicts...) if len(deprecatedProperties) > 0 { logrus.Warnf("%s: ignoring deprecated options: %s", appEnv["RECIPE"], 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 }