diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 6292ed8..c5f6ed9 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -16,6 +16,7 @@ import ( "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/pkg" "github.com/coreos/coreos-cloudinit/system" @@ -35,6 +36,7 @@ var ( sources struct { file string configDrive string + waagent string metadataService bool ec2MetadataService string cloudSigmaMetadataService bool @@ -54,6 +56,7 @@ func init() { 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") @@ -82,6 +85,9 @@ var ( "from-configdrive": "/media/configdrive", "convert-netconf": "debian", }, + "azure": oemConfig{ + "from-waagent": "/var/lib/waagent", + }, } ) @@ -279,6 +285,9 @@ func getDatasources() []datasource.Datasource { 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()) } diff --git a/datasource/waagent/waagent.go b/datasource/waagent/waagent.go new file mode 100644 index 0000000..07bffe7 --- /dev/null +++ b/datasource/waagent/waagent.go @@ -0,0 +1,108 @@ +package waagent + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "net" + "os" + "path" +) + +type waagent struct { + root string + readFile func(filename string) ([]byte, error) +} + +func NewDatasource(root string) *waagent { + return &waagent{root, ioutil.ReadFile} +} + +func (a *waagent) IsAvailable() bool { + _, err := os.Stat(path.Join(a.root, "provisioned")) + return !os.IsNotExist(err) +} + +func (a *waagent) AvailabilityChanges() bool { + return true +} + +func (a *waagent) ConfigRoot() string { + return a.root +} + +func (a *waagent) FetchMetadata() ([]byte, error) { + metadataBytes, err := a.tryReadFile(path.Join(a.root, "SharedConfig.xml")) + if err != nil { + return nil, err + } + if len(metadataBytes) == 0 { + return metadataBytes, nil + } + + type Instance struct { + Id string `xml:"id,attr"` + Address string `xml:"address,attr"` + InputEndpoints struct { + Endpoints []struct { + LoadBalancedPublicAddress string `xml:"loadBalancedPublicAddress,attr"` + } `xml:"Endpoint"` + } + } + + type SharedConfig struct { + Incarnation struct { + Instance string `xml:"instance,attr"` + } + Instances struct { + Instances []Instance `xml:"Instance"` + } + } + + var metadata SharedConfig + if err := xml.Unmarshal(metadataBytes, &metadata); err != nil { + return nil, err + } + + var instance Instance + for _, i := range metadata.Instances.Instances { + if i.Id == metadata.Incarnation.Instance { + instance = i + break + } + } + + attrs := map[string]string{ + "local-ipv4": instance.Address, + } + for _, e := range instance.InputEndpoints.Endpoints { + host, _, err := net.SplitHostPort(e.LoadBalancedPublicAddress) + if err == nil { + attrs["public-ipv4"] = host + break + } + } + return json.Marshal(attrs) +} + +func (a *waagent) FetchUserdata() ([]byte, error) { + return a.tryReadFile(path.Join(a.root, "CustomData")) +} + +func (a *waagent) FetchNetworkConfig(filename string) ([]byte, error) { + return nil, nil +} + +func (a *waagent) Type() string { + return "waagent" +} + +func (a *waagent) tryReadFile(filename string) ([]byte, error) { + fmt.Printf("Attempting to read from %q\n", filename) + data, err := a.readFile(filename) + if os.IsNotExist(err) { + err = nil + } + return data, err +} diff --git a/datasource/waagent/waagent_test.go b/datasource/waagent/waagent_test.go new file mode 100644 index 0000000..2b9eee9 --- /dev/null +++ b/datasource/waagent/waagent_test.go @@ -0,0 +1,168 @@ +package waagent + +import ( + "encoding/json" + "os" + "reflect" + "testing" +) + +type mockFilesystem map[string][]byte + +func (m mockFilesystem) readFile(filename string) ([]byte, error) { + if contents := m[filename]; contents != nil { + return contents, nil + } + return nil, os.ErrNotExist +} + +func TestFetchMetadata(t *testing.T) { + for _, tt := range []struct { + root string + files mockFilesystem + metadata map[string]string + }{ + { + "/", + mockFilesystem{}, + nil, + }, + { + "/", + mockFilesystem{"/SharedConfig.xml": []byte("")}, + nil, + }, + { + "/var/lib/waagent", + mockFilesystem{"/var/lib/waagent/SharedConfig.xml": []byte("")}, + nil, + }, + { + "/var/lib/waagent", + mockFilesystem{"/var/lib/waagent/SharedConfig.xml": []byte(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`)}, + map[string]string{ + "local-ipv4": "100.73.202.64", + "public-ipv4": "191.239.39.77", + }, + }, + } { + a := waagent{tt.root, tt.files.readFile} + metadataBytes, err := a.FetchMetadata() + if err != nil { + t.Fatalf("bad error for %q: want %q, got %q", tt, nil, err) + } + var metadata map[string]string + if len(metadataBytes) > 0 { + if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + panic(err) + } + } + if !reflect.DeepEqual(tt.metadata, metadata) { + t.Fatalf("bad metadata for %q: want %q, got %q", tt, tt.metadata, metadata) + } + } +} + +func TestFetchUserdata(t *testing.T) { + for _, tt := range []struct { + root string + files mockFilesystem + }{ + { + "/", + mockFilesystem{}, + }, + { + "/", + mockFilesystem{"/CustomData": []byte{}}, + }, + { + "/var/lib/waagent/", + mockFilesystem{"/var/lib/waagent/CustomData": []byte{}}, + }, + } { + a := waagent{tt.root, tt.files.readFile} + _, err := a.FetchUserdata() + if err != nil { + t.Fatalf("bad error for %q: want %q, got %q", tt, nil, err) + } + } +} + +func TestConfigRoot(t *testing.T) { + for _, tt := range []struct { + root string + configRoot string + }{ + { + "/", + "/", + }, + { + "/var/lib/waagent", + "/var/lib/waagent", + }, + } { + a := waagent{tt.root, nil} + if configRoot := a.ConfigRoot(); configRoot != tt.configRoot { + t.Fatalf("bad config root for %q: want %q, got %q", tt, tt.configRoot, configRoot) + } + } +} + +func TestNewDatasource(t *testing.T) { + for _, tt := range []struct { + root string + expectRoot string + }{ + { + root: "", + expectRoot: "", + }, + { + root: "/var/lib/waagent", + expectRoot: "/var/lib/waagent", + }, + } { + service := NewDatasource(tt.root) + if service.root != tt.expectRoot { + t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.root) + } + } +} diff --git a/test b/test index d34ff5b..aace4dc 100755 --- a/test +++ b/test @@ -24,6 +24,7 @@ declare -a TESTPKGS=( datasource/metadata/ec2 datasource/proc_cmdline datasource/url + datasource/waagent initialize network pkg