Exposes compose `loader.Transform` function…

This should make it easier for people to write custom composefile
parser without duplicating too much code. It takes the default
transformers and any additional number of transformer for any
types. That way it's possible to transform a `cli/compose` map into a
custom type that would use some of `cli/compose` types and its own.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2018-07-31 09:37:09 +02:00
parent b4e50635a2
commit 0246bc1b3b
No known key found for this signature in database
GPG Key ID: 083CC6FD6EB699A3
1 changed files with 20 additions and 8 deletions

View File

@ -265,11 +265,13 @@ func getServices(configDict map[string]interface{}) map[string]interface{} {
return map[string]interface{}{} return map[string]interface{}{}
} }
func transform(source map[string]interface{}, target interface{}) error { // Transform converts the source map into the target struct with compose types transformer
// and the specified transformers if any.
func Transform(source map[string]interface{}, target interface{}, additionalTransformers ...Transformer) error {
data := mapstructure.Metadata{} data := mapstructure.Metadata{}
config := &mapstructure.DecoderConfig{ config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc( DecodeHook: mapstructure.ComposeDecodeHookFunc(
createTransformHook(), createTransformHook(additionalTransformers...),
mapstructure.StringToTimeDurationHookFunc()), mapstructure.StringToTimeDurationHookFunc()),
Result: target, Result: target,
Metadata: &data, Metadata: &data,
@ -281,7 +283,13 @@ func transform(source map[string]interface{}, target interface{}) error {
return decoder.Decode(source) return decoder.Decode(source)
} }
func createTransformHook() mapstructure.DecodeHookFuncType { // Transformer defines a map to type transformer
type Transformer struct {
TypeOf reflect.Type
Func func(interface{}) (interface{}, error)
}
func createTransformHook(additionalTransformers ...Transformer) mapstructure.DecodeHookFuncType {
transforms := map[reflect.Type]func(interface{}) (interface{}, error){ transforms := map[reflect.Type]func(interface{}) (interface{}, error){
reflect.TypeOf(types.External{}): transformExternal, reflect.TypeOf(types.External{}): transformExternal,
reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest, reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest,
@ -303,6 +311,10 @@ func createTransformHook() mapstructure.DecodeHookFuncType {
reflect.TypeOf(types.BuildConfig{}): transformBuildConfig, reflect.TypeOf(types.BuildConfig{}): transformBuildConfig,
} }
for _, transformer := range additionalTransformers {
transforms[transformer.TypeOf] = transformer.Func
}
return func(_ reflect.Type, target reflect.Type, data interface{}) (interface{}, error) { return func(_ reflect.Type, target reflect.Type, data interface{}) (interface{}, error) {
transform, ok := transforms[target] transform, ok := transforms[target]
if !ok { if !ok {
@ -380,7 +392,7 @@ func LoadServices(servicesDict map[string]interface{}, workingDir string, lookup
// the serviceDict is not validated if directly used. Use Load() to enable validation // the serviceDict is not validated if directly used. Use Load() to enable validation
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) { func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) {
serviceConfig := &types.ServiceConfig{} serviceConfig := &types.ServiceConfig{}
if err := transform(serviceDict, serviceConfig); err != nil { if err := Transform(serviceDict, serviceConfig); err != nil {
return nil, err return nil, err
} }
serviceConfig.Name = name serviceConfig.Name = name
@ -509,7 +521,7 @@ func transformUlimits(data interface{}) (interface{}, error) {
// the source Dict is not validated if directly used. Use Load() to enable validation // the source Dict is not validated if directly used. Use Load() to enable validation
func LoadNetworks(source map[string]interface{}, version string) (map[string]types.NetworkConfig, error) { func LoadNetworks(source map[string]interface{}, version string) (map[string]types.NetworkConfig, error) {
networks := make(map[string]types.NetworkConfig) networks := make(map[string]types.NetworkConfig)
err := transform(source, &networks) err := Transform(source, &networks)
if err != nil { if err != nil {
return networks, err return networks, err
} }
@ -546,7 +558,7 @@ func externalVolumeError(volume, key string) error {
// the source Dict is not validated if directly used. Use Load() to enable validation // the source Dict is not validated if directly used. Use Load() to enable validation
func LoadVolumes(source map[string]interface{}, version string) (map[string]types.VolumeConfig, error) { func LoadVolumes(source map[string]interface{}, version string) (map[string]types.VolumeConfig, error) {
volumes := make(map[string]types.VolumeConfig) volumes := make(map[string]types.VolumeConfig)
if err := transform(source, &volumes); err != nil { if err := Transform(source, &volumes); err != nil {
return volumes, err return volumes, err
} }
@ -583,7 +595,7 @@ func LoadVolumes(source map[string]interface{}, version string) (map[string]type
// the source Dict is not validated if directly used. Use Load() to enable validation // the source Dict is not validated if directly used. Use Load() to enable validation
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) { func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) {
secrets := make(map[string]types.SecretConfig) secrets := make(map[string]types.SecretConfig)
if err := transform(source, &secrets); err != nil { if err := Transform(source, &secrets); err != nil {
return secrets, err return secrets, err
} }
for name, secret := range secrets { for name, secret := range secrets {
@ -602,7 +614,7 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
// the source Dict is not validated if directly used. Use Load() to enable validation // the source Dict is not validated if directly used. Use Load() to enable validation
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) { func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) {
configs := make(map[string]types.ConfigObjConfig) configs := make(map[string]types.ConfigObjConfig)
if err := transform(source, &configs); err != nil { if err := Transform(source, &configs); err != nil {
return configs, err return configs, err
} }
for name, config := range configs { for name, config := range configs {