]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // Package ec2metadata provides the client for making API calls to the |
2 | // EC2 Metadata service. | |
3 | package ec2metadata | |
4 | ||
5 | import ( | |
6 | "bytes" | |
7 | "errors" | |
8 | "io" | |
9 | "net/http" | |
10 | "time" | |
11 | ||
12 | "github.com/aws/aws-sdk-go/aws" | |
13 | "github.com/aws/aws-sdk-go/aws/awserr" | |
14 | "github.com/aws/aws-sdk-go/aws/client" | |
15 | "github.com/aws/aws-sdk-go/aws/client/metadata" | |
16 | "github.com/aws/aws-sdk-go/aws/request" | |
17 | ) | |
18 | ||
19 | // ServiceName is the name of the service. | |
20 | const ServiceName = "ec2metadata" | |
21 | ||
22 | // A EC2Metadata is an EC2 Metadata service Client. | |
23 | type EC2Metadata struct { | |
24 | *client.Client | |
25 | } | |
26 | ||
27 | // New creates a new instance of the EC2Metadata client with a session. | |
28 | // This client is safe to use across multiple goroutines. | |
29 | // | |
30 | // | |
31 | // Example: | |
32 | // // Create a EC2Metadata client from just a session. | |
33 | // svc := ec2metadata.New(mySession) | |
34 | // | |
35 | // // Create a EC2Metadata client with additional configuration | |
36 | // svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody)) | |
37 | func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata { | |
38 | c := p.ClientConfig(ServiceName, cfgs...) | |
39 | return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion) | |
40 | } | |
41 | ||
42 | // NewClient returns a new EC2Metadata client. Should be used to create | |
43 | // a client when not using a session. Generally using just New with a session | |
44 | // is preferred. | |
45 | // | |
46 | // If an unmodified HTTP client is provided from the stdlib default, or no client | |
47 | // the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened. | |
48 | // To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default. | |
49 | func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata { | |
50 | if !aws.BoolValue(cfg.EC2MetadataDisableTimeoutOverride) && httpClientZero(cfg.HTTPClient) { | |
51 | // If the http client is unmodified and this feature is not disabled | |
52 | // set custom timeouts for EC2Metadata requests. | |
53 | cfg.HTTPClient = &http.Client{ | |
54 | // use a shorter timeout than default because the metadata | |
55 | // service is local if it is running, and to fail faster | |
56 | // if not running on an ec2 instance. | |
57 | Timeout: 5 * time.Second, | |
58 | } | |
59 | } | |
60 | ||
61 | svc := &EC2Metadata{ | |
62 | Client: client.New( | |
63 | cfg, | |
64 | metadata.ClientInfo{ | |
65 | ServiceName: ServiceName, | |
66 | Endpoint: endpoint, | |
67 | APIVersion: "latest", | |
68 | }, | |
69 | handlers, | |
70 | ), | |
71 | } | |
72 | ||
73 | svc.Handlers.Unmarshal.PushBack(unmarshalHandler) | |
74 | svc.Handlers.UnmarshalError.PushBack(unmarshalError) | |
75 | svc.Handlers.Validate.Clear() | |
76 | svc.Handlers.Validate.PushBack(validateEndpointHandler) | |
77 | ||
78 | // Add additional options to the service config | |
79 | for _, option := range opts { | |
80 | option(svc.Client) | |
81 | } | |
82 | ||
83 | return svc | |
84 | } | |
85 | ||
86 | func httpClientZero(c *http.Client) bool { | |
87 | return c == nil || (c.Transport == nil && c.CheckRedirect == nil && c.Jar == nil && c.Timeout == 0) | |
88 | } | |
89 | ||
90 | type metadataOutput struct { | |
91 | Content string | |
92 | } | |
93 | ||
94 | func unmarshalHandler(r *request.Request) { | |
95 | defer r.HTTPResponse.Body.Close() | |
96 | b := &bytes.Buffer{} | |
97 | if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil { | |
98 | r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata respose", err) | |
99 | return | |
100 | } | |
101 | ||
102 | if data, ok := r.Data.(*metadataOutput); ok { | |
103 | data.Content = b.String() | |
104 | } | |
105 | } | |
106 | ||
107 | func unmarshalError(r *request.Request) { | |
108 | defer r.HTTPResponse.Body.Close() | |
109 | b := &bytes.Buffer{} | |
110 | if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil { | |
111 | r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata error respose", err) | |
112 | return | |
113 | } | |
114 | ||
115 | // Response body format is not consistent between metadata endpoints. | |
116 | // Grab the error message as a string and include that as the source error | |
117 | r.Error = awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New(b.String())) | |
118 | } | |
119 | ||
120 | func validateEndpointHandler(r *request.Request) { | |
121 | if r.ClientInfo.Endpoint == "" { | |
122 | r.Error = aws.ErrMissingEndpoint | |
123 | } | |
124 | } |