cloudinit.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. // Copyright 2015 CoreOS, Inc.
  2. // Copyright 2015 Rancher Labs, Inc.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. package cloudinit
  16. import (
  17. "flag"
  18. "io/ioutil"
  19. "os"
  20. "path"
  21. "strings"
  22. "sync"
  23. "time"
  24. "gopkg.in/yaml.v2"
  25. log "github.com/Sirupsen/logrus"
  26. "github.com/coreos/coreos-cloudinit/config"
  27. "github.com/coreos/coreos-cloudinit/datasource"
  28. "github.com/coreos/coreos-cloudinit/datasource/configdrive"
  29. "github.com/coreos/coreos-cloudinit/datasource/file"
  30. "github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
  31. "github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
  32. "github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
  33. "github.com/coreos/coreos-cloudinit/datasource/url"
  34. "github.com/coreos/coreos-cloudinit/initialize"
  35. "github.com/coreos/coreos-cloudinit/pkg"
  36. "github.com/coreos/coreos-cloudinit/system"
  37. "github.com/rancherio/os/cmd/cloudinit/hostname"
  38. rancherNetwork "github.com/rancherio/os/cmd/network"
  39. rancherConfig "github.com/rancherio/os/config"
  40. "github.com/rancherio/os/util"
  41. )
  42. const (
  43. datasourceInterval = 100 * time.Millisecond
  44. datasourceMaxInterval = 30 * time.Second
  45. datasourceTimeout = 5 * time.Minute
  46. )
  47. var (
  48. baseConfigDir string
  49. outputDir string
  50. outputFile string
  51. metaDataFile string
  52. scriptFile string
  53. rancherYml string
  54. save bool
  55. execute bool
  56. network bool
  57. sshKeyName string
  58. flags *flag.FlagSet
  59. )
  60. func init() {
  61. flags = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
  62. flags.StringVar(&baseConfigDir, "base-config-dir", "/var/lib/rancher/conf/cloud-config.d", "base cloud config")
  63. flags.StringVar(&outputDir, "dir", "/var/lib/rancher/conf", "working directory")
  64. flags.StringVar(&outputFile, "file", "cloud-config-processed.yml", "output cloud config file name")
  65. flags.StringVar(&metaDataFile, "metadata", "metadata", "output metdata file name")
  66. flags.StringVar(&scriptFile, "script-file", "cloud-config-script", "output cloud config script file name")
  67. flags.StringVar(&rancherYml, "rancher", "cloud-config-rancher.yml", "output cloud config rancher file name")
  68. flags.StringVar(&sshKeyName, "ssh-key-name", "rancheros-cloud-config", "SSH key name")
  69. flags.BoolVar(&network, "network", true, "use network based datasources")
  70. flags.BoolVar(&save, "save", false, "save cloud config and exit")
  71. flags.BoolVar(&execute, "execute", false, "execute saved cloud config")
  72. }
  73. func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadata) error {
  74. scriptOutput := path.Join(outputDir, scriptFile)
  75. cloudConfigOutput := path.Join(outputDir, outputFile)
  76. rancherYmlOutput := path.Join(outputDir, rancherYml)
  77. metaDataOutput := path.Join(outputDir, metaDataFile)
  78. os.Remove(scriptOutput)
  79. os.Remove(cloudConfigOutput)
  80. os.Remove(rancherYmlOutput)
  81. os.Remove(metaDataOutput)
  82. if len(scriptBytes) > 0 {
  83. log.Infof("Writing to %s", scriptOutput)
  84. if err := ioutil.WriteFile(scriptOutput, scriptBytes, 500); err != nil {
  85. log.Errorf("Error while writing file %s: %v", scriptOutput, err)
  86. return err
  87. }
  88. }
  89. cloudConfigBytes = append([]byte("#cloud-config\n"), cloudConfigBytes...)
  90. log.Infof("Writing to %s", cloudConfigOutput)
  91. if err := ioutil.WriteFile(cloudConfigOutput, cloudConfigBytes, 500); err != nil {
  92. log.Errorf("Error while writing file %s: %v", cloudConfigOutput, err)
  93. return err
  94. }
  95. ccData := make(map[string]interface{})
  96. if err := yaml.Unmarshal(cloudConfigBytes, ccData); err != nil {
  97. return err
  98. }
  99. if rancher, ok := ccData["rancher"]; ok {
  100. bytes, err := yaml.Marshal(rancher)
  101. if err != nil {
  102. return err
  103. }
  104. if err = ioutil.WriteFile(rancherYmlOutput, bytes, 400); err != nil {
  105. return err
  106. }
  107. }
  108. metaDataBytes, err := yaml.Marshal(metadata)
  109. if err != nil {
  110. return err
  111. }
  112. if err = ioutil.WriteFile(metaDataOutput, metaDataBytes, 400); err != nil {
  113. return err
  114. }
  115. return nil
  116. }
  117. func currentDatasource() (datasource.Datasource, error) {
  118. cfg, err := rancherConfig.LoadConfig()
  119. if err != nil {
  120. log.Fatalf("Failed to read rancher config %v", err)
  121. }
  122. dss := getDatasources(cfg)
  123. if len(dss) == 0 {
  124. return nil, nil
  125. }
  126. ds := selectDatasource(dss)
  127. return ds, nil
  128. }
  129. func mergeBaseConfig(current, currentScript []byte) ([]byte, []byte, error) {
  130. files, err := ioutil.ReadDir(baseConfigDir)
  131. if err != nil {
  132. if os.IsNotExist(err) {
  133. log.Infof("%s does not exist, not merging", baseConfigDir)
  134. return current, currentScript, nil
  135. }
  136. log.Errorf("Failed to read %s: %v", baseConfigDir, err)
  137. return nil, nil, err
  138. }
  139. scriptResult := currentScript
  140. result := []byte{}
  141. for _, file := range files {
  142. if file.IsDir() || strings.HasPrefix(file.Name(), ".") {
  143. continue
  144. }
  145. input := path.Join(baseConfigDir, file.Name())
  146. content, err := ioutil.ReadFile(input)
  147. if err != nil {
  148. log.Errorf("Failed to read %s: %v", input, err)
  149. // ignore error
  150. continue
  151. }
  152. if config.IsScript(string(content)) {
  153. scriptResult = content
  154. continue
  155. }
  156. log.Infof("Merging %s", input)
  157. if isCompose(string(content)) {
  158. content, err = toCompose(content)
  159. if err != nil {
  160. log.Errorf("Failed to convert %s to cloud-config syntax: %v", input, err)
  161. }
  162. }
  163. result, err = util.MergeBytes(result, content)
  164. if err != nil {
  165. log.Errorf("Failed to merge bytes: %v", err)
  166. return nil, nil, err
  167. }
  168. }
  169. if len(result) == 0 {
  170. return current, scriptResult, nil
  171. } else {
  172. result, err := util.MergeBytes(result, current)
  173. return result, scriptResult, err
  174. }
  175. }
  176. func saveCloudConfig() error {
  177. var userDataBytes []byte
  178. var metadata datasource.Metadata
  179. ds, err := currentDatasource()
  180. if err != nil {
  181. log.Errorf("Failed to select datasource: %v", err)
  182. return err
  183. }
  184. if ds != nil {
  185. log.Infof("Fetching user-data from datasource %v", ds.Type())
  186. userDataBytes, err = ds.FetchUserdata()
  187. if err != nil {
  188. log.Errorf("Failed fetching user-data from datasource: %v", err)
  189. return err
  190. }
  191. log.Infof("Fetching meta-data from datasource of type %v", ds.Type())
  192. metadata, err = ds.FetchMetadata()
  193. if err != nil {
  194. log.Errorf("Failed fetching meta-data from datasource: %v", err)
  195. return err
  196. }
  197. }
  198. userDataBytes = substituteUserDataVars(userDataBytes, metadata)
  199. userData := string(userDataBytes)
  200. scriptBytes := []byte{}
  201. if config.IsScript(userData) {
  202. scriptBytes = userDataBytes
  203. userDataBytes = []byte{}
  204. } else if isCompose(userData) {
  205. if userDataBytes, err = toCompose(userDataBytes); err != nil {
  206. log.Errorf("Failed to convert to compose syntax: %v", err)
  207. return err
  208. }
  209. } else if config.IsCloudConfig(userData) {
  210. // nothing to do
  211. } else {
  212. log.Errorf("Unrecognized cloud-init\n%s", userData)
  213. userDataBytes = []byte{}
  214. }
  215. if userDataBytes, scriptBytes, err = mergeBaseConfig(userDataBytes, scriptBytes); err != nil {
  216. log.Errorf("Failed to merge base config: %v", err)
  217. return err
  218. }
  219. return saveFiles(userDataBytes, scriptBytes, metadata)
  220. }
  221. func getSaveCloudConfig() (*config.CloudConfig, error) {
  222. cloudConfig := path.Join(outputDir, outputFile)
  223. ds := file.NewDatasource(cloudConfig)
  224. if !ds.IsAvailable() {
  225. log.Infof("%s does not exist", cloudConfig)
  226. return nil, nil
  227. }
  228. ccBytes, err := ds.FetchUserdata()
  229. if err != nil {
  230. log.Errorf("Failed to read user-data from %s: %v", cloudConfig, err)
  231. return nil, err
  232. }
  233. var cc config.CloudConfig
  234. err = yaml.Unmarshal(ccBytes, &cc)
  235. if err != nil {
  236. log.Errorf("Failed to unmarshall user-data from %s: %v", cloudConfig, err)
  237. return nil, err
  238. }
  239. return &cc, err
  240. }
  241. func executeCloudConfig() error {
  242. ccu, err := getSaveCloudConfig()
  243. if err != nil {
  244. return err
  245. }
  246. var metadata datasource.Metadata
  247. metaDataBytes, err := ioutil.ReadFile(path.Join(outputDir, metaDataFile))
  248. if err != nil {
  249. return err
  250. }
  251. if err = yaml.Unmarshal(metaDataBytes, &metadata); err != nil {
  252. return err
  253. }
  254. log.Info("Merging cloud-config from meta-data and user-data")
  255. cc := mergeConfigs(ccu, metadata)
  256. if cc.Hostname != "" {
  257. //set hostname
  258. if err := hostname.SetHostname(cc.Hostname); err != nil {
  259. log.Fatal(err)
  260. }
  261. }
  262. if len(cc.SSHAuthorizedKeys) > 0 {
  263. authorizeSSHKeys("rancher", cc.SSHAuthorizedKeys, sshKeyName)
  264. authorizeSSHKeys("docker", cc.SSHAuthorizedKeys, sshKeyName)
  265. }
  266. for _, user := range cc.Users {
  267. if user.Name == "" {
  268. continue
  269. }
  270. if len(user.SSHAuthorizedKeys) > 0 {
  271. authorizeSSHKeys(user.Name, user.SSHAuthorizedKeys, sshKeyName)
  272. }
  273. }
  274. for _, file := range cc.WriteFiles {
  275. f := system.File{File: file}
  276. fullPath, err := system.WriteFile(&f, "/")
  277. if err != nil {
  278. log.Fatal(err)
  279. }
  280. log.Printf("Wrote file %s to filesystem", fullPath)
  281. }
  282. return nil
  283. }
  284. func Main() {
  285. flags.Parse(rancherConfig.FilterGlobalConfig(os.Args[1:]))
  286. if save {
  287. err := saveCloudConfig()
  288. if err != nil {
  289. log.Fatalf("Failed to save cloud config: %v", err)
  290. }
  291. }
  292. if execute {
  293. err := executeCloudConfig()
  294. if err != nil {
  295. log.Fatalf("Failed to save cloud config: %v", err)
  296. }
  297. }
  298. }
  299. // mergeConfigs merges certain options from md (meta-data from the datasource)
  300. // onto cc (a CloudConfig derived from user-data), if they are not already set
  301. // on cc (i.e. user-data always takes precedence)
  302. func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.CloudConfig) {
  303. if cc != nil {
  304. out = *cc
  305. }
  306. if md.Hostname != "" {
  307. if out.Hostname != "" {
  308. log.Infof("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname)
  309. } else {
  310. out.Hostname = md.Hostname
  311. }
  312. }
  313. for _, key := range md.SSHPublicKeys {
  314. out.SSHAuthorizedKeys = append(out.SSHAuthorizedKeys, key)
  315. }
  316. return
  317. }
  318. // getDatasources creates a slice of possible Datasources for cloudinit based
  319. // on the different source command-line flags.
  320. func getDatasources(cfg *rancherConfig.Config) []datasource.Datasource {
  321. dss := make([]datasource.Datasource, 0, 5)
  322. for _, ds := range cfg.CloudInit.Datasources {
  323. parts := strings.SplitN(ds, ":", 2)
  324. switch parts[0] {
  325. case "ec2":
  326. if network {
  327. if len(parts) == 1 {
  328. dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
  329. } else {
  330. dss = append(dss, ec2.NewDatasource(parts[1]))
  331. }
  332. }
  333. case "file":
  334. if len(parts) == 2 {
  335. dss = append(dss, file.NewDatasource(parts[1]))
  336. }
  337. case "url":
  338. if network {
  339. if len(parts) == 2 {
  340. dss = append(dss, url.NewDatasource(parts[1]))
  341. }
  342. }
  343. case "cmdline":
  344. if network {
  345. if len(parts) == 1 {
  346. dss = append(dss, proc_cmdline.NewDatasource())
  347. }
  348. }
  349. case "configdrive":
  350. if len(parts) == 2 {
  351. dss = append(dss, configdrive.NewDatasource(parts[1]))
  352. }
  353. case "digitalocean":
  354. if network {
  355. if len(parts) == 1 {
  356. dss = append(dss, digitalocean.NewDatasource(digitalocean.DefaultAddress))
  357. } else {
  358. dss = append(dss, digitalocean.NewDatasource(parts[1]))
  359. }
  360. } else {
  361. enableDoLinkLocal()
  362. }
  363. case "gce":
  364. if network {
  365. gceCloudConfigFile, err := GetAndCreateGceDataSourceFilename()
  366. if err != nil {
  367. log.Errorf("Could not retrieve GCE CloudConfig %s", err)
  368. continue
  369. }
  370. dss = append(dss, file.NewDatasource(gceCloudConfigFile))
  371. }
  372. }
  373. }
  374. return dss
  375. }
  376. func enableDoLinkLocal() {
  377. err := rancherNetwork.ApplyNetworkConfigs(&rancherConfig.NetworkConfig{
  378. Interfaces: map[string]rancherConfig.InterfaceConfig{
  379. "eth0": {
  380. IPV4LL: true,
  381. },
  382. },
  383. })
  384. if err != nil {
  385. log.Errorf("Failed to apply link local on eth0: %v", err)
  386. }
  387. }
  388. // selectDatasource attempts to choose a valid Datasource to use based on its
  389. // current availability. The first Datasource to report to be available is
  390. // returned. Datasources will be retried if possible if they are not
  391. // immediately available. If all Datasources are permanently unavailable or
  392. // datasourceTimeout is reached before one becomes available, nil is returned.
  393. func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
  394. ds := make(chan datasource.Datasource)
  395. stop := make(chan struct{})
  396. var wg sync.WaitGroup
  397. for _, s := range sources {
  398. wg.Add(1)
  399. go func(s datasource.Datasource) {
  400. defer wg.Done()
  401. duration := datasourceInterval
  402. for {
  403. log.Infof("Checking availability of %q\n", s.Type())
  404. if s.IsAvailable() {
  405. ds <- s
  406. return
  407. } else if !s.AvailabilityChanges() {
  408. return
  409. }
  410. select {
  411. case <-stop:
  412. return
  413. case <-time.After(duration):
  414. duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
  415. }
  416. }
  417. }(s)
  418. }
  419. done := make(chan struct{})
  420. go func() {
  421. wg.Wait()
  422. close(done)
  423. }()
  424. var s datasource.Datasource
  425. select {
  426. case s = <-ds:
  427. case <-done:
  428. case <-time.After(datasourceTimeout):
  429. }
  430. close(stop)
  431. return s
  432. }
  433. func isCompose(content string) bool {
  434. return strings.HasPrefix(content, "#compose\n")
  435. }
  436. func toCompose(bytes []byte) ([]byte, error) {
  437. compose := make(map[interface{}]interface{})
  438. err := yaml.Unmarshal(bytes, &compose)
  439. if err != nil {
  440. return nil, err
  441. }
  442. return yaml.Marshal(map[interface{}]interface{}{
  443. "rancher": map[interface{}]interface{}{
  444. "services": compose,
  445. },
  446. })
  447. }
  448. func substituteUserDataVars(userDataBytes []byte, metadata datasource.Metadata) []byte {
  449. env := initialize.NewEnvironment("", "", "", "", metadata)
  450. userData := env.Apply(string(userDataBytes))
  451. return []byte(userData)
  452. }