@roberta.soliman/n8n-nodes-aws-cost-explorer 0.1.1

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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # n8n-nodes-aws-cost-explorer
2
+
3
+ This is an n8n community node that lets you retrieve cost and usage data from AWS Cost Explorer in your n8n workflows.
4
+
5
+ [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
6
+
7
+ ## Installation
8
+
9
+ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
10
+
11
+ To install via n8n settings:
12
+ 1. Go to Settings > Community Nodes
13
+ 2. Click "Install Community Node"
14
+ 3. Enter `@roberta.soliman/n8n-nodes-aws-cost-explorer`
15
+ 4. Click Install
16
+
17
+ ## Publish (maintainer)
18
+
19
+ 1. Create an [npm access token](https://www.npmjs.com/settings/~your-user/tokens) (type **Automation**).
20
+ 2. In GitHub: **Settings → Secrets and variables → Actions** → add `NPM_TOKEN` with that token.
21
+ 3. Bump `version` in `package.json`, commit, push.
22
+ 4. Create a [GitHub Release](https://github.com/robertasolimandonofreo/n8n-aws-cost-explorer/releases/new) (tag `v0.1.2` matching the version in `package.json`).
23
+
24
+ The workflow [publish-npm.yml](.github/workflows/publish-npm.yml) runs on release publish and pushes the package to npm. You can also run it manually from **Actions → Publish to npm → Run workflow**.
25
+
26
+ ## Prerequisites
27
+
28
+ You need:
29
+ - AWS account with Cost Explorer enabled
30
+ - AWS IAM user/role with Cost Explorer permissions
31
+ - AWS Access Key ID and Secret Access Key
32
+
33
+ ## Credentials
34
+
35
+ This node requires AWS Cost Explorer API credentials:
36
+ - **AWS Access Key ID**: Your AWS access key
37
+ - **AWS Secret Access Key**: Your AWS secret key
38
+ - **Region**: AWS region (usually us-east-1 for Cost Explorer)
39
+
40
+ Required IAM permissions:
41
+ ```json
42
+ {
43
+ "Version": "2012-10-17",
44
+ "Statement": [
45
+ {
46
+ "Effect": "Allow",
47
+ "Action": [
48
+ "ce:GetCostAndUsage",
49
+ "ce:GetDimensionValues"
50
+ ],
51
+ "Resource": "*"
52
+ }
53
+ ]
54
+ }
55
+ ```
56
+
57
+ ## Operations
58
+
59
+ ### Cost and Usage
60
+ - **Get**: Retrieve cost and usage data for a specified time period
61
+
62
+ ### Dimension Values
63
+ - **Get**: Get available values for AWS cost dimensions (Service, Account, Instance Type, Region)
64
+
65
+ ## Usage Example
66
+
67
+ 1. Add AWS Cost Explorer node to your workflow
68
+ 2. Configure credentials
69
+ 3. Select "Cost and Usage" > "Get"
70
+ 4. Set start/end dates (YYYY-MM-DD format)
71
+ 5. Choose granularity (Daily/Monthly/Hourly)
72
+ 6. Select metrics (Blended Cost, Unblended Cost, Usage Quantity)
73
+
74
+ ## License
75
+
76
+ [MIT](https://github.com/n8n-io/n8n-nodes-starter/blob/master/LICENSE.md)
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class AwsCostExplorer implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,579 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.AwsCostExplorer = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ class AwsCostExplorer {
29
+ constructor() {
30
+ this.description = {
31
+ displayName: 'AWS Cost Explorer',
32
+ name: 'awsCostExplorer',
33
+ icon: 'file:awscostexplorer.svg',
34
+ group: ['transform'],
35
+ version: 1,
36
+ subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
37
+ description: 'Get cost and usage data from AWS Cost Explorer',
38
+ defaults: {
39
+ name: 'AWS Cost Explorer',
40
+ },
41
+ inputs: ['main'],
42
+ outputs: ['main'],
43
+ credentials: [
44
+ {
45
+ name: 'awsCostExplorerApi',
46
+ required: true,
47
+ },
48
+ ],
49
+ properties: [
50
+ // ----------------------------------------------------------------
51
+ // Resource
52
+ // ----------------------------------------------------------------
53
+ {
54
+ displayName: 'Resource',
55
+ name: 'resource',
56
+ type: 'options',
57
+ noDataExpression: true,
58
+ options: [
59
+ { name: 'Cost and Usage', value: 'costAndUsage' },
60
+ { name: 'Cost Forecast', value: 'costForecast' },
61
+ { name: 'Dimension Values', value: 'dimensionValues' },
62
+ { name: 'Reserved Instances', value: 'reservedInstances' },
63
+ { name: 'Savings Plans', value: 'savingsPlans' },
64
+ ],
65
+ default: 'costAndUsage',
66
+ },
67
+ // ----------------------------------------------------------------
68
+ // Operation — Cost and Usage
69
+ // ----------------------------------------------------------------
70
+ {
71
+ displayName: 'Operation',
72
+ name: 'operation',
73
+ type: 'options',
74
+ noDataExpression: true,
75
+ displayOptions: { show: { resource: ['costAndUsage'] } },
76
+ options: [
77
+ { name: 'Get', value: 'get', description: 'Get cost and usage data', action: 'Get cost and usage data' },
78
+ ],
79
+ default: 'get',
80
+ },
81
+ // ----------------------------------------------------------------
82
+ // Operation — Cost Forecast
83
+ // ----------------------------------------------------------------
84
+ {
85
+ displayName: 'Operation',
86
+ name: 'operation',
87
+ type: 'options',
88
+ noDataExpression: true,
89
+ displayOptions: { show: { resource: ['costForecast'] } },
90
+ options: [
91
+ { name: 'Get', value: 'get', description: 'Get cost forecast', action: 'Get cost forecast' },
92
+ ],
93
+ default: 'get',
94
+ },
95
+ // ----------------------------------------------------------------
96
+ // Operation — Dimension Values
97
+ // ----------------------------------------------------------------
98
+ {
99
+ displayName: 'Operation',
100
+ name: 'operation',
101
+ type: 'options',
102
+ noDataExpression: true,
103
+ displayOptions: { show: { resource: ['dimensionValues'] } },
104
+ options: [
105
+ { name: 'Get', value: 'get', description: 'Get dimension values', action: 'Get dimension values' },
106
+ ],
107
+ default: 'get',
108
+ },
109
+ // ----------------------------------------------------------------
110
+ // Operation — Reserved Instances
111
+ // ----------------------------------------------------------------
112
+ {
113
+ displayName: 'Operation',
114
+ name: 'operation',
115
+ type: 'options',
116
+ noDataExpression: true,
117
+ displayOptions: { show: { resource: ['reservedInstances'] } },
118
+ options: [
119
+ { name: 'Get Utilization', value: 'getUtilization', description: 'Get RI utilization and unused hours', action: 'Get RI utilization' },
120
+ { name: 'Get Coverage', value: 'getCoverage', description: 'Get percentage of usage covered by RIs', action: 'Get RI coverage' },
121
+ ],
122
+ default: 'getUtilization',
123
+ },
124
+ // ----------------------------------------------------------------
125
+ // Operation — Savings Plans
126
+ // ----------------------------------------------------------------
127
+ {
128
+ displayName: 'Operation',
129
+ name: 'operation',
130
+ type: 'options',
131
+ noDataExpression: true,
132
+ displayOptions: { show: { resource: ['savingsPlans'] } },
133
+ options: [
134
+ { name: 'Get Utilization', value: 'getUtilization', description: 'Get SP utilization and unused commitment', action: 'Get SP utilization' },
135
+ { name: 'Get Coverage', value: 'getCoverage', description: 'Get percentage of usage covered by SPs', action: 'Get SP coverage' },
136
+ ],
137
+ default: 'getUtilization',
138
+ },
139
+ // ----------------------------------------------------------------
140
+ // Shared: Start Date / End Date
141
+ // ----------------------------------------------------------------
142
+ {
143
+ displayName: 'Start Date',
144
+ name: 'startDate',
145
+ type: 'string',
146
+ displayOptions: {
147
+ show: {
148
+ resource: ['costAndUsage', 'costForecast', 'dimensionValues', 'reservedInstances', 'savingsPlans'],
149
+ operation: ['get', 'getUtilization', 'getCoverage'],
150
+ },
151
+ },
152
+ default: '',
153
+ placeholder: '2024-01-01',
154
+ description: 'Start date in YYYY-MM-DD format',
155
+ required: true,
156
+ },
157
+ {
158
+ displayName: 'End Date',
159
+ name: 'endDate',
160
+ type: 'string',
161
+ displayOptions: {
162
+ show: {
163
+ resource: ['costAndUsage', 'costForecast', 'dimensionValues', 'reservedInstances', 'savingsPlans'],
164
+ operation: ['get', 'getUtilization', 'getCoverage'],
165
+ },
166
+ },
167
+ default: '',
168
+ placeholder: '2024-01-31',
169
+ description: 'End date in YYYY-MM-DD format (exclusive)',
170
+ required: true,
171
+ },
172
+ // ----------------------------------------------------------------
173
+ // Cost and Usage: Granularity
174
+ // ----------------------------------------------------------------
175
+ {
176
+ displayName: 'Granularity',
177
+ name: 'granularity',
178
+ type: 'options',
179
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
180
+ options: [
181
+ { name: 'Daily', value: 'DAILY' },
182
+ { name: 'Monthly', value: 'MONTHLY' },
183
+ { name: 'Hourly', value: 'HOURLY' },
184
+ ],
185
+ default: 'MONTHLY',
186
+ },
187
+ // ----------------------------------------------------------------
188
+ // Cost Forecast: Granularity
189
+ // ----------------------------------------------------------------
190
+ {
191
+ displayName: 'Granularity',
192
+ name: 'granularity',
193
+ type: 'options',
194
+ displayOptions: { show: { resource: ['costForecast'], operation: ['get'] } },
195
+ options: [
196
+ { name: 'Daily', value: 'DAILY' },
197
+ { name: 'Monthly', value: 'MONTHLY' },
198
+ ],
199
+ default: 'MONTHLY',
200
+ },
201
+ // ----------------------------------------------------------------
202
+ // RI / SP: Granularity
203
+ // ----------------------------------------------------------------
204
+ {
205
+ displayName: 'Granularity',
206
+ name: 'granularity',
207
+ type: 'options',
208
+ displayOptions: {
209
+ show: {
210
+ resource: ['reservedInstances', 'savingsPlans'],
211
+ operation: ['getUtilization', 'getCoverage'],
212
+ },
213
+ },
214
+ options: [
215
+ { name: 'Daily', value: 'DAILY' },
216
+ { name: 'Monthly', value: 'MONTHLY' },
217
+ ],
218
+ default: 'MONTHLY',
219
+ },
220
+ // ----------------------------------------------------------------
221
+ // Cost and Usage: Metrics
222
+ // ----------------------------------------------------------------
223
+ {
224
+ displayName: 'Metrics',
225
+ name: 'metrics',
226
+ type: 'multiOptions',
227
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
228
+ options: [
229
+ { name: 'Amortized Cost', value: 'AmortizedCost' },
230
+ { name: 'Blended Cost', value: 'BlendedCost' },
231
+ { name: 'Net Amortized Cost', value: 'NetAmortizedCost' },
232
+ { name: 'Net Unblended Cost', value: 'NetUnblendedCost' },
233
+ { name: 'Unblended Cost', value: 'UnblendedCost' },
234
+ { name: 'Usage Quantity', value: 'UsageQuantity' },
235
+ { name: 'Normalized Usage Amount', value: 'NormalizedUsageAmount' },
236
+ ],
237
+ default: ['UnblendedCost'],
238
+ description: 'Which cost metrics to return. Use Unblended Cost for most cases.',
239
+ },
240
+ // ----------------------------------------------------------------
241
+ // Cost Forecast: Metric (single)
242
+ // ----------------------------------------------------------------
243
+ {
244
+ displayName: 'Metric',
245
+ name: 'forecastMetric',
246
+ type: 'options',
247
+ displayOptions: { show: { resource: ['costForecast'], operation: ['get'] } },
248
+ options: [
249
+ { name: 'Amortized Cost', value: 'AMORTIZED_COST' },
250
+ { name: 'Blended Cost', value: 'BLENDED_COST' },
251
+ { name: 'Net Amortized Cost', value: 'NET_AMORTIZED_COST' },
252
+ { name: 'Net Unblended Cost', value: 'NET_UNBLENDED_COST' },
253
+ { name: 'Unblended Cost', value: 'UNBLENDED_COST' },
254
+ ],
255
+ default: 'UNBLENDED_COST',
256
+ },
257
+ // ----------------------------------------------------------------
258
+ // Cost and Usage: Group By
259
+ // ----------------------------------------------------------------
260
+ {
261
+ displayName: 'Group By',
262
+ name: 'groupBy',
263
+ type: 'options',
264
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
265
+ options: [
266
+ { name: 'None', value: 'none' },
267
+ { name: 'Service', value: 'SERVICE' },
268
+ { name: 'Linked Account', value: 'LINKED_ACCOUNT' },
269
+ { name: 'Region', value: 'REGION' },
270
+ { name: 'Purchase Type', value: 'PURCHASE_TYPE' },
271
+ { name: 'Instance Type', value: 'INSTANCE_TYPE' },
272
+ { name: 'Usage Type', value: 'USAGE_TYPE' },
273
+ { name: 'Tag', value: 'TAG' },
274
+ ],
275
+ default: 'none',
276
+ description: 'Dimension to group costs by',
277
+ },
278
+ {
279
+ displayName: 'Tag Key',
280
+ name: 'tagKey',
281
+ type: 'string',
282
+ displayOptions: {
283
+ show: {
284
+ resource: ['costAndUsage'],
285
+ operation: ['get'],
286
+ groupBy: ['TAG'],
287
+ },
288
+ },
289
+ default: '',
290
+ placeholder: 'Environment',
291
+ description: 'Tag key to group costs by (required when Group By = Tag)',
292
+ required: true,
293
+ },
294
+ // ----------------------------------------------------------------
295
+ // Cost and Usage: Secondary Group By
296
+ // ----------------------------------------------------------------
297
+ {
298
+ displayName: 'Secondary Group By',
299
+ name: 'groupBySecondary',
300
+ type: 'options',
301
+ displayOptions: {
302
+ show: {
303
+ resource: ['costAndUsage'],
304
+ operation: ['get'],
305
+ },
306
+ hide: {
307
+ groupBy: ['none'],
308
+ },
309
+ },
310
+ options: [
311
+ { name: 'None', value: 'none' },
312
+ { name: 'Service', value: 'SERVICE' },
313
+ { name: 'Linked Account', value: 'LINKED_ACCOUNT' },
314
+ { name: 'Region', value: 'REGION' },
315
+ { name: 'Purchase Type', value: 'PURCHASE_TYPE' },
316
+ ],
317
+ default: 'none',
318
+ description: 'Add a second grouping dimension (max 2 GroupBy supported by AWS)',
319
+ },
320
+ // ----------------------------------------------------------------
321
+ // Cost and Usage: Filters
322
+ // ----------------------------------------------------------------
323
+ {
324
+ displayName: 'Filter by Service',
325
+ name: 'serviceFilter',
326
+ type: 'string',
327
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
328
+ default: '',
329
+ placeholder: 'Amazon EC2',
330
+ description: 'Filter by a specific AWS service name. Leave empty for all services.',
331
+ },
332
+ {
333
+ displayName: 'Filter by Linked Account',
334
+ name: 'linkedAccountFilter',
335
+ type: 'string',
336
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
337
+ default: '',
338
+ placeholder: '123456789012',
339
+ description: 'Filter by a specific linked account ID. Leave empty for all accounts.',
340
+ },
341
+ {
342
+ displayName: 'Filter by Region',
343
+ name: 'regionFilter',
344
+ type: 'string',
345
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
346
+ default: '',
347
+ placeholder: 'us-east-1',
348
+ description: 'Filter by a specific AWS region. Leave empty for all regions.',
349
+ },
350
+ {
351
+ displayName: 'Exclude Credits',
352
+ name: 'excludeCredits',
353
+ type: 'boolean',
354
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
355
+ default: false,
356
+ description: 'Whether to exclude credits, refunds and discounts from the response',
357
+ },
358
+ // ----------------------------------------------------------------
359
+ // Dimension Values: Dimension
360
+ // ----------------------------------------------------------------
361
+ {
362
+ displayName: 'Dimension',
363
+ name: 'dimension',
364
+ type: 'options',
365
+ displayOptions: { show: { resource: ['dimensionValues'], operation: ['get'] } },
366
+ options: [
367
+ { name: 'AZ', value: 'AZ' },
368
+ { name: 'Instance Type', value: 'INSTANCE_TYPE' },
369
+ { name: 'Legal Entity Name', value: 'LEGAL_ENTITY_NAME' },
370
+ { name: 'Linked Account', value: 'LINKED_ACCOUNT' },
371
+ { name: 'Operating System', value: 'OPERATING_SYSTEM' },
372
+ { name: 'Operation', value: 'OPERATION' },
373
+ { name: 'Platform', value: 'PLATFORM' },
374
+ { name: 'Purchase Type', value: 'PURCHASE_TYPE' },
375
+ { name: 'Region', value: 'REGION' },
376
+ { name: 'Service', value: 'SERVICE' },
377
+ { name: 'Usage Type', value: 'USAGE_TYPE' },
378
+ { name: 'Usage Type Group', value: 'USAGE_TYPE_GROUP' },
379
+ ],
380
+ default: 'SERVICE',
381
+ required: true,
382
+ },
383
+ // ----------------------------------------------------------------
384
+ // RI: Group By Service
385
+ // ----------------------------------------------------------------
386
+ {
387
+ displayName: 'Group By Service',
388
+ name: 'riGroupByService',
389
+ type: 'boolean',
390
+ displayOptions: {
391
+ show: {
392
+ resource: ['reservedInstances'],
393
+ operation: ['getUtilization', 'getCoverage'],
394
+ },
395
+ },
396
+ default: true,
397
+ description: 'Whether to break down RI utilization/coverage by AWS service',
398
+ },
399
+ ],
400
+ };
401
+ }
402
+ async execute() {
403
+ const items = this.getInputData();
404
+ const returnData = [];
405
+ const credentials = await this.getCredentials('awsCostExplorerApi');
406
+ const { CostExplorerClient, GetCostAndUsageCommand, GetCostForecastCommand, GetDimensionValuesCommand, GetReservationUtilizationCommand, GetReservationCoverageCommand, GetSavingsPlansUtilizationCommand, GetSavingsPlansCoverageCommand, } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-cost-explorer')));
407
+ const client = new CostExplorerClient({
408
+ region: credentials.region || 'us-east-1',
409
+ credentials: {
410
+ accessKeyId: credentials.accessKeyId,
411
+ secretAccessKey: credentials.secretAccessKey,
412
+ ...(credentials.sessionToken ? { sessionToken: credentials.sessionToken } : {}),
413
+ },
414
+ });
415
+ for (let i = 0; i < items.length; i++) {
416
+ try {
417
+ const resource = this.getNodeParameter('resource', i);
418
+ const operation = this.getNodeParameter('operation', i);
419
+ const startDate = this.getNodeParameter('startDate', i, '');
420
+ const endDate = this.getNodeParameter('endDate', i, '');
421
+ // ----------------------------------------------------------------
422
+ // Cost and Usage
423
+ // ----------------------------------------------------------------
424
+ if (resource === 'costAndUsage' && operation === 'get') {
425
+ const granularity = this.getNodeParameter('granularity', i);
426
+ const metrics = this.getNodeParameter('metrics', i);
427
+ const groupBy = this.getNodeParameter('groupBy', i);
428
+ const groupBySecondary = this.getNodeParameter('groupBySecondary', i, 'none');
429
+ const tagKey = this.getNodeParameter('tagKey', i, '');
430
+ const serviceFilter = this.getNodeParameter('serviceFilter', i, '');
431
+ const linkedAccountFilter = this.getNodeParameter('linkedAccountFilter', i, '');
432
+ const regionFilter = this.getNodeParameter('regionFilter', i, '');
433
+ const excludeCredits = this.getNodeParameter('excludeCredits', i, false);
434
+ const params = {
435
+ TimePeriod: { Start: startDate, End: endDate },
436
+ Granularity: granularity,
437
+ Metrics: metrics,
438
+ };
439
+ // Group By
440
+ const groupByList = [];
441
+ if (groupBy !== 'none') {
442
+ groupByList.push({
443
+ Type: groupBy === 'TAG' ? 'TAG' : 'DIMENSION',
444
+ Key: groupBy === 'TAG' ? tagKey : groupBy,
445
+ });
446
+ }
447
+ if (groupBySecondary !== 'none') {
448
+ groupByList.push({ Type: 'DIMENSION', Key: groupBySecondary });
449
+ }
450
+ if (groupByList.length > 0) {
451
+ params.GroupBy = groupByList;
452
+ }
453
+ // Filters — AND together when multiple are set
454
+ const filterConditions = [];
455
+ if (serviceFilter === null || serviceFilter === void 0 ? void 0 : serviceFilter.trim()) {
456
+ filterConditions.push({
457
+ Dimensions: { Key: 'SERVICE', Values: [serviceFilter.trim()] },
458
+ });
459
+ }
460
+ if (linkedAccountFilter === null || linkedAccountFilter === void 0 ? void 0 : linkedAccountFilter.trim()) {
461
+ filterConditions.push({
462
+ Dimensions: { Key: 'LINKED_ACCOUNT', Values: [linkedAccountFilter.trim()] },
463
+ });
464
+ }
465
+ if (regionFilter === null || regionFilter === void 0 ? void 0 : regionFilter.trim()) {
466
+ filterConditions.push({
467
+ Dimensions: { Key: 'REGION', Values: [regionFilter.trim()] },
468
+ });
469
+ }
470
+ if (excludeCredits) {
471
+ filterConditions.push({
472
+ Not: {
473
+ Dimensions: {
474
+ Key: 'RECORD_TYPE',
475
+ Values: ['Credit', 'Refund', 'Discount'],
476
+ },
477
+ },
478
+ });
479
+ }
480
+ if (filterConditions.length === 1) {
481
+ params.Filter = filterConditions[0];
482
+ }
483
+ else if (filterConditions.length > 1) {
484
+ params.Filter = { And: filterConditions };
485
+ }
486
+ const response = await client.send(new GetCostAndUsageCommand(params));
487
+ returnData.push(response);
488
+ }
489
+ // ----------------------------------------------------------------
490
+ // Cost Forecast
491
+ // ----------------------------------------------------------------
492
+ if (resource === 'costForecast' && operation === 'get') {
493
+ const granularity = this.getNodeParameter('granularity', i);
494
+ const forecastMetric = this.getNodeParameter('forecastMetric', i);
495
+ const response = await client.send(new GetCostForecastCommand({
496
+ TimePeriod: { Start: startDate, End: endDate },
497
+ Granularity: granularity,
498
+ Metric: forecastMetric,
499
+ }));
500
+ returnData.push(response);
501
+ }
502
+ // ----------------------------------------------------------------
503
+ // Dimension Values
504
+ // ----------------------------------------------------------------
505
+ if (resource === 'dimensionValues' && operation === 'get') {
506
+ const dimension = this.getNodeParameter('dimension', i);
507
+ const response = await client.send(new GetDimensionValuesCommand({
508
+ TimePeriod: { Start: startDate, End: endDate },
509
+ Dimension: dimension,
510
+ }));
511
+ returnData.push(response);
512
+ }
513
+ // ----------------------------------------------------------------
514
+ // Reserved Instances — Utilization
515
+ // ----------------------------------------------------------------
516
+ if (resource === 'reservedInstances' && operation === 'getUtilization') {
517
+ const granularity = this.getNodeParameter('granularity', i);
518
+ const riGroupByService = this.getNodeParameter('riGroupByService', i, true);
519
+ const params = {
520
+ TimePeriod: { Start: startDate, End: endDate },
521
+ Granularity: granularity,
522
+ };
523
+ if (riGroupByService) {
524
+ params.GroupBy = [{ Type: 'DIMENSION', Key: 'SERVICE' }];
525
+ }
526
+ const response = await client.send(new GetReservationUtilizationCommand(params));
527
+ returnData.push(response);
528
+ }
529
+ // ----------------------------------------------------------------
530
+ // Reserved Instances — Coverage
531
+ // ----------------------------------------------------------------
532
+ if (resource === 'reservedInstances' && operation === 'getCoverage') {
533
+ const granularity = this.getNodeParameter('granularity', i);
534
+ const riGroupByService = this.getNodeParameter('riGroupByService', i, true);
535
+ const params = {
536
+ TimePeriod: { Start: startDate, End: endDate },
537
+ Granularity: granularity,
538
+ };
539
+ if (riGroupByService) {
540
+ params.GroupBy = [{ Type: 'DIMENSION', Key: 'SERVICE' }];
541
+ }
542
+ const response = await client.send(new GetReservationCoverageCommand(params));
543
+ returnData.push(response);
544
+ }
545
+ // ----------------------------------------------------------------
546
+ // Savings Plans — Utilization
547
+ // ----------------------------------------------------------------
548
+ if (resource === 'savingsPlans' && operation === 'getUtilization') {
549
+ const granularity = this.getNodeParameter('granularity', i);
550
+ const response = await client.send(new GetSavingsPlansUtilizationCommand({
551
+ TimePeriod: { Start: startDate, End: endDate },
552
+ Granularity: granularity,
553
+ }));
554
+ returnData.push(response);
555
+ }
556
+ // ----------------------------------------------------------------
557
+ // Savings Plans — Coverage
558
+ // ----------------------------------------------------------------
559
+ if (resource === 'savingsPlans' && operation === 'getCoverage') {
560
+ const granularity = this.getNodeParameter('granularity', i);
561
+ const response = await client.send(new GetSavingsPlansCoverageCommand({
562
+ TimePeriod: { Start: startDate, End: endDate },
563
+ Granularity: granularity,
564
+ }));
565
+ returnData.push(response);
566
+ }
567
+ }
568
+ catch (error) {
569
+ if (this.continueOnFail()) {
570
+ returnData.push({ error: error.message });
571
+ continue;
572
+ }
573
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error);
574
+ }
575
+ }
576
+ return [this.helpers.returnJsonArray(returnData)];
577
+ }
578
+ }
579
+ exports.AwsCostExplorer = AwsCostExplorer;
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class AwsCostExplorer implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,579 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.AwsCostExplorer = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ class AwsCostExplorer {
29
+ constructor() {
30
+ this.description = {
31
+ displayName: 'AWS Cost Explorer',
32
+ name: 'awsCostExplorer',
33
+ icon: 'file:awscostexplorer.svg',
34
+ group: ['transform'],
35
+ version: 1,
36
+ subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
37
+ description: 'Get cost and usage data from AWS Cost Explorer',
38
+ defaults: {
39
+ name: 'AWS Cost Explorer',
40
+ },
41
+ inputs: ['main'],
42
+ outputs: ['main'],
43
+ credentials: [
44
+ {
45
+ name: 'awsCostExplorerApi',
46
+ required: true,
47
+ },
48
+ ],
49
+ properties: [
50
+ // ----------------------------------------------------------------
51
+ // Resource
52
+ // ----------------------------------------------------------------
53
+ {
54
+ displayName: 'Resource',
55
+ name: 'resource',
56
+ type: 'options',
57
+ noDataExpression: true,
58
+ options: [
59
+ { name: 'Cost and Usage', value: 'costAndUsage' },
60
+ { name: 'Cost Forecast', value: 'costForecast' },
61
+ { name: 'Dimension Values', value: 'dimensionValues' },
62
+ { name: 'Reserved Instances', value: 'reservedInstances' },
63
+ { name: 'Savings Plans', value: 'savingsPlans' },
64
+ ],
65
+ default: 'costAndUsage',
66
+ },
67
+ // ----------------------------------------------------------------
68
+ // Operation — Cost and Usage
69
+ // ----------------------------------------------------------------
70
+ {
71
+ displayName: 'Operation',
72
+ name: 'operation',
73
+ type: 'options',
74
+ noDataExpression: true,
75
+ displayOptions: { show: { resource: ['costAndUsage'] } },
76
+ options: [
77
+ { name: 'Get', value: 'get', description: 'Get cost and usage data', action: 'Get cost and usage data' },
78
+ ],
79
+ default: 'get',
80
+ },
81
+ // ----------------------------------------------------------------
82
+ // Operation — Cost Forecast
83
+ // ----------------------------------------------------------------
84
+ {
85
+ displayName: 'Operation',
86
+ name: 'operation',
87
+ type: 'options',
88
+ noDataExpression: true,
89
+ displayOptions: { show: { resource: ['costForecast'] } },
90
+ options: [
91
+ { name: 'Get', value: 'get', description: 'Get cost forecast', action: 'Get cost forecast' },
92
+ ],
93
+ default: 'get',
94
+ },
95
+ // ----------------------------------------------------------------
96
+ // Operation — Dimension Values
97
+ // ----------------------------------------------------------------
98
+ {
99
+ displayName: 'Operation',
100
+ name: 'operation',
101
+ type: 'options',
102
+ noDataExpression: true,
103
+ displayOptions: { show: { resource: ['dimensionValues'] } },
104
+ options: [
105
+ { name: 'Get', value: 'get', description: 'Get dimension values', action: 'Get dimension values' },
106
+ ],
107
+ default: 'get',
108
+ },
109
+ // ----------------------------------------------------------------
110
+ // Operation — Reserved Instances
111
+ // ----------------------------------------------------------------
112
+ {
113
+ displayName: 'Operation',
114
+ name: 'operation',
115
+ type: 'options',
116
+ noDataExpression: true,
117
+ displayOptions: { show: { resource: ['reservedInstances'] } },
118
+ options: [
119
+ { name: 'Get Utilization', value: 'getUtilization', description: 'Get RI utilization and unused hours', action: 'Get RI utilization' },
120
+ { name: 'Get Coverage', value: 'getCoverage', description: 'Get percentage of usage covered by RIs', action: 'Get RI coverage' },
121
+ ],
122
+ default: 'getUtilization',
123
+ },
124
+ // ----------------------------------------------------------------
125
+ // Operation — Savings Plans
126
+ // ----------------------------------------------------------------
127
+ {
128
+ displayName: 'Operation',
129
+ name: 'operation',
130
+ type: 'options',
131
+ noDataExpression: true,
132
+ displayOptions: { show: { resource: ['savingsPlans'] } },
133
+ options: [
134
+ { name: 'Get Utilization', value: 'getUtilization', description: 'Get SP utilization and unused commitment', action: 'Get SP utilization' },
135
+ { name: 'Get Coverage', value: 'getCoverage', description: 'Get percentage of usage covered by SPs', action: 'Get SP coverage' },
136
+ ],
137
+ default: 'getUtilization',
138
+ },
139
+ // ----------------------------------------------------------------
140
+ // Shared: Start Date / End Date
141
+ // ----------------------------------------------------------------
142
+ {
143
+ displayName: 'Start Date',
144
+ name: 'startDate',
145
+ type: 'string',
146
+ displayOptions: {
147
+ show: {
148
+ resource: ['costAndUsage', 'costForecast', 'dimensionValues', 'reservedInstances', 'savingsPlans'],
149
+ operation: ['get', 'getUtilization', 'getCoverage'],
150
+ },
151
+ },
152
+ default: '',
153
+ placeholder: '2024-01-01',
154
+ description: 'Start date in YYYY-MM-DD format',
155
+ required: true,
156
+ },
157
+ {
158
+ displayName: 'End Date',
159
+ name: 'endDate',
160
+ type: 'string',
161
+ displayOptions: {
162
+ show: {
163
+ resource: ['costAndUsage', 'costForecast', 'dimensionValues', 'reservedInstances', 'savingsPlans'],
164
+ operation: ['get', 'getUtilization', 'getCoverage'],
165
+ },
166
+ },
167
+ default: '',
168
+ placeholder: '2024-01-31',
169
+ description: 'End date in YYYY-MM-DD format (exclusive)',
170
+ required: true,
171
+ },
172
+ // ----------------------------------------------------------------
173
+ // Cost and Usage: Granularity
174
+ // ----------------------------------------------------------------
175
+ {
176
+ displayName: 'Granularity',
177
+ name: 'granularity',
178
+ type: 'options',
179
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
180
+ options: [
181
+ { name: 'Daily', value: 'DAILY' },
182
+ { name: 'Monthly', value: 'MONTHLY' },
183
+ { name: 'Hourly', value: 'HOURLY' },
184
+ ],
185
+ default: 'MONTHLY',
186
+ },
187
+ // ----------------------------------------------------------------
188
+ // Cost Forecast: Granularity
189
+ // ----------------------------------------------------------------
190
+ {
191
+ displayName: 'Granularity',
192
+ name: 'granularity',
193
+ type: 'options',
194
+ displayOptions: { show: { resource: ['costForecast'], operation: ['get'] } },
195
+ options: [
196
+ { name: 'Daily', value: 'DAILY' },
197
+ { name: 'Monthly', value: 'MONTHLY' },
198
+ ],
199
+ default: 'MONTHLY',
200
+ },
201
+ // ----------------------------------------------------------------
202
+ // RI / SP: Granularity
203
+ // ----------------------------------------------------------------
204
+ {
205
+ displayName: 'Granularity',
206
+ name: 'granularity',
207
+ type: 'options',
208
+ displayOptions: {
209
+ show: {
210
+ resource: ['reservedInstances', 'savingsPlans'],
211
+ operation: ['getUtilization', 'getCoverage'],
212
+ },
213
+ },
214
+ options: [
215
+ { name: 'Daily', value: 'DAILY' },
216
+ { name: 'Monthly', value: 'MONTHLY' },
217
+ ],
218
+ default: 'MONTHLY',
219
+ },
220
+ // ----------------------------------------------------------------
221
+ // Cost and Usage: Metrics
222
+ // ----------------------------------------------------------------
223
+ {
224
+ displayName: 'Metrics',
225
+ name: 'metrics',
226
+ type: 'multiOptions',
227
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
228
+ options: [
229
+ { name: 'Amortized Cost', value: 'AmortizedCost' },
230
+ { name: 'Blended Cost', value: 'BlendedCost' },
231
+ { name: 'Net Amortized Cost', value: 'NetAmortizedCost' },
232
+ { name: 'Net Unblended Cost', value: 'NetUnblendedCost' },
233
+ { name: 'Unblended Cost', value: 'UnblendedCost' },
234
+ { name: 'Usage Quantity', value: 'UsageQuantity' },
235
+ { name: 'Normalized Usage Amount', value: 'NormalizedUsageAmount' },
236
+ ],
237
+ default: ['UnblendedCost'],
238
+ description: 'Which cost metrics to return. Use Unblended Cost for most cases.',
239
+ },
240
+ // ----------------------------------------------------------------
241
+ // Cost Forecast: Metric (single)
242
+ // ----------------------------------------------------------------
243
+ {
244
+ displayName: 'Metric',
245
+ name: 'forecastMetric',
246
+ type: 'options',
247
+ displayOptions: { show: { resource: ['costForecast'], operation: ['get'] } },
248
+ options: [
249
+ { name: 'Amortized Cost', value: 'AMORTIZED_COST' },
250
+ { name: 'Blended Cost', value: 'BLENDED_COST' },
251
+ { name: 'Net Amortized Cost', value: 'NET_AMORTIZED_COST' },
252
+ { name: 'Net Unblended Cost', value: 'NET_UNBLENDED_COST' },
253
+ { name: 'Unblended Cost', value: 'UNBLENDED_COST' },
254
+ ],
255
+ default: 'UNBLENDED_COST',
256
+ },
257
+ // ----------------------------------------------------------------
258
+ // Cost and Usage: Group By
259
+ // ----------------------------------------------------------------
260
+ {
261
+ displayName: 'Group By',
262
+ name: 'groupBy',
263
+ type: 'options',
264
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
265
+ options: [
266
+ { name: 'None', value: 'none' },
267
+ { name: 'Service', value: 'SERVICE' },
268
+ { name: 'Linked Account', value: 'LINKED_ACCOUNT' },
269
+ { name: 'Region', value: 'REGION' },
270
+ { name: 'Purchase Type', value: 'PURCHASE_TYPE' },
271
+ { name: 'Instance Type', value: 'INSTANCE_TYPE' },
272
+ { name: 'Usage Type', value: 'USAGE_TYPE' },
273
+ { name: 'Tag', value: 'TAG' },
274
+ ],
275
+ default: 'none',
276
+ description: 'Dimension to group costs by',
277
+ },
278
+ {
279
+ displayName: 'Tag Key',
280
+ name: 'tagKey',
281
+ type: 'string',
282
+ displayOptions: {
283
+ show: {
284
+ resource: ['costAndUsage'],
285
+ operation: ['get'],
286
+ groupBy: ['TAG'],
287
+ },
288
+ },
289
+ default: '',
290
+ placeholder: 'Environment',
291
+ description: 'Tag key to group costs by (required when Group By = Tag)',
292
+ required: true,
293
+ },
294
+ // ----------------------------------------------------------------
295
+ // Cost and Usage: Secondary Group By
296
+ // ----------------------------------------------------------------
297
+ {
298
+ displayName: 'Secondary Group By',
299
+ name: 'groupBySecondary',
300
+ type: 'options',
301
+ displayOptions: {
302
+ show: {
303
+ resource: ['costAndUsage'],
304
+ operation: ['get'],
305
+ },
306
+ hide: {
307
+ groupBy: ['none'],
308
+ },
309
+ },
310
+ options: [
311
+ { name: 'None', value: 'none' },
312
+ { name: 'Service', value: 'SERVICE' },
313
+ { name: 'Linked Account', value: 'LINKED_ACCOUNT' },
314
+ { name: 'Region', value: 'REGION' },
315
+ { name: 'Purchase Type', value: 'PURCHASE_TYPE' },
316
+ ],
317
+ default: 'none',
318
+ description: 'Add a second grouping dimension (max 2 GroupBy supported by AWS)',
319
+ },
320
+ // ----------------------------------------------------------------
321
+ // Cost and Usage: Filters
322
+ // ----------------------------------------------------------------
323
+ {
324
+ displayName: 'Filter by Service',
325
+ name: 'serviceFilter',
326
+ type: 'string',
327
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
328
+ default: '',
329
+ placeholder: 'Amazon EC2',
330
+ description: 'Filter by a specific AWS service name. Leave empty for all services.',
331
+ },
332
+ {
333
+ displayName: 'Filter by Linked Account',
334
+ name: 'linkedAccountFilter',
335
+ type: 'string',
336
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
337
+ default: '',
338
+ placeholder: '123456789012',
339
+ description: 'Filter by a specific linked account ID. Leave empty for all accounts.',
340
+ },
341
+ {
342
+ displayName: 'Filter by Region',
343
+ name: 'regionFilter',
344
+ type: 'string',
345
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
346
+ default: '',
347
+ placeholder: 'us-east-1',
348
+ description: 'Filter by a specific AWS region. Leave empty for all regions.',
349
+ },
350
+ {
351
+ displayName: 'Exclude Credits',
352
+ name: 'excludeCredits',
353
+ type: 'boolean',
354
+ displayOptions: { show: { resource: ['costAndUsage'], operation: ['get'] } },
355
+ default: false,
356
+ description: 'Whether to exclude credits, refunds and discounts from the response',
357
+ },
358
+ // ----------------------------------------------------------------
359
+ // Dimension Values: Dimension
360
+ // ----------------------------------------------------------------
361
+ {
362
+ displayName: 'Dimension',
363
+ name: 'dimension',
364
+ type: 'options',
365
+ displayOptions: { show: { resource: ['dimensionValues'], operation: ['get'] } },
366
+ options: [
367
+ { name: 'AZ', value: 'AZ' },
368
+ { name: 'Instance Type', value: 'INSTANCE_TYPE' },
369
+ { name: 'Legal Entity Name', value: 'LEGAL_ENTITY_NAME' },
370
+ { name: 'Linked Account', value: 'LINKED_ACCOUNT' },
371
+ { name: 'Operating System', value: 'OPERATING_SYSTEM' },
372
+ { name: 'Operation', value: 'OPERATION' },
373
+ { name: 'Platform', value: 'PLATFORM' },
374
+ { name: 'Purchase Type', value: 'PURCHASE_TYPE' },
375
+ { name: 'Region', value: 'REGION' },
376
+ { name: 'Service', value: 'SERVICE' },
377
+ { name: 'Usage Type', value: 'USAGE_TYPE' },
378
+ { name: 'Usage Type Group', value: 'USAGE_TYPE_GROUP' },
379
+ ],
380
+ default: 'SERVICE',
381
+ required: true,
382
+ },
383
+ // ----------------------------------------------------------------
384
+ // RI: Group By Service
385
+ // ----------------------------------------------------------------
386
+ {
387
+ displayName: 'Group By Service',
388
+ name: 'riGroupByService',
389
+ type: 'boolean',
390
+ displayOptions: {
391
+ show: {
392
+ resource: ['reservedInstances'],
393
+ operation: ['getUtilization', 'getCoverage'],
394
+ },
395
+ },
396
+ default: true,
397
+ description: 'Whether to break down RI utilization/coverage by AWS service',
398
+ },
399
+ ],
400
+ };
401
+ }
402
+ async execute() {
403
+ const items = this.getInputData();
404
+ const returnData = [];
405
+ const credentials = await this.getCredentials('awsCostExplorerApi');
406
+ const { CostExplorerClient, GetCostAndUsageCommand, GetCostForecastCommand, GetDimensionValuesCommand, GetReservationUtilizationCommand, GetReservationCoverageCommand, GetSavingsPlansUtilizationCommand, GetSavingsPlansCoverageCommand, } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-cost-explorer')));
407
+ const client = new CostExplorerClient({
408
+ region: credentials.region || 'us-east-1',
409
+ credentials: {
410
+ accessKeyId: credentials.accessKeyId,
411
+ secretAccessKey: credentials.secretAccessKey,
412
+ ...(credentials.sessionToken ? { sessionToken: credentials.sessionToken } : {}),
413
+ },
414
+ });
415
+ for (let i = 0; i < items.length; i++) {
416
+ try {
417
+ const resource = this.getNodeParameter('resource', i);
418
+ const operation = this.getNodeParameter('operation', i);
419
+ const startDate = this.getNodeParameter('startDate', i, '');
420
+ const endDate = this.getNodeParameter('endDate', i, '');
421
+ // ----------------------------------------------------------------
422
+ // Cost and Usage
423
+ // ----------------------------------------------------------------
424
+ if (resource === 'costAndUsage' && operation === 'get') {
425
+ const granularity = this.getNodeParameter('granularity', i);
426
+ const metrics = this.getNodeParameter('metrics', i);
427
+ const groupBy = this.getNodeParameter('groupBy', i);
428
+ const groupBySecondary = this.getNodeParameter('groupBySecondary', i, 'none');
429
+ const tagKey = this.getNodeParameter('tagKey', i, '');
430
+ const serviceFilter = this.getNodeParameter('serviceFilter', i, '');
431
+ const linkedAccountFilter = this.getNodeParameter('linkedAccountFilter', i, '');
432
+ const regionFilter = this.getNodeParameter('regionFilter', i, '');
433
+ const excludeCredits = this.getNodeParameter('excludeCredits', i, false);
434
+ const params = {
435
+ TimePeriod: { Start: startDate, End: endDate },
436
+ Granularity: granularity,
437
+ Metrics: metrics,
438
+ };
439
+ // Group By
440
+ const groupByList = [];
441
+ if (groupBy !== 'none') {
442
+ groupByList.push({
443
+ Type: groupBy === 'TAG' ? 'TAG' : 'DIMENSION',
444
+ Key: groupBy === 'TAG' ? tagKey : groupBy,
445
+ });
446
+ }
447
+ if (groupBySecondary !== 'none') {
448
+ groupByList.push({ Type: 'DIMENSION', Key: groupBySecondary });
449
+ }
450
+ if (groupByList.length > 0) {
451
+ params.GroupBy = groupByList;
452
+ }
453
+ // Filters — AND together when multiple are set
454
+ const filterConditions = [];
455
+ if (serviceFilter === null || serviceFilter === void 0 ? void 0 : serviceFilter.trim()) {
456
+ filterConditions.push({
457
+ Dimensions: { Key: 'SERVICE', Values: [serviceFilter.trim()] },
458
+ });
459
+ }
460
+ if (linkedAccountFilter === null || linkedAccountFilter === void 0 ? void 0 : linkedAccountFilter.trim()) {
461
+ filterConditions.push({
462
+ Dimensions: { Key: 'LINKED_ACCOUNT', Values: [linkedAccountFilter.trim()] },
463
+ });
464
+ }
465
+ if (regionFilter === null || regionFilter === void 0 ? void 0 : regionFilter.trim()) {
466
+ filterConditions.push({
467
+ Dimensions: { Key: 'REGION', Values: [regionFilter.trim()] },
468
+ });
469
+ }
470
+ if (excludeCredits) {
471
+ filterConditions.push({
472
+ Not: {
473
+ Dimensions: {
474
+ Key: 'RECORD_TYPE',
475
+ Values: ['Credit', 'Refund', 'Discount'],
476
+ },
477
+ },
478
+ });
479
+ }
480
+ if (filterConditions.length === 1) {
481
+ params.Filter = filterConditions[0];
482
+ }
483
+ else if (filterConditions.length > 1) {
484
+ params.Filter = { And: filterConditions };
485
+ }
486
+ const response = await client.send(new GetCostAndUsageCommand(params));
487
+ returnData.push(response);
488
+ }
489
+ // ----------------------------------------------------------------
490
+ // Cost Forecast
491
+ // ----------------------------------------------------------------
492
+ if (resource === 'costForecast' && operation === 'get') {
493
+ const granularity = this.getNodeParameter('granularity', i);
494
+ const forecastMetric = this.getNodeParameter('forecastMetric', i);
495
+ const response = await client.send(new GetCostForecastCommand({
496
+ TimePeriod: { Start: startDate, End: endDate },
497
+ Granularity: granularity,
498
+ Metric: forecastMetric,
499
+ }));
500
+ returnData.push(response);
501
+ }
502
+ // ----------------------------------------------------------------
503
+ // Dimension Values
504
+ // ----------------------------------------------------------------
505
+ if (resource === 'dimensionValues' && operation === 'get') {
506
+ const dimension = this.getNodeParameter('dimension', i);
507
+ const response = await client.send(new GetDimensionValuesCommand({
508
+ TimePeriod: { Start: startDate, End: endDate },
509
+ Dimension: dimension,
510
+ }));
511
+ returnData.push(response);
512
+ }
513
+ // ----------------------------------------------------------------
514
+ // Reserved Instances — Utilization
515
+ // ----------------------------------------------------------------
516
+ if (resource === 'reservedInstances' && operation === 'getUtilization') {
517
+ const granularity = this.getNodeParameter('granularity', i);
518
+ const riGroupByService = this.getNodeParameter('riGroupByService', i, true);
519
+ const params = {
520
+ TimePeriod: { Start: startDate, End: endDate },
521
+ Granularity: granularity,
522
+ };
523
+ if (riGroupByService) {
524
+ params.GroupBy = [{ Type: 'DIMENSION', Key: 'SERVICE' }];
525
+ }
526
+ const response = await client.send(new GetReservationUtilizationCommand(params));
527
+ returnData.push(response);
528
+ }
529
+ // ----------------------------------------------------------------
530
+ // Reserved Instances — Coverage
531
+ // ----------------------------------------------------------------
532
+ if (resource === 'reservedInstances' && operation === 'getCoverage') {
533
+ const granularity = this.getNodeParameter('granularity', i);
534
+ const riGroupByService = this.getNodeParameter('riGroupByService', i, true);
535
+ const params = {
536
+ TimePeriod: { Start: startDate, End: endDate },
537
+ Granularity: granularity,
538
+ };
539
+ if (riGroupByService) {
540
+ params.GroupBy = [{ Type: 'DIMENSION', Key: 'SERVICE' }];
541
+ }
542
+ const response = await client.send(new GetReservationCoverageCommand(params));
543
+ returnData.push(response);
544
+ }
545
+ // ----------------------------------------------------------------
546
+ // Savings Plans — Utilization
547
+ // ----------------------------------------------------------------
548
+ if (resource === 'savingsPlans' && operation === 'getUtilization') {
549
+ const granularity = this.getNodeParameter('granularity', i);
550
+ const response = await client.send(new GetSavingsPlansUtilizationCommand({
551
+ TimePeriod: { Start: startDate, End: endDate },
552
+ Granularity: granularity,
553
+ }));
554
+ returnData.push(response);
555
+ }
556
+ // ----------------------------------------------------------------
557
+ // Savings Plans — Coverage
558
+ // ----------------------------------------------------------------
559
+ if (resource === 'savingsPlans' && operation === 'getCoverage') {
560
+ const granularity = this.getNodeParameter('granularity', i);
561
+ const response = await client.send(new GetSavingsPlansCoverageCommand({
562
+ TimePeriod: { Start: startDate, End: endDate },
563
+ Granularity: granularity,
564
+ }));
565
+ returnData.push(response);
566
+ }
567
+ }
568
+ catch (error) {
569
+ if (this.continueOnFail()) {
570
+ returnData.push({ error: error.message });
571
+ continue;
572
+ }
573
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error);
574
+ }
575
+ }
576
+ return [this.helpers.returnJsonArray(returnData)];
577
+ }
578
+ }
579
+ exports.AwsCostExplorer = AwsCostExplorer;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@roberta.soliman/n8n-nodes-aws-cost-explorer",
3
+ "version": "0.1.1",
4
+ "description": "N8N node for AWS Cost Explorer to retrieve cost and usage data",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "aws",
8
+ "cost-explorer",
9
+ "billing",
10
+ "cost-management"
11
+ ],
12
+ "license": "MIT",
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "homepage": "https://github.com/robertasolimandonofreo/n8n-aws-cost-explorer",
17
+ "author": {
18
+ "name": "roberta.soliman"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/robertasolimandonofreo/n8n-aws-cost-explorer.git"
23
+ },
24
+ "main": "index.js",
25
+ "scripts": {
26
+ "build": "tsc --noEmit false --skipLibCheck && gulp build:icons",
27
+ "dev": "tsc --watch",
28
+ "format": "prettier nodes credentials --write",
29
+ "lint": "eslint nodes credentials package.json",
30
+ "lintfix": "eslint nodes credentials package.json --fix",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "n8n": {
37
+ "n8nNodesApiVersion": 1,
38
+ "credentials": [
39
+ "dist/credentials/AwsCostExplorerApi.credentials.js"
40
+ ],
41
+ "nodes": [
42
+ "dist/nodes/AwsCostExplorer/AwsCostExplorer.node.js"
43
+ ]
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^18.16.16",
47
+ "gulp": "^4.0.2",
48
+ "n8n-workflow": "*",
49
+ "prettier": "^2.7.1",
50
+ "typescript": "^4.8.4"
51
+ },
52
+ "dependencies": {
53
+ "@aws-sdk/client-cost-explorer": "^3.0.0"
54
+ }
55
+ }