@aws-sdk/ec2-metadata-service 3.953.0 → 3.954.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cjs/index.js CHANGED
@@ -40,6 +40,8 @@ const IMDSv1_DISABLED_SELECTORS = {
40
40
  class MetadataService {
41
41
  disableFetchToken;
42
42
  config;
43
+ retries;
44
+ backoffFn;
43
45
  constructor(options = {}) {
44
46
  this.config = (async () => {
45
47
  const profile = options?.profile || process.env.AWS_PROFILE;
@@ -52,58 +54,106 @@ class MetadataService {
52
54
  };
53
55
  })();
54
56
  this.disableFetchToken = options?.disableFetchToken || false;
57
+ this.retries = options?.retries ?? 3;
58
+ this.backoffFn = this.createBackoffFunction(options?.backoff);
55
59
  }
56
- async request(path, options) {
57
- const { endpoint, ec2MetadataV1Disabled, httpOptions } = await this.config;
58
- const handler = new nodeHttpHandler.NodeHttpHandler({
59
- requestTimeout: httpOptions?.timeout,
60
- throwOnRequestTimeout: true,
61
- connectionTimeout: httpOptions?.timeout,
62
- });
63
- const endpointUrl = new URL(endpoint);
64
- const headers = options.headers || {};
65
- if (this.disableFetchToken && ec2MetadataV1Disabled) {
66
- throw new Error("IMDSv1 is disabled and fetching token is disabled, cannot make the request.");
60
+ createBackoffFunction(backoff) {
61
+ if (typeof backoff === "function") {
62
+ return backoff;
63
+ }
64
+ if (typeof backoff === "number") {
65
+ return () => backoff;
67
66
  }
68
- if (!this.disableFetchToken) {
67
+ return (numFailures) => Math.pow(1.2, numFailures);
68
+ }
69
+ sleep(ms) {
70
+ return new Promise((resolve) => setTimeout(resolve, ms));
71
+ }
72
+ async retryWithBackoff(operation) {
73
+ let lastError;
74
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
69
75
  try {
70
- headers["x-aws-ec2-metadata-token"] = await this.fetchMetadataToken();
76
+ return await operation();
71
77
  }
72
- catch (err) {
73
- if (ec2MetadataV1Disabled) {
74
- throw err;
78
+ catch (error) {
79
+ lastError = error;
80
+ if (attempt === this.retries) {
81
+ break;
82
+ }
83
+ if (this.shouldNotRetry(error)) {
84
+ throw error;
85
+ }
86
+ const backoffResult = this.backoffFn(attempt);
87
+ if (typeof backoffResult === "number") {
88
+ await this.sleep(backoffResult * 1000);
89
+ }
90
+ else {
91
+ await backoffResult;
75
92
  }
76
93
  }
77
94
  }
78
- const request = new protocolHttp.HttpRequest({
79
- method: options.method || "GET",
80
- headers: headers,
81
- hostname: endpointUrl.hostname,
82
- path: endpointUrl.pathname + path,
83
- protocol: endpointUrl.protocol,
84
- port: endpointUrl.port ? parseInt(endpointUrl.port) : undefined,
85
- });
86
- try {
87
- const { response } = await handler.handle(request, {});
88
- if (response.statusCode === 200 && response.body) {
89
- return utilStream.sdkStreamMixin(response.body).transformToString();
95
+ throw lastError;
96
+ }
97
+ shouldNotRetry(error) {
98
+ const statusCode = error.statusCode || error.$metadata?.httpStatusCode;
99
+ return statusCode === 400 || statusCode === 403 || statusCode === 404;
100
+ }
101
+ async request(path, options) {
102
+ return this.retryWithBackoff(async () => {
103
+ const { endpoint, ec2MetadataV1Disabled, httpOptions } = await this.config;
104
+ const handler = new nodeHttpHandler.NodeHttpHandler({
105
+ requestTimeout: httpOptions?.timeout,
106
+ throwOnRequestTimeout: true,
107
+ connectionTimeout: httpOptions?.timeout,
108
+ });
109
+ const endpointUrl = new URL(endpoint);
110
+ const headers = options.headers || {};
111
+ if (this.disableFetchToken && ec2MetadataV1Disabled) {
112
+ throw new Error("IMDSv1 is disabled and fetching token is disabled, cannot make the request.");
90
113
  }
91
- else {
92
- throw Object.assign(new Error(`Request failed with status code ${response.statusCode}`), {
93
- $metadata: { httpStatusCode: response.statusCode },
94
- });
114
+ if (!this.disableFetchToken) {
115
+ try {
116
+ headers["x-aws-ec2-metadata-token"] = await this.fetchMetadataTokenInternal();
117
+ }
118
+ catch (err) {
119
+ if (ec2MetadataV1Disabled) {
120
+ throw err;
121
+ }
122
+ }
95
123
  }
96
- }
97
- catch (error) {
98
- const wrappedError = new Error(`Error making request to the metadata service: ${error}`);
99
- const { $metadata } = error;
100
- if ($metadata?.httpStatusCode !== undefined) {
101
- Object.assign(wrappedError, { $metadata });
124
+ const request = new protocolHttp.HttpRequest({
125
+ method: options.method || "GET",
126
+ headers: headers,
127
+ hostname: endpointUrl.hostname,
128
+ path: endpointUrl.pathname + path,
129
+ protocol: endpointUrl.protocol,
130
+ port: endpointUrl.port ? parseInt(endpointUrl.port) : undefined,
131
+ });
132
+ try {
133
+ const { response } = await handler.handle(request, {});
134
+ if (response.statusCode === 200 && response.body) {
135
+ return utilStream.sdkStreamMixin(response.body).transformToString();
136
+ }
137
+ else {
138
+ throw Object.assign(new Error(`Request failed with status code ${response.statusCode}`), {
139
+ $metadata: { httpStatusCode: response.statusCode },
140
+ });
141
+ }
102
142
  }
103
- throw wrappedError;
104
- }
143
+ catch (error) {
144
+ const wrappedError = new Error(`Error making request to the metadata service: ${error}`);
145
+ const { $metadata } = error;
146
+ if ($metadata?.httpStatusCode !== undefined) {
147
+ Object.assign(wrappedError, { $metadata });
148
+ }
149
+ throw wrappedError;
150
+ }
151
+ });
105
152
  }
106
153
  async fetchMetadataToken() {
154
+ return this.retryWithBackoff(() => this.fetchMetadataTokenInternal());
155
+ }
156
+ async fetchMetadataTokenInternal() {
107
157
  const { endpoint, httpOptions } = await this.config;
108
158
  const handler = new nodeHttpHandler.NodeHttpHandler({
109
159
  requestTimeout: httpOptions?.timeout,
@@ -132,12 +182,13 @@ class MetadataService {
132
182
  }
133
183
  else {
134
184
  throw Object.assign(new Error(`Failed to fetch metadata token with status code ${response.statusCode}`), {
135
- statusCode: response.statusCode,
185
+ $metadata: { httpStatusCode: response.statusCode },
136
186
  });
137
187
  }
138
188
  }
139
189
  catch (error) {
140
- if (error.message === "TimeoutError" || [403, 404, 405].includes(error.statusCode)) {
190
+ if (error.message === "TimeoutError" ||
191
+ [403, 404, 405].includes(error.statusCode || error.$metadata?.httpStatusCode)) {
141
192
  this.disableFetchToken = true;
142
193
  throw new Error(`Error fetching metadata token: ${error}. [disableFetchToken] is now set to true.`);
143
194
  }
@@ -6,6 +6,8 @@ import { ENDPOINT_SELECTORS, IMDSv1_DISABLED_SELECTORS } from "./ConfigLoaders";
6
6
  export class MetadataService {
7
7
  disableFetchToken;
8
8
  config;
9
+ retries;
10
+ backoffFn;
9
11
  constructor(options = {}) {
10
12
  this.config = (async () => {
11
13
  const profile = options?.profile || process.env.AWS_PROFILE;
@@ -18,58 +20,106 @@ export class MetadataService {
18
20
  };
19
21
  })();
20
22
  this.disableFetchToken = options?.disableFetchToken || false;
23
+ this.retries = options?.retries ?? 3;
24
+ this.backoffFn = this.createBackoffFunction(options?.backoff);
21
25
  }
22
- async request(path, options) {
23
- const { endpoint, ec2MetadataV1Disabled, httpOptions } = await this.config;
24
- const handler = new NodeHttpHandler({
25
- requestTimeout: httpOptions?.timeout,
26
- throwOnRequestTimeout: true,
27
- connectionTimeout: httpOptions?.timeout,
28
- });
29
- const endpointUrl = new URL(endpoint);
30
- const headers = options.headers || {};
31
- if (this.disableFetchToken && ec2MetadataV1Disabled) {
32
- throw new Error("IMDSv1 is disabled and fetching token is disabled, cannot make the request.");
26
+ createBackoffFunction(backoff) {
27
+ if (typeof backoff === "function") {
28
+ return backoff;
29
+ }
30
+ if (typeof backoff === "number") {
31
+ return () => backoff;
33
32
  }
34
- if (!this.disableFetchToken) {
33
+ return (numFailures) => Math.pow(1.2, numFailures);
34
+ }
35
+ sleep(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+ async retryWithBackoff(operation) {
39
+ let lastError;
40
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
35
41
  try {
36
- headers["x-aws-ec2-metadata-token"] = await this.fetchMetadataToken();
42
+ return await operation();
37
43
  }
38
- catch (err) {
39
- if (ec2MetadataV1Disabled) {
40
- throw err;
44
+ catch (error) {
45
+ lastError = error;
46
+ if (attempt === this.retries) {
47
+ break;
48
+ }
49
+ if (this.shouldNotRetry(error)) {
50
+ throw error;
51
+ }
52
+ const backoffResult = this.backoffFn(attempt);
53
+ if (typeof backoffResult === "number") {
54
+ await this.sleep(backoffResult * 1000);
55
+ }
56
+ else {
57
+ await backoffResult;
41
58
  }
42
59
  }
43
60
  }
44
- const request = new HttpRequest({
45
- method: options.method || "GET",
46
- headers: headers,
47
- hostname: endpointUrl.hostname,
48
- path: endpointUrl.pathname + path,
49
- protocol: endpointUrl.protocol,
50
- port: endpointUrl.port ? parseInt(endpointUrl.port) : undefined,
51
- });
52
- try {
53
- const { response } = await handler.handle(request, {});
54
- if (response.statusCode === 200 && response.body) {
55
- return sdkStreamMixin(response.body).transformToString();
61
+ throw lastError;
62
+ }
63
+ shouldNotRetry(error) {
64
+ const statusCode = error.statusCode || error.$metadata?.httpStatusCode;
65
+ return statusCode === 400 || statusCode === 403 || statusCode === 404;
66
+ }
67
+ async request(path, options) {
68
+ return this.retryWithBackoff(async () => {
69
+ const { endpoint, ec2MetadataV1Disabled, httpOptions } = await this.config;
70
+ const handler = new NodeHttpHandler({
71
+ requestTimeout: httpOptions?.timeout,
72
+ throwOnRequestTimeout: true,
73
+ connectionTimeout: httpOptions?.timeout,
74
+ });
75
+ const endpointUrl = new URL(endpoint);
76
+ const headers = options.headers || {};
77
+ if (this.disableFetchToken && ec2MetadataV1Disabled) {
78
+ throw new Error("IMDSv1 is disabled and fetching token is disabled, cannot make the request.");
56
79
  }
57
- else {
58
- throw Object.assign(new Error(`Request failed with status code ${response.statusCode}`), {
59
- $metadata: { httpStatusCode: response.statusCode },
60
- });
80
+ if (!this.disableFetchToken) {
81
+ try {
82
+ headers["x-aws-ec2-metadata-token"] = await this.fetchMetadataTokenInternal();
83
+ }
84
+ catch (err) {
85
+ if (ec2MetadataV1Disabled) {
86
+ throw err;
87
+ }
88
+ }
61
89
  }
62
- }
63
- catch (error) {
64
- const wrappedError = new Error(`Error making request to the metadata service: ${error}`);
65
- const { $metadata } = error;
66
- if ($metadata?.httpStatusCode !== undefined) {
67
- Object.assign(wrappedError, { $metadata });
90
+ const request = new HttpRequest({
91
+ method: options.method || "GET",
92
+ headers: headers,
93
+ hostname: endpointUrl.hostname,
94
+ path: endpointUrl.pathname + path,
95
+ protocol: endpointUrl.protocol,
96
+ port: endpointUrl.port ? parseInt(endpointUrl.port) : undefined,
97
+ });
98
+ try {
99
+ const { response } = await handler.handle(request, {});
100
+ if (response.statusCode === 200 && response.body) {
101
+ return sdkStreamMixin(response.body).transformToString();
102
+ }
103
+ else {
104
+ throw Object.assign(new Error(`Request failed with status code ${response.statusCode}`), {
105
+ $metadata: { httpStatusCode: response.statusCode },
106
+ });
107
+ }
68
108
  }
69
- throw wrappedError;
70
- }
109
+ catch (error) {
110
+ const wrappedError = new Error(`Error making request to the metadata service: ${error}`);
111
+ const { $metadata } = error;
112
+ if ($metadata?.httpStatusCode !== undefined) {
113
+ Object.assign(wrappedError, { $metadata });
114
+ }
115
+ throw wrappedError;
116
+ }
117
+ });
71
118
  }
72
119
  async fetchMetadataToken() {
120
+ return this.retryWithBackoff(() => this.fetchMetadataTokenInternal());
121
+ }
122
+ async fetchMetadataTokenInternal() {
73
123
  const { endpoint, httpOptions } = await this.config;
74
124
  const handler = new NodeHttpHandler({
75
125
  requestTimeout: httpOptions?.timeout,
@@ -98,12 +148,13 @@ export class MetadataService {
98
148
  }
99
149
  else {
100
150
  throw Object.assign(new Error(`Failed to fetch metadata token with status code ${response.statusCode}`), {
101
- statusCode: response.statusCode,
151
+ $metadata: { httpStatusCode: response.statusCode },
102
152
  });
103
153
  }
104
154
  }
105
155
  catch (error) {
106
- if (error.message === "TimeoutError" || [403, 404, 405].includes(error.statusCode)) {
156
+ if (error.message === "TimeoutError" ||
157
+ [403, 404, 405].includes(error.statusCode || error.$metadata?.httpStatusCode)) {
107
158
  this.disableFetchToken = true;
108
159
  throw new Error(`Error fetching metadata token: ${error}. [disableFetchToken] is now set to true.`);
109
160
  }
@@ -5,13 +5,20 @@ import { MetadataServiceOptions } from "./MetadataServiceOptions";
5
5
  export declare class MetadataService {
6
6
  private disableFetchToken;
7
7
  private config;
8
+ private retries;
9
+ private backoffFn;
8
10
  /**
9
11
  * Creates a new MetadataService object with a given set of options.
10
12
  */
11
13
  constructor(options?: MetadataServiceOptions);
14
+ private createBackoffFunction;
15
+ private sleep;
16
+ private retryWithBackoff;
17
+ private shouldNotRetry;
12
18
  request(path: string, options: {
13
19
  method?: string;
14
20
  headers?: Record<string, string>;
15
21
  }): Promise<string>;
16
22
  fetchMetadataToken(): Promise<string>;
23
+ private fetchMetadataTokenInternal;
17
24
  }
@@ -27,4 +27,14 @@ export interface MetadataServiceOptions {
27
27
  * when true, metadata service will not fetch token, which indicates usage of IMDSv1
28
28
  */
29
29
  disableFetchToken?: boolean;
30
+ /**
31
+ * the number of retry attempts for any failed request, defaulting to 3.
32
+ */
33
+ retries?: number;
34
+ /**
35
+ * the number of seconds to sleep in-between retries and/or a customer provided backoff function to call.
36
+ * if the function returns a promise, it will be awaited and its resolved value ignored.
37
+ * if the function returns a number, the number will be used as seconds duration to wait before the following retry attempt.
38
+ */
39
+ backoff?: number | ((numFailures: number) => Promise<void> | number);
30
40
  }
@@ -2,7 +2,13 @@ import { MetadataServiceOptions } from "./MetadataServiceOptions";
2
2
  export declare class MetadataService {
3
3
  private disableFetchToken;
4
4
  private config;
5
+ private retries;
6
+ private backoffFn;
5
7
  constructor(options?: MetadataServiceOptions);
8
+ private createBackoffFunction;
9
+ private sleep;
10
+ private retryWithBackoff;
11
+ private shouldNotRetry;
6
12
  request(
7
13
  path: string,
8
14
  options: {
@@ -11,4 +17,5 @@ export declare class MetadataService {
11
17
  }
12
18
  ): Promise<string>;
13
19
  fetchMetadataToken(): Promise<string>;
20
+ private fetchMetadataTokenInternal;
14
21
  }
@@ -6,4 +6,6 @@ export interface MetadataServiceOptions {
6
6
  ec2MetadataV1Disabled?: boolean;
7
7
  profile?: string;
8
8
  disableFetchToken?: boolean;
9
+ retries?: number;
10
+ backoff?: number | ((numFailures: number) => Promise<void> | number);
9
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-sdk/ec2-metadata-service",
3
- "version": "3.953.0",
3
+ "version": "3.954.0",
4
4
  "scripts": {
5
5
  "build": "concurrently 'yarn:build:types' 'yarn:build:es' && yarn build:cjs",
6
6
  "build:cjs": "node ../../scripts/compilation/inline ec2-metadata-service",
@@ -33,7 +33,7 @@
33
33
  "tslib": "^2.6.2"
34
34
  },
35
35
  "devDependencies": {
36
- "@aws-sdk/credential-providers": "3.953.0",
36
+ "@aws-sdk/credential-providers": "3.954.0",
37
37
  "@tsconfig/recommended": "1.0.1",
38
38
  "@types/node": "^18.19.69",
39
39
  "concurrently": "7.0.0",