github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/command/stack/loader/loader.go (about) 1 package loader 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/docker/cli/cli/command" 12 "github.com/docker/cli/cli/command/stack/options" 13 "github.com/docker/cli/cli/compose/loader" 14 "github.com/docker/cli/cli/compose/schema" 15 composetypes "github.com/docker/cli/cli/compose/types" 16 "github.com/pkg/errors" 17 ) 18 19 // LoadComposefile parse the composefile specified in the cli and returns its Config and version. 20 func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.Config, error) { 21 configDetails, err := GetConfigDetails(opts.Composefiles, dockerCli.In()) 22 if err != nil { 23 return nil, err 24 } 25 26 dicts := getDictsFrom(configDetails.ConfigFiles) 27 config, err := loader.Load(configDetails) 28 if err != nil { 29 if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok { 30 //nolint:revive // ignore capitalization error; this error is intentionally formatted multi-line 31 return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n", 32 propertyWarnings(fpe.Properties)) 33 } 34 35 return nil, err 36 } 37 38 unsupportedProperties := loader.GetUnsupportedProperties(dicts...) 39 if len(unsupportedProperties) > 0 { 40 fmt.Fprintf(dockerCli.Err(), "Ignoring unsupported options: %s\n\n", 41 strings.Join(unsupportedProperties, ", ")) 42 } 43 44 deprecatedProperties := loader.GetDeprecatedProperties(dicts...) 45 if len(deprecatedProperties) > 0 { 46 fmt.Fprintf(dockerCli.Err(), "Ignoring deprecated options:\n\n%s\n\n", 47 propertyWarnings(deprecatedProperties)) 48 } 49 return config, nil 50 } 51 52 func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]interface{} { 53 dicts := []map[string]interface{}{} 54 55 for _, configFile := range configFiles { 56 dicts = append(dicts, configFile.Config) 57 } 58 59 return dicts 60 } 61 62 func propertyWarnings(properties map[string]string) string { 63 var msgs []string 64 for name, description := range properties { 65 msgs = append(msgs, fmt.Sprintf("%s: %s", name, description)) 66 } 67 sort.Strings(msgs) 68 return strings.Join(msgs, "\n\n") 69 } 70 71 // GetConfigDetails parse the composefiles specified in the cli and returns their ConfigDetails 72 func GetConfigDetails(composefiles []string, stdin io.Reader) (composetypes.ConfigDetails, error) { 73 var details composetypes.ConfigDetails 74 75 if len(composefiles) == 0 { 76 return details, errors.New("Please specify a Compose file (with --compose-file)") 77 } 78 79 if composefiles[0] == "-" && len(composefiles) == 1 { 80 workingDir, err := os.Getwd() 81 if err != nil { 82 return details, err 83 } 84 details.WorkingDir = workingDir 85 } else { 86 absPath, err := filepath.Abs(composefiles[0]) 87 if err != nil { 88 return details, err 89 } 90 details.WorkingDir = filepath.Dir(absPath) 91 } 92 93 var err error 94 details.ConfigFiles, err = loadConfigFiles(composefiles, stdin) 95 if err != nil { 96 return details, err 97 } 98 // Take the first file version (2 files can't have different version) 99 details.Version = schema.Version(details.ConfigFiles[0].Config) 100 details.Environment, err = buildEnvironment(os.Environ()) 101 return details, err 102 } 103 104 func buildEnvironment(env []string) (map[string]string, error) { 105 result := make(map[string]string, len(env)) 106 for _, s := range env { 107 k, v, ok := strings.Cut(s, "=") 108 if !ok || k == "" { 109 return result, errors.Errorf("unexpected environment %q", s) 110 } 111 // value may be set, but empty if "s" is like "K=", not "K". 112 result[k] = v 113 } 114 return result, nil 115 } 116 117 func loadConfigFiles(filenames []string, stdin io.Reader) ([]composetypes.ConfigFile, error) { 118 var configFiles []composetypes.ConfigFile 119 120 for _, filename := range filenames { 121 configFile, err := loadConfigFile(filename, stdin) 122 if err != nil { 123 return configFiles, err 124 } 125 configFiles = append(configFiles, *configFile) 126 } 127 128 return configFiles, nil 129 } 130 131 func loadConfigFile(filename string, stdin io.Reader) (*composetypes.ConfigFile, error) { 132 var bytes []byte 133 var err error 134 135 if filename == "-" { 136 bytes, err = io.ReadAll(stdin) 137 } else { 138 bytes, err = os.ReadFile(filename) 139 } 140 if err != nil { 141 return nil, err 142 } 143 144 config, err := loader.ParseYAML(bytes) 145 if err != nil { 146 return nil, err 147 } 148 149 return &composetypes.ConfigFile{ 150 Filename: filename, 151 Config: config, 152 }, nil 153 }