github.com/r3labs/libcompose@v0.4.1-0.20171123133234-495fe0619cc3/config/merge_v2.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/r3labs/libcompose/utils"
    10  	"github.com/sirupsen/logrus"
    11  )
    12  
    13  // MergeServicesV2 merges a v2 compose file into an existing set of service configs
    14  func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, datas RawServiceMap, options *ParseOptions) (map[string]*ServiceConfig, error) {
    15  	if options.Validate {
    16  		if err := validateV2(datas); err != nil {
    17  			return nil, err
    18  		}
    19  	}
    20  
    21  	for name, data := range datas {
    22  		data, err := parseV2(resourceLookup, environmentLookup, file, data, datas, options)
    23  		if err != nil {
    24  			logrus.Errorf("Failed to parse service %s: %v", name, err)
    25  			return nil, err
    26  		}
    27  
    28  		if serviceConfig, ok := existingServices.Get(name); ok {
    29  			var rawExistingService RawService
    30  			if err := utils.Convert(serviceConfig, &rawExistingService); err != nil {
    31  				return nil, err
    32  			}
    33  
    34  			data = mergeConfig(rawExistingService, data)
    35  		}
    36  
    37  		datas[name] = data
    38  	}
    39  
    40  	if options.Validate {
    41  		var errs []string
    42  		for name, data := range datas {
    43  			err := validateServiceConstraintsv2(data, name)
    44  			if err != nil {
    45  				errs = append(errs, err.Error())
    46  			}
    47  		}
    48  		if len(errs) != 0 {
    49  			return nil, fmt.Errorf(strings.Join(errs, "\n"))
    50  		}
    51  	}
    52  
    53  	serviceConfigs := make(map[string]*ServiceConfig)
    54  	if err := utils.Convert(datas, &serviceConfigs); err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return serviceConfigs, nil
    59  }
    60  
    61  func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap, options *ParseOptions) (RawService, error) {
    62  	serviceData, err := readEnvFile(resourceLookup, inFile, serviceData)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	serviceData = resolveContextV2(inFile, serviceData)
    68  
    69  	value, ok := serviceData["extends"]
    70  	if !ok {
    71  		return serviceData, nil
    72  	}
    73  
    74  	mapValue, ok := value.(map[interface{}]interface{})
    75  	if !ok {
    76  		return serviceData, nil
    77  	}
    78  
    79  	if resourceLookup == nil {
    80  		return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile)
    81  	}
    82  
    83  	file := asString(mapValue["file"])
    84  	service := asString(mapValue["service"])
    85  
    86  	if service == "" {
    87  		return serviceData, nil
    88  	}
    89  
    90  	var baseService RawService
    91  
    92  	if file == "" {
    93  		if serviceData, ok := datas[service]; ok {
    94  			baseService, err = parseV2(resourceLookup, environmentLookup, inFile, serviceData, datas, options)
    95  		} else {
    96  			return nil, fmt.Errorf("Failed to find service %s to extend", service)
    97  		}
    98  	} else {
    99  		bytes, resolved, err := resourceLookup.Lookup(file, inFile)
   100  		if err != nil {
   101  			logrus.Errorf("Failed to lookup file %s: %v", file, err)
   102  			return nil, err
   103  		}
   104  
   105  		config, err := CreateConfig(bytes)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  		baseRawServices := config.Services
   110  
   111  		if options.Interpolate {
   112  			if err = InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
   113  				return nil, err
   114  			}
   115  		}
   116  
   117  		if options.Validate {
   118  			if err := validateV2(baseRawServices); err != nil {
   119  				return nil, err
   120  			}
   121  		}
   122  
   123  		baseService, ok = baseRawServices[service]
   124  		if !ok {
   125  			return nil, fmt.Errorf("Failed to find service %s in file %s", service, file)
   126  		}
   127  
   128  		baseService, err = parseV2(resourceLookup, environmentLookup, resolved, baseService, baseRawServices, options)
   129  	}
   130  
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	baseService = clone(baseService)
   136  
   137  	logrus.Debugf("Merging %#v, %#v", baseService, serviceData)
   138  
   139  	for _, k := range noMerge {
   140  		if _, ok := baseService[k]; ok {
   141  			source := file
   142  			if source == "" {
   143  				source = inFile
   144  			}
   145  			return nil, fmt.Errorf("Cannot extend service '%s' in %s: services with '%s' cannot be extended", service, source, k)
   146  		}
   147  	}
   148  
   149  	baseService = mergeConfig(baseService, serviceData)
   150  
   151  	logrus.Debugf("Merged result %#v", baseService)
   152  
   153  	return baseService, nil
   154  }
   155  
   156  func resolveContextV2(inFile string, serviceData RawService) RawService {
   157  	if _, ok := serviceData["build"]; !ok {
   158  		return serviceData
   159  	}
   160  	var build map[interface{}]interface{}
   161  	if buildAsString, ok := serviceData["build"].(string); ok {
   162  		build = map[interface{}]interface{}{
   163  			"context": buildAsString,
   164  		}
   165  	} else {
   166  		build = serviceData["build"].(map[interface{}]interface{})
   167  	}
   168  	context := asString(build["context"])
   169  	if context == "" {
   170  		return serviceData
   171  	}
   172  
   173  	if IsValidRemote(context) || filepath.IsAbs(context) {
   174  		return serviceData
   175  	}
   176  
   177  	current := path.Dir(inFile)
   178  
   179  	if context == "." {
   180  		context = current
   181  	} else {
   182  		current = path.Join(current, context)
   183  	}
   184  	if _, ok := serviceData["build"].(string); ok {
   185  		//build is specified as a string containing a path to the build context
   186  		serviceData["build"] = current
   187  	} else {
   188  		//build is specified as an object with the path specified under context
   189  		build["context"] = current
   190  	}
   191  
   192  	return serviceData
   193  }