2014-10-18 02:36:22 +04:00
/ *
Copyright 2014 CoreOS , Inc .
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 .
* /
2014-03-05 04:36:05 +04:00
package main
import (
"flag"
2014-03-18 20:00:41 +04:00
"fmt"
"os"
2014-06-27 02:17:53 +04:00
"sync"
"time"
2014-03-05 04:36:05 +04:00
2014-09-22 03:46:58 +04:00
"github.com/coreos/coreos-cloudinit/config"
2014-09-16 02:38:31 +04:00
"github.com/coreos/coreos-cloudinit/config/validate"
2014-03-18 20:00:41 +04:00
"github.com/coreos/coreos-cloudinit/datasource"
2014-07-31 00:56:36 +04:00
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
"github.com/coreos/coreos-cloudinit/datasource/file"
2014-08-05 13:50:21 +04:00
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
2014-08-16 05:14:34 +04:00
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
2014-07-31 01:23:10 +04:00
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
2014-07-31 00:56:36 +04:00
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
"github.com/coreos/coreos-cloudinit/datasource/url"
2014-10-10 22:12:57 +04:00
"github.com/coreos/coreos-cloudinit/datasource/waagent"
2014-03-18 20:00:41 +04:00
"github.com/coreos/coreos-cloudinit/initialize"
2014-06-27 02:17:53 +04:00
"github.com/coreos/coreos-cloudinit/pkg"
2014-03-18 20:00:41 +04:00
"github.com/coreos/coreos-cloudinit/system"
2014-03-05 04:36:05 +04:00
)
2014-06-27 02:17:53 +04:00
const (
2014-11-27 01:19:29 +03:00
version = "1.0.0+git"
2014-06-27 02:17:53 +04:00
datasourceInterval = 100 * time . Millisecond
datasourceMaxInterval = 30 * time . Second
datasourceTimeout = 5 * time . Minute
)
2014-03-05 05:01:58 +04:00
2014-06-25 00:18:35 +04:00
var (
2014-09-11 05:53:46 +04:00
flags = struct {
printVersion bool
ignoreFailure bool
sources struct {
file string
configDrive string
2014-10-10 22:12:57 +04:00
waagent string
2014-09-11 05:53:46 +04:00
metadataService bool
ec2MetadataService string
cloudSigmaMetadataService bool
digitalOceanMetadataService string
url string
procCmdLine bool
}
convertNetconf string
workspace string
sshKeyName string
oem string
2014-09-16 02:38:31 +04:00
validate bool
2014-09-11 05:53:46 +04:00
} { }
2014-06-25 00:18:35 +04:00
)
2014-03-05 05:01:58 +04:00
2014-06-25 00:18:35 +04:00
func init ( ) {
2014-09-11 05:53:46 +04:00
flag . BoolVar ( & flags . printVersion , "version" , false , "Print the version and exit" )
flag . BoolVar ( & flags . ignoreFailure , "ignore-failure" , false , "Exits with 0 status in the event of malformed input from user-data" )
flag . StringVar ( & flags . sources . file , "from-file" , "" , "Read user-data from provided file" )
flag . StringVar ( & flags . sources . configDrive , "from-configdrive" , "" , "Read data from provided cloud-drive directory" )
2014-10-10 22:12:57 +04:00
flag . StringVar ( & flags . sources . waagent , "from-waagent" , "" , "Read data from provided waagent directory" )
2014-09-11 05:53:46 +04:00
flag . BoolVar ( & flags . sources . metadataService , "from-metadata-service" , false , "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service" )
flag . StringVar ( & flags . sources . ec2MetadataService , "from-ec2-metadata" , "" , "Download EC2 data from the provided url" )
flag . BoolVar ( & flags . sources . cloudSigmaMetadataService , "from-cloudsigma-metadata" , false , "Download data from CloudSigma server context" )
flag . StringVar ( & flags . sources . digitalOceanMetadataService , "from-digitalocean-metadata" , "" , "Download DigitalOcean data from the provided url" )
flag . StringVar ( & flags . sources . url , "from-url" , "" , "Download user-data from provided url" )
flag . BoolVar ( & flags . sources . procCmdLine , "from-proc-cmdline" , false , fmt . Sprintf ( "Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>" , proc_cmdline . ProcCmdlineLocation , proc_cmdline . ProcCmdlineCloudConfigFlag ) )
flag . StringVar ( & flags . oem , "oem" , "" , "Use the settings specific to the provided OEM" )
flag . StringVar ( & flags . convertNetconf , "convert-netconf" , "" , "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files" )
flag . StringVar ( & flags . workspace , "workspace" , "/var/lib/coreos-cloudinit" , "Base directory coreos-cloudinit should use to store data" )
flag . StringVar ( & flags . sshKeyName , "ssh-key-name" , initialize . DefaultSSHKeyName , "Add SSH keys to the system with the given name" )
2014-09-16 02:38:31 +04:00
flag . BoolVar ( & flags . validate , "validate" , false , "[EXPERIMENTAL] Validate the user-data but do not apply it to the system" )
2014-06-25 00:18:35 +04:00
}
2014-03-06 02:30:38 +04:00
2014-09-11 05:46:38 +04:00
type oemConfig map [ string ] string
var (
oemConfigs = map [ string ] oemConfig {
"digitalocean" : oemConfig {
"from-digitalocean-metadata" : "http://169.254.169.254/" ,
"convert-netconf" : "digitalocean" ,
} ,
"ec2-compat" : oemConfig {
"from-ec2-metadata" : "http://169.254.169.254/" ,
"from-configdrive" : "/media/configdrive" ,
} ,
"rackspace-onmetal" : oemConfig {
"from-configdrive" : "/media/configdrive" ,
"convert-netconf" : "debian" ,
} ,
2014-10-10 22:12:57 +04:00
"azure" : oemConfig {
"from-waagent" : "/var/lib/waagent" ,
} ,
2014-09-11 05:46:38 +04:00
}
)
2014-06-25 00:18:35 +04:00
func main ( ) {
2014-09-11 01:56:05 +04:00
failure := false
2014-03-05 04:36:05 +04:00
2014-09-11 01:56:05 +04:00
flag . Parse ( )
2014-06-25 00:18:35 +04:00
2014-09-11 05:53:46 +04:00
if c , ok := oemConfigs [ flags . oem ] ; ok {
2014-09-11 05:46:38 +04:00
for k , v := range c {
flag . Set ( k , v )
}
} else if flags . oem != "" {
oems := make ( [ ] string , 0 , len ( oemConfigs ) )
for k := range oemConfigs {
oems = append ( oems , k )
}
fmt . Printf ( "Invalid option to --oem: %q. Supported options: %q\n" , flags . oem , oems )
os . Exit ( 2 )
}
2014-09-11 05:53:46 +04:00
if flags . printVersion == true {
2014-03-05 05:01:58 +04:00
fmt . Printf ( "coreos-cloudinit version %s\n" , version )
os . Exit ( 0 )
}
2014-09-11 05:53:46 +04:00
switch flags . convertNetconf {
2014-05-23 02:00:41 +04:00
case "" :
case "debian" :
2014-08-16 05:14:34 +04:00
case "digitalocean" :
2014-05-23 02:00:41 +04:00
default :
2014-09-11 05:53:46 +04:00
fmt . Printf ( "Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n" , flags . convertNetconf )
2014-09-11 01:56:05 +04:00
os . Exit ( 2 )
2014-03-05 04:36:05 +04:00
}
2014-06-27 02:17:53 +04:00
dss := getDatasources ( )
if len ( dss ) == 0 {
2014-08-05 13:50:21 +04:00
fmt . Println ( "Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-url or --from-proc-cmdline" )
2014-09-11 01:56:05 +04:00
os . Exit ( 2 )
2014-06-25 00:18:35 +04:00
}
2014-06-27 02:17:53 +04:00
ds := selectDatasource ( dss )
if ds == nil {
fmt . Println ( "No datasources available in time" )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-06-27 02:17:53 +04:00
}
2014-05-23 02:00:41 +04:00
fmt . Printf ( "Fetching user-data from datasource of type %q\n" , ds . Type ( ) )
2014-06-18 22:58:18 +04:00
userdataBytes , err := ds . FetchUserdata ( )
2014-03-18 20:00:41 +04:00
if err != nil {
2014-09-11 01:56:05 +04:00
fmt . Printf ( "Failed fetching user-data from datasource: %v\nContinuing...\n" , err )
failure = true
2014-03-18 20:00:41 +04:00
}
2014-09-16 02:38:31 +04:00
if report , err := validate . Validate ( userdataBytes ) ; err == nil {
ret := 0
for _ , e := range report . Entries ( ) {
fmt . Println ( e )
ret = 1
}
if flags . validate {
os . Exit ( ret )
}
} else {
fmt . Printf ( "Failed while validating user_data (%q)\n" , err )
if flags . validate {
os . Exit ( 1 )
}
}
2014-06-18 23:08:10 +04:00
fmt . Printf ( "Fetching meta-data from datasource of type %q\n" , ds . Type ( ) )
metadataBytes , err := ds . FetchMetadata ( )
if err != nil {
fmt . Printf ( "Failed fetching meta-data from datasource: %v\n" , err )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-06-18 23:08:10 +04:00
}
2014-06-27 04:17:58 +04:00
// Extract IPv4 addresses from metadata if possible
2014-06-25 04:46:06 +04:00
var subs map [ string ] string
if len ( metadataBytes ) > 0 {
subs , err = initialize . ExtractIPsFromMetadata ( metadataBytes )
if err != nil {
fmt . Printf ( "Failed extracting IPs from meta-data: %v\n" , err )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-06-25 04:46:06 +04:00
}
}
2014-06-27 04:17:58 +04:00
2014-07-01 23:08:42 +04:00
// Apply environment to user-data
2014-09-11 05:53:46 +04:00
env := initialize . NewEnvironment ( "/" , ds . ConfigRoot ( ) , flags . workspace , flags . convertNetconf , flags . sshKeyName , subs )
2014-07-01 23:08:42 +04:00
userdata := env . Apply ( string ( userdataBytes ) )
2014-06-25 04:46:06 +04:00
2014-09-22 03:46:58 +04:00
var ccm , ccu * config . CloudConfig
2014-09-27 22:11:57 +04:00
var script * config . Script
2014-06-27 04:17:58 +04:00
if ccm , err = initialize . ParseMetaData ( string ( metadataBytes ) ) ; err != nil {
fmt . Printf ( "Failed to parse meta-data: %v\n" , err )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-06-27 04:17:58 +04:00
}
2014-08-18 23:20:25 +04:00
2014-09-14 08:02:08 +04:00
if ccm != nil && flags . convertNetconf != "" {
2014-08-18 23:20:25 +04:00
fmt . Printf ( "Fetching network config from datasource of type %q\n" , ds . Type ( ) )
netconfBytes , err := ds . FetchNetworkConfig ( ccm . NetworkConfigPath )
if err != nil {
fmt . Printf ( "Failed fetching network config from datasource: %v\n" , err )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-08-18 23:20:25 +04:00
}
ccm . NetworkConfig = string ( netconfBytes )
}
2014-07-01 23:08:42 +04:00
if ud , err := initialize . ParseUserData ( userdata ) ; err != nil {
2014-09-11 01:56:05 +04:00
fmt . Printf ( "Failed to parse user-data: %v\nContinuing...\n" , err )
failure = true
2014-06-27 04:17:58 +04:00
} else {
switch t := ud . ( type ) {
2014-09-22 03:46:58 +04:00
case * config . CloudConfig :
2014-06-27 04:17:58 +04:00
ccu = t
2014-09-27 22:11:57 +04:00
case config . Script :
2014-06-28 10:58:27 +04:00
script = & t
2014-06-25 04:39:48 +04:00
}
2014-06-27 04:17:58 +04:00
}
2014-09-22 03:46:58 +04:00
var cc * config . CloudConfig
2014-06-27 04:17:58 +04:00
if ccm != nil && ccu != nil {
fmt . Println ( "Merging cloud-config from meta-data and user-data" )
2014-07-01 09:02:41 +04:00
merged := mergeCloudConfig ( * ccm , * ccu )
2014-06-27 04:17:58 +04:00
cc = & merged
} else if ccm != nil && ccu == nil {
fmt . Println ( "Processing cloud-config from meta-data" )
cc = ccm
} else if ccm == nil && ccu != nil {
fmt . Println ( "Processing cloud-config from user-data" )
cc = ccu
2014-06-25 04:39:48 +04:00
} else {
2014-06-27 04:17:58 +04:00
fmt . Println ( "No cloud-config data to handle." )
2014-06-25 04:39:48 +04:00
}
2014-06-27 04:17:58 +04:00
if cc != nil {
if err = initialize . Apply ( * cc , env ) ; err != nil {
fmt . Printf ( "Failed to apply cloud-config: %v\n" , err )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-05-23 01:02:10 +04:00
}
2014-03-14 08:12:14 +04:00
}
2014-06-27 04:17:58 +04:00
if script != nil {
2014-06-28 10:58:27 +04:00
if err = runScript ( * script , env ) ; err != nil {
2014-06-27 04:17:58 +04:00
fmt . Printf ( "Failed to run script: %v\n" , err )
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
2014-06-27 04:17:58 +04:00
}
}
2014-09-11 01:56:05 +04:00
2014-09-11 05:53:46 +04:00
if failure && ! flags . ignoreFailure {
2014-09-11 01:56:05 +04:00
os . Exit ( 1 )
}
2014-06-27 04:17:58 +04:00
}
// mergeCloudConfig merges certain options from mdcc (a CloudConfig derived from
// meta-data) onto udcc (a CloudConfig derived from user-data), if they are
// not already set on udcc (i.e. user-data always takes precedence)
// NB: This needs to be kept in sync with ParseMetadata so that it tracks all
// elements of a CloudConfig which that function can populate.
2014-09-22 03:46:58 +04:00
func mergeCloudConfig ( mdcc , udcc config . CloudConfig ) ( cc config . CloudConfig ) {
2014-06-27 04:17:58 +04:00
if mdcc . Hostname != "" {
if udcc . Hostname != "" {
2014-07-31 00:56:36 +04:00
fmt . Printf ( "Warning: user-data hostname (%s) overrides metadata hostname (%s)\n" , udcc . Hostname , mdcc . Hostname )
2014-06-27 04:17:58 +04:00
} else {
udcc . Hostname = mdcc . Hostname
}
}
for _ , key := range mdcc . SSHAuthorizedKeys {
udcc . SSHAuthorizedKeys = append ( udcc . SSHAuthorizedKeys , key )
}
if mdcc . NetworkConfigPath != "" {
if udcc . NetworkConfigPath != "" {
2014-07-31 00:56:36 +04:00
fmt . Printf ( "Warning: user-data NetworkConfigPath %s overrides metadata NetworkConfigPath %s\n" , udcc . NetworkConfigPath , mdcc . NetworkConfigPath )
2014-06-27 04:17:58 +04:00
} else {
udcc . NetworkConfigPath = mdcc . NetworkConfigPath
}
}
2014-08-30 00:18:16 +04:00
if mdcc . NetworkConfig != "" {
if udcc . NetworkConfig != "" {
fmt . Printf ( "Warning: user-data NetworkConfig %s overrides metadata NetworkConfig %s\n" , udcc . NetworkConfig , mdcc . NetworkConfig )
} else {
udcc . NetworkConfig = mdcc . NetworkConfig
}
}
2014-06-27 04:17:58 +04:00
return udcc
2014-05-23 01:02:10 +04:00
}
2014-03-21 21:35:18 +04:00
2014-06-27 04:17:58 +04:00
// getDatasources creates a slice of possible Datasources for cloudinit based
// on the different source command-line flags.
2014-06-27 02:17:53 +04:00
func getDatasources ( ) [ ] datasource . Datasource {
dss := make ( [ ] datasource . Datasource , 0 , 5 )
2014-09-11 05:53:46 +04:00
if flags . sources . file != "" {
dss = append ( dss , file . NewDatasource ( flags . sources . file ) )
2014-06-25 00:18:35 +04:00
}
2014-09-11 05:53:46 +04:00
if flags . sources . url != "" {
dss = append ( dss , url . NewDatasource ( flags . sources . url ) )
2014-06-25 00:18:35 +04:00
}
2014-09-11 05:53:46 +04:00
if flags . sources . configDrive != "" {
dss = append ( dss , configdrive . NewDatasource ( flags . sources . configDrive ) )
2014-06-25 00:18:35 +04:00
}
2014-09-11 05:53:46 +04:00
if flags . sources . metadataService {
2014-07-31 01:23:10 +04:00
dss = append ( dss , ec2 . NewDatasource ( ec2 . DefaultAddress ) )
}
2014-09-11 05:53:46 +04:00
if flags . sources . ec2MetadataService != "" {
dss = append ( dss , ec2 . NewDatasource ( flags . sources . ec2MetadataService ) )
2014-06-21 08:11:57 +04:00
}
2014-09-11 05:53:46 +04:00
if flags . sources . cloudSigmaMetadataService {
2014-08-05 13:50:21 +04:00
dss = append ( dss , cloudsigma . NewServerContextService ( ) )
}
2014-09-11 05:53:46 +04:00
if flags . sources . digitalOceanMetadataService != "" {
dss = append ( dss , digitalocean . NewDatasource ( flags . sources . digitalOceanMetadataService ) )
2014-08-16 05:14:34 +04:00
}
2014-10-10 22:12:57 +04:00
if flags . sources . waagent != "" {
dss = append ( dss , waagent . NewDatasource ( flags . sources . waagent ) )
}
2014-09-11 05:53:46 +04:00
if flags . sources . procCmdLine {
2014-07-31 00:56:36 +04:00
dss = append ( dss , proc_cmdline . NewDatasource ( ) )
2014-06-25 00:18:35 +04:00
}
2014-06-27 02:17:53 +04:00
return dss
}
2014-06-27 04:17:58 +04: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.
2014-06-27 02:17:53 +04:00
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 {
fmt . Printf ( "Checking availability of %q\n" , s . Type ( ) )
if s . IsAvailable ( ) {
ds <- s
return
} else if ! s . AvailabilityChanges ( ) {
return
}
select {
case <- stop :
return
2014-07-03 05:25:47 +04:00
case <- time . After ( duration ) :
2014-06-27 02:17:53 +04:00
duration = pkg . ExpBackoff ( duration , datasourceMaxInterval )
}
}
} ( s )
}
done := make ( chan struct { } )
go func ( ) {
wg . Wait ( )
close ( done )
} ( )
var s datasource . Datasource
select {
case s = <- ds :
case <- done :
2014-07-03 05:25:47 +04:00
case <- time . After ( datasourceTimeout ) :
2014-06-25 00:18:35 +04:00
}
2014-06-27 02:17:53 +04:00
close ( stop )
return s
2014-06-25 00:18:35 +04:00
}
2014-06-27 04:17:58 +04:00
// TODO(jonboulle): this should probably be refactored and moved into a different module
2014-09-27 22:11:57 +04:00
func runScript ( script config . Script , env * initialize . Environment ) error {
2014-06-27 04:17:58 +04:00
err := initialize . PrepWorkspace ( env . Workspace ( ) )
2014-03-05 04:36:05 +04:00
if err != nil {
2014-05-23 02:00:41 +04:00
fmt . Printf ( "Failed preparing workspace: %v\n" , err )
return err
2014-03-05 04:36:05 +04:00
}
2014-06-27 04:17:58 +04:00
path , err := initialize . PersistScriptInWorkspace ( script , env . Workspace ( ) )
if err == nil {
var name string
name , err = system . ExecuteScript ( path )
initialize . PersistUnitNameInWorkspace ( name , env . Workspace ( ) )
2014-03-05 04:36:05 +04:00
}
2014-05-23 01:02:10 +04:00
return err
2014-03-05 04:36:05 +04:00
}