1
0
mirror of https://github.com/rancher/os.git synced 2025-09-02 15:24:32 +00:00

Refactor cloud-init

This commit is contained in:
Darren Shepherd
2015-02-19 20:05:17 -07:00
parent c2a8de2b5f
commit e211279636
4 changed files with 71 additions and 193 deletions

View File

@@ -1,4 +1,5 @@
// Copyright 2015 CoreOS, Inc.
// Copyright 2015 Rancher Labs, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,193 +20,100 @@ import (
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
"time"
log "github.com/Sirupsen/logrus"
"github.com/coreos/coreos-cloudinit/config"
"github.com/coreos/coreos-cloudinit/config/validate"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
"github.com/coreos/coreos-cloudinit/datasource/file"
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
"github.com/coreos/coreos-cloudinit/datasource/url"
"github.com/coreos/coreos-cloudinit/datasource/waagent"
"github.com/coreos/coreos-cloudinit/initialize"
"github.com/coreos/coreos-cloudinit/network"
"github.com/coreos/coreos-cloudinit/pkg"
"github.com/coreos/coreos-cloudinit/system"
rancherConfig "github.com/rancherio/os/config"
"gopkg.in/yaml.v2"
)
const (
version = "1.3.2+git"
datasourceInterval = 100 * time.Millisecond
datasourceMaxInterval = 30 * time.Second
datasourceTimeout = 5 * time.Minute
)
var (
flags = struct {
printVersion bool
ignoreFailure bool
sources struct {
file string
configDrive string
waagent string
metadataService bool
ec2MetadataService string
cloudSigmaMetadataService bool
digitalOceanMetadataService string
url string
procCmdLine bool
}
convertNetconf string
workspace string
sshKeyName string
oem string
validate bool
preInit bool
outDir string
}{}
outputDir string
outputFile string
save bool
sshKeyName string
)
func init() {
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")
flag.StringVar(&flags.sources.waagent, "from-waagent", "", "Read data from provided waagent directory")
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")
flag.BoolVar(&flags.validate, "validate", false, "[EXPERIMENTAL] Validate the user-data but do not apply it to the system")
flag.BoolVar(&flags.preInit, "preinit", true, "Does not initialize the cloud config. Just reads, validates and saves the merged file to disk")
flag.StringVar(&flags.outDir, "outdir", "/var/lib/rancher/", "output directory to store cloud-config")
flag.StringVar(&outputDir, "dir", "/var/lib/rancher/conf", "working directory")
flag.StringVar(&outputFile, "file", "/var/lib/rancher/conf/cloud-config.yml", "cloud config file name")
flag.StringVar(&sshKeyName, "ssh-key-name", "rancheros-cloud-config", "SSH key name")
flag.BoolVar(&save, "save", false, "save cloud config and exit")
}
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",
},
"azure": oemConfig{
"from-waagent": "/var/lib/waagent",
},
"cloudsigma": oemConfig{
"from-cloudsigma-metadata": "true",
},
}
)
func Main() {
failure := false
flag.Parse()
if !flags.preInit && flags.sources.file == "" {
fmt.Println("specify either --preinit or provide a filename to load config from")
os.Exit(2)
cfg, err := rancherConfig.LoadConfig()
if err != nil {
log.Fatalf("Failed to read rancher config %v", err)
}
if c, ok := oemConfigs[flags.oem]; ok {
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)
}
if flags.printVersion == true {
fmt.Printf("coreos-cloudinit version %s\n", version)
os.Exit(0)
}
switch flags.convertNetconf {
case "":
case "debian":
case "digitalocean":
default:
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", flags.convertNetconf)
os.Exit(2)
}
dss := getDatasources()
dss := getDatasources(cfg)
if len(dss) == 0 {
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-url or --from-proc-cmdline")
os.Exit(2)
os.Exit(0)
}
ds := selectDatasource(dss)
if ds == nil {
fmt.Println("No datasources available in time")
os.Exit(1)
log.Info("No datasources found")
os.Exit(0)
}
fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
log.Info("Fetching user-data from datasource %s", ds.Type())
userdataBytes, err := ds.FetchUserdata()
if err != nil {
fmt.Printf("Failed fetching user-data from datasource: %v\nContinuing...\n", err)
failure = true
log.Fatalf("Failed fetching user-data from datasource: %v", err)
}
if report, err := validate.Validate(userdataBytes); err == nil {
ret := 0
fail := false
for _, e := range report.Entries() {
fmt.Println(e)
ret = 1
log.Error(e)
fail = true
}
if flags.validate {
os.Exit(ret)
}
} else {
fmt.Printf("Failed while validating user_data (%q)\n", err)
if flags.validate {
if fail {
os.Exit(1)
}
} else {
log.Fatalf("Failed while validating user_data (%v)", err)
}
fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
fmt.Printf("Fetching meta-data from datasource of type %v", ds.Type())
metadata, err := ds.FetchMetadata()
if err != nil {
fmt.Printf("Failed fetching meta-data from datasource: %v\n", err)
fmt.Printf("Failed fetching meta-data from datasource: %v", err)
os.Exit(1)
}
// Apply environment to user-data
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, metadata)
env := initialize.NewEnvironment("/", ds.ConfigRoot(), outputDir, sshKeyName, metadata)
userdata := env.Apply(string(userdataBytes))
var ccu *config.CloudConfig
var script *config.Script
if ud, err := initialize.ParseUserData(userdata); err != nil {
fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err)
failure = true
log.Fatalf("Failed to parse user-data: %v\n", err)
} else {
switch t := ud.(type) {
case *config.CloudConfig:
@@ -218,68 +126,35 @@ func Main() {
fmt.Println("Merging cloud-config from meta-data and user-data")
cc := mergeConfigs(ccu, metadata)
if flags.preInit {
if save {
var fileData []byte
if script != nil {
fileData = userdataBytes
} else {
if data, err := yaml.Marshal(cc); err != nil {
fmt.Println("Error while marshalling cloud config ", err.Error())
os.Exit(1)
log.Fatalf("Error while marshalling cloud config %v", err)
} else {
fileData = data
}
}
outDir := flags.outDir
if len(outDir) > 0 && outDir[len(outDir)-1:] != "/" {
outDir = outDir + "/"
}
outFile := outDir + "cloud-config"
if err := ioutil.WriteFile(outFile, fileData, 444); err != nil {
fmt.Println("Error while writing file ", err.Error())
os.Exit(1)
if err := ioutil.WriteFile(outputFile, fileData, 400); err != nil {
log.Fatalf("Error while writing file %v", err)
}
os.Exit(0)
}
var ifaces []network.InterfaceGenerator
if flags.convertNetconf != "" {
var err error
switch flags.convertNetconf {
case "debian":
ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig)
case "digitalocean":
ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig)
default:
err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf)
}
if err != nil {
fmt.Printf("Failed to generate interfaces: %v\n", err)
os.Exit(1)
}
}
if err = initialize.Apply(cc, ifaces, env); err != nil {
fmt.Printf("Failed to apply cloud-config: %v\n", err)
os.Exit(1)
if err = initialize.Apply(cc, []network.InterfaceGenerator{}, env); err != nil {
log.Fatalf("Failed to apply cloud-config: %v", err)
}
if script != nil {
if err = runScript(*script, env); err != nil {
fmt.Printf("Failed to run script: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to run script: %v", err)
}
}
if failure && !flags.ignoreFailure {
os.Exit(1)
}
}
// mergeConfigs merges certain options from md (meta-data from the datasource)
@@ -305,35 +180,38 @@ func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.Cl
// getDatasources creates a slice of possible Datasources for cloudinit based
// on the different source command-line flags.
func getDatasources() []datasource.Datasource {
func getDatasources(cfg *rancherConfig.Config) []datasource.Datasource {
dss := make([]datasource.Datasource, 0, 5)
if flags.sources.file != "" {
dss = append(dss, file.NewDatasource(flags.sources.file))
}
if flags.sources.url != "" {
dss = append(dss, url.NewDatasource(flags.sources.url))
}
if flags.sources.configDrive != "" {
dss = append(dss, configdrive.NewDatasource(flags.sources.configDrive))
}
if flags.sources.metadataService {
dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
}
if flags.sources.ec2MetadataService != "" {
dss = append(dss, ec2.NewDatasource(flags.sources.ec2MetadataService))
}
if flags.sources.cloudSigmaMetadataService {
dss = append(dss, cloudsigma.NewServerContextService())
}
if flags.sources.digitalOceanMetadataService != "" {
dss = append(dss, digitalocean.NewDatasource(flags.sources.digitalOceanMetadataService))
}
if flags.sources.waagent != "" {
dss = append(dss, waagent.NewDatasource(flags.sources.waagent))
}
if flags.sources.procCmdLine {
dss = append(dss, proc_cmdline.NewDatasource())
for _, ds := range cfg.CloudInit.Datasources {
parts := strings.SplitN(ds, ":", 1)
switch parts[0] {
case "ec2":
if len(parts) == 1 {
dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
} else {
dss = append(dss, ec2.NewDatasource(parts[1]))
}
case "file":
if len(parts) == 2 {
dss = append(dss, file.NewDatasource(parts[1]))
}
case "url":
if len(parts) == 2 {
dss = append(dss, url.NewDatasource(parts[1]))
}
case "cmdline":
if len(parts) == 2 {
dss = append(dss, proc_cmdline.NewDatasource())
}
case "configdrive":
if len(parts) == 2 {
dss = append(dss, configdrive.NewDatasource(parts[1]))
}
}
}
return dss
}