From fae81c78f3160aad17323a4f598d3d4e53e3f29c Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Tue, 24 Jun 2014 17:56:05 -0700 Subject: [PATCH] metadataService: Check both ec2 and openstack urls more explicitly Remove the root url parameter for -from-metadata-service since this is a guarenteed value. Additionally, check for both ec2 and openstack urls for the metadata and userdata. Fix a bug with the -from-url option and a panic on an empty response. --- coreos-cloudinit.go | 20 ++++++------ datasource/metadata_service.go | 47 +++++++++++++++++++---------- datasource/metadata_service_test.go | 18 +++++------ 3 files changed, 50 insertions(+), 35 deletions(-) diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 8079bbc..9e89c4b 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -16,11 +16,11 @@ var ( printVersion bool ignoreFailure bool sources struct { - file string - configDrive string - metadataService string - url string - procCmdLine bool + file string + configDrive string + metadataService bool + url string + procCmdLine bool } convertNetconf string workspace string @@ -32,7 +32,7 @@ func init() { flag.BoolVar(&ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data") flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file") flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory") - flag.StringVar(&sources.metadataService, "from-metadata-service", "", "Download data from provided url") + flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "Download data from metadata service") 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=', using the cloud-config served by an HTTP GET to ", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag)) 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)") @@ -55,7 +55,7 @@ func main() { os.Exit(0) } - if convertNetconf != "" && sources.configDrive == "" && sources.metadataService == "" { + if convertNetconf != "" && sources.configDrive == "" && !sources.metadataService { fmt.Println("-convert-netconf flag requires -from-configdrive or -from-metadata-service") os.Exit(1) } @@ -127,15 +127,15 @@ func getDatasource() datasource.Datasource { n++ } if sources.url != "" { - ds = datasource.NewMetadataService(sources.url) + ds = datasource.NewRemoteFile(sources.url) n++ } if sources.configDrive != "" { ds = datasource.NewConfigDrive(sources.configDrive) n++ } - if sources.metadataService != "" { - ds = datasource.NewMetadataService(sources.metadataService) + if sources.metadataService { + ds = datasource.NewMetadataService() n++ } if sources.procCmdLine { diff --git a/datasource/metadata_service.go b/datasource/metadata_service.go index 996071c..06d2412 100644 --- a/datasource/metadata_service.go +++ b/datasource/metadata_service.go @@ -9,23 +9,34 @@ import ( "github.com/coreos/coreos-cloudinit/pkg" ) -// metadataService retrieves metadata from either an OpenStack[1] or EC2[2] compatible endpoint. -// It will first attempt to directly retrieve a JSON blob from the OpenStack endpoint. If that -// fails with a 404, it then attempts to retrieve metadata bit-by-bit from the EC2 endpoint, -// and populates that into an equivalent JSON blob. +// metadataService retrieves metadata from either an OpenStack[1] (2012-08-10) +// or EC2[2] (2009-04-04) compatible endpoint. It will first attempt to +// directly retrieve a JSON blob from the OpenStack endpoint. If that fails +// with a 404, it then attempts to retrieve metadata bit-by-bit from the EC2 +// endpoint, and populates that into an equivalent JSON blob. metadataService +// also checks for userdata from EC2 and, if that fails with a 404, OpenStack. // // [1] http://docs.openstack.org/grizzly/openstack-compute/admin/content/metadata-service.html // [2] http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html#instancedata-data-categories -type metadataService struct { - url string -} + +const ( + BaseUrl = "http://169.254.169.254/" + Ec2ApiVersion = "2009-04-04" + Ec2UserdataUrl = BaseUrl + Ec2ApiVersion + "/user-data" + Ec2MetadataUrl = BaseUrl + Ec2ApiVersion + "/meta-data/" + OpenstackApiVersion = "openstack/2012-08-10" + OpenstackUserdataUrl = BaseUrl + OpenstackApiVersion + "/user_data" + OpenstackMetadataUrl = BaseUrl + OpenstackApiVersion + "/meta_data.json" +) + +type metadataService struct{} type getter interface { Get(string) ([]byte, error) } -func NewMetadataService(url string) *metadataService { - return &metadataService{strings.TrimSuffix(url, "/")} +func NewMetadataService() *metadataService { + return &metadataService{} } func (ms *metadataService) ConfigRoot() string { @@ -33,27 +44,31 @@ func (ms *metadataService) ConfigRoot() string { } func (ms *metadataService) FetchMetadata() ([]byte, error) { - client := pkg.NewHttpClient() - return fetchMetadata(client, ms.url) + return fetchMetadata(pkg.NewHttpClient()) } func (ms *metadataService) FetchUserdata() ([]byte, error) { client := pkg.NewHttpClient() - return client.Get(ms.url + "/latest/user-data") + if data, err := client.Get(Ec2UserdataUrl); err == nil { + return data, err + } else if _, ok := err.(pkg.ErrTimeout); ok { + return data, err + } + return client.Get(OpenstackUserdataUrl) } func (ms *metadataService) Type() string { return "metadata-service" } -func fetchMetadata(client getter, url string) ([]byte, error) { - if metadata, err := client.Get(url + "/latest/meta-data.json"); err == nil { +func fetchMetadata(client getter) ([]byte, error) { + if metadata, err := client.Get(OpenstackMetadataUrl); err == nil { return metadata, nil } else if _, ok := err.(pkg.ErrTimeout); ok { return nil, err } - attrs, err := fetchChildAttributes(client, url+"/latest/meta-data/") + attrs, err := fetchChildAttributes(client, Ec2MetadataUrl) if err != nil { return nil, err } @@ -74,7 +89,7 @@ func fetchAttributes(client getter, url string) ([]string, error) { } func fetchAttribute(client getter, url string) (interface{}, error) { - if attrs, err := fetchAttributes(client, url); err == nil { + if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 { return attrs[0], nil } else { return "", err diff --git a/datasource/metadata_service_test.go b/datasource/metadata_service_test.go index 0089533..104f97e 100644 --- a/datasource/metadata_service_test.go +++ b/datasource/metadata_service_test.go @@ -137,19 +137,19 @@ func TestFetchMetadata(t *testing.T) { }{ { metadata: map[string]string{ - "/latest/meta-data/": "a\nb\nc/", - "/latest/meta-data/c/": "d\ne/", - "/latest/meta-data/c/e/": "f", - "/latest/meta-data/a": "1", - "/latest/meta-data/b": "2", - "/latest/meta-data/c/d": "3", - "/latest/meta-data/c/e/f": "4", + "http://169.254.169.254/2009-04-04/meta-data/": "a\nb\nc/", + "http://169.254.169.254/2009-04-04/meta-data/c/": "d\ne/", + "http://169.254.169.254/2009-04-04/meta-data/c/e/": "f", + "http://169.254.169.254/2009-04-04/meta-data/a": "1", + "http://169.254.169.254/2009-04-04/meta-data/b": "2", + "http://169.254.169.254/2009-04-04/meta-data/c/d": "3", + "http://169.254.169.254/2009-04-04/meta-data/c/e/f": "4", }, expect: []byte(`{"a":"1","b":"2","c":{"d":"3","e":{"f":"4"}}}`), }, { metadata: map[string]string{ - "/latest/meta-data.json": "test", + "http://169.254.169.254/openstack/2012-08-10/meta_data.json": "test", }, expect: []byte("test"), }, @@ -157,7 +157,7 @@ func TestFetchMetadata(t *testing.T) { {err: pkg.ErrNotFound{fmt.Errorf("test error")}}, } { client := &TestHttpClient{tt.metadata, tt.err} - metadata, err := fetchMetadata(client, "") + metadata, err := fetchMetadata(client) if err != tt.err { t.Fatalf("bad error (%q): want %q, got %q", tt.metadata, tt.err, err) }