config.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. package control
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "os/exec"
  10. "sort"
  11. "strings"
  12. "text/template"
  13. "github.com/rancher/os/config"
  14. "github.com/rancher/os/pkg/log"
  15. "github.com/rancher/os/pkg/util"
  16. yaml "github.com/cloudfoundry-incubator/candiedyaml"
  17. "github.com/codegangsta/cli"
  18. "github.com/pkg/errors"
  19. )
  20. func configSubcommands() []cli.Command {
  21. return []cli.Command{
  22. {
  23. Name: "get",
  24. Usage: "get value",
  25. Action: configGet,
  26. },
  27. {
  28. Name: "set",
  29. Usage: "set a value",
  30. Action: configSet,
  31. },
  32. {
  33. Name: "images",
  34. Usage: "List Docker images for a configuration from a file",
  35. Action: runImages,
  36. Flags: []cli.Flag{
  37. cli.StringFlag{
  38. Name: "input, i",
  39. Usage: "File from which to read config",
  40. },
  41. },
  42. },
  43. {
  44. Name: "generate",
  45. Usage: "Generate a configuration file from a template",
  46. Action: runGenerate,
  47. HideHelp: true,
  48. },
  49. {
  50. Name: "export",
  51. Usage: "export configuration",
  52. Flags: []cli.Flag{
  53. cli.StringFlag{
  54. Name: "output, o",
  55. Usage: "File to which to save",
  56. },
  57. cli.BoolFlag{
  58. Name: "private, p",
  59. Usage: "Include the generated private keys",
  60. },
  61. cli.BoolFlag{
  62. Name: "full, f",
  63. Usage: "Export full configuration, including internal and default settings",
  64. },
  65. },
  66. Action: export,
  67. },
  68. {
  69. Name: "merge",
  70. Usage: "merge configuration from stdin",
  71. Action: merge,
  72. Flags: []cli.Flag{
  73. cli.StringFlag{
  74. Name: "input, i",
  75. Usage: "File from which to read",
  76. },
  77. },
  78. },
  79. {
  80. Name: "syslinux",
  81. Usage: "edit Syslinux boot global.cfg",
  82. Action: editSyslinux,
  83. },
  84. {
  85. Name: "validate",
  86. Usage: "validate configuration from stdin",
  87. Action: validate,
  88. Flags: []cli.Flag{
  89. cli.StringFlag{
  90. Name: "input, i",
  91. Usage: "File from which to read",
  92. },
  93. },
  94. },
  95. }
  96. }
  97. func imagesFromConfig(cfg *config.CloudConfig) []string {
  98. imagesMap := map[string]int{}
  99. for _, service := range cfg.Rancher.BootstrapContainers {
  100. imagesMap[service.Image] = 1
  101. }
  102. for _, service := range cfg.Rancher.Services {
  103. imagesMap[service.Image] = 1
  104. }
  105. images := make([]string, len(imagesMap))
  106. i := 0
  107. for image := range imagesMap {
  108. images[i] = image
  109. i++
  110. }
  111. sort.Strings(images)
  112. return images
  113. }
  114. func runImages(c *cli.Context) error {
  115. configFile := c.String("input")
  116. cfg, err := config.ReadConfig(nil, false, configFile)
  117. if err != nil {
  118. log.WithFields(log.Fields{"err": err, "file": configFile}).Fatalf("Could not read config from file")
  119. }
  120. images := imagesFromConfig(cfg)
  121. fmt.Println(strings.Join(images, " "))
  122. return nil
  123. }
  124. func runGenerate(c *cli.Context) error {
  125. if err := genTpl(os.Stdin, os.Stdout); err != nil {
  126. log.Fatalf("Failed to generate config, err: '%s'", err)
  127. }
  128. return nil
  129. }
  130. func genTpl(in io.Reader, out io.Writer) error {
  131. bytes, err := ioutil.ReadAll(in)
  132. if err != nil {
  133. log.Fatal("Could not read from stdin")
  134. }
  135. tpl := template.Must(template.New("osconfig").Parse(string(bytes)))
  136. return tpl.Execute(out, env2map(os.Environ()))
  137. }
  138. func env2map(env []string) map[string]string {
  139. m := make(map[string]string, len(env))
  140. for _, s := range env {
  141. d := strings.Split(s, "=")
  142. m[d[0]] = d[1]
  143. }
  144. return m
  145. }
  146. func editSyslinux(c *cli.Context) error {
  147. // check whether is Raspberry Pi or not
  148. bytes, err := ioutil.ReadFile("/proc/device-tree/model")
  149. if err == nil && strings.Contains(strings.ToLower(string(bytes)), "raspberry") {
  150. buf := bufio.NewWriter(os.Stdout)
  151. fmt.Fprintln(buf, "raspberry pi can not use this command")
  152. buf.Flush()
  153. return errors.New("raspberry pi can not use this command")
  154. }
  155. if isExist := checkGlobalCfg(); !isExist {
  156. buf := bufio.NewWriter(os.Stdout)
  157. fmt.Fprintln(buf, "global.cfg can not be found")
  158. buf.Flush()
  159. return errors.New("global.cfg can not be found")
  160. }
  161. cmd := exec.Command("system-docker", "run", "--rm", "-it",
  162. "-v", "/:/host",
  163. "-w", "/host",
  164. "--entrypoint=vi",
  165. "rancher/os-console:"+config.Version,
  166. "boot/global.cfg")
  167. cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
  168. return cmd.Run()
  169. }
  170. func configSet(c *cli.Context) error {
  171. if c.NArg() < 2 {
  172. return nil
  173. }
  174. key := c.Args().Get(0)
  175. value := c.Args().Get(1)
  176. if key == "" {
  177. return nil
  178. }
  179. err := config.Set(key, value)
  180. if err != nil {
  181. log.Fatal(err)
  182. }
  183. return nil
  184. }
  185. func configGet(c *cli.Context) error {
  186. arg := c.Args().Get(0)
  187. if arg == "" {
  188. return nil
  189. }
  190. val, err := config.Get(arg)
  191. if err != nil {
  192. log.WithFields(log.Fields{"key": arg, "val": val, "err": err}).Fatal("config get: failed to retrieve value")
  193. }
  194. printYaml := false
  195. switch val.(type) {
  196. case []interface{}:
  197. printYaml = true
  198. case map[interface{}]interface{}:
  199. printYaml = true
  200. }
  201. if printYaml {
  202. bytes, err := yaml.Marshal(val)
  203. if err != nil {
  204. log.Fatal(err)
  205. }
  206. fmt.Println(string(bytes))
  207. } else {
  208. fmt.Println(val)
  209. }
  210. return nil
  211. }
  212. func merge(c *cli.Context) error {
  213. bytes, err := inputBytes(c)
  214. if err != nil {
  215. log.Fatal(err)
  216. }
  217. if err = config.Merge(bytes); err != nil {
  218. log.Error(err)
  219. validationErrors, err := config.ValidateBytes(bytes)
  220. if err != nil {
  221. log.Fatal(err)
  222. }
  223. for _, validationError := range validationErrors.Errors() {
  224. log.Error(validationError)
  225. }
  226. log.Fatal("EXITING: Failed to parse configuration")
  227. }
  228. return nil
  229. }
  230. func export(c *cli.Context) error {
  231. content, err := config.Export(c.Bool("private"), c.Bool("full"))
  232. if err != nil {
  233. log.Fatal(err)
  234. }
  235. output := c.String("output")
  236. if output == "" {
  237. fmt.Println(content)
  238. } else {
  239. err := util.WriteFileAtomic(output, []byte(content), 0400)
  240. if err != nil {
  241. log.Fatal(err)
  242. }
  243. }
  244. return nil
  245. }
  246. func validate(c *cli.Context) error {
  247. bytes, err := inputBytes(c)
  248. if err != nil {
  249. log.Fatal(err)
  250. }
  251. validationErrors, err := config.ValidateBytes(bytes)
  252. if err != nil {
  253. log.Fatal(err)
  254. }
  255. for _, validationError := range validationErrors.Errors() {
  256. log.Error(validationError)
  257. }
  258. return nil
  259. }
  260. func inputBytes(c *cli.Context) ([]byte, error) {
  261. inputFile := c.String("input")
  262. if inputFile == "" {
  263. return nil, errors.New("input parameter can not be empty")
  264. }
  265. content, err := ioutil.ReadFile(inputFile)
  266. if err != nil {
  267. return nil, err
  268. }
  269. if bytes.Contains(content, []byte{13, 10}) {
  270. return nil, errors.New("file format shouldn't contain CRLF characters")
  271. }
  272. return content, nil
  273. }