218 lines
5.2 KiB
Go
218 lines
5.2 KiB
Go
|
package config
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"path"
|
||
|
|
||
|
"github.com/Sirupsen/logrus"
|
||
|
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||
|
"github.com/docker/libcompose/utils"
|
||
|
)
|
||
|
|
||
|
// MergeServicesV2 merges a v2 compose file into an existing set of service configs
|
||
|
func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte, options *ParseOptions) (map[string]*ServiceConfig, error) {
|
||
|
var config Config
|
||
|
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
datas := config.Services
|
||
|
|
||
|
if options.Interpolate {
|
||
|
if err := Interpolate(environmentLookup, &datas); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if options.Preprocess != nil {
|
||
|
var err error
|
||
|
datas, err = options.Preprocess(datas)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for name, data := range datas {
|
||
|
data, err := parseV2(resourceLookup, environmentLookup, file, data, datas, options)
|
||
|
if err != nil {
|
||
|
logrus.Errorf("Failed to parse service %s: %v", name, err)
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if serviceConfig, ok := existingServices.Get(name); ok {
|
||
|
var rawExistingService RawService
|
||
|
if err := utils.Convert(serviceConfig, &rawExistingService); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
data = mergeConfig(rawExistingService, data)
|
||
|
}
|
||
|
|
||
|
datas[name] = data
|
||
|
}
|
||
|
|
||
|
serviceConfigs := make(map[string]*ServiceConfig)
|
||
|
if err := utils.Convert(datas, &serviceConfigs); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return serviceConfigs, nil
|
||
|
}
|
||
|
|
||
|
// ParseVolumes parses volumes in a compose file
|
||
|
func ParseVolumes(bytes []byte) (map[string]*VolumeConfig, error) {
|
||
|
volumeConfigs := make(map[string]*VolumeConfig)
|
||
|
|
||
|
var config Config
|
||
|
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if err := utils.Convert(config.Volumes, &volumeConfigs); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return volumeConfigs, nil
|
||
|
}
|
||
|
|
||
|
// ParseNetworks parses networks in a compose file
|
||
|
func ParseNetworks(bytes []byte) (map[string]*NetworkConfig, error) {
|
||
|
networkConfigs := make(map[string]*NetworkConfig)
|
||
|
|
||
|
var config Config
|
||
|
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if err := utils.Convert(config.Networks, &networkConfigs); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return networkConfigs, nil
|
||
|
}
|
||
|
|
||
|
func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap, options *ParseOptions) (RawService, error) {
|
||
|
serviceData, err := readEnvFile(resourceLookup, inFile, serviceData)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
serviceData = resolveContextV2(inFile, serviceData)
|
||
|
|
||
|
value, ok := serviceData["extends"]
|
||
|
if !ok {
|
||
|
return serviceData, nil
|
||
|
}
|
||
|
|
||
|
mapValue, ok := value.(map[interface{}]interface{})
|
||
|
if !ok {
|
||
|
return serviceData, nil
|
||
|
}
|
||
|
|
||
|
if resourceLookup == nil {
|
||
|
return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile)
|
||
|
}
|
||
|
|
||
|
file := asString(mapValue["file"])
|
||
|
service := asString(mapValue["service"])
|
||
|
|
||
|
if service == "" {
|
||
|
return serviceData, nil
|
||
|
}
|
||
|
|
||
|
var baseService RawService
|
||
|
|
||
|
if file == "" {
|
||
|
if serviceData, ok := datas[service]; ok {
|
||
|
baseService, err = parseV2(resourceLookup, environmentLookup, inFile, serviceData, datas, options)
|
||
|
} else {
|
||
|
return nil, fmt.Errorf("Failed to find service %s to extend", service)
|
||
|
}
|
||
|
} else {
|
||
|
bytes, resolved, err := resourceLookup.Lookup(file, inFile)
|
||
|
if err != nil {
|
||
|
logrus.Errorf("Failed to lookup file %s: %v", file, err)
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var config Config
|
||
|
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
baseRawServices := config.Services
|
||
|
|
||
|
if options.Interpolate {
|
||
|
err = Interpolate(environmentLookup, &baseRawServices)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
baseService, ok = baseRawServices[service]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("Failed to find service %s in file %s", service, file)
|
||
|
}
|
||
|
|
||
|
baseService, err = parseV2(resourceLookup, environmentLookup, resolved, baseService, baseRawServices, options)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
baseService = clone(baseService)
|
||
|
|
||
|
logrus.Debugf("Merging %#v, %#v", baseService, serviceData)
|
||
|
|
||
|
for _, k := range noMerge {
|
||
|
if _, ok := baseService[k]; ok {
|
||
|
source := file
|
||
|
if source == "" {
|
||
|
source = inFile
|
||
|
}
|
||
|
return nil, fmt.Errorf("Cannot extend service '%s' in %s: services with '%s' cannot be extended", service, source, k)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
baseService = mergeConfig(baseService, serviceData)
|
||
|
|
||
|
logrus.Debugf("Merged result %#v", baseService)
|
||
|
|
||
|
return baseService, nil
|
||
|
}
|
||
|
|
||
|
func resolveContextV2(inFile string, serviceData RawService) RawService {
|
||
|
if _, ok := serviceData["build"]; !ok {
|
||
|
return serviceData
|
||
|
}
|
||
|
var build map[interface{}]interface{}
|
||
|
if buildAsString, ok := serviceData["build"].(string); ok {
|
||
|
build = map[interface{}]interface{}{
|
||
|
"context": buildAsString,
|
||
|
}
|
||
|
} else {
|
||
|
build = serviceData["build"].(map[interface{}]interface{})
|
||
|
}
|
||
|
context := asString(build["context"])
|
||
|
if context == "" {
|
||
|
return serviceData
|
||
|
}
|
||
|
|
||
|
if IsValidRemote(context) {
|
||
|
return serviceData
|
||
|
}
|
||
|
|
||
|
current := path.Dir(inFile)
|
||
|
|
||
|
if context == "." {
|
||
|
context = current
|
||
|
} else {
|
||
|
current = path.Join(current, context)
|
||
|
}
|
||
|
|
||
|
build["context"] = current
|
||
|
|
||
|
return serviceData
|
||
|
}
|