merge_v2.go 5.0 KB

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