console.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package control
  2. import (
  3. "fmt"
  4. "sort"
  5. "strings"
  6. "golang.org/x/net/context"
  7. "github.com/codegangsta/cli"
  8. "github.com/docker/docker/reference"
  9. composeConfig "github.com/docker/libcompose/config"
  10. "github.com/docker/libcompose/project/options"
  11. "github.com/rancher/os/cmd/control/service"
  12. "github.com/rancher/os/compose"
  13. "github.com/rancher/os/config"
  14. "github.com/rancher/os/docker"
  15. "github.com/rancher/os/log"
  16. "github.com/rancher/os/util"
  17. "github.com/rancher/os/util/network"
  18. )
  19. func consoleSubcommands() []cli.Command {
  20. return []cli.Command{
  21. {
  22. Name: "switch",
  23. Usage: "switch console without a reboot",
  24. Action: consoleSwitch,
  25. Flags: []cli.Flag{
  26. cli.BoolFlag{
  27. Name: "force, f",
  28. Usage: "do not prompt for input",
  29. },
  30. cli.BoolFlag{
  31. Name: "no-pull",
  32. Usage: "don't pull console image",
  33. },
  34. },
  35. },
  36. {
  37. Name: "enable",
  38. Usage: "set console to be switched on next reboot",
  39. Action: consoleEnable,
  40. },
  41. {
  42. Name: "list",
  43. Usage: "list available consoles",
  44. Action: consoleList,
  45. },
  46. }
  47. }
  48. func consoleSwitch(c *cli.Context) error {
  49. if len(c.Args()) != 1 {
  50. log.Fatal("Must specify exactly one console to switch to")
  51. }
  52. newConsole := c.Args()[0]
  53. cfg := config.LoadConfig()
  54. validateConsole(newConsole, cfg)
  55. if newConsole == CurrentConsole() {
  56. log.Warnf("Console is already set to %s", newConsole)
  57. }
  58. if !c.Bool("force") {
  59. fmt.Println(`Switching consoles will
  60. 1. destroy the current console container
  61. 2. log you out
  62. 3. restart Docker`)
  63. if !yes("Continue") {
  64. return nil
  65. }
  66. }
  67. if !c.Bool("no-pull") && newConsole != "default" {
  68. if err := compose.StageServices(cfg, newConsole); err != nil {
  69. return err
  70. }
  71. }
  72. service, err := compose.CreateService(nil, "switch-console", &composeConfig.ServiceConfigV1{
  73. LogDriver: "json-file",
  74. Privileged: true,
  75. Net: "host",
  76. Pid: "host",
  77. Image: config.OsBase,
  78. Labels: map[string]string{
  79. config.ScopeLabel: config.System,
  80. },
  81. Command: []string{"/usr/bin/ros", "switch-console", newConsole},
  82. VolumesFrom: []string{"all-volumes"},
  83. })
  84. if err != nil {
  85. return err
  86. }
  87. if err = service.Delete(context.Background(), options.Delete{}); err != nil {
  88. return err
  89. }
  90. if err = service.Up(context.Background(), options.Up{}); err != nil {
  91. return err
  92. }
  93. return service.Log(context.Background(), true)
  94. }
  95. func consoleEnable(c *cli.Context) error {
  96. if len(c.Args()) != 1 {
  97. log.Fatal("Must specify exactly one console to enable")
  98. }
  99. newConsole := c.Args()[0]
  100. cfg := config.LoadConfig()
  101. validateConsole(newConsole, cfg)
  102. if newConsole != "default" {
  103. if err := compose.StageServices(cfg, newConsole); err != nil {
  104. return err
  105. }
  106. }
  107. if err := config.Set("rancher.console", newConsole); err != nil {
  108. log.Errorf("Failed to update 'rancher.console': %v", err)
  109. }
  110. return nil
  111. }
  112. func consoleList(c *cli.Context) error {
  113. cfg := config.LoadConfig()
  114. consoles := availableConsoles(cfg)
  115. CurrentConsole := CurrentConsole()
  116. for _, console := range consoles {
  117. if console == CurrentConsole {
  118. fmt.Printf("current %s\n", console)
  119. } else if console == cfg.Rancher.Console {
  120. fmt.Printf("enabled %s\n", console)
  121. } else {
  122. fmt.Printf("disabled %s\n", console)
  123. }
  124. }
  125. return nil
  126. }
  127. func validateConsole(console string, cfg *config.CloudConfig) {
  128. consoles := availableConsoles(cfg)
  129. if !service.IsLocalOrURL(console) && !util.Contains(consoles, console) {
  130. log.Fatalf("%s is not a valid console", console)
  131. }
  132. }
  133. func availableConsoles(cfg *config.CloudConfig) []string {
  134. consoles, err := network.GetConsoles(cfg.Rancher.Repositories.ToArray())
  135. if err != nil {
  136. log.Fatal(err)
  137. }
  138. consoles = append(consoles, "default")
  139. sort.Strings(consoles)
  140. return consoles
  141. }
  142. // CurrentConsole gets the name of the console that's running
  143. func CurrentConsole() (console string) {
  144. // TODO: replace this docker container look up with a libcompose service lookup?
  145. // sudo system-docker inspect --format "{{.Config.Image}}" console
  146. client, err := docker.NewSystemClient()
  147. if err != nil {
  148. log.Warnf("Failed to detect current console: %v", err)
  149. return
  150. }
  151. info, err := client.ContainerInspect(context.Background(), "console")
  152. if err != nil {
  153. log.Warnf("Failed to detect current console: %v", err)
  154. return
  155. }
  156. // parse image name, then remove os- prefix and the console suffix
  157. image, err := reference.ParseNamed(info.Config.Image)
  158. if err != nil {
  159. log.Warnf("Failed to detect current console(%s): %v", info.Config.Image, err)
  160. return
  161. }
  162. if strings.Contains(image.Name(), "os-console") {
  163. console = "default"
  164. return
  165. }
  166. console = strings.TrimPrefix(strings.TrimSuffix(image.Name(), "console"), "rancher/os-")
  167. return
  168. }