container.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. package docker
  2. import (
  3. "crypto/sha1"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "reflect"
  11. "sort"
  12. "strings"
  13. log "github.com/Sirupsen/logrus"
  14. flag "github.com/docker/docker/pkg/mflag"
  15. "github.com/docker/docker/pkg/parsers"
  16. "github.com/docker/docker/runconfig"
  17. shlex "github.com/flynn/go-shlex"
  18. dockerClient "github.com/fsouza/go-dockerclient"
  19. "github.com/rancherio/os/config"
  20. "github.com/rancherio/os/util"
  21. "github.com/rancherio/rancher-compose/librcompose/docker"
  22. "github.com/rancherio/rancher-compose/librcompose/project"
  23. )
  24. type Container struct {
  25. Err error
  26. Name string
  27. remove bool
  28. detach bool
  29. Config *runconfig.Config
  30. HostConfig *runconfig.HostConfig
  31. dockerHost string
  32. Container *dockerClient.Container
  33. ContainerCfg *config.ContainerConfig
  34. }
  35. type ByCreated []dockerClient.APIContainers
  36. func (c ByCreated) Len() int { return len(c) }
  37. func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
  38. func (c ByCreated) Less(i, j int) bool { return c[j].Created < c[i].Created }
  39. func getHash(containerCfg *config.ContainerConfig) string {
  40. hash := sha1.New()
  41. io.WriteString(hash, fmt.Sprintln(containerCfg.Id))
  42. io.WriteString(hash, fmt.Sprintln(containerCfg.Cmd))
  43. io.WriteString(hash, fmt.Sprintln(containerCfg.MigrateVolumes))
  44. io.WriteString(hash, fmt.Sprintln(containerCfg.ReloadConfig))
  45. io.WriteString(hash, fmt.Sprintln(containerCfg.CreateOnly))
  46. if containerCfg.Service != nil {
  47. //Get values of Service through reflection
  48. val := reflect.ValueOf(containerCfg.Service).Elem()
  49. //Create slice to sort the keys in Service Config, which allow constant hash ordering
  50. serviceKeys := []string{}
  51. //Create a data structure of map of values keyed by a string
  52. unsortedKeyValue := make(map[string]interface{})
  53. //Get all keys and values in Service Configuration
  54. for i := 0; i < val.NumField(); i++ {
  55. valueField := val.Field(i)
  56. keyField := val.Type().Field(i)
  57. serviceKeys = append(serviceKeys, keyField.Name)
  58. unsortedKeyValue[keyField.Name] = valueField.Interface()
  59. }
  60. //Sort serviceKeys alphabetically
  61. sort.Strings(serviceKeys)
  62. //Go through keys and write hash
  63. for _, serviceKey := range serviceKeys {
  64. serviceValue := unsortedKeyValue[serviceKey]
  65. io.WriteString(hash, fmt.Sprintf("\n %v: ", serviceKey))
  66. switch s := serviceValue.(type) {
  67. case project.SliceorMap:
  68. sliceKeys := []string{}
  69. for lkey := range s.MapParts() {
  70. if lkey != "io.rancher.os.hash" {
  71. sliceKeys = append(sliceKeys, lkey)
  72. }
  73. }
  74. sort.Strings(sliceKeys)
  75. for _, sliceKey := range sliceKeys {
  76. io.WriteString(hash, fmt.Sprintf("%s=%v, ", sliceKey, s.MapParts()[sliceKey]))
  77. }
  78. case project.MaporEqualSlice:
  79. sliceKeys := s.Slice()
  80. // do not sort keys as the order matters
  81. for _, sliceKey := range sliceKeys {
  82. io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
  83. }
  84. case project.MaporColonSlice:
  85. sliceKeys := s.Slice()
  86. // do not sort keys as the order matters
  87. for _, sliceKey := range sliceKeys {
  88. io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
  89. }
  90. case project.MaporSpaceSlice:
  91. sliceKeys := s.Slice()
  92. // do not sort keys as the order matters
  93. for _, sliceKey := range sliceKeys {
  94. io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
  95. }
  96. case project.Command:
  97. sliceKeys := s.Slice()
  98. // do not sort keys as the order matters
  99. for _, sliceKey := range sliceKeys {
  100. io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
  101. }
  102. case project.Stringorslice:
  103. sliceKeys := s.Slice()
  104. sort.Strings(sliceKeys)
  105. for _, sliceKey := range sliceKeys {
  106. io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
  107. }
  108. case []string:
  109. sliceKeys := s
  110. sort.Strings(sliceKeys)
  111. for _, sliceKey := range sliceKeys {
  112. io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
  113. }
  114. default:
  115. io.WriteString(hash, fmt.Sprintf("%v", serviceValue))
  116. }
  117. }
  118. }
  119. return hex.EncodeToString(hash.Sum(nil))
  120. }
  121. func StartAndWait(dockerHost string, containerCfg *config.ContainerConfig) error {
  122. container := NewContainer(dockerHost, containerCfg).start(false, true)
  123. return container.Err
  124. }
  125. func NewContainerFromService(dockerHost string, name string, service *project.ServiceConfig) *Container {
  126. c := &Container{
  127. Name: name,
  128. dockerHost: dockerHost,
  129. ContainerCfg: &config.ContainerConfig{
  130. Id: name,
  131. Service: service,
  132. },
  133. }
  134. return c.Parse()
  135. }
  136. func NewContainer(dockerHost string, containerCfg *config.ContainerConfig) *Container {
  137. c := &Container{
  138. dockerHost: dockerHost,
  139. ContainerCfg: containerCfg,
  140. }
  141. return c.Parse()
  142. }
  143. func (c *Container) returnErr(err error) *Container {
  144. c.Err = err
  145. return c
  146. }
  147. func getByLabel(client *dockerClient.Client, key, value string) (*dockerClient.APIContainers, error) {
  148. containers, err := client.ListContainers(dockerClient.ListContainersOptions{
  149. All: true,
  150. Filters: map[string][]string{
  151. config.LABEL: {fmt.Sprintf("%s=%s", key, value)},
  152. },
  153. })
  154. if err != nil {
  155. return nil, err
  156. }
  157. if len(containers) == 0 {
  158. return nil, nil
  159. }
  160. sort.Sort(ByCreated(containers))
  161. return &containers[0], nil
  162. }
  163. func (c *Container) Lookup() *Container {
  164. c.Parse()
  165. if c.Err != nil || (c.Container != nil && c.Container.HostConfig != nil) {
  166. return c
  167. }
  168. hash := getHash(c.ContainerCfg)
  169. client, err := NewClient(c.dockerHost)
  170. if err != nil {
  171. return c.returnErr(err)
  172. }
  173. containers, err := client.ListContainers(dockerClient.ListContainersOptions{
  174. All: true,
  175. Filters: map[string][]string{
  176. config.LABEL: {fmt.Sprintf("%s=%s", config.HASH, hash)},
  177. },
  178. })
  179. if err != nil {
  180. return c.returnErr(err)
  181. }
  182. if len(containers) == 0 {
  183. return c
  184. }
  185. c.Container, c.Err = inspect(client, containers[0].ID)
  186. return c
  187. }
  188. func inspect(client *dockerClient.Client, id string) (*dockerClient.Container, error) {
  189. c, err := client.InspectContainer(id)
  190. if err != nil {
  191. return nil, err
  192. }
  193. if strings.HasPrefix(c.Name, "/") {
  194. c.Name = c.Name[1:]
  195. }
  196. return c, err
  197. }
  198. func (c *Container) Exists() bool {
  199. c.Lookup()
  200. return c.Container != nil
  201. }
  202. func (c *Container) Reset() *Container {
  203. c.Config = nil
  204. c.HostConfig = nil
  205. c.Container = nil
  206. c.Err = nil
  207. return c
  208. }
  209. func (c *Container) requiresSyslog() bool {
  210. return (c.ContainerCfg.Service.LogDriver == "" || c.ContainerCfg.Service.LogDriver == "syslog")
  211. }
  212. func (c *Container) requiresUserDocker() bool {
  213. if c.dockerHost == config.DOCKER_HOST {
  214. return true
  215. }
  216. return false
  217. }
  218. func (c *Container) hasLink(link string) bool {
  219. return util.Contains(c.ContainerCfg.Service.Links.Slice(), link)
  220. }
  221. func (c *Container) addLink(link string) {
  222. if c.hasLink(link) {
  223. return
  224. }
  225. log.Debugf("Adding %s link to %s", link, c.Name)
  226. c.ContainerCfg.Service.Links = project.NewMaporColonSlice(append(c.ContainerCfg.Service.Links.Slice(), link))
  227. }
  228. func (c *Container) parseService() {
  229. if c.requiresSyslog() {
  230. c.addLink("syslog")
  231. log.Infof("[%v]: Implicitly linked to 'syslog'", c.Name)
  232. }
  233. if c.requiresUserDocker() {
  234. c.addLink("dockerwait")
  235. log.Infof("[%v]: Implicitly linked to 'dockerwait'", c.Name)
  236. } else if c.ContainerCfg.Service.Image != "" {
  237. client, err := NewClient(c.dockerHost)
  238. if err != nil {
  239. c.Err = err
  240. return
  241. }
  242. i, _ := client.InspectImage(c.ContainerCfg.Service.Image)
  243. if i == nil {
  244. c.addLink("network")
  245. log.Infof("[%v]: Implicitly linked to 'network'", c.Name)
  246. }
  247. }
  248. cfg, hostConfig, err := docker.Convert(c.ContainerCfg.Service)
  249. if err != nil {
  250. c.Err = err
  251. return
  252. }
  253. c.Config = cfg
  254. c.HostConfig = hostConfig
  255. c.detach = c.Config.Labels[config.DETACH] != "false"
  256. c.remove = c.Config.Labels[config.REMOVE] != "false"
  257. c.ContainerCfg.CreateOnly = c.Config.Labels[config.CREATE_ONLY] == "true"
  258. c.ContainerCfg.ReloadConfig = c.Config.Labels[config.RELOAD_CONFIG] == "true"
  259. }
  260. func (c *Container) parseCmd() {
  261. flags := flag.NewFlagSet("run", flag.ExitOnError)
  262. flRemove := flags.Bool([]string{"#rm", "-rm"}, false, "")
  263. flDetach := flags.Bool([]string{"d", "-detach"}, false, "")
  264. flName := flags.String([]string{"#name", "-name"}, "", "")
  265. args, err := shlex.Split(c.ContainerCfg.Cmd)
  266. if err != nil {
  267. c.Err = err
  268. return
  269. }
  270. log.Debugf("Parsing [%s]", strings.Join(args, ","))
  271. c.Config, c.HostConfig, _, c.Err = runconfig.Parse(flags, args)
  272. c.Name = *flName
  273. c.detach = *flDetach
  274. c.remove = *flRemove
  275. }
  276. func (c *Container) Parse() *Container {
  277. if c.Config != nil || c.Err != nil {
  278. return c
  279. }
  280. if len(c.ContainerCfg.Cmd) > 0 {
  281. c.parseCmd()
  282. } else if c.ContainerCfg.Service != nil {
  283. c.parseService()
  284. } else {
  285. c.Err = errors.New("Cmd or Service must be set")
  286. return c
  287. }
  288. if c.ContainerCfg.Id == "" {
  289. c.ContainerCfg.Id = c.Name
  290. }
  291. return c
  292. }
  293. func (c *Container) Create() *Container {
  294. return c.start(true, false)
  295. }
  296. func (c *Container) Start() *Container {
  297. return c.start(false, false)
  298. }
  299. func (c *Container) StartAndWait() *Container {
  300. return c.start(false, true)
  301. }
  302. func (c *Container) Stage() *Container {
  303. c.Parse()
  304. if c.Err != nil {
  305. return c
  306. }
  307. client, err := NewClient(c.dockerHost)
  308. if err != nil {
  309. c.Err = err
  310. return c
  311. }
  312. _, err = client.InspectImage(c.Config.Image)
  313. if err == dockerClient.ErrNoSuchImage {
  314. toPull := c.Config.Image
  315. _, tag := parsers.ParseRepositoryTag(toPull)
  316. if tag == "" {
  317. toPull += ":latest"
  318. }
  319. c.Err = client.PullImage(dockerClient.PullImageOptions{
  320. Repository: toPull,
  321. OutputStream: os.Stdout,
  322. }, dockerClient.AuthConfiguration{})
  323. } else if err != nil {
  324. log.Errorf("Failed to stage: %s: %v", c.Config.Image, err)
  325. c.Err = err
  326. }
  327. return c
  328. }
  329. func (c *Container) Delete() *Container {
  330. c.Parse()
  331. c.Stage()
  332. c.Lookup()
  333. if c.Err != nil {
  334. return c
  335. }
  336. if !c.Exists() {
  337. return c
  338. }
  339. client, err := NewClient(c.dockerHost)
  340. if err != nil {
  341. return c.returnErr(err)
  342. }
  343. err = client.RemoveContainer(dockerClient.RemoveContainerOptions{
  344. ID: c.Container.ID,
  345. Force: true,
  346. })
  347. if err != nil {
  348. return c.returnErr(err)
  349. }
  350. return c
  351. }
  352. func (c *Container) renameCurrent(client *dockerClient.Client) error {
  353. if c.Name == "" {
  354. return nil
  355. }
  356. if c.Name == c.Container.Name {
  357. return nil
  358. }
  359. err := client.RenameContainer(dockerClient.RenameContainerOptions{ID: c.Container.ID, Name: c.Name})
  360. if err != nil {
  361. return err
  362. }
  363. c.Container, err = inspect(client, c.Container.ID)
  364. return err
  365. }
  366. func (c *Container) renameOld(client *dockerClient.Client, opts *dockerClient.CreateContainerOptions) error {
  367. if len(opts.Name) == 0 {
  368. return nil
  369. }
  370. existing, err := inspect(client, opts.Name)
  371. if _, ok := err.(*dockerClient.NoSuchContainer); ok {
  372. return nil
  373. }
  374. if err != nil {
  375. return nil
  376. }
  377. if c.Container != nil && existing.ID == c.Container.ID {
  378. return nil
  379. }
  380. var newName string
  381. if label, ok := existing.Config.Labels[config.HASH]; ok {
  382. newName = fmt.Sprintf("%s-%s", existing.Name, label)
  383. } else {
  384. newName = fmt.Sprintf("%s-unknown-%s", existing.Name, util.RandSeq(12))
  385. }
  386. if existing.State.Running {
  387. err := client.StopContainer(existing.ID, 2)
  388. if err != nil {
  389. return err
  390. }
  391. _, err = client.WaitContainer(existing.ID)
  392. if err != nil {
  393. return err
  394. }
  395. }
  396. log.Debugf("Renaming %s to %s", existing.Name, newName)
  397. return client.RenameContainer(dockerClient.RenameContainerOptions{ID: existing.ID, Name: newName})
  398. }
  399. func (c *Container) getCreateOpts(client *dockerClient.Client) (*dockerClient.CreateContainerOptions, error) {
  400. bytes, err := json.Marshal(c)
  401. if err != nil {
  402. log.Errorf("Failed to marshall: %v", c)
  403. return nil, err
  404. }
  405. var opts dockerClient.CreateContainerOptions
  406. err = json.Unmarshal(bytes, &opts)
  407. if err != nil {
  408. log.Errorf("Failed to unmarshall: %s", string(bytes))
  409. return nil, err
  410. }
  411. if opts.Config.Labels == nil {
  412. opts.Config.Labels = make(map[string]string)
  413. }
  414. hash := getHash(c.ContainerCfg)
  415. opts.Config.Labels[config.HASH] = hash
  416. opts.Config.Labels[config.ID] = c.ContainerCfg.Id
  417. return &opts, nil
  418. }
  419. func appendVolumesFrom(client *dockerClient.Client, containerCfg *config.ContainerConfig, opts *dockerClient.CreateContainerOptions) error {
  420. if !containerCfg.MigrateVolumes {
  421. return nil
  422. }
  423. container, err := getByLabel(client, config.ID, containerCfg.Id)
  424. if err != nil || container == nil {
  425. return err
  426. }
  427. if opts.HostConfig.VolumesFrom == nil {
  428. opts.HostConfig.VolumesFrom = []string{container.ID}
  429. } else {
  430. opts.HostConfig.VolumesFrom = append(opts.HostConfig.VolumesFrom, container.ID)
  431. }
  432. return nil
  433. }
  434. func (c *Container) start(createOnly, wait bool) *Container {
  435. c.Lookup()
  436. c.Stage()
  437. if c.Err != nil {
  438. return c
  439. }
  440. client, err := NewClient(c.dockerHost)
  441. if err != nil {
  442. return c.returnErr(err)
  443. }
  444. created := false
  445. opts, err := c.getCreateOpts(client)
  446. if err != nil {
  447. log.Errorf("Failed to create container create options: %v", err)
  448. return c.returnErr(err)
  449. }
  450. if c.Exists() && c.remove {
  451. log.Debugf("Deleting container %s", c.Container.ID)
  452. c.Delete()
  453. if c.Err != nil {
  454. return c
  455. }
  456. c.Reset().Lookup()
  457. if c.Err != nil {
  458. return c
  459. }
  460. }
  461. if !c.Exists() {
  462. err = c.renameOld(client, opts)
  463. if err != nil {
  464. return c.returnErr(err)
  465. }
  466. err := appendVolumesFrom(client, c.ContainerCfg, opts)
  467. if err != nil {
  468. return c.returnErr(err)
  469. }
  470. c.Container, err = client.CreateContainer(*opts)
  471. created = true
  472. if err != nil {
  473. return c.returnErr(err)
  474. }
  475. }
  476. hostConfig := c.Container.HostConfig
  477. if created {
  478. hostConfig = opts.HostConfig
  479. }
  480. if createOnly {
  481. return c
  482. }
  483. if !c.Container.State.Running {
  484. if !created {
  485. err = c.renameOld(client, opts)
  486. if err != nil {
  487. return c.returnErr(err)
  488. }
  489. }
  490. err = c.renameCurrent(client)
  491. if err != nil {
  492. return c.returnErr(err)
  493. }
  494. err = client.StartContainer(c.Container.ID, hostConfig)
  495. if err != nil {
  496. log.Errorf("Error from Docker %s", err)
  497. return c.returnErr(err)
  498. }
  499. }
  500. if !c.detach && wait {
  501. var exitCode int
  502. exitCode, c.Err = client.WaitContainer(c.Container.ID)
  503. if exitCode != 0 {
  504. c.Err = errors.New(fmt.Sprintf("Container %s exited with code %d", c.Name, exitCode))
  505. }
  506. return c
  507. }
  508. return c
  509. }