github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/compose/loader/loader.go (about)

     1  package loader
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"path/filepath"
     7  	"reflect"
     8  	"sort"
     9  	"strings"
    10  	"time"
    11  
    12  	interp "github.com/docker/cli/cli/compose/interpolation"
    13  	"github.com/docker/cli/cli/compose/schema"
    14  	"github.com/docker/cli/cli/compose/template"
    15  	"github.com/docker/cli/cli/compose/types"
    16  	"github.com/docker/cli/opts"
    17  	"github.com/docker/docker/api/types/versions"
    18  	"github.com/docker/go-connections/nat"
    19  	units "github.com/docker/go-units"
    20  	"github.com/google/shlex"
    21  	"github.com/mitchellh/mapstructure"
    22  	"github.com/pkg/errors"
    23  	"github.com/sirupsen/logrus"
    24  	yaml "gopkg.in/yaml.v2"
    25  )
    26  
    27  // Options supported by Load
    28  type Options struct {
    29  	// Skip schema validation
    30  	SkipValidation bool
    31  	// Skip interpolation
    32  	SkipInterpolation bool
    33  	// Interpolation options
    34  	Interpolate *interp.Options
    35  	// Discard 'env_file' entries after resolving to 'environment' section
    36  	discardEnvFiles bool
    37  }
    38  
    39  // WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to
    40  // the `environment` section
    41  func WithDiscardEnvFiles(opts *Options) {
    42  	opts.discardEnvFiles = true
    43  }
    44  
    45  // ParseYAML reads the bytes from a file, parses the bytes into a mapping
    46  // structure, and returns it.
    47  func ParseYAML(source []byte) (map[string]interface{}, error) {
    48  	var cfg interface{}
    49  	if err := yaml.Unmarshal(source, &cfg); err != nil {
    50  		return nil, err
    51  	}
    52  	cfgMap, ok := cfg.(map[interface{}]interface{})
    53  	if !ok {
    54  		return nil, errors.Errorf("Top-level object must be a mapping")
    55  	}
    56  	converted, err := convertToStringKeysRecursive(cfgMap, "")
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	return converted.(map[string]interface{}), nil
    61  }
    62  
    63  // Load reads a ConfigDetails and returns a fully loaded configuration
    64  func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.Config, error) {
    65  	if len(configDetails.ConfigFiles) < 1 {
    66  		return nil, errors.Errorf("No files specified")
    67  	}
    68  
    69  	opts := &Options{
    70  		Interpolate: &interp.Options{
    71  			Substitute:      template.Substitute,
    72  			LookupValue:     configDetails.LookupEnv,
    73  			TypeCastMapping: interpolateTypeCastMapping,
    74  		},
    75  	}
    76  
    77  	for _, op := range options {
    78  		op(opts)
    79  	}
    80  
    81  	configs := []*types.Config{}
    82  	var err error
    83  
    84  	for _, file := range configDetails.ConfigFiles {
    85  		configDict := file.Config
    86  		version := schema.Version(configDict)
    87  		if configDetails.Version == "" {
    88  			configDetails.Version = version
    89  		}
    90  		if configDetails.Version != version {
    91  			return nil, errors.Errorf("version mismatched between two composefiles : %v and %v", configDetails.Version, version)
    92  		}
    93  
    94  		if err := validateForbidden(configDict); err != nil {
    95  			return nil, err
    96  		}
    97  
    98  		if !opts.SkipInterpolation {
    99  			configDict, err = interpolateConfig(configDict, *opts.Interpolate)
   100  			if err != nil {
   101  				return nil, err
   102  			}
   103  		}
   104  
   105  		if !opts.SkipValidation {
   106  			if err := schema.Validate(configDict, configDetails.Version); err != nil {
   107  				return nil, err
   108  			}
   109  		}
   110  
   111  		cfg, err := loadSections(configDict, configDetails)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  		cfg.Filename = file.Filename
   116  		if opts.discardEnvFiles {
   117  			for i := range cfg.Services {
   118  				cfg.Services[i].EnvFile = nil
   119  			}
   120  		}
   121  
   122  		configs = append(configs, cfg)
   123  	}
   124  
   125  	return merge(configs)
   126  }
   127  
   128  func validateForbidden(configDict map[string]interface{}) error {
   129  	servicesDict, ok := configDict["services"].(map[string]interface{})
   130  	if !ok {
   131  		return nil
   132  	}
   133  	forbidden := getProperties(servicesDict, types.ForbiddenProperties)
   134  	if len(forbidden) > 0 {
   135  		return &ForbiddenPropertiesError{Properties: forbidden}
   136  	}
   137  	return nil
   138  }
   139  
   140  func loadSections(config map[string]interface{}, configDetails types.ConfigDetails) (*types.Config, error) {
   141  	var err error
   142  	cfg := types.Config{
   143  		Version: schema.Version(config),
   144  	}
   145  
   146  	var loaders = []struct {
   147  		key string
   148  		fnc func(config map[string]interface{}) error
   149  	}{
   150  		{
   151  			key: "services",
   152  			fnc: func(config map[string]interface{}) error {
   153  				cfg.Services, err = LoadServices(config, configDetails.WorkingDir, configDetails.LookupEnv)
   154  				return err
   155  			},
   156  		},
   157  		{
   158  			key: "networks",
   159  			fnc: func(config map[string]interface{}) error {
   160  				cfg.Networks, err = LoadNetworks(config, configDetails.Version)
   161  				return err
   162  			},
   163  		},
   164  		{
   165  			key: "volumes",
   166  			fnc: func(config map[string]interface{}) error {
   167  				cfg.Volumes, err = LoadVolumes(config, configDetails.Version)
   168  				return err
   169  			},
   170  		},
   171  		{
   172  			key: "secrets",
   173  			fnc: func(config map[string]interface{}) error {
   174  				cfg.Secrets, err = LoadSecrets(config, configDetails)
   175  				return err
   176  			},
   177  		},
   178  		{
   179  			key: "configs",
   180  			fnc: func(config map[string]interface{}) error {
   181  				cfg.Configs, err = LoadConfigObjs(config, configDetails)
   182  				return err
   183  			},
   184  		},
   185  	}
   186  	for _, loader := range loaders {
   187  		if err := loader.fnc(getSection(config, loader.key)); err != nil {
   188  			return nil, err
   189  		}
   190  	}
   191  	cfg.Extras = getExtras(config)
   192  	return &cfg, nil
   193  }
   194  
   195  func getSection(config map[string]interface{}, key string) map[string]interface{} {
   196  	section, ok := config[key]
   197  	if !ok {
   198  		return make(map[string]interface{})
   199  	}
   200  	return section.(map[string]interface{})
   201  }
   202  
   203  // GetUnsupportedProperties returns the list of any unsupported properties that are
   204  // used in the Compose files.
   205  func GetUnsupportedProperties(configDicts ...map[string]interface{}) []string {
   206  	unsupported := map[string]bool{}
   207  
   208  	for _, configDict := range configDicts {
   209  		for _, service := range getServices(configDict) {
   210  			serviceDict := service.(map[string]interface{})
   211  			for _, property := range types.UnsupportedProperties {
   212  				if _, isSet := serviceDict[property]; isSet {
   213  					unsupported[property] = true
   214  				}
   215  			}
   216  		}
   217  	}
   218  
   219  	return sortedKeys(unsupported)
   220  }
   221  
   222  func sortedKeys(set map[string]bool) []string {
   223  	var keys []string
   224  	for key := range set {
   225  		keys = append(keys, key)
   226  	}
   227  	sort.Strings(keys)
   228  	return keys
   229  }
   230  
   231  // GetDeprecatedProperties returns the list of any deprecated properties that
   232  // are used in the compose files.
   233  func GetDeprecatedProperties(configDicts ...map[string]interface{}) map[string]string {
   234  	deprecated := map[string]string{}
   235  
   236  	for _, configDict := range configDicts {
   237  		deprecatedProperties := getProperties(getServices(configDict), types.DeprecatedProperties)
   238  		for key, value := range deprecatedProperties {
   239  			deprecated[key] = value
   240  		}
   241  	}
   242  
   243  	return deprecated
   244  }
   245  
   246  func getProperties(services map[string]interface{}, propertyMap map[string]string) map[string]string {
   247  	output := map[string]string{}
   248  
   249  	for _, service := range services {
   250  		if serviceDict, ok := service.(map[string]interface{}); ok {
   251  			for property, description := range propertyMap {
   252  				if _, isSet := serviceDict[property]; isSet {
   253  					output[property] = description
   254  				}
   255  			}
   256  		}
   257  	}
   258  
   259  	return output
   260  }
   261  
   262  // ForbiddenPropertiesError is returned when there are properties in the Compose
   263  // file that are forbidden.
   264  type ForbiddenPropertiesError struct {
   265  	Properties map[string]string
   266  }
   267  
   268  func (e *ForbiddenPropertiesError) Error() string {
   269  	return "Configuration contains forbidden properties"
   270  }
   271  
   272  func getServices(configDict map[string]interface{}) map[string]interface{} {
   273  	if services, ok := configDict["services"]; ok {
   274  		if servicesDict, ok := services.(map[string]interface{}); ok {
   275  			return servicesDict
   276  		}
   277  	}
   278  
   279  	return map[string]interface{}{}
   280  }
   281  
   282  // Transform converts the source into the target struct with compose types transformer
   283  // and the specified transformers if any.
   284  func Transform(source interface{}, target interface{}, additionalTransformers ...Transformer) error {
   285  	data := mapstructure.Metadata{}
   286  	config := &mapstructure.DecoderConfig{
   287  		DecodeHook: mapstructure.ComposeDecodeHookFunc(
   288  			createTransformHook(additionalTransformers...),
   289  			mapstructure.StringToTimeDurationHookFunc()),
   290  		Result:   target,
   291  		Metadata: &data,
   292  	}
   293  	decoder, err := mapstructure.NewDecoder(config)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	return decoder.Decode(source)
   298  }
   299  
   300  // TransformerFunc defines a function to perform the actual transformation
   301  type TransformerFunc func(interface{}) (interface{}, error)
   302  
   303  // Transformer defines a map to type transformer
   304  type Transformer struct {
   305  	TypeOf reflect.Type
   306  	Func   TransformerFunc
   307  }
   308  
   309  func createTransformHook(additionalTransformers ...Transformer) mapstructure.DecodeHookFuncType {
   310  	transforms := map[reflect.Type]func(interface{}) (interface{}, error){
   311  		reflect.TypeOf(types.External{}):                         transformExternal,
   312  		reflect.TypeOf(types.HealthCheckTest{}):                  transformHealthCheckTest,
   313  		reflect.TypeOf(types.ShellCommand{}):                     transformShellCommand,
   314  		reflect.TypeOf(types.StringList{}):                       transformStringList,
   315  		reflect.TypeOf(map[string]string{}):                      transformMapStringString,
   316  		reflect.TypeOf(types.UlimitsConfig{}):                    transformUlimits,
   317  		reflect.TypeOf(types.UnitBytes(0)):                       transformSize,
   318  		reflect.TypeOf([]types.ServicePortConfig{}):              transformServicePort,
   319  		reflect.TypeOf(types.ServiceSecretConfig{}):              transformStringSourceMap,
   320  		reflect.TypeOf(types.ServiceConfigObjConfig{}):           transformStringSourceMap,
   321  		reflect.TypeOf(types.StringOrNumberList{}):               transformStringOrNumberList,
   322  		reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
   323  		reflect.TypeOf(types.Mapping{}):                          transformMappingOrListFunc("=", false),
   324  		reflect.TypeOf(types.MappingWithEquals{}):                transformMappingOrListFunc("=", true),
   325  		reflect.TypeOf(types.Labels{}):                           transformMappingOrListFunc("=", false),
   326  		reflect.TypeOf(types.MappingWithColon{}):                 transformMappingOrListFunc(":", false),
   327  		reflect.TypeOf(types.HostsList{}):                        transformListOrMappingFunc(":", false),
   328  		reflect.TypeOf(types.ServiceVolumeConfig{}):              transformServiceVolumeConfig,
   329  		reflect.TypeOf(types.BuildConfig{}):                      transformBuildConfig,
   330  		reflect.TypeOf(types.Duration(0)):                        transformStringToDuration,
   331  	}
   332  
   333  	for _, transformer := range additionalTransformers {
   334  		transforms[transformer.TypeOf] = transformer.Func
   335  	}
   336  
   337  	return func(_ reflect.Type, target reflect.Type, data interface{}) (interface{}, error) {
   338  		transform, ok := transforms[target]
   339  		if !ok {
   340  			return data, nil
   341  		}
   342  		return transform(data)
   343  	}
   344  }
   345  
   346  // keys needs to be converted to strings for jsonschema
   347  func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) {
   348  	if mapping, ok := value.(map[interface{}]interface{}); ok {
   349  		dict := make(map[string]interface{})
   350  		for key, entry := range mapping {
   351  			str, ok := key.(string)
   352  			if !ok {
   353  				return nil, formatInvalidKeyError(keyPrefix, key)
   354  			}
   355  			var newKeyPrefix string
   356  			if keyPrefix == "" {
   357  				newKeyPrefix = str
   358  			} else {
   359  				newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, str)
   360  			}
   361  			convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
   362  			if err != nil {
   363  				return nil, err
   364  			}
   365  			dict[str] = convertedEntry
   366  		}
   367  		return dict, nil
   368  	}
   369  	if list, ok := value.([]interface{}); ok {
   370  		var convertedList []interface{}
   371  		for index, entry := range list {
   372  			newKeyPrefix := fmt.Sprintf("%s[%d]", keyPrefix, index)
   373  			convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
   374  			if err != nil {
   375  				return nil, err
   376  			}
   377  			convertedList = append(convertedList, convertedEntry)
   378  		}
   379  		return convertedList, nil
   380  	}
   381  	return value, nil
   382  }
   383  
   384  func formatInvalidKeyError(keyPrefix string, key interface{}) error {
   385  	var location string
   386  	if keyPrefix == "" {
   387  		location = "at top level"
   388  	} else {
   389  		location = fmt.Sprintf("in %s", keyPrefix)
   390  	}
   391  	return errors.Errorf("Non-string key %s: %#v", location, key)
   392  }
   393  
   394  // LoadServices produces a ServiceConfig map from a compose file Dict
   395  // the servicesDict is not validated if directly used. Use Load() to enable validation
   396  func LoadServices(servicesDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) ([]types.ServiceConfig, error) {
   397  	var services []types.ServiceConfig
   398  
   399  	for name, serviceDef := range servicesDict {
   400  		serviceConfig, err := LoadService(name, serviceDef.(map[string]interface{}), workingDir, lookupEnv)
   401  		if err != nil {
   402  			return nil, err
   403  		}
   404  		services = append(services, *serviceConfig)
   405  	}
   406  
   407  	return services, nil
   408  }
   409  
   410  // LoadService produces a single ServiceConfig from a compose file Dict
   411  // the serviceDict is not validated if directly used. Use Load() to enable validation
   412  func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) {
   413  	serviceConfig := &types.ServiceConfig{}
   414  	if err := Transform(serviceDict, serviceConfig); err != nil {
   415  		return nil, err
   416  	}
   417  	serviceConfig.Name = name
   418  
   419  	if err := resolveEnvironment(serviceConfig, workingDir, lookupEnv); err != nil {
   420  		return nil, err
   421  	}
   422  
   423  	if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil {
   424  		return nil, err
   425  	}
   426  
   427  	serviceConfig.Extras = getExtras(serviceDict)
   428  
   429  	return serviceConfig, nil
   430  }
   431  
   432  func loadExtras(name string, source map[string]interface{}) map[string]interface{} {
   433  	if dict, ok := source[name].(map[string]interface{}); ok {
   434  		return getExtras(dict)
   435  	}
   436  	return nil
   437  }
   438  
   439  func getExtras(dict map[string]interface{}) map[string]interface{} {
   440  	extras := map[string]interface{}{}
   441  	for key, value := range dict {
   442  		if strings.HasPrefix(key, "x-") {
   443  			extras[key] = value
   444  		}
   445  	}
   446  	if len(extras) == 0 {
   447  		return nil
   448  	}
   449  	return extras
   450  }
   451  
   452  func updateEnvironment(environment map[string]*string, vars map[string]*string, lookupEnv template.Mapping) {
   453  	for k, v := range vars {
   454  		interpolatedV, ok := lookupEnv(k)
   455  		if (v == nil || *v == "") && ok {
   456  			// lookupEnv is prioritized over vars
   457  			environment[k] = &interpolatedV
   458  		} else {
   459  			environment[k] = v
   460  		}
   461  	}
   462  }
   463  
   464  func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, lookupEnv template.Mapping) error {
   465  	environment := make(map[string]*string)
   466  
   467  	if len(serviceConfig.EnvFile) > 0 {
   468  		var envVars []string
   469  
   470  		for _, file := range serviceConfig.EnvFile {
   471  			filePath := absPath(workingDir, file)
   472  			fileVars, err := opts.ParseEnvFile(filePath)
   473  			if err != nil {
   474  				return err
   475  			}
   476  			envVars = append(envVars, fileVars...)
   477  		}
   478  		updateEnvironment(environment,
   479  			opts.ConvertKVStringsToMapWithNil(envVars), lookupEnv)
   480  	}
   481  
   482  	updateEnvironment(environment, serviceConfig.Environment, lookupEnv)
   483  	serviceConfig.Environment = environment
   484  	return nil
   485  }
   486  
   487  func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) error {
   488  	for i, volume := range volumes {
   489  		if volume.Type != "bind" {
   490  			continue
   491  		}
   492  
   493  		if volume.Source == "" {
   494  			return errors.New(`invalid mount config for type "bind": field Source must not be empty`)
   495  		}
   496  
   497  		filePath := expandUser(volume.Source, lookupEnv)
   498  		// Check if source is an absolute path (either Unix or Windows), to
   499  		// handle a Windows client with a Unix daemon or vice-versa.
   500  		//
   501  		// Note that this is not required for Docker for Windows when specifying
   502  		// a local Windows path, because Docker for Windows translates the Windows
   503  		// path into a valid path within the VM.
   504  		if !path.IsAbs(filePath) && !isAbs(filePath) {
   505  			filePath = absPath(workingDir, filePath)
   506  		}
   507  		volume.Source = filePath
   508  		volumes[i] = volume
   509  	}
   510  	return nil
   511  }
   512  
   513  // TODO: make this more robust
   514  func expandUser(path string, lookupEnv template.Mapping) string {
   515  	if strings.HasPrefix(path, "~") {
   516  		home, ok := lookupEnv("HOME")
   517  		if !ok {
   518  			logrus.Warn("cannot expand '~', because the environment lacks HOME")
   519  			return path
   520  		}
   521  		return strings.Replace(path, "~", home, 1)
   522  	}
   523  	return path
   524  }
   525  
   526  func transformUlimits(data interface{}) (interface{}, error) {
   527  	switch value := data.(type) {
   528  	case int:
   529  		return types.UlimitsConfig{Single: value}, nil
   530  	case map[string]interface{}:
   531  		ulimit := types.UlimitsConfig{}
   532  		ulimit.Soft = value["soft"].(int)
   533  		ulimit.Hard = value["hard"].(int)
   534  		return ulimit, nil
   535  	default:
   536  		return data, errors.Errorf("invalid type %T for ulimits", value)
   537  	}
   538  }
   539  
   540  // LoadNetworks produces a NetworkConfig map from a compose file Dict
   541  // the source Dict is not validated if directly used. Use Load() to enable validation
   542  func LoadNetworks(source map[string]interface{}, version string) (map[string]types.NetworkConfig, error) {
   543  	networks := make(map[string]types.NetworkConfig)
   544  	err := Transform(source, &networks)
   545  	if err != nil {
   546  		return networks, err
   547  	}
   548  	for name, network := range networks {
   549  		if !network.External.External {
   550  			continue
   551  		}
   552  		switch {
   553  		case network.External.Name != "":
   554  			if network.Name != "" {
   555  				return nil, errors.Errorf("network %s: network.external.name and network.name conflict; only use network.name", name)
   556  			}
   557  			if versions.GreaterThanOrEqualTo(version, "3.5") {
   558  				logrus.Warnf("network %s: network.external.name is deprecated in favor of network.name", name)
   559  			}
   560  			network.Name = network.External.Name
   561  			network.External.Name = ""
   562  		case network.Name == "":
   563  			network.Name = name
   564  		}
   565  		network.Extras = loadExtras(name, source)
   566  		networks[name] = network
   567  	}
   568  	return networks, nil
   569  }
   570  
   571  func externalVolumeError(volume, key string) error {
   572  	return errors.Errorf(
   573  		"conflicting parameters \"external\" and %q specified for volume %q",
   574  		key, volume)
   575  }
   576  
   577  // LoadVolumes produces a VolumeConfig map from a compose file Dict
   578  // the source Dict is not validated if directly used. Use Load() to enable validation
   579  func LoadVolumes(source map[string]interface{}, version string) (map[string]types.VolumeConfig, error) {
   580  	volumes := make(map[string]types.VolumeConfig)
   581  	if err := Transform(source, &volumes); err != nil {
   582  		return volumes, err
   583  	}
   584  
   585  	for name, volume := range volumes {
   586  		if !volume.External.External {
   587  			continue
   588  		}
   589  		switch {
   590  		case volume.Driver != "":
   591  			return nil, externalVolumeError(name, "driver")
   592  		case len(volume.DriverOpts) > 0:
   593  			return nil, externalVolumeError(name, "driver_opts")
   594  		case len(volume.Labels) > 0:
   595  			return nil, externalVolumeError(name, "labels")
   596  		case volume.External.Name != "":
   597  			if volume.Name != "" {
   598  				return nil, errors.Errorf("volume %s: volume.external.name and volume.name conflict; only use volume.name", name)
   599  			}
   600  			if versions.GreaterThanOrEqualTo(version, "3.4") {
   601  				logrus.Warnf("volume %s: volume.external.name is deprecated in favor of volume.name", name)
   602  			}
   603  			volume.Name = volume.External.Name
   604  			volume.External.Name = ""
   605  		case volume.Name == "":
   606  			volume.Name = name
   607  		}
   608  		volume.Extras = loadExtras(name, source)
   609  		volumes[name] = volume
   610  	}
   611  	return volumes, nil
   612  }
   613  
   614  // LoadSecrets produces a SecretConfig map from a compose file Dict
   615  // the source Dict is not validated if directly used. Use Load() to enable validation
   616  func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) {
   617  	secrets := make(map[string]types.SecretConfig)
   618  	if err := Transform(source, &secrets); err != nil {
   619  		return secrets, err
   620  	}
   621  	for name, secret := range secrets {
   622  		obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details)
   623  		if err != nil {
   624  			return nil, err
   625  		}
   626  		secretConfig := types.SecretConfig(obj)
   627  		secretConfig.Extras = loadExtras(name, source)
   628  		secrets[name] = secretConfig
   629  	}
   630  	return secrets, nil
   631  }
   632  
   633  // LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
   634  // the source Dict is not validated if directly used. Use Load() to enable validation
   635  func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) {
   636  	configs := make(map[string]types.ConfigObjConfig)
   637  	if err := Transform(source, &configs); err != nil {
   638  		return configs, err
   639  	}
   640  	for name, config := range configs {
   641  		obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details)
   642  		if err != nil {
   643  			return nil, err
   644  		}
   645  		configConfig := types.ConfigObjConfig(obj)
   646  		configConfig.Extras = loadExtras(name, source)
   647  		configs[name] = configConfig
   648  	}
   649  	return configs, nil
   650  }
   651  
   652  func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) {
   653  	// if "external: true"
   654  	switch {
   655  	case obj.External.External:
   656  		// handle deprecated external.name
   657  		if obj.External.Name != "" {
   658  			if obj.Name != "" {
   659  				return obj, errors.Errorf("%[1]s %[2]s: %[1]s.external.name and %[1]s.name conflict; only use %[1]s.name", objType, name)
   660  			}
   661  			if versions.GreaterThanOrEqualTo(details.Version, "3.5") {
   662  				logrus.Warnf("%[1]s %[2]s: %[1]s.external.name is deprecated in favor of %[1]s.name", objType, name)
   663  			}
   664  			obj.Name = obj.External.Name
   665  			obj.External.Name = ""
   666  		} else {
   667  			if obj.Name == "" {
   668  				obj.Name = name
   669  			}
   670  		}
   671  		// if not "external: true"
   672  	case obj.Driver != "":
   673  		if obj.File != "" {
   674  			return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
   675  		}
   676  	default:
   677  		obj.File = absPath(details.WorkingDir, obj.File)
   678  	}
   679  
   680  	return obj, nil
   681  }
   682  
   683  func absPath(workingDir string, filePath string) string {
   684  	if filepath.IsAbs(filePath) {
   685  		return filePath
   686  	}
   687  	return filepath.Join(workingDir, filePath)
   688  }
   689  
   690  var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) {
   691  	switch value := data.(type) {
   692  	case map[string]interface{}:
   693  		return toMapStringString(value, false), nil
   694  	case map[string]string:
   695  		return value, nil
   696  	default:
   697  		return data, errors.Errorf("invalid type %T for map[string]string", value)
   698  	}
   699  }
   700  
   701  var transformExternal TransformerFunc = func(data interface{}) (interface{}, error) {
   702  	switch value := data.(type) {
   703  	case bool:
   704  		return map[string]interface{}{"external": value}, nil
   705  	case map[string]interface{}:
   706  		return map[string]interface{}{"external": true, "name": value["name"]}, nil
   707  	default:
   708  		return data, errors.Errorf("invalid type %T for external", value)
   709  	}
   710  }
   711  
   712  var transformServicePort TransformerFunc = func(data interface{}) (interface{}, error) {
   713  	switch entries := data.(type) {
   714  	case []interface{}:
   715  		// We process the list instead of individual items here.
   716  		// The reason is that one entry might be mapped to multiple ServicePortConfig.
   717  		// Therefore we take an input of a list and return an output of a list.
   718  		ports := []interface{}{}
   719  		for _, entry := range entries {
   720  			switch value := entry.(type) {
   721  			case int:
   722  				v, err := toServicePortConfigs(fmt.Sprint(value))
   723  				if err != nil {
   724  					return data, err
   725  				}
   726  				ports = append(ports, v...)
   727  			case string:
   728  				v, err := toServicePortConfigs(value)
   729  				if err != nil {
   730  					return data, err
   731  				}
   732  				ports = append(ports, v...)
   733  			case map[string]interface{}:
   734  				ports = append(ports, value)
   735  			default:
   736  				return data, errors.Errorf("invalid type %T for port", value)
   737  			}
   738  		}
   739  		return ports, nil
   740  	default:
   741  		return data, errors.Errorf("invalid type %T for port", entries)
   742  	}
   743  }
   744  
   745  var transformStringSourceMap TransformerFunc = func(data interface{}) (interface{}, error) {
   746  	switch value := data.(type) {
   747  	case string:
   748  		return map[string]interface{}{"source": value}, nil
   749  	case map[string]interface{}:
   750  		return data, nil
   751  	default:
   752  		return data, errors.Errorf("invalid type %T for secret", value)
   753  	}
   754  }
   755  
   756  var transformBuildConfig TransformerFunc = func(data interface{}) (interface{}, error) {
   757  	switch value := data.(type) {
   758  	case string:
   759  		return map[string]interface{}{"context": value}, nil
   760  	case map[string]interface{}:
   761  		return data, nil
   762  	default:
   763  		return data, errors.Errorf("invalid type %T for service build", value)
   764  	}
   765  }
   766  
   767  var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) {
   768  	switch value := data.(type) {
   769  	case string:
   770  		return ParseVolume(value)
   771  	case map[string]interface{}:
   772  		return data, nil
   773  	default:
   774  		return data, errors.Errorf("invalid type %T for service volume", value)
   775  	}
   776  }
   777  
   778  var transformServiceNetworkMap TransformerFunc = func(value interface{}) (interface{}, error) {
   779  	if list, ok := value.([]interface{}); ok {
   780  		mapValue := map[interface{}]interface{}{}
   781  		for _, name := range list {
   782  			mapValue[name] = nil
   783  		}
   784  		return mapValue, nil
   785  	}
   786  	return value, nil
   787  }
   788  
   789  var transformStringOrNumberList TransformerFunc = func(value interface{}) (interface{}, error) {
   790  	list := value.([]interface{})
   791  	result := make([]string, len(list))
   792  	for i, item := range list {
   793  		result[i] = fmt.Sprint(item)
   794  	}
   795  	return result, nil
   796  }
   797  
   798  var transformStringList TransformerFunc = func(data interface{}) (interface{}, error) {
   799  	switch value := data.(type) {
   800  	case string:
   801  		return []string{value}, nil
   802  	case []interface{}:
   803  		return value, nil
   804  	default:
   805  		return data, errors.Errorf("invalid type %T for string list", value)
   806  	}
   807  }
   808  
   809  func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
   810  	return func(data interface{}) (interface{}, error) {
   811  		return transformMappingOrList(data, sep, allowNil), nil
   812  	}
   813  }
   814  
   815  func transformListOrMappingFunc(sep string, allowNil bool) TransformerFunc {
   816  	return func(data interface{}) (interface{}, error) {
   817  		return transformListOrMapping(data, sep, allowNil), nil
   818  	}
   819  }
   820  
   821  func transformListOrMapping(listOrMapping interface{}, sep string, allowNil bool) interface{} {
   822  	switch value := listOrMapping.(type) {
   823  	case map[string]interface{}:
   824  		return toStringList(value, sep, allowNil)
   825  	case []interface{}:
   826  		return listOrMapping
   827  	}
   828  	panic(errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping))
   829  }
   830  
   831  func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) interface{} {
   832  	switch value := mappingOrList.(type) {
   833  	case map[string]interface{}:
   834  		return toMapStringString(value, allowNil)
   835  	case ([]interface{}):
   836  		result := make(map[string]interface{})
   837  		for _, value := range value {
   838  			parts := strings.SplitN(value.(string), sep, 2)
   839  			key := parts[0]
   840  			switch {
   841  			case len(parts) == 1 && allowNil:
   842  				result[key] = nil
   843  			case len(parts) == 1 && !allowNil:
   844  				result[key] = ""
   845  			default:
   846  				result[key] = parts[1]
   847  			}
   848  		}
   849  		return result
   850  	}
   851  	panic(errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList))
   852  }
   853  
   854  var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
   855  	if str, ok := value.(string); ok {
   856  		return shlex.Split(str)
   857  	}
   858  	return value, nil
   859  }
   860  
   861  var transformHealthCheckTest TransformerFunc = func(data interface{}) (interface{}, error) {
   862  	switch value := data.(type) {
   863  	case string:
   864  		return append([]string{"CMD-SHELL"}, value), nil
   865  	case []interface{}:
   866  		return value, nil
   867  	default:
   868  		return value, errors.Errorf("invalid type %T for healthcheck.test", value)
   869  	}
   870  }
   871  
   872  var transformSize TransformerFunc = func(value interface{}) (interface{}, error) {
   873  	switch value := value.(type) {
   874  	case int:
   875  		return int64(value), nil
   876  	case string:
   877  		return units.RAMInBytes(value)
   878  	}
   879  	panic(errors.Errorf("invalid type for size %T", value))
   880  }
   881  
   882  var transformStringToDuration TransformerFunc = func(value interface{}) (interface{}, error) {
   883  	switch value := value.(type) {
   884  	case string:
   885  		d, err := time.ParseDuration(value)
   886  		if err != nil {
   887  			return value, err
   888  		}
   889  		return types.Duration(d), nil
   890  	default:
   891  		return value, errors.Errorf("invalid type %T for duration", value)
   892  	}
   893  }
   894  
   895  func toServicePortConfigs(value string) ([]interface{}, error) {
   896  	var portConfigs []interface{}
   897  
   898  	ports, portBindings, err := nat.ParsePortSpecs([]string{value})
   899  	if err != nil {
   900  		return nil, err
   901  	}
   902  	// We need to sort the key of the ports to make sure it is consistent
   903  	keys := []string{}
   904  	for port := range ports {
   905  		keys = append(keys, string(port))
   906  	}
   907  	sort.Strings(keys)
   908  
   909  	for _, key := range keys {
   910  		// Reuse ConvertPortToPortConfig so that it is consistent
   911  		portConfig, err := opts.ConvertPortToPortConfig(nat.Port(key), portBindings)
   912  		if err != nil {
   913  			return nil, err
   914  		}
   915  		for _, p := range portConfig {
   916  			portConfigs = append(portConfigs, types.ServicePortConfig{
   917  				Protocol:  string(p.Protocol),
   918  				Target:    p.TargetPort,
   919  				Published: p.PublishedPort,
   920  				Mode:      string(p.PublishMode),
   921  			})
   922  		}
   923  	}
   924  
   925  	return portConfigs, nil
   926  }
   927  
   928  func toMapStringString(value map[string]interface{}, allowNil bool) map[string]interface{} {
   929  	output := make(map[string]interface{})
   930  	for key, value := range value {
   931  		output[key] = toString(value, allowNil)
   932  	}
   933  	return output
   934  }
   935  
   936  func toString(value interface{}, allowNil bool) interface{} {
   937  	switch {
   938  	case value != nil:
   939  		return fmt.Sprint(value)
   940  	case allowNil:
   941  		return nil
   942  	default:
   943  		return ""
   944  	}
   945  }
   946  
   947  func toStringList(value map[string]interface{}, separator string, allowNil bool) []string {
   948  	output := []string{}
   949  	for key, value := range value {
   950  		if value == nil && !allowNil {
   951  			continue
   952  		}
   953  		output = append(output, fmt.Sprintf("%s%s%s", key, separator, value))
   954  	}
   955  	sort.Strings(output)
   956  	return output
   957  }