2014-03-04 16:36:05 -08:00
package main
import (
"flag"
2014-03-18 09:00:41 -07:00
"fmt"
"os"
2014-06-26 15:17:53 -07:00
"sync"
"time"
2014-03-04 16:36:05 -08:00
2014-03-18 09:00:41 -07:00
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/initialize"
2014-06-26 15:17:53 -07:00
"github.com/coreos/coreos-cloudinit/pkg"
2014-03-18 09:00:41 -07:00
"github.com/coreos/coreos-cloudinit/system"
2014-03-04 16:36:05 -08:00
)
2014-06-26 15:17:53 -07:00
const (
2014-06-27 14:56:56 -07:00
version = "0.8.1"
2014-06-26 15:17:53 -07:00
datasourceInterval = 100 * time . Millisecond
datasourceMaxInterval = 30 * time . Second
datasourceTimeout = 5 * time . Minute
)
2014-03-04 17:01:58 -08:00
2014-06-24 13:18:35 -07:00
var (
printVersion bool
ignoreFailure bool
sources struct {
2014-06-24 17:56:05 -07:00
file string
configDrive string
metadataService bool
url string
procCmdLine bool
2014-06-24 13:18:35 -07:00
}
convertNetconf string
workspace string
sshKeyName string
)
2014-03-04 17:01:58 -08:00
2014-06-24 13:18:35 -07:00
func init ( ) {
flag . BoolVar ( & printVersion , "version" , false , "Print the version and exit" )
2014-03-18 20:47:20 -07:00
flag . BoolVar ( & ignoreFailure , "ignore-failure" , false , "Exits with 0 status in the event of malformed input from user-data" )
2014-06-24 13:18:35 -07:00
flag . StringVar ( & sources . file , "from-file" , "" , "Read user-data from provided file" )
2014-06-20 21:11:57 -07:00
flag . StringVar ( & sources . configDrive , "from-configdrive" , "" , "Read data from provided cloud-drive directory" )
2014-06-24 17:56:05 -07:00
flag . BoolVar ( & sources . metadataService , "from-metadata-service" , false , "Download data from metadata service" )
2014-06-24 13:18:35 -07:00
flag . StringVar ( & sources . url , "from-url" , "" , "Download user-data from provided url" )
flag . BoolVar ( & sources . procCmdLine , "from-proc-cmdline" , false , fmt . Sprintf ( "Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>" , datasource . ProcCmdlineLocation , datasource . ProcCmdlineCloudConfigFlag ) )
2014-05-22 15:00:41 -07:00
flag . StringVar ( & convertNetconf , "convert-netconf" , "" , "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files (requires the -from-configdrive flag)" )
2014-03-04 16:36:05 -08:00
flag . StringVar ( & workspace , "workspace" , "/var/lib/coreos-cloudinit" , "Base directory coreos-cloudinit should use to store data" )
2014-03-18 09:00:41 -07:00
flag . StringVar ( & sshKeyName , "ssh-key-name" , initialize . DefaultSSHKeyName , "Add SSH keys to the system with the given name" )
2014-06-24 13:18:35 -07:00
}
2014-03-05 14:30:38 -08:00
2014-06-24 13:18:35 -07:00
func main ( ) {
2014-03-04 16:36:05 -08:00
flag . Parse ( )
2014-06-24 13:18:35 -07:00
die := func ( ) {
if ignoreFailure {
os . Exit ( 0 )
}
os . Exit ( 1 )
}
2014-03-04 17:01:58 -08:00
if printVersion == true {
fmt . Printf ( "coreos-cloudinit version %s\n" , version )
os . Exit ( 0 )
}
2014-06-25 14:28:11 -07:00
if convertNetconf != "" && sources . configDrive == "" {
fmt . Println ( "-convert-netconf flag requires -from-configdrive" )
2014-05-22 15:00:41 -07:00
os . Exit ( 1 )
}
switch convertNetconf {
case "" :
case "debian" :
default :
fmt . Printf ( "Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n" , convertNetconf )
2014-03-04 17:06:52 -08:00
os . Exit ( 1 )
2014-03-04 16:36:05 -08:00
}
2014-06-26 15:17:53 -07:00
dss := getDatasources ( )
if len ( dss ) == 0 {
fmt . Println ( "Provide at least one of --from-file, --from-configdrive, --from-metadata-service, --from-url or --from-proc-cmdline" )
2014-06-24 13:18:35 -07:00
os . Exit ( 1 )
}
2014-06-26 15:17:53 -07:00
ds := selectDatasource ( dss )
if ds == nil {
fmt . Println ( "No datasources available in time" )
die ( )
}
2014-05-22 15:00:41 -07:00
fmt . Printf ( "Fetching user-data from datasource of type %q\n" , ds . Type ( ) )
2014-06-18 11:58:18 -07:00
userdataBytes , err := ds . FetchUserdata ( )
2014-03-18 09:00:41 -07:00
if err != nil {
2014-05-22 15:00:41 -07:00
fmt . Printf ( "Failed fetching user-data from datasource: %v\n" , err )
2014-06-24 13:18:35 -07:00
die ( )
2014-03-18 09:00:41 -07:00
}
2014-06-18 12:08:10 -07: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-06-24 13:18:35 -07:00
die ( )
2014-06-18 12:08:10 -07:00
}
2014-06-24 17:46:06 -07: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 )
die ( )
}
}
env := initialize . NewEnvironment ( "/" , ds . ConfigRoot ( ) , workspace , convertNetconf , sshKeyName , subs )
2014-06-24 17:39:48 -07:00
if len ( metadataBytes ) > 0 {
if err := processMetadata ( string ( metadataBytes ) , env ) ; err != nil {
fmt . Printf ( "Failed to process meta-data: %v\n" , err )
die ( )
}
} else {
fmt . Println ( "No meta-data to handle." )
}
2014-05-22 14:02:10 -07:00
if len ( userdataBytes ) > 0 {
if err := processUserdata ( string ( userdataBytes ) , env ) ; err != nil {
2014-06-18 12:08:10 -07:00
fmt . Printf ( "Failed to process user-data: %v\n" , err )
2014-05-22 14:02:10 -07:00
if ! ignoreFailure {
2014-06-24 13:18:35 -07:00
die ( )
2014-05-22 14:02:10 -07:00
}
}
} else {
2014-06-18 12:08:10 -07:00
fmt . Println ( "No user-data to handle." )
2014-03-13 21:12:14 -07:00
}
2014-05-22 14:02:10 -07:00
}
2014-03-21 10:35:18 -07:00
2014-06-26 15:17:53 -07:00
func getDatasources ( ) [ ] datasource . Datasource {
dss := make ( [ ] datasource . Datasource , 0 , 5 )
2014-06-24 13:18:35 -07:00
if sources . file != "" {
2014-06-26 15:17:53 -07:00
dss = append ( dss , datasource . NewLocalFile ( sources . file ) )
2014-06-24 13:18:35 -07:00
}
if sources . url != "" {
2014-06-26 15:17:53 -07:00
dss = append ( dss , datasource . NewRemoteFile ( sources . url ) )
2014-06-24 13:18:35 -07:00
}
if sources . configDrive != "" {
2014-06-26 15:17:53 -07:00
dss = append ( dss , datasource . NewConfigDrive ( sources . configDrive ) )
2014-06-24 13:18:35 -07:00
}
2014-06-24 17:56:05 -07:00
if sources . metadataService {
2014-06-26 15:17:53 -07:00
dss = append ( dss , datasource . NewMetadataService ( ) )
2014-06-20 21:11:57 -07:00
}
2014-06-24 13:18:35 -07:00
if sources . procCmdLine {
2014-06-26 15:17:53 -07:00
dss = append ( dss , datasource . NewProcCmdline ( ) )
2014-06-24 13:18:35 -07:00
}
2014-06-26 15:17:53 -07:00
return dss
}
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
case <- time . Tick ( 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 :
case <- done :
case <- time . Tick ( datasourceTimeout ) :
2014-06-24 13:18:35 -07:00
}
2014-06-26 15:17:53 -07:00
close ( stop )
return s
2014-06-24 13:18:35 -07:00
}
2014-05-22 14:02:10 -07:00
func processUserdata ( userdata string , env * initialize . Environment ) error {
2014-03-21 10:35:18 -07:00
userdata = env . Apply ( userdata )
2014-04-21 13:31:23 -07:00
parsed , err := initialize . ParseUserData ( userdata )
2014-03-04 16:36:05 -08:00
if err != nil {
2014-05-22 15:00:41 -07:00
fmt . Printf ( "Failed parsing user-data: %v\n" , err )
2014-05-22 14:02:10 -07:00
return err
2014-03-04 16:36:05 -08:00
}
2014-03-18 09:00:41 -07:00
err = initialize . PrepWorkspace ( env . Workspace ( ) )
2014-03-04 16:36:05 -08:00
if err != nil {
2014-05-22 15:00:41 -07:00
fmt . Printf ( "Failed preparing workspace: %v\n" , err )
return err
2014-03-04 16:36:05 -08:00
}
switch t := parsed . ( type ) {
2014-03-18 09:00:41 -07:00
case initialize . CloudConfig :
err = initialize . Apply ( t , env )
case system . Script :
2014-03-04 16:36:05 -08:00
var path string
2014-03-18 09:00:41 -07:00
path , err = initialize . PersistScriptInWorkspace ( t , env . Workspace ( ) )
2014-03-04 16:36:05 -08:00
if err == nil {
2014-03-04 21:02:20 -08:00
var name string
2014-03-18 09:00:41 -07:00
name , err = system . ExecuteScript ( path )
2014-05-22 14:02:10 -07:00
initialize . PersistUnitNameInWorkspace ( name , env . Workspace ( ) )
2014-03-04 16:36:05 -08:00
}
}
2014-05-22 14:02:10 -07:00
return err
2014-03-04 16:36:05 -08:00
}
2014-05-22 15:00:41 -07:00
2014-06-18 12:08:10 -07:00
func processMetadata ( metadata string , env * initialize . Environment ) error {
parsed , err := initialize . ParseMetaData ( metadata )
2014-05-22 15:00:41 -07:00
if err != nil {
2014-06-18 12:08:10 -07:00
fmt . Printf ( "Failed parsing meta-data: %v\n" , err )
2014-05-22 15:00:41 -07:00
return err
}
2014-06-18 12:08:10 -07:00
err = initialize . PrepWorkspace ( env . Workspace ( ) )
2014-05-22 15:00:41 -07:00
if err != nil {
2014-06-18 12:08:10 -07:00
fmt . Printf ( "Failed preparing workspace: %v\n" , err )
2014-05-22 15:00:41 -07:00
return err
}
2014-06-18 12:08:10 -07:00
return initialize . Apply ( parsed , env )
2014-03-04 16:36:05 -08:00
}