github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/config/merge_v2.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "path" 6 7 "github.com/Sirupsen/logrus" 8 "github.com/docker/libcompose/utils" 9 "gopkg.in/yaml.v2" 10 ) 11 12 // MergeServicesV2 merges a v2 compose file into an existing set of service configs 13 func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte, options *ParseOptions) (map[string]*ServiceConfig, error) { 14 var config Config 15 if err := yaml.Unmarshal(bytes, &config); err != nil { 16 return nil, err 17 } 18 19 datas := config.Services 20 21 if options.Interpolate { 22 if err := Interpolate(environmentLookup, &datas); err != nil { 23 return nil, err 24 } 25 } 26 27 if options.Preprocess != nil { 28 var err error 29 datas, err = options.Preprocess(datas) 30 if err != nil { 31 return nil, err 32 } 33 } 34 35 for name, data := range datas { 36 data, err := parseV2(resourceLookup, environmentLookup, file, data, datas, options) 37 if err != nil { 38 logrus.Errorf("Failed to parse service %s: %v", name, err) 39 return nil, err 40 } 41 42 if serviceConfig, ok := existingServices.Get(name); ok { 43 var rawExistingService RawService 44 if err := utils.Convert(serviceConfig, &rawExistingService); err != nil { 45 return nil, err 46 } 47 48 data = mergeConfig(rawExistingService, data) 49 } 50 51 datas[name] = data 52 } 53 54 serviceConfigs := make(map[string]*ServiceConfig) 55 if err := utils.Convert(datas, &serviceConfigs); err != nil { 56 return nil, err 57 } 58 59 return serviceConfigs, nil 60 } 61 62 // ParseVolumes parses volumes in a compose file 63 func ParseVolumes(bytes []byte) (map[string]*VolumeConfig, error) { 64 volumeConfigs := make(map[string]*VolumeConfig) 65 66 var config Config 67 if err := yaml.Unmarshal(bytes, &config); err != nil { 68 return nil, err 69 } 70 71 if err := utils.Convert(config.Volumes, &volumeConfigs); err != nil { 72 return nil, err 73 } 74 75 return volumeConfigs, nil 76 } 77 78 // ParseNetworks parses networks in a compose file 79 func ParseNetworks(bytes []byte) (map[string]*NetworkConfig, error) { 80 networkConfigs := make(map[string]*NetworkConfig) 81 82 var config Config 83 if err := yaml.Unmarshal(bytes, &config); err != nil { 84 return nil, err 85 } 86 87 if err := utils.Convert(config.Networks, &networkConfigs); err != nil { 88 return nil, err 89 } 90 91 for key, value := range networkConfigs { 92 if value == nil { 93 networkConfigs[key] = &NetworkConfig{} 94 } 95 } 96 97 return networkConfigs, nil 98 } 99 100 func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap, options *ParseOptions) (RawService, error) { 101 serviceData, err := readEnvFile(resourceLookup, inFile, serviceData) 102 if err != nil { 103 return nil, err 104 } 105 106 serviceData = resolveContextV2(inFile, serviceData) 107 108 value, ok := serviceData["extends"] 109 if !ok { 110 return serviceData, nil 111 } 112 113 mapValue, ok := value.(map[interface{}]interface{}) 114 if !ok { 115 return serviceData, nil 116 } 117 118 if resourceLookup == nil { 119 return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile) 120 } 121 122 file := asString(mapValue["file"]) 123 service := asString(mapValue["service"]) 124 125 if service == "" { 126 return serviceData, nil 127 } 128 129 var baseService RawService 130 131 if file == "" { 132 if serviceData, ok := datas[service]; ok { 133 baseService, err = parseV2(resourceLookup, environmentLookup, inFile, serviceData, datas, options) 134 } else { 135 return nil, fmt.Errorf("Failed to find service %s to extend", service) 136 } 137 } else { 138 bytes, resolved, err := resourceLookup.Lookup(file, inFile) 139 if err != nil { 140 logrus.Errorf("Failed to lookup file %s: %v", file, err) 141 return nil, err 142 } 143 144 var config Config 145 if err := yaml.Unmarshal(bytes, &config); err != nil { 146 return nil, err 147 } 148 149 baseRawServices := config.Services 150 151 if options.Interpolate { 152 err = Interpolate(environmentLookup, &baseRawServices) 153 if err != nil { 154 return nil, err 155 } 156 } 157 158 baseService, ok = baseRawServices[service] 159 if !ok { 160 return nil, fmt.Errorf("Failed to find service %s in file %s", service, file) 161 } 162 163 baseService, err = parseV2(resourceLookup, environmentLookup, resolved, baseService, baseRawServices, options) 164 } 165 166 if err != nil { 167 return nil, err 168 } 169 170 baseService = clone(baseService) 171 172 logrus.Debugf("Merging %#v, %#v", baseService, serviceData) 173 174 for _, k := range noMerge { 175 if _, ok := baseService[k]; ok { 176 source := file 177 if source == "" { 178 source = inFile 179 } 180 return nil, fmt.Errorf("Cannot extend service '%s' in %s: services with '%s' cannot be extended", service, source, k) 181 } 182 } 183 184 baseService = mergeConfig(baseService, serviceData) 185 186 logrus.Debugf("Merged result %#v", baseService) 187 188 return baseService, nil 189 } 190 191 func resolveContextV2(inFile string, serviceData RawService) RawService { 192 if _, ok := serviceData["build"]; !ok { 193 return serviceData 194 } 195 var build map[interface{}]interface{} 196 if buildAsString, ok := serviceData["build"].(string); ok { 197 build = map[interface{}]interface{}{ 198 "context": buildAsString, 199 } 200 } else { 201 build = serviceData["build"].(map[interface{}]interface{}) 202 } 203 context := asString(build["context"]) 204 if context == "" { 205 return serviceData 206 } 207 208 if IsValidRemote(context) { 209 return serviceData 210 } 211 212 current := path.Dir(inFile) 213 214 if context == "." { 215 context = current 216 } else { 217 current = path.Join(current, context) 218 } 219 220 build["context"] = current 221 222 return serviceData 223 }