Merge pull request #159 from crawford/metadata
metadataService: Check both ec2 and openstack urls more explicitly
This commit is contained in:
		| @@ -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=<url>', using the cloud-config served by an HTTP GET to <url>", 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) | ||||
| 	} | ||||
| @@ -98,6 +98,15 @@ func main() { | ||||
| 	} | ||||
| 	env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName, subs) | ||||
|  | ||||
| 	if len(metadataBytes) > 0 { | ||||
| 		if err := processMetadata(string(metadataBytes), env); err != nil { | ||||
| 			fmt.Printf("Failed to process meta-data: %v\n", err) | ||||
| 			die() | ||||
| 		} | ||||
| 	} else { | ||||
| 		fmt.Println("No meta-data to handle.") | ||||
| 	} | ||||
|  | ||||
| 	if len(userdataBytes) > 0 { | ||||
| 		if err := processUserdata(string(userdataBytes), env); err != nil { | ||||
| 			fmt.Printf("Failed to process user-data: %v\n", err) | ||||
| @@ -108,15 +117,6 @@ func main() { | ||||
| 	} else { | ||||
| 		fmt.Println("No user-data to handle.") | ||||
| 	} | ||||
|  | ||||
| 	if len(metadataBytes) > 0 { | ||||
| 		if err := processMetadata(string(metadataBytes), env); err != nil { | ||||
| 			fmt.Printf("Failed to process meta-data: %v\n", err) | ||||
| 			die() | ||||
| 		} | ||||
| 	} else { | ||||
| 		fmt.Println("No meta-data to handle.") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getDatasource() datasource.Datasource { | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user