os.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package control
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/url"
  8. "os"
  9. "strings"
  10. "golang.org/x/net/context"
  11. log "github.com/Sirupsen/logrus"
  12. yaml "github.com/cloudfoundry-incubator/candiedyaml"
  13. "github.com/codegangsta/cli"
  14. dockerClient "github.com/docker/engine-api/client"
  15. composeConfig "github.com/docker/libcompose/config"
  16. "github.com/docker/libcompose/project/options"
  17. "github.com/rancher/os/cmd/power"
  18. "github.com/rancher/os/compose"
  19. "github.com/rancher/os/config"
  20. "github.com/rancher/os/docker"
  21. )
  22. type Images struct {
  23. Current string `yaml:"current,omitempty"`
  24. Available []string `yaml:"available,omitempty"`
  25. }
  26. func osSubcommands() []cli.Command {
  27. return []cli.Command{
  28. {
  29. Name: "upgrade",
  30. Usage: "upgrade to latest version",
  31. Action: osUpgrade,
  32. Flags: []cli.Flag{
  33. cli.BoolFlag{
  34. Name: "stage, s",
  35. Usage: "Only stage the new upgrade, don't apply it",
  36. },
  37. cli.StringFlag{
  38. Name: "image, i",
  39. Usage: "upgrade to a certain image",
  40. },
  41. cli.BoolFlag{
  42. Name: "force, f",
  43. Usage: "do not prompt for input",
  44. },
  45. cli.BoolFlag{
  46. Name: "no-reboot",
  47. Usage: "do not reboot after upgrade",
  48. },
  49. cli.BoolFlag{
  50. Name: "kexec",
  51. Usage: "reboot using kexec",
  52. },
  53. cli.StringFlag{
  54. Name: "append",
  55. Usage: "append additional kernel parameters",
  56. },
  57. cli.BoolFlag{
  58. Name: "upgrade-console",
  59. Usage: "upgrade console even if persistent",
  60. },
  61. },
  62. },
  63. {
  64. Name: "list",
  65. Usage: "list the current available versions",
  66. Action: osMetaDataGet,
  67. },
  68. {
  69. Name: "version",
  70. Usage: "show the currently installed version",
  71. Action: osVersion,
  72. },
  73. }
  74. }
  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. for _, image := range images.Available {
  117. _, _, err := client.ImageInspectWithRaw(context.Background(), image, false)
  118. if dockerClient.IsErrImageNotFound(err) {
  119. fmt.Println(image, "remote")
  120. } else {
  121. fmt.Println(image, "local")
  122. }
  123. }
  124. return nil
  125. }
  126. func getLatestImage() (string, error) {
  127. images, err := getImages()
  128. if err != nil {
  129. return "", err
  130. }
  131. return images.Current, nil
  132. }
  133. func osUpgrade(c *cli.Context) error {
  134. image := c.String("image")
  135. if image == "" {
  136. var err error
  137. image, err = getLatestImage()
  138. if err != nil {
  139. log.Fatal(err)
  140. }
  141. if image == "" {
  142. log.Fatal("Failed to find latest image")
  143. }
  144. }
  145. if c.Args().Present() {
  146. log.Fatalf("invalid arguments %v", c.Args())
  147. }
  148. 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 {
  149. log.Fatal(err)
  150. }
  151. return nil
  152. }
  153. func osVersion(c *cli.Context) error {
  154. fmt.Println(config.VERSION)
  155. return nil
  156. }
  157. func startUpgradeContainer(image string, stage, force, reboot, kexec bool, upgradeConsole bool, kernelArgs string) error {
  158. in := bufio.NewReader(os.Stdin)
  159. command := []string{
  160. "-t", "rancher-upgrade",
  161. "-r", config.VERSION,
  162. }
  163. if kexec {
  164. command = append(command, "-k")
  165. }
  166. kernelArgs = strings.TrimSpace(kernelArgs)
  167. if kernelArgs != "" {
  168. command = append(command, "-a", kernelArgs)
  169. }
  170. if upgradeConsole {
  171. if err := config.Set("rancher.force_console_rebuild", true); err != nil {
  172. log.Fatal(err)
  173. }
  174. }
  175. fmt.Printf("Upgrading to %s\n", image)
  176. confirmation := "Continue"
  177. imageSplit := strings.Split(image, ":")
  178. if len(imageSplit) > 1 && imageSplit[1] == config.VERSION+config.SUFFIX {
  179. confirmation = fmt.Sprintf("Already at version %s. Continue anyway", imageSplit[1])
  180. }
  181. if !force && !yes(in, confirmation) {
  182. os.Exit(1)
  183. }
  184. container, err := compose.CreateService(nil, "os-upgrade", &composeConfig.ServiceConfigV1{
  185. LogDriver: "json-file",
  186. Privileged: true,
  187. Net: "host",
  188. Pid: "host",
  189. Image: image,
  190. Labels: map[string]string{
  191. config.SCOPE: config.SYSTEM,
  192. },
  193. Command: command,
  194. })
  195. if err != nil {
  196. return err
  197. }
  198. client, err := docker.NewSystemClient()
  199. if err != nil {
  200. return err
  201. }
  202. // Only pull image if not found locally
  203. if _, _, err := client.ImageInspectWithRaw(context.Background(), image, false); err != nil {
  204. if err := container.Pull(context.Background()); err != nil {
  205. return err
  206. }
  207. }
  208. if !stage {
  209. // If there is already an upgrade container, delete it
  210. // Up() should to this, but currently does not due to a bug
  211. if err := container.Delete(context.Background(), options.Delete{}); err != nil {
  212. return err
  213. }
  214. if err := container.Up(context.Background(), options.Up{}); err != nil {
  215. return err
  216. }
  217. if err := container.Log(context.Background(), true); err != nil {
  218. return err
  219. }
  220. if err := container.Delete(context.Background(), options.Delete{}); err != nil {
  221. return err
  222. }
  223. if reboot && (force || yes(in, "Continue with reboot")) {
  224. log.Info("Rebooting")
  225. power.Reboot()
  226. }
  227. }
  228. return nil
  229. }
  230. func parseBody(body []byte) (*Images, error) {
  231. update := &Images{}
  232. err := yaml.Unmarshal(body, update)
  233. if err != nil {
  234. return nil, err
  235. }
  236. return update, nil
  237. }
  238. func getUpgradeUrl() (string, error) {
  239. cfg := config.LoadConfig()
  240. return cfg.Rancher.Upgrade.Url, nil
  241. }