Merge pull request #152 from crawford/metadata

feat(meta_data): Add partial support for meta_data.json
This commit is contained in:
Alex Crawford 2014-06-20 17:47:08 -07:00
commit 5c89afc18a
11 changed files with 152 additions and 69 deletions

View File

@ -1,16 +1,12 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path"
"github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/initialize" "github.com/coreos/coreos-cloudinit/initialize"
"github.com/coreos/coreos-cloudinit/network"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
@ -79,7 +75,7 @@ func main() {
} }
fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type()) fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
userdataBytes, err := ds.Fetch() userdataBytes, err := ds.FetchUserdata()
if err != nil { if err != nil {
fmt.Printf("Failed fetching user-data from datasource: %v\n", err) fmt.Printf("Failed fetching user-data from datasource: %v\n", err)
if ignoreFailure { if ignoreFailure {
@ -89,25 +85,38 @@ func main() {
} }
} }
env := initialize.NewEnvironment("/", workspace) 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)
if ignoreFailure {
os.Exit(0)
} else {
os.Exit(1)
}
}
env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName)
if len(userdataBytes) > 0 { if len(userdataBytes) > 0 {
if err := processUserdata(string(userdataBytes), env); err != nil { if err := processUserdata(string(userdataBytes), env); err != nil {
fmt.Printf("Failed resolving user-data: %v\n", err) fmt.Printf("Failed to process user-data: %v\n", err)
if !ignoreFailure { if !ignoreFailure {
os.Exit(1) os.Exit(1)
} }
} }
} else { } else {
fmt.Println("No user data to handle.") fmt.Println("No user-data to handle.")
} }
if convertNetconf != "" { if len(metadataBytes) > 0 {
if err := processNetconf(convertNetconf, configdrive); err != nil { if err := processMetadata(string(metadataBytes), env); err != nil {
fmt.Printf("Failed to process network config: %v\n", err) fmt.Printf("Failed to process meta-data: %v\n", err)
if !ignoreFailure { if !ignoreFailure {
os.Exit(1) os.Exit(1)
} }
} }
} else {
fmt.Println("No meta-data to handle.")
} }
} }
@ -142,47 +151,17 @@ func processUserdata(userdata string, env *initialize.Environment) error {
return err return err
} }
func processNetconf(convertNetconf, configdrive string) error { func processMetadata(metadata string, env *initialize.Environment) error {
openstackRoot := path.Join(configdrive, "openstack") parsed, err := initialize.ParseMetaData(metadata)
metadataFilename := path.Join(openstackRoot, "latest", "meta_data.json")
metadataBytes, err := ioutil.ReadFile(metadataFilename)
if err != nil { if err != nil {
fmt.Printf("Failed parsing meta-data: %v\n", err)
return err return err
} }
err = initialize.PrepWorkspace(env.Workspace())
var metadata struct {
NetworkConfig struct {
ContentPath string `json:"content_path"`
} `json:"network_config"`
}
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
return err
}
configPath := metadata.NetworkConfig.ContentPath
if configPath == "" {
fmt.Printf("No network config specified in %q.\n", metadataFilename)
return nil
}
netconfBytes, err := ioutil.ReadFile(path.Join(openstackRoot, configPath))
if err != nil { if err != nil {
fmt.Printf("Failed preparing workspace: %v\n", err)
return err return err
} }
var interfaces []network.InterfaceGenerator return initialize.Apply(parsed, env)
switch convertNetconf {
case "debian":
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
default:
return fmt.Errorf("Unsupported network config format %q", convertNetconf)
}
if err != nil {
return err
}
if err := system.WriteNetworkdConfigs(interfaces); err != nil {
return err
}
return system.RestartNetwork(interfaces)
} }

View File

@ -7,21 +7,33 @@ import (
) )
type configDrive struct { type configDrive struct {
path string root string
} }
func NewConfigDrive(path string) *configDrive { func NewConfigDrive(root string) *configDrive {
return &configDrive{path} return &configDrive{path.Join(root, "openstack")}
} }
func (self *configDrive) Fetch() ([]byte, error) { func (self *configDrive) ConfigRoot() string {
data, err := ioutil.ReadFile(path.Join(self.path, "openstack", "latest", "user_data")) return self.root
if os.IsNotExist(err) { }
err = nil
} func (self *configDrive) FetchMetadata() ([]byte, error) {
return data, err return self.readFile("meta_data.json")
}
func (self *configDrive) FetchUserdata() ([]byte, error) {
return self.readFile("user_data")
} }
func (self *configDrive) Type() string { func (self *configDrive) Type() string {
return "cloud-drive" return "cloud-drive"
} }
func (self *configDrive) readFile(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(path.Join(self.root, "latest", filename))
if os.IsNotExist(err) {
err = nil
}
return data, err
}

View File

@ -1,6 +1,8 @@
package datasource package datasource
type Datasource interface { type Datasource interface {
Fetch() ([]byte, error) ConfigRoot() string
FetchMetadata() ([]byte, error)
FetchUserdata() ([]byte, error)
Type() string Type() string
} }

View File

@ -12,7 +12,15 @@ func NewLocalFile(path string) *localFile {
return &localFile{path} return &localFile{path}
} }
func (self *localFile) Fetch() ([]byte, error) { func (self *localFile) ConfigRoot() string {
return ""
}
func (self *localFile) FetchMetadata() ([]byte, error) {
return []byte{}, nil
}
func (self *localFile) FetchUserdata() ([]byte, error) {
return ioutil.ReadFile(self.path) return ioutil.ReadFile(self.path)
} }

View File

@ -10,7 +10,15 @@ func NewMetadataService(url string) *metadataService {
return &metadataService{url} return &metadataService{url}
} }
func (ms *metadataService) Fetch() ([]byte, error) { func (self *metadataService) ConfigRoot() string {
return ""
}
func (self *metadataService) FetchMetadata() ([]byte, error) {
return []byte{}, nil
}
func (ms *metadataService) FetchUserdata() ([]byte, error) {
client := pkg.NewHttpClient() client := pkg.NewHttpClient()
return client.Get(ms.url) return client.Get(ms.url)
} }

View File

@ -14,7 +14,7 @@ const (
ProcCmdlineCloudConfigFlag = "cloud-config-url" ProcCmdlineCloudConfigFlag = "cloud-config-url"
) )
type procCmdline struct{ type procCmdline struct {
Location string Location string
} }
@ -22,7 +22,15 @@ func NewProcCmdline() *procCmdline {
return &procCmdline{Location: ProcCmdlineLocation} return &procCmdline{Location: ProcCmdlineLocation}
} }
func (self *procCmdline) Fetch() ([]byte, error) { func (self *procCmdline) ConfigRoot() string {
return ""
}
func (self *procCmdline) FetchMetadata() ([]byte, error) {
return []byte{}, nil
}
func (self *procCmdline) FetchUserdata() ([]byte, error) {
contents, err := ioutil.ReadFile(self.Location) contents, err := ioutil.ReadFile(self.Location)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -77,7 +77,7 @@ func TestProcCmdlineAndFetchConfig(t *testing.T) {
p := NewProcCmdline() p := NewProcCmdline()
p.Location = file.Name() p.Location = file.Name()
cfg, err := p.Fetch() cfg, err := p.FetchUserdata()
if err != nil { if err != nil {
t.Errorf("Test produced error: %v", err) t.Errorf("Test produced error: %v", err)
} }

View File

@ -3,10 +3,13 @@ package initialize
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"path"
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml" "github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
"github.com/coreos/coreos-cloudinit/network"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
@ -34,10 +37,11 @@ type CloudConfig struct {
Update UpdateConfig Update UpdateConfig
Units []system.Unit Units []system.Unit
} }
WriteFiles []system.File `yaml:"write_files"` WriteFiles []system.File `yaml:"write_files"`
Hostname string Hostname string
Users []system.User Users []system.User
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"` ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
NetworkConfigPath string
} }
type warner func(format string, v ...interface{}) type warner func(format string, v ...interface{})
@ -227,6 +231,32 @@ func Apply(cfg CloudConfig, env *Environment) error {
log.Printf("Wrote file %s to filesystem", path) log.Printf("Wrote file %s to filesystem", path)
} }
if env.NetconfType() != "" {
netconfBytes, err := ioutil.ReadFile(path.Join(env.ConfigRoot(), cfg.NetworkConfigPath))
if err != nil {
return err
}
var interfaces []network.InterfaceGenerator
switch env.NetconfType() {
case "debian":
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
default:
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
}
if err != nil {
return err
}
if err := system.WriteNetworkdConfigs(interfaces); err != nil {
return err
}
if err := system.RestartNetwork(interfaces); err != nil {
return err
}
}
commands := make(map[string]string, 0) commands := make(map[string]string, 0)
reload := false reload := false
for _, unit := range cfg.Coreos.Units { for _, unit := range cfg.Coreos.Units {

View File

@ -10,17 +10,19 @@ const DefaultSSHKeyName = "coreos-cloudinit"
type Environment struct { type Environment struct {
root string root string
configRoot string
workspace string workspace string
netconfType string
sshKeyName string sshKeyName string
substitutions map[string]string substitutions map[string]string
} }
func NewEnvironment(root, workspace string) *Environment { func NewEnvironment(root, configRoot, workspace, netconfType, sshKeyName string) *Environment {
substitutions := map[string]string{ substitutions := map[string]string{
"$public_ipv4": os.Getenv("COREOS_PUBLIC_IPV4"), "$public_ipv4": os.Getenv("COREOS_PUBLIC_IPV4"),
"$private_ipv4": os.Getenv("COREOS_PRIVATE_IPV4"), "$private_ipv4": os.Getenv("COREOS_PRIVATE_IPV4"),
} }
return &Environment{root, workspace, DefaultSSHKeyName, substitutions} return &Environment{root, configRoot, workspace, netconfType, sshKeyName, substitutions}
} }
func (self *Environment) Workspace() string { func (self *Environment) Workspace() string {
@ -31,6 +33,14 @@ func (self *Environment) Root() string {
return self.root return self.root
} }
func (self *Environment) ConfigRoot() string {
return self.configRoot
}
func (self *Environment) NetconfType() string {
return self.netconfType
}
func (self *Environment) SSHKeyName() string { func (self *Environment) SSHKeyName() string {
return self.sshKeyName return self.sshKeyName
} }

View File

@ -8,7 +8,7 @@ import (
func TestEnvironmentApply(t *testing.T) { func TestEnvironmentApply(t *testing.T) {
os.Setenv("COREOS_PUBLIC_IPV4", "192.0.2.3") os.Setenv("COREOS_PUBLIC_IPV4", "192.0.2.3")
os.Setenv("COREOS_PRIVATE_IPV4", "192.0.2.203") os.Setenv("COREOS_PRIVATE_IPV4", "192.0.2.203")
env := NewEnvironment("./", "./") env := NewEnvironment("./", "./", "./", "", "")
input := `[Service] input := `[Service]
ExecStart=/usr/bin/echo "$public_ipv4" ExecStart=/usr/bin/echo "$public_ipv4"
ExecStop=/usr/bin/echo $private_ipv4 ExecStop=/usr/bin/echo $private_ipv4

26
initialize/meta_data.go Normal file
View File

@ -0,0 +1,26 @@
package initialize
import (
"encoding/json"
)
func ParseMetaData(contents string) (cfg CloudConfig, err error) {
var metadata struct {
SSHAuthorizedKeyMap map[string]string `json:"public_keys"`
Hostname string `json:"hostname"`
NetworkConfig struct {
ContentPath string `json:"content_path"`
} `json:"network_config"`
}
if err = json.Unmarshal([]byte(contents), &metadata); err != nil {
return
}
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
for _, key := range metadata.SSHAuthorizedKeyMap {
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, key)
}
cfg.Hostname = metadata.Hostname
cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath
return
}