os.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. package control
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "strings"
  9. "golang.org/x/net/context"
  10. yaml "github.com/cloudfoundry-incubator/candiedyaml"
  11. "github.com/rancher/os/log"
  12. "github.com/codegangsta/cli"
  13. dockerClient "github.com/docker/engine-api/client"
  14. composeConfig "github.com/docker/libcompose/config"
  15. "github.com/docker/libcompose/project/options"
  16. "github.com/rancher/os/cmd/power"
  17. "github.com/rancher/os/compose"
  18. "github.com/rancher/os/config"
  19. "github.com/rancher/os/docker"
  20. )
  21. type Images struct {
  22. Current string `yaml:"current,omitempty"`
  23. Available []string `yaml:"available,omitempty"`
  24. }
  25. func osSubcommands() []cli.Command {
  26. return []cli.Command{
  27. {
  28. Name: "upgrade",
  29. Usage: "upgrade to latest version",
  30. Action: osUpgrade,
  31. Flags: []cli.Flag{
  32. cli.BoolFlag{
  33. Name: "stage, s",
  34. Usage: "Only stage the new upgrade, don't apply it",
  35. },
  36. cli.StringFlag{
  37. Name: "image, i",
  38. Usage: "upgrade to a certain image",
  39. },
  40. cli.BoolFlag{
  41. Name: "force, f",
  42. Usage: "do not prompt for input",
  43. },
  44. cli.BoolFlag{
  45. Name: "no-reboot",
  46. Usage: "do not reboot after upgrade",
  47. },
  48. cli.BoolFlag{
  49. Name: "kexec, k",
  50. Usage: "reboot using kexec",
  51. },
  52. cli.StringFlag{
  53. Name: "append",
  54. Usage: "append additional kernel parameters",
  55. },
  56. cli.BoolFlag{
  57. Name: "upgrade-console",
  58. Usage: "upgrade console even if persistent",
  59. },
  60. },
  61. },
  62. {
  63. Name: "list",
  64. Usage: "list the current available versions",
  65. Action: osMetaDataGet,
  66. },
  67. {
  68. Name: "version",
  69. Usage: "show the currently installed version",
  70. Action: osVersion,
  71. },
  72. }
  73. }
  74. // TODO: this and the getLatestImage should probably move to utils/network and be suitably cached.
  75. func getImages() (*Images, error) {
  76. upgradeURL, err := getUpgradeURL()
  77. if err != nil {
  78. return nil, err
  79. }
  80. var body []byte
  81. if strings.HasPrefix(upgradeURL, "/") {
  82. body, err = ioutil.ReadFile(upgradeURL)
  83. if err != nil {
  84. return nil, err
  85. }
  86. } else {
  87. u, err := url.Parse(upgradeURL)
  88. if err != nil {
  89. return nil, err
  90. }
  91. q := u.Query()
  92. q.Set("current", config.Version)
  93. u.RawQuery = q.Encode()
  94. upgradeURL = u.String()
  95. resp, err := http.Get(upgradeURL)
  96. if err != nil {
  97. return nil, err
  98. }
  99. defer resp.Body.Close()
  100. body, err = ioutil.ReadAll(resp.Body)
  101. if err != nil {
  102. return nil, err
  103. }
  104. }
  105. return parseBody(body)
  106. }
  107. func osMetaDataGet(c *cli.Context) error {
  108. images, err := getImages()
  109. if err != nil {
  110. log.Fatal(err)
  111. }
  112. client, err := docker.NewSystemClient()
  113. if err != nil {
  114. log.Fatal(err)
  115. }
  116. cfg := config.LoadConfig()
  117. runningName := cfg.Rancher.Upgrade.Image + ":" + config.Version
  118. foundRunning := false
  119. for i := len(images.Available) - 1; i >= 0; i-- {
  120. image := images.Available[i]
  121. _, _, err := client.ImageInspectWithRaw(context.Background(), image, false)
  122. local := "local"
  123. if dockerClient.IsErrImageNotFound(err) {
  124. local = "remote"
  125. }
  126. available := "available"
  127. if image == images.Current {
  128. available = "latest"
  129. }
  130. var running string
  131. if image == runningName {
  132. foundRunning = true
  133. running = "running"
  134. }
  135. fmt.Println(image, local, available, running)
  136. }
  137. if !foundRunning {
  138. fmt.Println(config.Version, "running")
  139. }
  140. return nil
  141. }
  142. func getLatestImage() (string, error) {
  143. images, err := getImages()
  144. if err != nil {
  145. return "", err
  146. }
  147. return images.Current, nil
  148. }
  149. func osUpgrade(c *cli.Context) error {
  150. image := c.String("image")
  151. if image == "" {
  152. var err error
  153. image, err = getLatestImage()
  154. if err != nil {
  155. log.Fatal(err)
  156. }
  157. if image == "" {
  158. log.Fatal("Failed to find latest image")
  159. }
  160. }
  161. if c.Args().Present() {
  162. log.Fatalf("invalid arguments %v", c.Args())
  163. }
  164. if err := startUpgradeContainer(image, c.Bool("stage"), c.Bool("force"), !c.Bool("no-reboot"), c.Bool("kexec"), c.Bool("upgrade-console"), c.String("append")); err != nil {
  165. log.Fatal(err)
  166. }
  167. return nil
  168. }
  169. func osVersion(c *cli.Context) error {
  170. fmt.Println(config.Version)
  171. return nil
  172. }
  173. func startUpgradeContainer(image string, stage, force, reboot, kexec bool, upgradeConsole bool, kernelArgs string) error {
  174. command := []string{
  175. "-t", "rancher-upgrade",
  176. "-r", config.Version,
  177. }
  178. if kexec {
  179. command = append(command, "--kexec")
  180. }
  181. kernelArgs = strings.TrimSpace(kernelArgs)
  182. if kernelArgs != "" {
  183. command = append(command, "-a", kernelArgs)
  184. }
  185. if upgradeConsole {
  186. if err := config.Set("rancher.force_console_rebuild", true); err != nil {
  187. log.Fatal(err)
  188. }
  189. }
  190. fmt.Printf("Upgrading to %s\n", image)
  191. confirmation := "Continue"
  192. imageSplit := strings.Split(image, ":")
  193. if len(imageSplit) > 1 && imageSplit[1] == config.Version+config.Suffix {
  194. confirmation = fmt.Sprintf("Already at version %s. Continue anyway", imageSplit[1])
  195. }
  196. if !force && !yes(confirmation) {
  197. os.Exit(1)
  198. }
  199. container, err := compose.CreateService(nil, "os-upgrade", &composeConfig.ServiceConfigV1{
  200. LogDriver: "json-file",
  201. Privileged: true,
  202. Net: "host",
  203. Pid: "host",
  204. Image: image,
  205. Labels: map[string]string{
  206. config.ScopeLabel: config.System,
  207. },
  208. Command: command,
  209. })
  210. if err != nil {
  211. return err
  212. }
  213. client, err := docker.NewSystemClient()
  214. if err != nil {
  215. return err
  216. }
  217. // Only pull image if not found locally
  218. if _, _, err := client.ImageInspectWithRaw(context.Background(), image, false); err != nil {
  219. if err := container.Pull(context.Background()); err != nil {
  220. return err
  221. }
  222. }
  223. if !stage {
  224. // If there is already an upgrade container, delete it
  225. // Up() should to this, but currently does not due to a bug
  226. if err := container.Delete(context.Background(), options.Delete{}); err != nil {
  227. return err
  228. }
  229. if err := container.Up(context.Background(), options.Up{}); err != nil {
  230. return err
  231. }
  232. if err := container.Log(context.Background(), true); err != nil {
  233. return err
  234. }
  235. if err := container.Delete(context.Background(), options.Delete{}); err != nil {
  236. return err
  237. }
  238. if reboot && (force || yes("Continue with reboot")) {
  239. log.Info("Rebooting")
  240. power.Reboot()
  241. }
  242. }
  243. return nil
  244. }
  245. func parseBody(body []byte) (*Images, error) {
  246. update := &Images{}
  247. err := yaml.Unmarshal(body, update)
  248. if err != nil {
  249. return nil, err
  250. }
  251. return update, nil
  252. }
  253. func getUpgradeURL() (string, error) {
  254. cfg := config.LoadConfig()
  255. return cfg.Rancher.Upgrade.URL, nil
  256. }