project.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. package compose
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "github.com/rancher/os/config"
  7. rosDocker "github.com/rancher/os/pkg/docker"
  8. "github.com/rancher/os/pkg/log"
  9. "github.com/rancher/os/pkg/util"
  10. "github.com/rancher/os/pkg/util/network"
  11. yaml "github.com/cloudfoundry-incubator/candiedyaml"
  12. dockerClient "github.com/docker/engine-api/client"
  13. "github.com/docker/libcompose/cli/logger"
  14. composeConfig "github.com/docker/libcompose/config"
  15. "github.com/docker/libcompose/docker"
  16. composeClient "github.com/docker/libcompose/docker/client"
  17. "github.com/docker/libcompose/project"
  18. "github.com/docker/libcompose/project/events"
  19. "github.com/docker/libcompose/project/options"
  20. "golang.org/x/net/context"
  21. )
  22. func CreateService(cfg *config.CloudConfig, name string, serviceConfig *composeConfig.ServiceConfigV1) (project.Service, error) {
  23. if cfg == nil {
  24. cfg = config.LoadConfig()
  25. }
  26. p, err := CreateServiceSet("once", cfg, map[string]*composeConfig.ServiceConfigV1{
  27. name: serviceConfig,
  28. })
  29. if err != nil {
  30. return nil, err
  31. }
  32. return p.CreateService(name)
  33. }
  34. func CreateServiceSet(name string, cfg *config.CloudConfig, configs map[string]*composeConfig.ServiceConfigV1) (*project.Project, error) {
  35. p, err := newProject(name, cfg, nil, nil)
  36. if err != nil {
  37. return nil, err
  38. }
  39. addServices(p, map[interface{}]interface{}{}, configs)
  40. return p, nil
  41. }
  42. func RunServiceSet(name string, cfg *config.CloudConfig, configs map[string]*composeConfig.ServiceConfigV1) (*project.Project, error) {
  43. p, err := CreateServiceSet(name, cfg, configs)
  44. if err != nil {
  45. return nil, err
  46. }
  47. return p, p.Up(context.Background(), options.Up{
  48. Log: cfg.Rancher.Log,
  49. })
  50. }
  51. func GetProject(cfg *config.CloudConfig, networkingAvailable, loadConsole bool) (*project.Project, error) {
  52. return newCoreServiceProject(cfg, networkingAvailable, loadConsole)
  53. }
  54. func newProject(name string, cfg *config.CloudConfig, environmentLookup composeConfig.EnvironmentLookup, authLookup *rosDocker.ConfigAuthLookup) (*project.Project, error) {
  55. clientFactory, err := rosDocker.NewClientFactory(composeClient.Options{})
  56. if err != nil {
  57. return nil, err
  58. }
  59. if environmentLookup == nil {
  60. environmentLookup = rosDocker.NewConfigEnvironment(cfg)
  61. }
  62. if authLookup == nil {
  63. authLookup = rosDocker.NewConfigAuthLookup(cfg)
  64. }
  65. serviceFactory := &rosDocker.ServiceFactory{
  66. Deps: map[string][]string{},
  67. }
  68. context := &docker.Context{
  69. ClientFactory: clientFactory,
  70. AuthLookup: authLookup,
  71. Context: project.Context{
  72. ProjectName: name,
  73. EnvironmentLookup: environmentLookup,
  74. ServiceFactory: serviceFactory,
  75. LoggerFactory: logger.NewColorLoggerFactory(),
  76. },
  77. }
  78. serviceFactory.Context = context
  79. authLookup.SetContext(context)
  80. return docker.NewProject(context, &composeConfig.ParseOptions{
  81. Interpolate: true,
  82. Validate: false,
  83. Preprocess: preprocessServiceMap,
  84. })
  85. }
  86. func preprocessServiceMap(serviceMap composeConfig.RawServiceMap) (composeConfig.RawServiceMap, error) {
  87. newServiceMap := make(composeConfig.RawServiceMap)
  88. for k, v := range serviceMap {
  89. newServiceMap[k] = make(composeConfig.RawService)
  90. for k2, v2 := range v {
  91. if k2 == "environment" || k2 == "labels" {
  92. newServiceMap[k][k2] = preprocess(v2, true)
  93. } else {
  94. newServiceMap[k][k2] = preprocess(v2, false)
  95. }
  96. }
  97. }
  98. return newServiceMap, nil
  99. }
  100. func preprocess(item interface{}, replaceTypes bool) interface{} {
  101. switch typedDatas := item.(type) {
  102. case map[interface{}]interface{}:
  103. newMap := make(map[interface{}]interface{})
  104. for key, value := range typedDatas {
  105. newMap[key] = preprocess(value, replaceTypes)
  106. }
  107. return newMap
  108. case []interface{}:
  109. // newArray := make([]interface{}, 0) will cause golint to complain
  110. var newArray []interface{}
  111. newArray = make([]interface{}, 0)
  112. for _, value := range typedDatas {
  113. newArray = append(newArray, preprocess(value, replaceTypes))
  114. }
  115. return newArray
  116. default:
  117. if replaceTypes {
  118. return fmt.Sprint(item)
  119. }
  120. return item
  121. }
  122. }
  123. func addServices(p *project.Project, enabled map[interface{}]interface{}, configs map[string]*composeConfig.ServiceConfigV1) map[interface{}]interface{} {
  124. serviceConfigsV2, _ := composeConfig.ConvertServices(configs)
  125. // Note: we ignore errors while loading services
  126. unchanged := true
  127. for name, serviceConfig := range serviceConfigsV2 {
  128. hash := composeConfig.GetServiceHash(name, serviceConfig)
  129. if enabled[name] == hash {
  130. continue
  131. }
  132. if err := p.AddConfig(name, serviceConfig); err != nil {
  133. log.Infof("Failed loading service %s", name)
  134. continue
  135. }
  136. if unchanged {
  137. enabled = util.MapCopy(enabled)
  138. unchanged = false
  139. }
  140. enabled[name] = hash
  141. }
  142. return enabled
  143. }
  144. func adjustContainerNames(m map[interface{}]interface{}) map[interface{}]interface{} {
  145. for k, v := range m {
  146. if k, ok := k.(string); ok {
  147. if v, ok := v.(map[interface{}]interface{}); ok {
  148. if _, ok := v["container_name"]; !ok {
  149. v["container_name"] = k
  150. }
  151. }
  152. }
  153. }
  154. return m
  155. }
  156. func newCoreServiceProject(cfg *config.CloudConfig, useNetwork, loadConsole bool) (*project.Project, error) {
  157. environmentLookup := rosDocker.NewConfigEnvironment(cfg)
  158. authLookup := rosDocker.NewConfigAuthLookup(cfg)
  159. p, err := newProject("os", cfg, environmentLookup, authLookup)
  160. if err != nil {
  161. return nil, err
  162. }
  163. projectEvents := make(chan events.Event)
  164. p.AddListener(project.NewDefaultListener(p))
  165. p.AddListener(projectEvents)
  166. p.ReloadCallback = projectReload(p, &useNetwork, loadConsole, environmentLookup, authLookup)
  167. go func() {
  168. for event := range projectEvents {
  169. if event.EventType == events.ContainerStarted && event.ServiceName == "network" {
  170. useNetwork = true
  171. }
  172. }
  173. }()
  174. err = p.ReloadCallback()
  175. if err != nil {
  176. log.Errorf("Failed to reload os: %v", err)
  177. return nil, err
  178. }
  179. return p, nil
  180. }
  181. func StageServices(cfg *config.CloudConfig, services ...string) error {
  182. p, err := newProject("stage-services", cfg, nil, nil)
  183. if err != nil {
  184. return err
  185. }
  186. // read engine services
  187. composeConfigs := map[string]composeConfig.ServiceConfigV1{}
  188. if _, err := os.Stat(config.MultiDockerConfFile); err == nil {
  189. // read from engine compose
  190. multiEngineBytes, err := ioutil.ReadFile(config.MultiDockerConfFile)
  191. if err != nil {
  192. return fmt.Errorf("Failed to read %s : %v", config.MultiDockerConfFile, err)
  193. }
  194. err = yaml.Unmarshal(multiEngineBytes, &composeConfigs)
  195. if err != nil {
  196. return fmt.Errorf("Failed to unmarshal %s : %v", config.MultiDockerConfFile, err)
  197. }
  198. }
  199. for _, service := range services {
  200. var bytes []byte
  201. foundServiceConfig := map[string]composeConfig.ServiceConfigV1{}
  202. if _, ok := composeConfigs[service]; ok {
  203. foundServiceConfig[service] = composeConfigs[service]
  204. bytes, err = yaml.Marshal(foundServiceConfig)
  205. } else {
  206. bytes, err = network.LoadServiceResource(service, true, cfg)
  207. }
  208. if err != nil {
  209. return fmt.Errorf("Failed to load %s : %v", service, err)
  210. }
  211. m := map[interface{}]interface{}{}
  212. if err := yaml.Unmarshal(bytes, &m); err != nil {
  213. return fmt.Errorf("Failed to parse YAML configuration: %s : %v", service, err)
  214. }
  215. bytes, err = yaml.Marshal(m)
  216. if err != nil {
  217. return fmt.Errorf("Failed to marshal YAML configuration: %s : %v", service, err)
  218. }
  219. err = p.Load(bytes)
  220. if err != nil {
  221. return fmt.Errorf("Failed to load %s : %v", service, err)
  222. }
  223. }
  224. // Reduce service configurations to just image and labels
  225. needToPull := false
  226. var client, userClient, systemClient dockerClient.APIClient
  227. for _, serviceName := range p.ServiceConfigs.Keys() {
  228. serviceConfig, _ := p.ServiceConfigs.Get(serviceName)
  229. // test to see if we need to Pull
  230. if serviceConfig.Labels[config.ScopeLabel] != config.System {
  231. if userClient == nil {
  232. userClient, err = rosDocker.NewDefaultClient()
  233. if err != nil {
  234. log.Error(err)
  235. }
  236. }
  237. client = userClient
  238. } else {
  239. if systemClient == nil {
  240. systemClient, err = rosDocker.NewSystemClient()
  241. if err != nil {
  242. log.Error(err)
  243. }
  244. client = systemClient
  245. }
  246. }
  247. if client != nil {
  248. _, _, err := client.ImageInspectWithRaw(context.Background(), serviceConfig.Image, false)
  249. if err == nil {
  250. log.Infof("Service %s using local image %s", serviceName, serviceConfig.Image)
  251. continue
  252. }
  253. }
  254. needToPull = true
  255. p.ServiceConfigs.Add(serviceName, &composeConfig.ServiceConfig{
  256. Image: serviceConfig.Image,
  257. Labels: serviceConfig.Labels,
  258. })
  259. }
  260. if needToPull {
  261. return p.Pull(context.Background())
  262. }
  263. return nil
  264. }