2015-02-23 19:00:24 +00:00
// Copyright 2015 CoreOS, Inc.
2017-02-22 01:19:10 +00:00
// Copyright 2015-2017 Rancher Labs, Inc.
2015-02-23 19:00:24 +00:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2016-08-04 22:47:12 +00:00
package cloudinitsave
2015-02-19 03:05:23 +00:00
import (
2017-03-07 03:21:14 +00:00
"bytes"
2015-09-23 11:36:28 +00:00
"errors"
2015-02-19 03:05:23 +00:00
"os"
2017-03-07 03:21:14 +00:00
"path"
2015-02-20 03:05:17 +00:00
"strings"
2015-02-19 03:05:23 +00:00
"sync"
"time"
2015-11-26 12:41:42 +00:00
yaml "github.com/cloudfoundry-incubator/candiedyaml"
2015-03-25 22:27:33 +00:00
2017-04-06 11:49:35 +00:00
"github.com/rancher/os/cmd/control"
2017-02-23 01:29:01 +00:00
"github.com/rancher/os/cmd/network"
rancherConfig "github.com/rancher/os/config"
2017-02-22 23:55:32 +00:00
"github.com/rancher/os/config/cloudinit/config"
"github.com/rancher/os/config/cloudinit/datasource"
"github.com/rancher/os/config/cloudinit/datasource/configdrive"
"github.com/rancher/os/config/cloudinit/datasource/file"
"github.com/rancher/os/config/cloudinit/datasource/metadata/digitalocean"
"github.com/rancher/os/config/cloudinit/datasource/metadata/ec2"
2017-02-28 05:34:10 +00:00
"github.com/rancher/os/config/cloudinit/datasource/metadata/gce"
2017-02-22 23:55:32 +00:00
"github.com/rancher/os/config/cloudinit/datasource/metadata/packet"
2017-02-23 01:29:01 +00:00
"github.com/rancher/os/config/cloudinit/datasource/proccmdline"
2017-02-22 23:55:32 +00:00
"github.com/rancher/os/config/cloudinit/datasource/url"
2017-06-17 12:56:25 +00:00
"github.com/rancher/os/config/cloudinit/datasource/vmware"
2017-02-22 23:55:32 +00:00
"github.com/rancher/os/config/cloudinit/pkg"
2016-11-23 10:49:35 +00:00
"github.com/rancher/os/log"
2016-10-19 23:21:35 +00:00
"github.com/rancher/os/netconf"
2016-06-02 21:32:26 +00:00
"github.com/rancher/os/util"
2015-02-19 03:05:23 +00:00
)
const (
datasourceInterval = 100 * time . Millisecond
datasourceMaxInterval = 30 * time . Second
datasourceTimeout = 5 * time . Minute
)
2016-08-04 22:47:12 +00:00
func Main ( ) {
2016-11-23 10:49:35 +00:00
log . InitLogger ( )
2016-11-09 19:08:30 +00:00
log . Info ( "Running cloud-init-save" )
2016-08-04 22:47:12 +00:00
2017-04-06 11:49:35 +00:00
if err := control . UdevSettle ( ) ; err != nil {
log . Errorf ( "Failed to run udev settle: %v" , err )
}
2017-04-19 12:47:44 +00:00
if err := saveCloudConfig ( ) ; err != nil {
2016-08-04 22:47:12 +00:00
log . Errorf ( "Failed to save cloud-config: %v" , err )
}
2015-02-19 03:05:23 +00:00
}
2017-04-19 12:47:44 +00:00
func saveCloudConfig ( ) error {
2017-03-01 05:12:56 +00:00
log . Debugf ( "SaveCloudConfig" )
2017-03-21 10:26:52 +00:00
2017-03-02 01:52:29 +00:00
cfg := rancherConfig . LoadConfig ( )
2017-03-21 10:26:52 +00:00
log . Debugf ( "init: SaveCloudConfig(pre ApplyNetworkConfig): %#v" , cfg . Rancher . Network )
network . ApplyNetworkConfig ( cfg )
2015-02-19 03:05:23 +00:00
2017-03-28 06:04:49 +00:00
log . Debugf ( "datasources that will be consided: %#v" , cfg . Rancher . CloudInit . Datasources )
2017-05-12 01:48:19 +00:00
dss := getDatasources ( cfg . Rancher . CloudInit . Datasources )
2017-03-02 01:52:29 +00:00
if len ( dss ) == 0 {
log . Errorf ( "currentDatasource - none found" )
return nil
2015-04-16 06:03:27 +00:00
}
2017-03-02 01:52:29 +00:00
selectDatasource ( dss )
2017-03-21 10:26:52 +00:00
// Apply any newly detected network config.
cfg = rancherConfig . LoadConfig ( )
log . Debugf ( "init: SaveCloudConfig(post ApplyNetworkConfig): %#v" , cfg . Rancher . Network )
network . ApplyNetworkConfig ( cfg )
2017-03-02 01:52:29 +00:00
return nil
2015-04-16 06:03:27 +00:00
}
2016-11-09 19:08:30 +00:00
func RequiresNetwork ( datasource string ) bool {
2017-03-02 01:52:29 +00:00
// TODO: move into the datasources (and metadatasources)
2017-03-10 03:40:15 +00:00
// and then we can enable that platforms defaults..
2016-11-09 19:08:30 +00:00
parts := strings . SplitN ( datasource , ":" , 2 )
requiresNetwork , ok := map [ string ] bool {
"ec2" : true ,
"file" : false ,
"url" : true ,
"cmdline" : true ,
"configdrive" : false ,
"digitalocean" : true ,
"gce" : true ,
"packet" : true ,
} [ parts [ 0 ] ]
return ok && requiresNetwork
}
func saveFiles ( cloudConfigBytes , scriptBytes [ ] byte , metadata datasource . Metadata ) error {
os . MkdirAll ( rancherConfig . CloudConfigDir , os . ModeDir | 0600 )
if len ( scriptBytes ) > 0 {
log . Infof ( "Writing to %s" , rancherConfig . CloudConfigScriptFile )
if err := util . WriteFileAtomic ( rancherConfig . CloudConfigScriptFile , scriptBytes , 500 ) ; err != nil {
log . Errorf ( "Error while writing file %s: %v" , rancherConfig . CloudConfigScriptFile , err )
return err
}
}
if len ( cloudConfigBytes ) > 0 {
if err := util . WriteFileAtomic ( rancherConfig . CloudConfigBootFile , cloudConfigBytes , 400 ) ; err != nil {
return err
}
2017-04-21 03:08:49 +00:00
log . Infof ( "Wrote to %s" , rancherConfig . CloudConfigBootFile )
2016-11-09 19:08:30 +00:00
}
metaDataBytes , err := yaml . Marshal ( metadata )
if err != nil {
return err
}
if err = util . WriteFileAtomic ( rancherConfig . MetaDataFile , metaDataBytes , 400 ) ; err != nil {
return err
}
2017-04-21 03:08:49 +00:00
log . Infof ( "Wrote to %s" , rancherConfig . MetaDataFile )
2017-03-07 03:21:14 +00:00
// if we write the empty meta yml, the merge fails.
// TODO: the problem is that a partially filled one will still have merge issues, so that needs fixing - presumably by making merge more clever, and making more fields optional
emptyMeta , err := yaml . Marshal ( datasource . Metadata { } )
if err != nil {
return err
}
if bytes . Compare ( metaDataBytes , emptyMeta ) == 0 {
log . Infof ( "not writing %s: its all defaults." , rancherConfig . CloudConfigNetworkFile )
return nil
}
2017-03-14 06:19:11 +00:00
type nonRancherCfg struct {
Network netconf . NetworkConfig ` yaml:"network,omitempty" `
}
type nonCfg struct {
Rancher nonRancherCfg ` yaml:"rancher,omitempty" `
}
2017-03-07 03:21:14 +00:00
// write the network.yml file from metadata
2017-03-14 06:19:11 +00:00
cc := nonCfg {
Rancher : nonRancherCfg {
2017-03-07 03:21:14 +00:00
Network : metadata . NetworkConfig ,
} ,
}
if err := os . MkdirAll ( path . Dir ( rancherConfig . CloudConfigNetworkFile ) , 0700 ) ; err != nil {
log . Errorf ( "Failed to create directory for file %s: %v" , rancherConfig . CloudConfigNetworkFile , err )
}
if err := rancherConfig . WriteToFile ( cc , rancherConfig . CloudConfigNetworkFile ) ; err != nil {
log . Errorf ( "Failed to save config file %s: %v" , rancherConfig . CloudConfigNetworkFile , err )
}
2017-04-21 03:08:49 +00:00
log . Infof ( "Wrote to %s" , rancherConfig . CloudConfigNetworkFile )
2016-11-09 19:08:30 +00:00
return nil
}
2017-03-02 01:52:29 +00:00
func fetchAndSave ( ds datasource . Datasource ) error {
2015-09-23 11:36:28 +00:00
var metadata datasource . Metadata
2017-03-02 01:52:29 +00:00
log . Infof ( "Fetching user-data from datasource %s" , ds )
2015-09-23 11:36:28 +00:00
userDataBytes , err := ds . FetchUserdata ( )
2015-04-16 06:03:27 +00:00
if err != nil {
2015-09-23 11:36:28 +00:00
log . Errorf ( "Failed fetching user-data from datasource: %v" , err )
2017-03-02 01:52:29 +00:00
return err
2015-04-16 06:03:27 +00:00
}
2015-09-23 11:36:28 +00:00
log . Infof ( "Fetching meta-data from datasource of type %v" , ds . Type ( ) )
metadata , err = ds . FetchMetadata ( )
2015-04-16 06:03:27 +00:00
if err != nil {
2015-09-23 11:36:28 +00:00
log . Errorf ( "Failed fetching meta-data from datasource: %v" , err )
2017-03-02 01:52:29 +00:00
return err
2015-04-16 06:03:27 +00:00
}
2017-03-02 01:52:29 +00:00
userData := string ( userDataBytes )
scriptBytes := [ ] byte { }
if config . IsScript ( userData ) {
scriptBytes = userDataBytes
userDataBytes = [ ] byte { }
} else if isCompose ( userData ) {
2017-03-14 02:11:24 +00:00
if userDataBytes , err = composeToCloudConfig ( userDataBytes ) ; err != nil {
2017-03-02 01:52:29 +00:00
log . Errorf ( "Failed to convert compose to cloud-config syntax: %v" , err )
return err
}
} else if config . IsCloudConfig ( userData ) {
if _ , err := rancherConfig . ReadConfig ( userDataBytes , false ) ; err != nil {
log . WithFields ( log . Fields { "cloud-config" : userData , "err" : err } ) . Warn ( "Failed to parse cloud-config, not saving." )
userDataBytes = [ ] byte { }
}
} else {
2017-03-07 03:21:14 +00:00
log . Errorf ( "Unrecognized user-data\n(%s)" , userData )
2017-03-02 01:52:29 +00:00
userDataBytes = [ ] byte { }
}
if _ , err := rancherConfig . ReadConfig ( userDataBytes , false ) ; err != nil {
log . WithFields ( log . Fields { "cloud-config" : userData , "err" : err } ) . Warn ( "Failed to parse cloud-config" )
return errors . New ( "Failed to parse cloud-config" )
}
return saveFiles ( userDataBytes , scriptBytes , metadata )
2015-04-16 06:03:27 +00:00
}
2015-02-19 03:05:23 +00:00
// getDatasources creates a slice of possible Datasources for cloudinit based
// on the different source command-line flags.
2017-05-12 01:48:19 +00:00
func getDatasources ( datasources [ ] string ) [ ] datasource . Datasource {
2015-02-19 03:05:23 +00:00
dss := make ( [ ] datasource . Datasource , 0 , 5 )
2015-02-20 03:05:17 +00:00
2017-05-12 01:48:19 +00:00
for _ , ds := range datasources {
2015-02-20 16:18:55 +00:00
parts := strings . SplitN ( ds , ":" , 2 )
2015-02-20 03:05:17 +00:00
2017-03-06 23:49:28 +00:00
root := ""
if len ( parts ) > 1 {
root = parts [ 1 ]
}
2015-02-20 03:05:17 +00:00
switch parts [ 0 ] {
2017-05-12 01:48:19 +00:00
case "*" :
2017-06-17 12:56:25 +00:00
dss = append ( dss , getDatasources ( [ ] string { "configdrive" , "vmware" , "ec2" , "digitalocean" , "packet" , "gce" } ) ... )
2015-02-20 03:05:17 +00:00
case "ec2" :
2017-03-10 03:40:15 +00:00
dss = append ( dss , ec2 . NewDatasource ( root ) )
2015-02-20 03:05:17 +00:00
case "file" :
2017-03-06 23:49:28 +00:00
if root != "" {
dss = append ( dss , file . NewDatasource ( root ) )
2015-02-20 03:05:17 +00:00
}
case "url" :
2017-03-10 03:40:15 +00:00
if root != "" {
dss = append ( dss , url . NewDatasource ( root ) )
2015-02-20 03:05:17 +00:00
}
case "cmdline" :
2017-03-10 03:40:15 +00:00
if len ( parts ) == 1 {
dss = append ( dss , proccmdline . NewDatasource ( ) )
2015-02-20 03:05:17 +00:00
}
case "configdrive" :
2017-05-12 01:48:19 +00:00
if root == "" {
root = "/media/config-2"
2015-02-20 03:05:17 +00:00
}
2017-05-12 01:48:19 +00:00
dss = append ( dss , configdrive . NewDatasource ( root ) )
2015-03-29 09:37:31 +00:00
case "digitalocean" :
2017-03-10 03:40:15 +00:00
// TODO: should we enableDoLinkLocal() - to avoid the need for the other kernel/oem options?
dss = append ( dss , digitalocean . NewDatasource ( root ) )
2015-04-03 22:01:19 +00:00
case "gce" :
2017-03-10 03:40:15 +00:00
dss = append ( dss , gce . NewDatasource ( root ) )
2015-12-20 05:37:57 +00:00
case "packet" :
2017-03-06 23:49:28 +00:00
dss = append ( dss , packet . NewDatasource ( root ) )
2017-06-17 12:56:25 +00:00
case "vmware" :
dss = append ( dss , vmware . NewDatasource ( root ) )
2015-02-20 03:05:17 +00:00
}
2015-02-19 03:05:23 +00:00
}
2015-02-20 03:05:17 +00:00
2015-02-19 03:05:23 +00:00
return dss
}
2015-03-29 09:37:31 +00:00
func enableDoLinkLocal ( ) {
2017-03-07 03:21:14 +00:00
err := netconf . ApplyNetworkConfigs ( & netconf . NetworkConfig {
Interfaces : map [ string ] netconf . InterfaceConfig {
2015-03-29 09:37:31 +00:00
"eth0" : {
IPV4LL : true ,
} ,
} ,
2017-05-19 11:41:09 +00:00
} , false , false )
2015-03-29 09:37:31 +00:00
if err != nil {
log . Errorf ( "Failed to apply link local on eth0: %v" , err )
}
}
2015-02-19 03:05:23 +00:00
// selectDatasource attempts to choose a valid Datasource to use based on its
// current availability. The first Datasource to report to be available is
// returned. Datasources will be retried if possible if they are not
// immediately available. If all Datasources are permanently unavailable or
// datasourceTimeout is reached before one becomes available, nil is returned.
func selectDatasource ( sources [ ] datasource . Datasource ) datasource . Datasource {
ds := make ( chan datasource . Datasource )
stop := make ( chan struct { } )
var wg sync . WaitGroup
for _ , s := range sources {
wg . Add ( 1 )
go func ( s datasource . Datasource ) {
defer wg . Done ( )
duration := datasourceInterval
for {
2017-03-01 05:12:56 +00:00
log . Infof ( "cloud-init: Checking availability of %q\n" , s . Type ( ) )
2015-02-19 03:05:23 +00:00
if s . IsAvailable ( ) {
2017-03-02 01:52:29 +00:00
log . Infof ( "cloud-init: Datasource available: %s" , s )
2015-02-19 03:05:23 +00:00
ds <- s
return
2017-03-01 05:12:56 +00:00
}
if ! s . AvailabilityChanges ( ) {
2017-03-02 01:52:29 +00:00
log . Infof ( "cloud-init: Datasource unavailable, skipping: %s" , s )
2015-02-19 03:05:23 +00:00
return
}
2017-03-02 01:52:29 +00:00
log . Errorf ( "cloud-init: Datasource not ready, will retry: %s" , s )
2015-02-19 03:05:23 +00:00
select {
case <- stop :
return
case <- time . After ( duration ) :
duration = pkg . ExpBackoff ( duration , datasourceMaxInterval )
}
}
} ( s )
}
done := make ( chan struct { } )
go func ( ) {
wg . Wait ( )
close ( done )
} ( )
var s datasource . Datasource
select {
case s = <- ds :
2017-03-02 01:52:29 +00:00
err := fetchAndSave ( s )
if err != nil {
log . Errorf ( "Error fetching cloud-init datasource(%s): %s" , s , err )
}
2015-02-19 03:05:23 +00:00
case <- done :
case <- time . After ( datasourceTimeout ) :
}
close ( stop )
return s
}
2015-04-16 06:03:27 +00:00
func isCompose ( content string ) bool {
return strings . HasPrefix ( content , "#compose\n" )
}
2017-03-14 02:11:24 +00:00
func composeToCloudConfig ( bytes [ ] byte ) ( [ ] byte , error ) {
2015-04-16 06:03:27 +00:00
compose := make ( map [ interface { } ] interface { } )
2015-04-16 05:57:59 +00:00
err := yaml . Unmarshal ( bytes , & compose )
2015-04-16 06:03:27 +00:00
if err != nil {
return nil , err
}
2015-04-17 13:18:33 +00:00
return yaml . Marshal ( map [ interface { } ] interface { } {
"rancher" : map [ interface { } ] interface { } {
"services" : compose ,
} ,
} )
2015-04-16 06:03:27 +00:00
}