From 0246bc1b3bc3f1faefa065165b1cab04b5de1fd5 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Tue, 31 Jul 2018 09:37:09 +0200 Subject: [PATCH] =?UTF-8?q?Exposes=20compose=20`loader.Transform`=20functi?= =?UTF-8?q?on=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- cli/compose/loader/loader.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index f11619b985..cea5d109c0 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -265,11 +265,13 @@ func getServices(configDict map[string]interface{}) 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{} config := &mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc( - createTransformHook(), + createTransformHook(additionalTransformers...), mapstructure.StringToTimeDurationHookFunc()), Result: target, Metadata: &data, @@ -281,7 +283,13 @@ func transform(source map[string]interface{}, target interface{}) error { 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){ reflect.TypeOf(types.External{}): transformExternal, reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest, @@ -303,6 +311,10 @@ func createTransformHook() mapstructure.DecodeHookFuncType { 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) { transform, ok := transforms[target] 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 func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) { serviceConfig := &types.ServiceConfig{} - if err := transform(serviceDict, serviceConfig); err != nil { + if err := Transform(serviceDict, serviceConfig); err != nil { return nil, err } 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 func LoadNetworks(source map[string]interface{}, version string) (map[string]types.NetworkConfig, error) { networks := make(map[string]types.NetworkConfig) - err := transform(source, &networks) + err := Transform(source, &networks) if err != nil { 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 func LoadVolumes(source map[string]interface{}, version string) (map[string]types.VolumeConfig, error) { volumes := make(map[string]types.VolumeConfig) - if err := transform(source, &volumes); err != nil { + if err := Transform(source, &volumes); err != nil { 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 func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) { secrets := make(map[string]types.SecretConfig) - if err := transform(source, &secrets); err != nil { + if err := Transform(source, &secrets); err != nil { return secrets, err } 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 func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) { configs := make(map[string]types.ConfigObjConfig) - if err := transform(source, &configs); err != nil { + if err := Transform(source, &configs); err != nil { return configs, err } for name, config := range configs {