os.go 7.3 KB

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