os.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. package control
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "runtime"
  9. "strings"
  10. "golang.org/x/net/context"
  11. yaml "github.com/cloudfoundry-incubator/candiedyaml"
  12. "github.com/rancher/os/log"
  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, k",
  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. cli.BoolFlag{
  62. Name: "debug",
  63. Usage: "Run installer with debug output",
  64. },
  65. },
  66. },
  67. {
  68. Name: "list",
  69. Usage: "list the current available versions",
  70. Action: osMetaDataGet,
  71. },
  72. {
  73. Name: "version",
  74. Usage: "show the currently installed version",
  75. Action: osVersion,
  76. },
  77. }
  78. }
  79. // TODO: this and the getLatestImage should probably move to utils/network and be suitably cached.
  80. func getImages() (*Images, error) {
  81. upgradeURL, err := getUpgradeURL()
  82. if err != nil {
  83. return nil, err
  84. }
  85. var body []byte
  86. if strings.HasPrefix(upgradeURL, "/") {
  87. body, err = ioutil.ReadFile(upgradeURL)
  88. if err != nil {
  89. return nil, err
  90. }
  91. } else {
  92. u, err := url.Parse(upgradeURL)
  93. if err != nil {
  94. return nil, err
  95. }
  96. q := u.Query()
  97. q.Set("current", config.Version)
  98. u.RawQuery = q.Encode()
  99. upgradeURL = u.String()
  100. resp, err := http.Get(upgradeURL)
  101. if err != nil {
  102. return nil, err
  103. }
  104. defer resp.Body.Close()
  105. body, err = ioutil.ReadAll(resp.Body)
  106. if err != nil {
  107. return nil, err
  108. }
  109. }
  110. return parseBody(body)
  111. }
  112. func osMetaDataGet(c *cli.Context) error {
  113. images, err := getImages()
  114. if err != nil {
  115. log.Fatal(err)
  116. }
  117. client, err := docker.NewSystemClient()
  118. if err != nil {
  119. log.Fatal(err)
  120. }
  121. cfg := config.LoadConfig()
  122. runningName := cfg.Rancher.Upgrade.Image + ":" + config.Version
  123. foundRunning := false
  124. for i := len(images.Available) - 1; i >= 0; i-- {
  125. image := images.Available[i]
  126. _, _, err := client.ImageInspectWithRaw(context.Background(), image, false)
  127. local := "local"
  128. if dockerClient.IsErrImageNotFound(err) {
  129. local = "remote"
  130. }
  131. available := "available"
  132. if image == images.Current {
  133. available = "latest"
  134. }
  135. var running string
  136. if image == runningName {
  137. foundRunning = true
  138. running = "running"
  139. }
  140. fmt.Println(image, local, available, running)
  141. }
  142. if !foundRunning {
  143. fmt.Println(config.Version, "running")
  144. }
  145. return nil
  146. }
  147. func getLatestImage() (string, error) {
  148. images, err := getImages()
  149. if err != nil {
  150. return "", err
  151. }
  152. return images.Current, nil
  153. }
  154. func osUpgrade(c *cli.Context) error {
  155. if runtime.GOARCH != "amd64" {
  156. log.Fatalf("ros install / upgrade only supported on 'amd64', not '%s'", runtime.GOARCH)
  157. }
  158. image := c.String("image")
  159. if image == "" {
  160. var err error
  161. image, err = getLatestImage()
  162. if err != nil {
  163. log.Fatal(err)
  164. }
  165. if image == "" {
  166. log.Fatal("Failed to find latest image")
  167. }
  168. }
  169. if c.Args().Present() {
  170. log.Fatalf("invalid arguments %v", c.Args())
  171. }
  172. if err := startUpgradeContainer(
  173. image,
  174. c.Bool("stage"),
  175. c.Bool("force"),
  176. !c.Bool("no-reboot"),
  177. c.Bool("kexec"),
  178. c.Bool("upgrade-console"),
  179. c.Bool("debug"),
  180. c.String("append"),
  181. ); err != nil {
  182. log.Fatal(err)
  183. }
  184. return nil
  185. }
  186. func osVersion(c *cli.Context) error {
  187. fmt.Println(config.Version)
  188. return nil
  189. }
  190. func startUpgradeContainer(image string, stage, force, reboot, kexec, debug bool, upgradeConsole bool, kernelArgs string) error {
  191. command := []string{
  192. "-t", "rancher-upgrade",
  193. "-r", config.Version,
  194. }
  195. if kexec {
  196. command = append(command, "--kexec")
  197. }
  198. if debug {
  199. command = append(command, "--debug")
  200. }
  201. kernelArgs = strings.TrimSpace(kernelArgs)
  202. if kernelArgs != "" {
  203. command = append(command, "-a", kernelArgs)
  204. }
  205. if upgradeConsole {
  206. if err := config.Set("rancher.force_console_rebuild", true); err != nil {
  207. log.Fatal(err)
  208. }
  209. }
  210. fmt.Printf("Upgrading to %s\n", image)
  211. confirmation := "Continue"
  212. imageSplit := strings.Split(image, ":")
  213. if len(imageSplit) > 1 && imageSplit[1] == config.Version+config.Suffix {
  214. confirmation = fmt.Sprintf("Already at version %s. Continue anyway", imageSplit[1])
  215. }
  216. if !force && !yes(confirmation) {
  217. os.Exit(1)
  218. }
  219. container, err := compose.CreateService(nil, "os-upgrade", &composeConfig.ServiceConfigV1{
  220. LogDriver: "json-file",
  221. Privileged: true,
  222. Net: "host",
  223. Pid: "host",
  224. Image: image,
  225. Labels: map[string]string{
  226. config.ScopeLabel: config.System,
  227. },
  228. Command: command,
  229. })
  230. if err != nil {
  231. return err
  232. }
  233. client, err := docker.NewSystemClient()
  234. if err != nil {
  235. return err
  236. }
  237. // Only pull image if not found locally
  238. if _, _, err := client.ImageInspectWithRaw(context.Background(), image, false); err != nil {
  239. if err := container.Pull(context.Background()); err != nil {
  240. return err
  241. }
  242. }
  243. if !stage {
  244. // If there is already an upgrade container, delete it
  245. // Up() should to this, but currently does not due to a bug
  246. if err := container.Delete(context.Background(), options.Delete{}); err != nil {
  247. return err
  248. }
  249. if err := container.Up(context.Background(), options.Up{}); err != nil {
  250. return err
  251. }
  252. if err := container.Log(context.Background(), true); err != nil {
  253. return err
  254. }
  255. if err := container.Delete(context.Background(), options.Delete{}); err != nil {
  256. return err
  257. }
  258. if reboot && (force || yes("Continue with reboot")) {
  259. log.Info("Rebooting")
  260. power.Reboot()
  261. }
  262. }
  263. return nil
  264. }
  265. func parseBody(body []byte) (*Images, error) {
  266. update := &Images{}
  267. err := yaml.Unmarshal(body, update)
  268. if err != nil {
  269. return nil, err
  270. }
  271. return update, nil
  272. }
  273. func getUpgradeURL() (string, error) {
  274. cfg := config.LoadConfig()
  275. return cfg.Rancher.Upgrade.URL, nil
  276. }