cloudconfig: refactor config
- Move CloudConfig into config package - Add YAML tags to CloudConfig
This commit is contained in:
parent
4b472795c4
commit
18e2f98414
132
config/config.go
132
config/config.go
@ -2,10 +2,58 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/third_party/gopkg.in/yaml.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CloudConfig encapsulates the entire cloud-config configuration file and maps
|
||||||
|
// directly to YAML. Fields that cannot be set in the cloud-config (fields
|
||||||
|
// used for internal use) have the YAML tag '-' so that they aren't marshalled.
|
||||||
|
type CloudConfig struct {
|
||||||
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||||
|
Coreos struct {
|
||||||
|
Etcd Etcd `yaml:"etcd"`
|
||||||
|
Fleet Fleet `yaml:"fleet"`
|
||||||
|
OEM OEM `yaml:"oem"`
|
||||||
|
Update Update `yaml:"update"`
|
||||||
|
Units []Unit `yaml:"units"`
|
||||||
|
} `yaml:"coreos"`
|
||||||
|
WriteFiles []File `yaml:"write_files"`
|
||||||
|
Hostname string `yaml:"hostname"`
|
||||||
|
Users []User `yaml:"users"`
|
||||||
|
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
||||||
|
NetworkConfigPath string `yaml:"-"`
|
||||||
|
NetworkConfig string `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
||||||
|
// string of YAML), returning any error encountered. It will ignore unknown
|
||||||
|
// fields but log encountering them.
|
||||||
|
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||||
|
var cfg CloudConfig
|
||||||
|
err := yaml.Unmarshal([]byte(contents), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return &cfg, err
|
||||||
|
}
|
||||||
|
warnOnUnrecognizedKeys(contents, log.Printf)
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc CloudConfig) String() string {
|
||||||
|
bytes, err := yaml.Marshal(cc)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
stringified := string(bytes)
|
||||||
|
stringified = fmt.Sprintf("#cloud-config\n%s", stringified)
|
||||||
|
|
||||||
|
return stringified
|
||||||
|
}
|
||||||
|
|
||||||
// IsZero returns whether or not the parameter is the zero value for its type.
|
// IsZero returns whether or not the parameter is the zero value for its type.
|
||||||
// If the parameter is a struct, only the exported fields are considered.
|
// If the parameter is a struct, only the exported fields are considered.
|
||||||
func IsZero(c interface{}) bool {
|
func IsZero(c interface{}) bool {
|
||||||
@ -64,3 +112,87 @@ func isValid(v reflect.Value, valid string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type warner func(format string, v ...interface{})
|
||||||
|
|
||||||
|
// warnOnUnrecognizedKeys parses the contents of a cloud-config file and calls
|
||||||
|
// warn(msg, key) for every unrecognized key (i.e. those not present in CloudConfig)
|
||||||
|
func warnOnUnrecognizedKeys(contents string, warn warner) {
|
||||||
|
// Generate a map of all understood cloud config options
|
||||||
|
var cc map[string]interface{}
|
||||||
|
b, _ := yaml.Marshal(&CloudConfig{})
|
||||||
|
yaml.Unmarshal(b, &cc)
|
||||||
|
|
||||||
|
// Now unmarshal the entire provided contents
|
||||||
|
var c map[string]interface{}
|
||||||
|
yaml.Unmarshal([]byte(contents), &c)
|
||||||
|
|
||||||
|
// Check that every key in the contents exists in the cloud config
|
||||||
|
for k, _ := range c {
|
||||||
|
if _, ok := cc[k]; !ok {
|
||||||
|
warn("Warning: unrecognized key %q in provided cloud config - ignoring section", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for unrecognized coreos options, if any are set
|
||||||
|
if coreos, ok := c["coreos"]; ok {
|
||||||
|
if set, ok := coreos.(map[interface{}]interface{}); ok {
|
||||||
|
known := cc["coreos"].(map[interface{}]interface{})
|
||||||
|
for k, _ := range set {
|
||||||
|
if key, ok := k.(string); ok {
|
||||||
|
if _, ok := known[key]; !ok {
|
||||||
|
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any badly-specified users, if any are set
|
||||||
|
if users, ok := c["users"]; ok {
|
||||||
|
var known map[string]interface{}
|
||||||
|
b, _ := yaml.Marshal(&User{})
|
||||||
|
yaml.Unmarshal(b, &known)
|
||||||
|
|
||||||
|
if set, ok := users.([]interface{}); ok {
|
||||||
|
for _, u := range set {
|
||||||
|
if user, ok := u.(map[interface{}]interface{}); ok {
|
||||||
|
for k, _ := range user {
|
||||||
|
if key, ok := k.(string); ok {
|
||||||
|
if _, ok := known[key]; !ok {
|
||||||
|
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any badly-specified files, if any are set
|
||||||
|
if files, ok := c["write_files"]; ok {
|
||||||
|
var known map[string]interface{}
|
||||||
|
b, _ := yaml.Marshal(&File{})
|
||||||
|
yaml.Unmarshal(b, &known)
|
||||||
|
|
||||||
|
if set, ok := files.([]interface{}); ok {
|
||||||
|
for _, f := range set {
|
||||||
|
if file, ok := f.(map[interface{}]interface{}); ok {
|
||||||
|
for k, _ := range file {
|
||||||
|
if key, ok := k.(string); ok {
|
||||||
|
if _, ok := known[key]; !ok {
|
||||||
|
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,9 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,3 +63,411 @@ func TestAssertValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCloudConfigInvalidKeys(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Fatalf("panic while instantiating CloudConfig with nil keys: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
contents string
|
||||||
|
}{
|
||||||
|
{"coreos:"},
|
||||||
|
{"ssh_authorized_keys:"},
|
||||||
|
{"ssh_authorized_keys:\n -"},
|
||||||
|
{"ssh_authorized_keys:\n - 0:"},
|
||||||
|
{"write_files:"},
|
||||||
|
{"write_files:\n -"},
|
||||||
|
{"write_files:\n - 0:"},
|
||||||
|
{"users:"},
|
||||||
|
{"users:\n -"},
|
||||||
|
{"users:\n - 0:"},
|
||||||
|
} {
|
||||||
|
_, err := NewCloudConfig(tt.contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error instantiating CloudConfig with invalid keys: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudConfigUnknownKeys(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
coreos:
|
||||||
|
etcd:
|
||||||
|
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||||
|
coreos_unknown:
|
||||||
|
foo: "bar"
|
||||||
|
section_unknown:
|
||||||
|
dunno:
|
||||||
|
something
|
||||||
|
bare_unknown:
|
||||||
|
bar
|
||||||
|
write_files:
|
||||||
|
- content: fun
|
||||||
|
path: /var/party
|
||||||
|
file_unknown: nofun
|
||||||
|
users:
|
||||||
|
- name: fry
|
||||||
|
passwd: somehash
|
||||||
|
user_unknown: philip
|
||||||
|
hostname:
|
||||||
|
foo
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error instantiating CloudConfig with unknown keys: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.Hostname != "foo" {
|
||||||
|
t.Fatalf("hostname not correctly set when invalid keys are present")
|
||||||
|
}
|
||||||
|
if cfg.Coreos.Etcd.Discovery != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
|
||||||
|
t.Fatalf("etcd section not correctly set when invalid keys are present")
|
||||||
|
}
|
||||||
|
if len(cfg.WriteFiles) < 1 || cfg.WriteFiles[0].Content != "fun" || cfg.WriteFiles[0].Path != "/var/party" {
|
||||||
|
t.Fatalf("write_files section not correctly set when invalid keys are present")
|
||||||
|
}
|
||||||
|
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
|
||||||
|
t.Fatalf("users section not correctly set when invalid keys are present")
|
||||||
|
}
|
||||||
|
|
||||||
|
var warnings string
|
||||||
|
catchWarn := func(f string, v ...interface{}) {
|
||||||
|
warnings += fmt.Sprintf(f, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
warnOnUnrecognizedKeys(contents, catchWarn)
|
||||||
|
|
||||||
|
if !strings.Contains(warnings, "coreos_unknown") {
|
||||||
|
t.Errorf("warnings did not catch unrecognized coreos option coreos_unknown")
|
||||||
|
}
|
||||||
|
if !strings.Contains(warnings, "bare_unknown") {
|
||||||
|
t.Errorf("warnings did not catch unrecognized key bare_unknown")
|
||||||
|
}
|
||||||
|
if !strings.Contains(warnings, "section_unknown") {
|
||||||
|
t.Errorf("warnings did not catch unrecognized key section_unknown")
|
||||||
|
}
|
||||||
|
if !strings.Contains(warnings, "user_unknown") {
|
||||||
|
t.Errorf("warnings did not catch unrecognized user key user_unknown")
|
||||||
|
}
|
||||||
|
if !strings.Contains(warnings, "file_unknown") {
|
||||||
|
t.Errorf("warnings did not catch unrecognized file key file_unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the parsing of a cloud config file "generally works"
|
||||||
|
func TestCloudConfigEmpty(t *testing.T) {
|
||||||
|
cfg, err := NewCloudConfig("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encountered unexpected error :%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := cfg.SSHAuthorizedKeys
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Error("Parsed incorrect number of SSH keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.WriteFiles) != 0 {
|
||||||
|
t.Error("Expected zero WriteFiles")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Hostname != "" {
|
||||||
|
t.Errorf("Expected hostname to be empty, got '%s'", cfg.Hostname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the parsing of a cloud config file "generally works"
|
||||||
|
func TestCloudConfig(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
coreos:
|
||||||
|
etcd:
|
||||||
|
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||||
|
update:
|
||||||
|
reboot-strategy: reboot
|
||||||
|
units:
|
||||||
|
- name: 50-eth0.network
|
||||||
|
runtime: yes
|
||||||
|
content: '[Match]
|
||||||
|
|
||||||
|
Name=eth47
|
||||||
|
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
|
||||||
|
Address=10.209.171.177/19
|
||||||
|
|
||||||
|
'
|
||||||
|
oem:
|
||||||
|
id: rackspace
|
||||||
|
name: Rackspace Cloud Servers
|
||||||
|
version-id: 168.0.0
|
||||||
|
home-url: https://www.rackspace.com/cloud/servers/
|
||||||
|
bug-report-url: https://github.com/coreos/coreos-overlay
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- foobar
|
||||||
|
- foobaz
|
||||||
|
write_files:
|
||||||
|
- content: |
|
||||||
|
penny
|
||||||
|
elroy
|
||||||
|
path: /etc/dogepack.conf
|
||||||
|
permissions: '0644'
|
||||||
|
owner: root:dogepack
|
||||||
|
hostname: trontastic
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encountered unexpected error :%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := cfg.SSHAuthorizedKeys
|
||||||
|
if len(keys) != 2 {
|
||||||
|
t.Error("Parsed incorrect number of SSH keys")
|
||||||
|
} else if keys[0] != "foobar" {
|
||||||
|
t.Error("Expected first SSH key to be 'foobar'")
|
||||||
|
} else if keys[1] != "foobaz" {
|
||||||
|
t.Error("Expected first SSH key to be 'foobaz'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.WriteFiles) != 1 {
|
||||||
|
t.Error("Failed to parse correct number of write_files")
|
||||||
|
} else {
|
||||||
|
wf := cfg.WriteFiles[0]
|
||||||
|
if wf.Content != "penny\nelroy\n" {
|
||||||
|
t.Errorf("WriteFile has incorrect contents '%s'", wf.Content)
|
||||||
|
}
|
||||||
|
if wf.Encoding != "" {
|
||||||
|
t.Errorf("WriteFile has incorrect encoding %s", wf.Encoding)
|
||||||
|
}
|
||||||
|
if wf.RawFilePermissions != "0644" {
|
||||||
|
t.Errorf("WriteFile has incorrect permissions %s", wf.RawFilePermissions)
|
||||||
|
}
|
||||||
|
if wf.Path != "/etc/dogepack.conf" {
|
||||||
|
t.Errorf("WriteFile has incorrect path %s", wf.Path)
|
||||||
|
}
|
||||||
|
if wf.Owner != "root:dogepack" {
|
||||||
|
t.Errorf("WriteFile has incorrect owner %s", wf.Owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Coreos.Units) != 1 {
|
||||||
|
t.Error("Failed to parse correct number of units")
|
||||||
|
} else {
|
||||||
|
u := cfg.Coreos.Units[0]
|
||||||
|
expect := `[Match]
|
||||||
|
Name=eth47
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Address=10.209.171.177/19
|
||||||
|
`
|
||||||
|
if u.Content != expect {
|
||||||
|
t.Errorf("Unit has incorrect contents '%s'.\nExpected '%s'.", u.Content, expect)
|
||||||
|
}
|
||||||
|
if u.Runtime != true {
|
||||||
|
t.Errorf("Unit has incorrect runtime value")
|
||||||
|
}
|
||||||
|
if u.Name != "50-eth0.network" {
|
||||||
|
t.Errorf("Unit has incorrect name %s", u.Name)
|
||||||
|
}
|
||||||
|
if u.Type() != "network" {
|
||||||
|
t.Errorf("Unit has incorrect type '%s'", u.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Coreos.OEM.ID != "rackspace" {
|
||||||
|
t.Errorf("Failed parsing coreos.oem. Expected ID 'rackspace', got %q.", cfg.Coreos.OEM.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Hostname != "trontastic" {
|
||||||
|
t.Errorf("Failed to parse hostname")
|
||||||
|
}
|
||||||
|
if cfg.Coreos.Update.RebootStrategy != "reboot" {
|
||||||
|
t.Errorf("Failed to parse locksmith strategy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that our interface conversion doesn't panic
|
||||||
|
func TestCloudConfigKeysNotList(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- foo: bar
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := cfg.SSHAuthorizedKeys
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Error("Parsed incorrect number of SSH keys")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudConfigSerializationHeader(t *testing.T) {
|
||||||
|
cfg, _ := NewCloudConfig("")
|
||||||
|
contents := cfg.String()
|
||||||
|
header := strings.SplitN(contents, "\n", 2)[0]
|
||||||
|
if header != "#cloud-config" {
|
||||||
|
t.Fatalf("Serialized config did not have expected header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDropInIgnored asserts that users are unable to set DropIn=True on units
|
||||||
|
func TestDropInIgnored(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
coreos:
|
||||||
|
units:
|
||||||
|
- name: test
|
||||||
|
dropin: true
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil || len(cfg.Coreos.Units) != 1 {
|
||||||
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(cfg.Coreos.Units) != 1 || cfg.Coreos.Units[0].Name != "test" {
|
||||||
|
t.Fatalf("Expected 1 unit, but got %d: %v", len(cfg.Coreos.Units), cfg.Coreos.Units)
|
||||||
|
}
|
||||||
|
if cfg.Coreos.Units[0].DropIn {
|
||||||
|
t.Errorf("dropin option on unit in cloud-config was not ignored!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudConfigUsers(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
users:
|
||||||
|
- name: elroy
|
||||||
|
passwd: somehash
|
||||||
|
ssh-authorized-keys:
|
||||||
|
- somekey
|
||||||
|
gecos: arbitrary comment
|
||||||
|
homedir: /home/place
|
||||||
|
no-create-home: yes
|
||||||
|
primary-group: things
|
||||||
|
groups:
|
||||||
|
- ping
|
||||||
|
- pong
|
||||||
|
no-user-group: true
|
||||||
|
system: y
|
||||||
|
no-log-init: True
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Users) != 1 {
|
||||||
|
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := cfg.Users[0]
|
||||||
|
|
||||||
|
if user.Name != "elroy" {
|
||||||
|
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.PasswordHash != "somehash" {
|
||||||
|
t.Errorf("User passwd is %q, expected 'somehash'", user.PasswordHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys := user.SSHAuthorizedKeys; len(keys) != 1 {
|
||||||
|
t.Errorf("Parsed %d ssh keys, expected 1", len(keys))
|
||||||
|
} else {
|
||||||
|
key := user.SSHAuthorizedKeys[0]
|
||||||
|
if key != "somekey" {
|
||||||
|
t.Errorf("User SSH key is %q, expected 'somekey'", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.GECOS != "arbitrary comment" {
|
||||||
|
t.Errorf("Failed to parse gecos field, got %q", user.GECOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Homedir != "/home/place" {
|
||||||
|
t.Errorf("Failed to parse homedir field, got %q", user.Homedir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.NoCreateHome {
|
||||||
|
t.Errorf("Failed to parse no-create-home field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.PrimaryGroup != "things" {
|
||||||
|
t.Errorf("Failed to parse primary-group field, got %q", user.PrimaryGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user.Groups) != 2 {
|
||||||
|
t.Errorf("Failed to parse 2 goups, got %d", len(user.Groups))
|
||||||
|
} else {
|
||||||
|
if user.Groups[0] != "ping" {
|
||||||
|
t.Errorf("First group was %q, not expected value 'ping'", user.Groups[0])
|
||||||
|
}
|
||||||
|
if user.Groups[1] != "pong" {
|
||||||
|
t.Errorf("First group was %q, not expected value 'pong'", user.Groups[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.NoUserGroup {
|
||||||
|
t.Errorf("Failed to parse no-user-group field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.System {
|
||||||
|
t.Errorf("Failed to parse system field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.NoLogInit {
|
||||||
|
t.Errorf("Failed to parse no-log-init field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudConfigUsersGithubUser(t *testing.T) {
|
||||||
|
|
||||||
|
contents := `
|
||||||
|
users:
|
||||||
|
- name: elroy
|
||||||
|
coreos-ssh-import-github: bcwaldon
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Users) != 1 {
|
||||||
|
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := cfg.Users[0]
|
||||||
|
|
||||||
|
if user.Name != "elroy" {
|
||||||
|
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.SSHImportGithubUser != "bcwaldon" {
|
||||||
|
t.Errorf("github user is %q, expected 'bcwaldon'", user.SSHImportGithubUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudConfigUsersSSHImportURL(t *testing.T) {
|
||||||
|
contents := `
|
||||||
|
users:
|
||||||
|
- name: elroy
|
||||||
|
coreos-ssh-import-url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
|
||||||
|
`
|
||||||
|
cfg, err := NewCloudConfig(contents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encountered unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Users) != 1 {
|
||||||
|
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := cfg.Users[0]
|
||||||
|
|
||||||
|
if user.Name != "elroy" {
|
||||||
|
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.SSHImportURL != "https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys" {
|
||||||
|
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource"
|
"github.com/coreos/coreos-cloudinit/datasource"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/file"
|
"github.com/coreos/coreos-cloudinit/datasource/file"
|
||||||
@ -156,7 +157,7 @@ func main() {
|
|||||||
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.convertNetconf, flags.sshKeyName, subs)
|
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.convertNetconf, flags.sshKeyName, subs)
|
||||||
userdata := env.Apply(string(userdataBytes))
|
userdata := env.Apply(string(userdataBytes))
|
||||||
|
|
||||||
var ccm, ccu *initialize.CloudConfig
|
var ccm, ccu *config.CloudConfig
|
||||||
var script *system.Script
|
var script *system.Script
|
||||||
if ccm, err = initialize.ParseMetaData(string(metadataBytes)); err != nil {
|
if ccm, err = initialize.ParseMetaData(string(metadataBytes)); err != nil {
|
||||||
fmt.Printf("Failed to parse meta-data: %v\n", err)
|
fmt.Printf("Failed to parse meta-data: %v\n", err)
|
||||||
@ -178,14 +179,14 @@ func main() {
|
|||||||
failure = true
|
failure = true
|
||||||
} else {
|
} else {
|
||||||
switch t := ud.(type) {
|
switch t := ud.(type) {
|
||||||
case *initialize.CloudConfig:
|
case *config.CloudConfig:
|
||||||
ccu = t
|
ccu = t
|
||||||
case system.Script:
|
case system.Script:
|
||||||
script = &t
|
script = &t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cc *initialize.CloudConfig
|
var cc *config.CloudConfig
|
||||||
if ccm != nil && ccu != nil {
|
if ccm != nil && ccu != nil {
|
||||||
fmt.Println("Merging cloud-config from meta-data and user-data")
|
fmt.Println("Merging cloud-config from meta-data and user-data")
|
||||||
merged := mergeCloudConfig(*ccm, *ccu)
|
merged := mergeCloudConfig(*ccm, *ccu)
|
||||||
@ -224,7 +225,7 @@ func main() {
|
|||||||
// not already set on udcc (i.e. user-data always takes precedence)
|
// 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
|
// NB: This needs to be kept in sync with ParseMetadata so that it tracks all
|
||||||
// elements of a CloudConfig which that function can populate.
|
// elements of a CloudConfig which that function can populate.
|
||||||
func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudConfig) {
|
func mergeCloudConfig(mdcc, udcc config.CloudConfig) (cc config.CloudConfig) {
|
||||||
if mdcc.Hostname != "" {
|
if mdcc.Hostname != "" {
|
||||||
if udcc.Hostname != "" {
|
if udcc.Hostname != "" {
|
||||||
fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", udcc.Hostname, mdcc.Hostname)
|
fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", udcc.Hostname, mdcc.Hostname)
|
||||||
|
@ -4,38 +4,37 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/initialize"
|
|
||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergeCloudConfig(t *testing.T) {
|
func TestMergeCloudConfig(t *testing.T) {
|
||||||
simplecc := initialize.CloudConfig{
|
simplecc := config.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
NetworkConfig: `{}`,
|
NetworkConfig: `{}`,
|
||||||
}
|
}
|
||||||
for i, tt := range []struct {
|
for i, tt := range []struct {
|
||||||
udcc initialize.CloudConfig
|
udcc config.CloudConfig
|
||||||
mdcc initialize.CloudConfig
|
mdcc config.CloudConfig
|
||||||
want initialize.CloudConfig
|
want config.CloudConfig
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// If mdcc is empty, udcc should be returned unchanged
|
// If mdcc is empty, udcc should be returned unchanged
|
||||||
simplecc,
|
simplecc,
|
||||||
initialize.CloudConfig{},
|
config.CloudConfig{},
|
||||||
simplecc,
|
simplecc,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// If udcc is empty, mdcc should be returned unchanged(overridden)
|
// If udcc is empty, mdcc should be returned unchanged(overridden)
|
||||||
initialize.CloudConfig{},
|
config.CloudConfig{},
|
||||||
simplecc,
|
simplecc,
|
||||||
simplecc,
|
simplecc,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// user-data should override completely in the case of conflicts
|
// user-data should override completely in the case of conflicts
|
||||||
simplecc,
|
simplecc,
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "meta-hostname",
|
Hostname: "meta-hostname",
|
||||||
NetworkConfigPath: "/path/meta",
|
NetworkConfigPath: "/path/meta",
|
||||||
NetworkConfig: `{"hostname":"test"}`,
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
@ -44,17 +43,17 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Mixed merge should succeed
|
// Mixed merge should succeed
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
Hostname: "user-hostname",
|
Hostname: "user-hostname",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
NetworkConfig: `{"hostname":"test"}`,
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"woof", "qux"},
|
SSHAuthorizedKeys: []string{"woof", "qux"},
|
||||||
Hostname: "meta-hostname",
|
Hostname: "meta-hostname",
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
||||||
Hostname: "user-hostname",
|
Hostname: "user-hostname",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
@ -63,15 +62,15 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Completely non-conflicting merge should be fine
|
// Completely non-conflicting merge should be fine
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "supercool",
|
Hostname: "supercool",
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
NetworkConfigPath: "/dev/fun",
|
NetworkConfigPath: "/dev/fun",
|
||||||
NetworkConfig: `{"hostname":"test"}`,
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "supercool",
|
Hostname: "supercool",
|
||||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
NetworkConfigPath: "/dev/fun",
|
NetworkConfigPath: "/dev/fun",
|
||||||
@ -80,16 +79,16 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Non-mergeable settings in user-data should not be affected
|
// Non-mergeable settings in user-data should not be affected
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
ManageEtcHosts: config.EtcHosts("lolz"),
|
ManageEtcHosts: config.EtcHosts("lolz"),
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "youyouyou",
|
Hostname: "youyouyou",
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
NetworkConfig: `{"hostname":"test"}`,
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
ManageEtcHosts: config.EtcHosts("lolz"),
|
ManageEtcHosts: config.EtcHosts("lolz"),
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
@ -98,15 +97,15 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Non-mergeable (unexpected) settings in meta-data are ignored
|
// Non-mergeable (unexpected) settings in meta-data are ignored
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
ManageEtcHosts: config.EtcHosts("lolz"),
|
ManageEtcHosts: config.EtcHosts("lolz"),
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
NetworkConfig: `{"hostname":"test"}`,
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
config.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
NetworkConfig: `{"hostname":"test"}`,
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
|
@ -6,8 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/gopkg.in/yaml.v1"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
"github.com/coreos/coreos-cloudinit/network"
|
"github.com/coreos/coreos-cloudinit/network"
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
@ -27,137 +25,10 @@ type CloudConfigUnit interface {
|
|||||||
Units() ([]system.Unit, error)
|
Units() ([]system.Unit, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloudConfig encapsulates the entire cloud-config configuration file and maps directly to YAML
|
|
||||||
type CloudConfig struct {
|
|
||||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
|
||||||
Coreos struct {
|
|
||||||
Etcd config.Etcd
|
|
||||||
Fleet config.Fleet
|
|
||||||
OEM config.OEM
|
|
||||||
Update config.Update
|
|
||||||
Units []config.Unit
|
|
||||||
}
|
|
||||||
WriteFiles []config.File `yaml:"write_files"`
|
|
||||||
Hostname string
|
|
||||||
Users []config.User
|
|
||||||
ManageEtcHosts config.EtcHosts `yaml:"manage_etc_hosts"`
|
|
||||||
NetworkConfigPath string
|
|
||||||
NetworkConfig string
|
|
||||||
}
|
|
||||||
|
|
||||||
type warner func(format string, v ...interface{})
|
|
||||||
|
|
||||||
// warnOnUnrecognizedKeys parses the contents of a cloud-config file and calls
|
|
||||||
// warn(msg, key) for every unrecognized key (i.e. those not present in CloudConfig)
|
|
||||||
func warnOnUnrecognizedKeys(contents string, warn warner) {
|
|
||||||
// Generate a map of all understood cloud config options
|
|
||||||
var cc map[string]interface{}
|
|
||||||
b, _ := yaml.Marshal(&CloudConfig{})
|
|
||||||
yaml.Unmarshal(b, &cc)
|
|
||||||
|
|
||||||
// Now unmarshal the entire provided contents
|
|
||||||
var c map[string]interface{}
|
|
||||||
yaml.Unmarshal([]byte(contents), &c)
|
|
||||||
|
|
||||||
// Check that every key in the contents exists in the cloud config
|
|
||||||
for k, _ := range c {
|
|
||||||
if _, ok := cc[k]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in provided cloud config - ignoring section", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for unrecognized coreos options, if any are set
|
|
||||||
if coreos, ok := c["coreos"]; ok {
|
|
||||||
if set, ok := coreos.(map[interface{}]interface{}); ok {
|
|
||||||
known := cc["coreos"].(map[interface{}]interface{})
|
|
||||||
for k, _ := range set {
|
|
||||||
if key, ok := k.(string); ok {
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any badly-specified users, if any are set
|
|
||||||
if users, ok := c["users"]; ok {
|
|
||||||
var known map[string]interface{}
|
|
||||||
b, _ := yaml.Marshal(&config.User{})
|
|
||||||
yaml.Unmarshal(b, &known)
|
|
||||||
|
|
||||||
if set, ok := users.([]interface{}); ok {
|
|
||||||
for _, u := range set {
|
|
||||||
if user, ok := u.(map[interface{}]interface{}); ok {
|
|
||||||
for k, _ := range user {
|
|
||||||
if key, ok := k.(string); ok {
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any badly-specified files, if any are set
|
|
||||||
if files, ok := c["write_files"]; ok {
|
|
||||||
var known map[string]interface{}
|
|
||||||
b, _ := yaml.Marshal(&system.File{})
|
|
||||||
yaml.Unmarshal(b, &known)
|
|
||||||
|
|
||||||
if set, ok := files.([]interface{}); ok {
|
|
||||||
for _, f := range set {
|
|
||||||
if file, ok := f.(map[interface{}]interface{}); ok {
|
|
||||||
for k, _ := range file {
|
|
||||||
if key, ok := k.(string); ok {
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
|
||||||
// string of YAML), returning any error encountered. It will ignore unknown
|
|
||||||
// fields but log encountering them.
|
|
||||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
|
||||||
var cfg CloudConfig
|
|
||||||
err := yaml.Unmarshal([]byte(contents), &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return &cfg, err
|
|
||||||
}
|
|
||||||
warnOnUnrecognizedKeys(contents, log.Printf)
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cc CloudConfig) String() string {
|
|
||||||
bytes, err := yaml.Marshal(cc)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
stringified := string(bytes)
|
|
||||||
stringified = fmt.Sprintf("#cloud-config\n%s", stringified)
|
|
||||||
|
|
||||||
return stringified
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply renders a CloudConfig to an Environment. This can involve things like
|
// Apply renders a CloudConfig to an Environment. This can involve things like
|
||||||
// configuring the hostname, adding new users, writing various configuration
|
// configuring the hostname, adding new users, writing various configuration
|
||||||
// files to disk, and manipulating systemd services.
|
// files to disk, and manipulating systemd services.
|
||||||
func Apply(cfg CloudConfig, env *Environment) error {
|
func Apply(cfg config.CloudConfig, env *Environment) error {
|
||||||
if cfg.Hostname != "" {
|
if cfg.Hostname != "" {
|
||||||
if err := system.SetHostname(cfg.Hostname); err != nil {
|
if err := system.SetHostname(cfg.Hostname); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1,369 +1,12 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCloudConfigInvalidKeys(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Fatalf("panic while instantiating CloudConfig with nil keys: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, tt := range []struct {
|
|
||||||
contents string
|
|
||||||
}{
|
|
||||||
{"coreos:"},
|
|
||||||
{"ssh_authorized_keys:"},
|
|
||||||
{"ssh_authorized_keys:\n -"},
|
|
||||||
{"ssh_authorized_keys:\n - 0:"},
|
|
||||||
{"write_files:"},
|
|
||||||
{"write_files:\n -"},
|
|
||||||
{"write_files:\n - 0:"},
|
|
||||||
{"users:"},
|
|
||||||
{"users:\n -"},
|
|
||||||
{"users:\n - 0:"},
|
|
||||||
} {
|
|
||||||
_, err := NewCloudConfig(tt.contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error instantiating CloudConfig with invalid keys: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudConfigUnknownKeys(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
coreos:
|
|
||||||
etcd:
|
|
||||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
|
||||||
coreos_unknown:
|
|
||||||
foo: "bar"
|
|
||||||
section_unknown:
|
|
||||||
dunno:
|
|
||||||
something
|
|
||||||
bare_unknown:
|
|
||||||
bar
|
|
||||||
write_files:
|
|
||||||
- content: fun
|
|
||||||
path: /var/party
|
|
||||||
file_unknown: nofun
|
|
||||||
users:
|
|
||||||
- name: fry
|
|
||||||
passwd: somehash
|
|
||||||
user_unknown: philip
|
|
||||||
hostname:
|
|
||||||
foo
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error instantiating CloudConfig with unknown keys: %v", err)
|
|
||||||
}
|
|
||||||
if cfg.Hostname != "foo" {
|
|
||||||
t.Fatalf("hostname not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
if cfg.Coreos.Etcd.Discovery != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
|
|
||||||
t.Fatalf("etcd section not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
if len(cfg.WriteFiles) < 1 || cfg.WriteFiles[0].Content != "fun" || cfg.WriteFiles[0].Path != "/var/party" {
|
|
||||||
t.Fatalf("write_files section not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
|
|
||||||
t.Fatalf("users section not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
|
|
||||||
var warnings string
|
|
||||||
catchWarn := func(f string, v ...interface{}) {
|
|
||||||
warnings += fmt.Sprintf(f, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
warnOnUnrecognizedKeys(contents, catchWarn)
|
|
||||||
|
|
||||||
if !strings.Contains(warnings, "coreos_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized coreos option coreos_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "bare_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized key bare_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "section_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized key section_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "user_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized user key user_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "file_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized file key file_unknown")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the parsing of a cloud config file "generally works"
|
|
||||||
func TestCloudConfigEmpty(t *testing.T) {
|
|
||||||
cfg, err := NewCloudConfig("")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encountered unexpected error :%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := cfg.SSHAuthorizedKeys
|
|
||||||
if len(keys) != 0 {
|
|
||||||
t.Error("Parsed incorrect number of SSH keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.WriteFiles) != 0 {
|
|
||||||
t.Error("Expected zero WriteFiles")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Hostname != "" {
|
|
||||||
t.Errorf("Expected hostname to be empty, got '%s'", cfg.Hostname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the parsing of a cloud config file "generally works"
|
|
||||||
func TestCloudConfig(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
coreos:
|
|
||||||
etcd:
|
|
||||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
|
||||||
update:
|
|
||||||
reboot-strategy: reboot
|
|
||||||
units:
|
|
||||||
- name: 50-eth0.network
|
|
||||||
runtime: yes
|
|
||||||
content: '[Match]
|
|
||||||
|
|
||||||
Name=eth47
|
|
||||||
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
|
|
||||||
Address=10.209.171.177/19
|
|
||||||
|
|
||||||
'
|
|
||||||
oem:
|
|
||||||
id: rackspace
|
|
||||||
name: Rackspace Cloud Servers
|
|
||||||
version-id: 168.0.0
|
|
||||||
home-url: https://www.rackspace.com/cloud/servers/
|
|
||||||
bug-report-url: https://github.com/coreos/coreos-overlay
|
|
||||||
ssh_authorized_keys:
|
|
||||||
- foobar
|
|
||||||
- foobaz
|
|
||||||
write_files:
|
|
||||||
- content: |
|
|
||||||
penny
|
|
||||||
elroy
|
|
||||||
path: /etc/dogepack.conf
|
|
||||||
permissions: '0644'
|
|
||||||
owner: root:dogepack
|
|
||||||
hostname: trontastic
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encountered unexpected error :%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := cfg.SSHAuthorizedKeys
|
|
||||||
if len(keys) != 2 {
|
|
||||||
t.Error("Parsed incorrect number of SSH keys")
|
|
||||||
} else if keys[0] != "foobar" {
|
|
||||||
t.Error("Expected first SSH key to be 'foobar'")
|
|
||||||
} else if keys[1] != "foobaz" {
|
|
||||||
t.Error("Expected first SSH key to be 'foobaz'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.WriteFiles) != 1 {
|
|
||||||
t.Error("Failed to parse correct number of write_files")
|
|
||||||
} else {
|
|
||||||
wf := system.File{cfg.WriteFiles[0]}
|
|
||||||
if wf.Content != "penny\nelroy\n" {
|
|
||||||
t.Errorf("WriteFile has incorrect contents '%s'", wf.Content)
|
|
||||||
}
|
|
||||||
if wf.Encoding != "" {
|
|
||||||
t.Errorf("WriteFile has incorrect encoding %s", wf.Encoding)
|
|
||||||
}
|
|
||||||
if perm, _ := wf.Permissions(); perm != 0644 {
|
|
||||||
t.Errorf("WriteFile has incorrect permissions %s", perm)
|
|
||||||
}
|
|
||||||
if wf.Path != "/etc/dogepack.conf" {
|
|
||||||
t.Errorf("WriteFile has incorrect path %s", wf.Path)
|
|
||||||
}
|
|
||||||
if wf.Owner != "root:dogepack" {
|
|
||||||
t.Errorf("WriteFile has incorrect owner %s", wf.Owner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Coreos.Units) != 1 {
|
|
||||||
t.Error("Failed to parse correct number of units")
|
|
||||||
} else {
|
|
||||||
u := cfg.Coreos.Units[0]
|
|
||||||
expect := `[Match]
|
|
||||||
Name=eth47
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
Address=10.209.171.177/19
|
|
||||||
`
|
|
||||||
if u.Content != expect {
|
|
||||||
t.Errorf("Unit has incorrect contents '%s'.\nExpected '%s'.", u.Content, expect)
|
|
||||||
}
|
|
||||||
if u.Runtime != true {
|
|
||||||
t.Errorf("Unit has incorrect runtime value")
|
|
||||||
}
|
|
||||||
if u.Name != "50-eth0.network" {
|
|
||||||
t.Errorf("Unit has incorrect name %s", u.Name)
|
|
||||||
}
|
|
||||||
if u.Type() != "network" {
|
|
||||||
t.Errorf("Unit has incorrect type '%s'", u.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Coreos.OEM.ID != "rackspace" {
|
|
||||||
t.Errorf("Failed parsing coreos.oem. Expected ID 'rackspace', got %q.", cfg.Coreos.OEM.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Hostname != "trontastic" {
|
|
||||||
t.Errorf("Failed to parse hostname")
|
|
||||||
}
|
|
||||||
if cfg.Coreos.Update.RebootStrategy != "reboot" {
|
|
||||||
t.Errorf("Failed to parse locksmith strategy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that our interface conversion doesn't panic
|
|
||||||
func TestCloudConfigKeysNotList(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
ssh_authorized_keys:
|
|
||||||
- foo: bar
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := cfg.SSHAuthorizedKeys
|
|
||||||
if len(keys) != 0 {
|
|
||||||
t.Error("Parsed incorrect number of SSH keys")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudConfigSerializationHeader(t *testing.T) {
|
|
||||||
cfg, _ := NewCloudConfig("")
|
|
||||||
contents := cfg.String()
|
|
||||||
header := strings.SplitN(contents, "\n", 2)[0]
|
|
||||||
if header != "#cloud-config" {
|
|
||||||
t.Fatalf("Serialized config did not have expected header")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDropInIgnored asserts that users are unable to set DropIn=True on units
|
|
||||||
func TestDropInIgnored(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
coreos:
|
|
||||||
units:
|
|
||||||
- name: test
|
|
||||||
dropin: true
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil || len(cfg.Coreos.Units) != 1 {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if len(cfg.Coreos.Units) != 1 || cfg.Coreos.Units[0].Name != "test" {
|
|
||||||
t.Fatalf("Expected 1 unit, but got %d: %v", len(cfg.Coreos.Units), cfg.Coreos.Units)
|
|
||||||
}
|
|
||||||
if cfg.Coreos.Units[0].DropIn {
|
|
||||||
t.Errorf("dropin option on unit in cloud-config was not ignored!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudConfigUsers(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
users:
|
|
||||||
- name: elroy
|
|
||||||
passwd: somehash
|
|
||||||
ssh-authorized-keys:
|
|
||||||
- somekey
|
|
||||||
gecos: arbitrary comment
|
|
||||||
homedir: /home/place
|
|
||||||
no-create-home: yes
|
|
||||||
primary-group: things
|
|
||||||
groups:
|
|
||||||
- ping
|
|
||||||
- pong
|
|
||||||
no-user-group: true
|
|
||||||
system: y
|
|
||||||
no-log-init: True
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Users) != 1 {
|
|
||||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
|
||||||
}
|
|
||||||
|
|
||||||
user := cfg.Users[0]
|
|
||||||
|
|
||||||
if user.Name != "elroy" {
|
|
||||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.PasswordHash != "somehash" {
|
|
||||||
t.Errorf("User passwd is %q, expected 'somehash'", user.PasswordHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys := user.SSHAuthorizedKeys; len(keys) != 1 {
|
|
||||||
t.Errorf("Parsed %d ssh keys, expected 1", len(keys))
|
|
||||||
} else {
|
|
||||||
key := user.SSHAuthorizedKeys[0]
|
|
||||||
if key != "somekey" {
|
|
||||||
t.Errorf("User SSH key is %q, expected 'somekey'", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.GECOS != "arbitrary comment" {
|
|
||||||
t.Errorf("Failed to parse gecos field, got %q", user.GECOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Homedir != "/home/place" {
|
|
||||||
t.Errorf("Failed to parse homedir field, got %q", user.Homedir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user.NoCreateHome {
|
|
||||||
t.Errorf("Failed to parse no-create-home field")
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.PrimaryGroup != "things" {
|
|
||||||
t.Errorf("Failed to parse primary-group field, got %q", user.PrimaryGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user.Groups) != 2 {
|
|
||||||
t.Errorf("Failed to parse 2 goups, got %d", len(user.Groups))
|
|
||||||
} else {
|
|
||||||
if user.Groups[0] != "ping" {
|
|
||||||
t.Errorf("First group was %q, not expected value 'ping'", user.Groups[0])
|
|
||||||
}
|
|
||||||
if user.Groups[1] != "pong" {
|
|
||||||
t.Errorf("First group was %q, not expected value 'pong'", user.Groups[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user.NoUserGroup {
|
|
||||||
t.Errorf("Failed to parse no-user-group field")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user.System {
|
|
||||||
t.Errorf("Failed to parse system field")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user.NoLogInit {
|
|
||||||
t.Errorf("Failed to parse no-log-init field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestUnitManager struct {
|
type TestUnitManager struct {
|
||||||
placed []string
|
placed []string
|
||||||
enabled []string
|
enabled []string
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
package initialize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCloudConfigUsersGithubUser(t *testing.T) {
|
|
||||||
|
|
||||||
contents := `
|
|
||||||
users:
|
|
||||||
- name: elroy
|
|
||||||
coreos-ssh-import-github: bcwaldon
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Users) != 1 {
|
|
||||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
|
||||||
}
|
|
||||||
|
|
||||||
user := cfg.Users[0]
|
|
||||||
|
|
||||||
if user.Name != "elroy" {
|
|
||||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.SSHImportGithubUser != "bcwaldon" {
|
|
||||||
t.Errorf("github user is %q, expected 'bcwaldon'", user.SSHImportGithubUser)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,11 +3,13 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseMetaData parses a JSON blob in the OpenStack metadata service format,
|
// ParseMetaData parses a JSON blob in the OpenStack metadata service format,
|
||||||
// and converts it to a partially hydrated CloudConfig.
|
// and converts it to a partially hydrated CloudConfig.
|
||||||
func ParseMetaData(contents string) (*CloudConfig, error) {
|
func ParseMetaData(contents string) (*config.CloudConfig, error) {
|
||||||
if len(contents) == 0 {
|
if len(contents) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -22,7 +24,7 @@ func ParseMetaData(contents string) (*CloudConfig, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg CloudConfig
|
var cfg config.CloudConfig
|
||||||
if len(metadata.SSHAuthorizedKeyMap) > 0 {
|
if len(metadata.SSHAuthorizedKeyMap) > 0 {
|
||||||
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
||||||
for _, name := range sortedKeys(metadata.SSHAuthorizedKeyMap) {
|
for _, name := range sortedKeys(metadata.SSHAuthorizedKeyMap) {
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import "reflect"
|
import (
|
||||||
import "testing"
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
|
)
|
||||||
|
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
for i, tt := range []struct {
|
for i, tt := range []struct {
|
||||||
in string
|
in string
|
||||||
want *CloudConfig
|
want *config.CloudConfig
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"", nil, false},
|
{"", nil, false},
|
||||||
{`garbage, invalid json`, nil, true},
|
{`garbage, invalid json`, nil, true},
|
||||||
{`{"foo": "bar"}`, &CloudConfig{}, false},
|
{`{"foo": "bar"}`, &config.CloudConfig{}, false},
|
||||||
{`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false},
|
{`{"network_config": {"content_path": "asdf"}}`, &config.CloudConfig{NetworkConfigPath: "asdf"}, false},
|
||||||
{`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false},
|
{`{"hostname": "turkleton"}`, &config.CloudConfig{Hostname: "turkleton"}, false},
|
||||||
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"alice", "jill"}}, false},
|
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &config.CloudConfig{SSHAuthorizedKeys: []string{"alice", "jill"}}, false},
|
||||||
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"re", "fa"}, Hostname: "my_host", NetworkConfigPath: "/root"}, false},
|
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &config.CloudConfig{SSHAuthorizedKeys: []string{"re", "fa"}, Hostname: "my_host", NetworkConfigPath: "/root"}, false},
|
||||||
} {
|
} {
|
||||||
got, err := ParseMetaData(tt.in)
|
got, err := ParseMetaData(tt.in)
|
||||||
if tt.err != (err != nil) {
|
if tt.err != (err != nil) {
|
||||||
|
@ -39,31 +39,4 @@ func TestCloudConfigUsersUrlMarshal(t *testing.T) {
|
|||||||
if keys[2] != expected {
|
if keys[2] != expected {
|
||||||
t.Fatalf("expected %s, got %s", expected, keys[2])
|
t.Fatalf("expected %s, got %s", expected, keys[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
func TestCloudConfigUsersSSHImportURL(t *testing.T) {
|
|
||||||
|
|
||||||
contents := `
|
|
||||||
users:
|
|
||||||
- name: elroy
|
|
||||||
coreos-ssh-import-url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Users) != 1 {
|
|
||||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
|
||||||
}
|
|
||||||
|
|
||||||
user := cfg.Users[0]
|
|
||||||
|
|
||||||
if user.Name != "elroy" {
|
|
||||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.SSHImportURL != "https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys" {
|
|
||||||
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ func ParseUserData(contents string) (interface{}, error) {
|
|||||||
return system.Script(contents), nil
|
return system.Script(contents), nil
|
||||||
} else if header == "#cloud-config" {
|
} else if header == "#cloud-config" {
|
||||||
log.Printf("Parsing user-data as cloud-config")
|
log.Printf("Parsing user-data as cloud-config")
|
||||||
return NewCloudConfig(contents)
|
return config.NewCloudConfig(contents)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
|
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package initialize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseHeaderCRLF(t *testing.T) {
|
func TestParseHeaderCRLF(t *testing.T) {
|
||||||
@ -37,7 +39,7 @@ func TestParseConfigCRLF(t *testing.T) {
|
|||||||
t.Fatalf("Failed parsing config: %v", err)
|
t.Fatalf("Failed parsing config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := ud.(*CloudConfig)
|
cfg := ud.(*config.CloudConfig)
|
||||||
|
|
||||||
if cfg.Hostname != "foo" {
|
if cfg.Hostname != "foo" {
|
||||||
t.Error("Failed parsing hostname from config")
|
t.Error("Failed parsing hostname from config")
|
||||||
|
Loading…
Reference in New Issue
Block a user