From 98b2897c52a4b879cf9380b59386a895c7a613c2 Mon Sep 17 00:00:00 2001 From: Olaf Conijn Date: Mon, 2 Nov 2020 12:32:11 +0100 Subject: [PATCH 1/3] feat(cost-explorer): WIP imported schemas fpr anomaly monitor and subscription --- cost-explorer/anomaly-monitor/.gitignore | 18 + cost-explorer/anomaly-monitor/.npmrc | 2 + cost-explorer/anomaly-monitor/.rpdk-config | 11 + cost-explorer/anomaly-monitor/README.md | 39 ++ ...community-costexplorer-anomalymonitor.json | 73 ++++ cost-explorer/anomaly-monitor/docs/README.md | 97 +++++ cost-explorer/anomaly-monitor/example.yml | 8 + .../anomaly-monitor/package-lock.json | 394 ++++++++++++++++++ cost-explorer/anomaly-monitor/package.json | 29 ++ .../anomaly-monitor/resource-role.yaml | 33 ++ cost-explorer/anomaly-monitor/src/handlers.ts | 47 +++ cost-explorer/anomaly-monitor/src/models.ts | 87 ++++ cost-explorer/anomaly-monitor/template.yml | 24 ++ cost-explorer/anomaly-monitor/tsconfig.json | 19 + cost-explorer/anomaly-subscription/.gitignore | 18 + cost-explorer/anomaly-subscription/.npmrc | 2 + .../anomaly-subscription/.rpdk-config | 11 + cost-explorer/anomaly-subscription/README.md | 39 ++ ...nity-costexplorer-anomalysubscription.json | 96 +++++ .../anomaly-subscription/docs/README.md | 107 +++++ .../anomaly-subscription/docs/subscriber.md | 42 ++ .../anomaly-subscription/package-lock.json | 394 ++++++++++++++++++ .../anomaly-subscription/package.json | 29 ++ .../anomaly-subscription/resource-role.yaml | 33 ++ .../anomaly-subscription/src/handlers.ts | 48 +++ .../anomaly-subscription/src/models.ts | 115 +++++ .../anomaly-subscription/template.yml | 24 ++ .../anomaly-subscription/tsconfig.json | 19 + 28 files changed, 1858 insertions(+) create mode 100644 cost-explorer/anomaly-monitor/.gitignore create mode 100644 cost-explorer/anomaly-monitor/.npmrc create mode 100644 cost-explorer/anomaly-monitor/.rpdk-config create mode 100644 cost-explorer/anomaly-monitor/README.md create mode 100644 cost-explorer/anomaly-monitor/community-costexplorer-anomalymonitor.json create mode 100644 cost-explorer/anomaly-monitor/docs/README.md create mode 100644 cost-explorer/anomaly-monitor/example.yml create mode 100644 cost-explorer/anomaly-monitor/package-lock.json create mode 100644 cost-explorer/anomaly-monitor/package.json create mode 100644 cost-explorer/anomaly-monitor/resource-role.yaml create mode 100644 cost-explorer/anomaly-monitor/src/handlers.ts create mode 100644 cost-explorer/anomaly-monitor/src/models.ts create mode 100644 cost-explorer/anomaly-monitor/template.yml create mode 100644 cost-explorer/anomaly-monitor/tsconfig.json create mode 100644 cost-explorer/anomaly-subscription/.gitignore create mode 100644 cost-explorer/anomaly-subscription/.npmrc create mode 100644 cost-explorer/anomaly-subscription/.rpdk-config create mode 100644 cost-explorer/anomaly-subscription/README.md create mode 100644 cost-explorer/anomaly-subscription/community-costexplorer-anomalysubscription.json create mode 100644 cost-explorer/anomaly-subscription/docs/README.md create mode 100644 cost-explorer/anomaly-subscription/docs/subscriber.md create mode 100644 cost-explorer/anomaly-subscription/package-lock.json create mode 100644 cost-explorer/anomaly-subscription/package.json create mode 100644 cost-explorer/anomaly-subscription/resource-role.yaml create mode 100644 cost-explorer/anomaly-subscription/src/handlers.ts create mode 100644 cost-explorer/anomaly-subscription/src/models.ts create mode 100644 cost-explorer/anomaly-subscription/template.yml create mode 100644 cost-explorer/anomaly-subscription/tsconfig.json diff --git a/cost-explorer/anomaly-monitor/.gitignore b/cost-explorer/anomaly-monitor/.gitignore new file mode 100644 index 00000000..b6fd3f16 --- /dev/null +++ b/cost-explorer/anomaly-monitor/.gitignore @@ -0,0 +1,18 @@ +# Distribution / packaging +build/ +dist/ + +# Unit test / coverage reports +.cache +.hypothesis/ +.pytest_cache/ + +# RPDK logs +rpdk.log + +# Node.js +node_modules/ +coverage/ + +# contains credentials +sam-tests/ diff --git a/cost-explorer/anomaly-monitor/.npmrc b/cost-explorer/anomaly-monitor/.npmrc new file mode 100644 index 00000000..c4d29f32 --- /dev/null +++ b/cost-explorer/anomaly-monitor/.npmrc @@ -0,0 +1,2 @@ +optional = false +save = false \ No newline at end of file diff --git a/cost-explorer/anomaly-monitor/.rpdk-config b/cost-explorer/anomaly-monitor/.rpdk-config new file mode 100644 index 00000000..45967af3 --- /dev/null +++ b/cost-explorer/anomaly-monitor/.rpdk-config @@ -0,0 +1,11 @@ +{ + "typeName": "Community::CostExplorer::AnomalyMonitor", + "language": "typescript", + "runtime": "nodejs12.x", + "entrypoint": "dist/handlers.entrypoint", + "testEntrypoint": "dist/handlers.testEntrypoint", + "settings": { + "useDocker": false, + "protocolVersion": "2.0.0" + } +} diff --git a/cost-explorer/anomaly-monitor/README.md b/cost-explorer/anomaly-monitor/README.md new file mode 100644 index 00000000..d6d58b1c --- /dev/null +++ b/cost-explorer/anomaly-monitor/README.md @@ -0,0 +1,39 @@ +// Community::CostExplorer::AnomalyMonitor + +Congratulations on starting development! Next steps: + +1. Write the JSON schema describing your resource, [community-costexplorer-anomalymonitor.json](./community-costexplorer-anomalymonitor.json) +2. Implement your resource handlers in [handlers.ts](./community-costexplorer-anomalymonitor/handlers.ts) + +> Don't modify [models.ts](./community-costexplorer-anomalymonitor/models.ts) by hand, any modifications will be overwritten when the `generate` or `package` commands are run. + +Implement CloudFormation resource here. Each function must always return a ProgressEvent. + +```typescript +const progress: ProgressEvent = ProgressEvent.builder() + + // Required + // Must be one of OperationStatus.InProgress, OperationStatus.Failed, OperationStatus.Success + .status(OperationStatus.InProgress) + // Required on SUCCESS (except for LIST where resourceModels is required) + // The current resource model after the operation; instance of ResourceModel class + .resourceModel(model) + .resourceModels(null) + // Required on FAILED + // Customer-facing message, displayed in e.g. CloudFormation stack events + .message('') + // Required on FAILED a HandlerErrorCode + .errorCode(HandlerErrorCode.InternalFailure) + // Optional + // Use to store any state between re-invocation via IN_PROGRESS + .callbackContext({}) + // Required on IN_PROGRESS + // The number of seconds to delay before re-invocation + .callbackDelaySeconds(0) + + .build() +``` + +While importing the [cfn-rpdk](https://github.com/eduardomourar/cloudformation-cli-typescript-plugin) library, failures can be passed back to CloudFormation by either raising an exception from `exceptions`, or setting the ProgressEvent's `status` to `OperationStatus.Failed` and `errorCode` to one of `HandlerErrorCode`. There is a static helper function, `ProgressEvent.failed`, for this common case. + +Keep in mind, during runtime all logs will be delivered to CloudWatch except those used with `debug` method. diff --git a/cost-explorer/anomaly-monitor/community-costexplorer-anomalymonitor.json b/cost-explorer/anomaly-monitor/community-costexplorer-anomalymonitor.json new file mode 100644 index 00000000..4977a701 --- /dev/null +++ b/cost-explorer/anomaly-monitor/community-costexplorer-anomalymonitor.json @@ -0,0 +1,73 @@ +{ + "typeName": "Community::CostExplorer::AnomalyMonitor", + "description": "An example resource schema demonstrating some basic constructs and validation rules.", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", + "definitions": { + }, + "properties": { + "MonitorName": { + "type": "string" + }, + "MonitorType": { + "type": "string", + "enum": [ + "DIMENSIONAL", + "CUSTOM" + ] + }, + "MonitorDimension": { + "type": "string", + "enum": [ + "SERVICE" + ] + }, + "MonitorSpecification": { + "type": "object" + }, + "DimensionalValueCount": { + "type": "number" + }, + "Arn": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "MonitorName", + "MonitorType", + "MonitorDimension" + ], + "readOnlyProperties": [ + "/properties/Arn" + ], + "primaryIdentifier": [ + "/properties/Arn" + ], + "handlers": { + "create": { + "permissions": [ + "costExplorer:createAnomalyMonitor" + ] + }, + "read": { + "permissions": [ + + ] + }, + "update": { + "permissions": [ + "costExplorer:updateAnomalyMonitor" + ] + }, + "delete": { + "permissions": [ + "costExplorer:deleteAnomalyMonitor" + ] + }, + "list": { + "permissions": [ + + ] + } + } +} diff --git a/cost-explorer/anomaly-monitor/docs/README.md b/cost-explorer/anomaly-monitor/docs/README.md new file mode 100644 index 00000000..9de56192 --- /dev/null +++ b/cost-explorer/anomaly-monitor/docs/README.md @@ -0,0 +1,97 @@ +# Community::CostExplorer::AnomalyMonitor + +An example resource schema demonstrating some basic constructs and validation rules. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Type" : "Community::CostExplorer::AnomalyMonitor",
+    "Properties" : {
+        "MonitorName" : String,
+        "MonitorType" : String,
+        "MonitorDimension" : String,
+        "MonitorSpecification" : Map,
+        "DimensionalValueCount" : Double,
+    }
+}
+
+ +### YAML + +
+Type: Community::CostExplorer::AnomalyMonitor
+Properties:
+    MonitorName: String
+    MonitorType: String
+    MonitorDimension: String
+    MonitorSpecification: Map
+    DimensionalValueCount: Double
+
+ +## Properties + +#### MonitorName + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### MonitorType + +_Required_: Yes + +_Type_: String + +_Allowed Values_: DIMENSIONAL | CUSTOM + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### MonitorDimension + +_Required_: Yes + +_Type_: String + +_Allowed Values_: SERVICE + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### MonitorSpecification + +_Required_: No + +_Type_: Map + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### DimensionalValueCount + +_Required_: No + +_Type_: Double + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the Arn. + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### Arn + +Returns the Arn value. + diff --git a/cost-explorer/anomaly-monitor/example.yml b/cost-explorer/anomaly-monitor/example.yml new file mode 100644 index 00000000..4d8379a9 --- /dev/null +++ b/cost-explorer/anomaly-monitor/example.yml @@ -0,0 +1,8 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Sample template + +Resources: + AnomalyMonitor: + Type: Community::CostExplorer::AnomalyMonitor + Properties: + MonitorName: 'test-monitor' diff --git a/cost-explorer/anomaly-monitor/package-lock.json b/cost-explorer/anomaly-monitor/package-lock.json new file mode 100644 index 00000000..a7942681 --- /dev/null +++ b/cost-explorer/anomaly-monitor/package-lock.json @@ -0,0 +1,394 @@ +{ + "name": "community-costexplorer-anomalymonitor", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@org-formation/tombok": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@org-formation/tombok/-/tombok-0.0.1.tgz", + "integrity": "sha512-6F0zitevY+H3VT3MVsAo4JFlDl5kfqnhGLUwXNc652/HYEBzMru5iLkTIF6+cp/lgvTWxQJQJzH4yoYja2f9Pg==" + }, + "@types/node": { + "version": "12.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.3.tgz", + "integrity": "sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg==", + "dev": true + }, + "@types/uuid": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.4.tgz", + "integrity": "sha512-WGZCqBZZ0mXN2RxvLHL6/7RCu+OWs28jgQMP04LWfpyJlQUMTR6YU9CNJAKDgbw+EV/u687INXuLUc7FuML/4g==", + "dev": true + }, + "autobind-decorator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", + "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==" + }, + "aws-resource-providers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/aws-resource-providers-common/-/aws-resource-providers-common-0.2.3.tgz", + "integrity": "sha512-DVLaqozhBnbrhUX5/OCtiuuHnrB21673bG9UKP33Gmb+2t15sAFQzRnl/IzyrU2kbl5gEW8L+4Jm7PYx+GRByw==", + "requires": { + "aws-sdk": "^2.656.0", + "cfn-rpdk": "npm:@org-formation/cfn-rpdk@0.x" + } + }, + "aws-sdk": { + "version": "2.775.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.775.0.tgz", + "integrity": "sha512-rlej1sgHmfhl+PJqpQ2qOOsbHEEnLBIKBmanMTUNGiEAfuS0MpFjXECXTpJIOrbUzl3OZuAYrGuBkg2qrBwRbQ==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "cfn-rpdk": { + "version": "npm:@org-formation/cfn-rpdk@0.4.0", + "resolved": "https://registry.npmjs.org/@org-formation/cfn-rpdk/-/cfn-rpdk-0.4.0.tgz", + "integrity": "sha512-3JSVoN3OxQrVD68O9H6yUhawUQg7nAITqRydTfs517qALgS5foD4qmZDV9NB3SCBF5XgU0pEwDTY3tqwit1exQ==", + "requires": { + "@org-formation/tombok": "^0.0.1", + "autobind-decorator": "^2.4.0", + "aws-sdk": "~2.712.0", + "class-transformer": "^0.3.1", + "reflect-metadata": "^0.1.13", + "string.prototype.replaceall": "^1.0.3", + "uuid": "^7.0.2" + }, + "dependencies": { + "aws-sdk": { + "version": "2.712.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.712.0.tgz", + "integrity": "sha512-C3SLWanFydoWJwtKNi73BG9uB6UzrUuECaAiplOEVBltO/R4sBsHWhwTBuxS02eTNdRrgulu19bJ5RWt+OuXiA==", + "optional": true, + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "optional": true + } + } + } + } + }, + "class-transformer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", + "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "string.prototype.replaceall": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.3.tgz", + "integrity": "sha512-GF8JS9jtHSDkIsVMsYBPR4dItwaU6xOSPsMcRGTAbBr12ZDfyKMtgxdC2HDFbsMogGel29pmwxioJoXeu9ztIg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-regex": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", + "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "string.prototype.trimstart": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", + "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } +} diff --git a/cost-explorer/anomaly-monitor/package.json b/cost-explorer/anomaly-monitor/package.json new file mode 100644 index 00000000..30757c35 --- /dev/null +++ b/cost-explorer/anomaly-monitor/package.json @@ -0,0 +1,29 @@ +{ + "name": "community-costexplorer-anomalymonitor", + "version": "0.1.0", + "description": "AWS custom resource provider named Community::CostExplorer::AnomalyMonitor.", + "main": "dist/handlers.js", + "files": [ + "dist" + ], + "scripts": { + "build": "npx tsc", + "prepack": "cfn generate && npm run build", + "submit": "npm run prepack && cfn submit -vv --region us-east-1 --set-default", + "package": "npm run prepack && cfn submit --dry-run -vv && cp ${npm_package_name}.zip ${npm_package_name}-${npm_package_version}.zip", + "version": "aws s3 cp ${npm_package_name}-${npm_package_version}.zip s3://community-resource-provider-catalog/${npm_package_name}-${npm_package_version}.zip && aws s3 cp resource-role.yaml s3://community-resource-provider-catalog/${npm_package_name}-resource-role-${npm_package_version}.yml", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "aws-resource-providers-common": "^0.2.0", + "cfn-rpdk": "npm:@org-formation/cfn-rpdk@^0.4.0", + "class-transformer": "^0.3.1", + "uuid": "^7.0.3", + "aws-sdk": "2.775.0" + }, + "devDependencies": { + "@types/node": "^12.0.0", + "@types/uuid": "^7.0.3", + "typescript": "^3.8.3" + } +} diff --git a/cost-explorer/anomaly-monitor/resource-role.yaml b/cost-explorer/anomaly-monitor/resource-role.yaml new file mode 100644 index 00000000..f6c81839 --- /dev/null +++ b/cost-explorer/anomaly-monitor/resource-role.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + This CloudFormation template creates a role assumed by CloudFormation + during CRUDL operations to mutate resources on behalf of the customer. + +Resources: + ExecutionRole: + Type: AWS::IAM::Role + Properties: + MaxSessionDuration: 8400 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: resources.cloudformation.amazonaws.com + Action: sts:AssumeRole + Path: "/" + Policies: + - PolicyName: ResourceTypePolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "costExplorer:createAnomalyMonitor" + - "costExplorer:deleteAnomalyMonitor" + - "costExplorer:updateAnomalyMonitor" + Resource: "*" +Outputs: + ExecutionRoleArn: + Value: + Fn::GetAtt: ExecutionRole.Arn diff --git a/cost-explorer/anomaly-monitor/src/handlers.ts b/cost-explorer/anomaly-monitor/src/handlers.ts new file mode 100644 index 00000000..36fd0f08 --- /dev/null +++ b/cost-explorer/anomaly-monitor/src/handlers.ts @@ -0,0 +1,47 @@ +import { Action, BaseResource, handlerEvent } from 'cfn-rpdk'; +import { commonAws, HandlerArgs } from 'aws-resource-providers-common'; +import { ResourceModel } from './models'; +import { CostExplorer } from 'aws-sdk'; + +// Use this logger to forward log messages to CloudWatch Logs. +const LOGGER = console; + +interface CallbackContext extends Record {} + +class Resource extends BaseResource { + + @handlerEvent(Action.Create) + @commonAws({ serviceName: 'CostExplorer', debug: true }) + public async create(action: Action, args: HandlerArgs, service: CostExplorer, model: ResourceModel): Promise { + + const response = await service.createAnomalyMonitor({ + AnomalyMonitor: { + MonitorName: model.monitorName, + MonitorDimension: model.monitorDimension, + MonitorType: model.monitorType, + MonitorSpecification: model.monitorSpecification as CostExplorer.Expression, + DimensionalValueCount: model.dimensionalValueCount + } + }).promise(); + + model.arn = response.MonitorArn; + return Promise.resolve(model); + } + + @handlerEvent(Action.Delete) + @commonAws({ serviceName: 'CostExplorer', debug: true }) + public async delete(action: Action, args: HandlerArgs, service: CostExplorer): Promise { + const model: ResourceModel = args.request.desiredResourceState; + + await service.deleteAnomalyMonitor( { MonitorArn: model.arn}).promise(); + + return Promise.resolve(null); + } + +} + +const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel); + +export const entrypoint = resource.entrypoint; + +export const testEntrypoint = resource.testEntrypoint; diff --git a/cost-explorer/anomaly-monitor/src/models.ts b/cost-explorer/anomaly-monitor/src/models.ts new file mode 100644 index 00000000..3224e9bc --- /dev/null +++ b/cost-explorer/anomaly-monitor/src/models.ts @@ -0,0 +1,87 @@ +// This is a generated file. Modifications will be overwritten. +import { BaseModel, Dict, integer, Integer, Optional, transformValue } from 'cfn-rpdk'; +import { Exclude, Expose, Type, Transform } from 'class-transformer'; + +export class ResourceModel extends BaseModel { + ['constructor']: typeof ResourceModel; + + @Exclude() + public static readonly TYPE_NAME: string = 'Community::CostExplorer::AnomalyMonitor'; + + @Exclude() + protected readonly IDENTIFIER_KEY_ARN: string = '/properties/Arn'; + + @Expose({ name: 'MonitorName' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'monitorName', value, obj, []), + { + toClassOnly: true, + } + ) + monitorName?: Optional; + @Expose({ name: 'MonitorType' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'monitorType', value, obj, []), + { + toClassOnly: true, + } + ) + monitorType?: Optional; + @Expose({ name: 'MonitorDimension' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'monitorDimension', value, obj, []), + { + toClassOnly: true, + } + ) + monitorDimension?: Optional; + @Expose({ name: 'MonitorSpecification' }) + @Transform( + (value: any, obj: any) => + transformValue(Object, 'monitorSpecification', value, obj, [Map]), + { + toClassOnly: true, + } + ) + monitorSpecification?: Optional>; + @Expose({ name: 'DimensionalValueCount' }) + @Transform( + (value: any, obj: any) => + transformValue(Number, 'dimensionalValueCount', value, obj, []), + { + toClassOnly: true, + } + ) + dimensionalValueCount?: Optional; + @Expose({ name: 'Arn' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'arn', value, obj, []), + { + toClassOnly: true, + } + ) + arn?: Optional; + + @Exclude() + public getPrimaryIdentifier(): Dict { + const identifier: Dict = {}; + if (this.arn != null) { + identifier[this.IDENTIFIER_KEY_ARN] = this.arn; + } + + // only return the identifier if it can be used, i.e. if all components are present + return Object.keys(identifier).length === 1 ? identifier : null; + } + + @Exclude() + public getAdditionalIdentifiers(): Array { + const identifiers: Array = new Array(); + // only return the identifiers if any can be used + return identifiers.length === 0 ? null : identifiers; + } +} + diff --git a/cost-explorer/anomaly-monitor/template.yml b/cost-explorer/anomaly-monitor/template.yml new file mode 100644 index 00000000..fc0ccd76 --- /dev/null +++ b/cost-explorer/anomaly-monitor/template.yml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: AWS SAM template for the Community::CostExplorer::AnomalyMonitor resource type + +Globals: + Function: + Timeout: 180 # docker start-up times can be long for SAM CLI + MemorySize: 256 + +Resources: + TestEntrypoint: + Type: AWS::Serverless::Function + Properties: + Handler: dist/handlers.testEntrypoint + Runtime: nodejs12.x + CodeUri: ./ + + TypeFunction: + Type: AWS::Serverless::Function + Properties: + Handler: dist/handlers.entrypoint + Runtime: nodejs12.x + CodeUri: ./ + diff --git a/cost-explorer/anomaly-monitor/tsconfig.json b/cost-explorer/anomaly-monitor/tsconfig.json new file mode 100644 index 00000000..ffbcc7d2 --- /dev/null +++ b/cost-explorer/anomaly-monitor/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "commonjs", + "noImplicitAny": true, + "alwaysStrict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "allowJs": true, + "experimentalDecorators": true, + "outDir": "dist" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/cost-explorer/anomaly-subscription/.gitignore b/cost-explorer/anomaly-subscription/.gitignore new file mode 100644 index 00000000..b6fd3f16 --- /dev/null +++ b/cost-explorer/anomaly-subscription/.gitignore @@ -0,0 +1,18 @@ +# Distribution / packaging +build/ +dist/ + +# Unit test / coverage reports +.cache +.hypothesis/ +.pytest_cache/ + +# RPDK logs +rpdk.log + +# Node.js +node_modules/ +coverage/ + +# contains credentials +sam-tests/ diff --git a/cost-explorer/anomaly-subscription/.npmrc b/cost-explorer/anomaly-subscription/.npmrc new file mode 100644 index 00000000..763190d1 --- /dev/null +++ b/cost-explorer/anomaly-subscription/.npmrc @@ -0,0 +1,2 @@ +optional = false +save = false diff --git a/cost-explorer/anomaly-subscription/.rpdk-config b/cost-explorer/anomaly-subscription/.rpdk-config new file mode 100644 index 00000000..6aee9559 --- /dev/null +++ b/cost-explorer/anomaly-subscription/.rpdk-config @@ -0,0 +1,11 @@ +{ + "typeName": "Community::CostExplorer::AnomalySubscription", + "language": "typescript", + "runtime": "nodejs12.x", + "entrypoint": "dist/handlers.entrypoint", + "testEntrypoint": "dist/handlers.testEntrypoint", + "settings": { + "useDocker": false, + "protocolVersion": "2.0.0" + } +} diff --git a/cost-explorer/anomaly-subscription/README.md b/cost-explorer/anomaly-subscription/README.md new file mode 100644 index 00000000..e0b54643 --- /dev/null +++ b/cost-explorer/anomaly-subscription/README.md @@ -0,0 +1,39 @@ +// Community::CostExplorer::AnomalySubscription + +Congratulations on starting development! Next steps: + +1. Write the JSON schema describing your resource, [community-costexplorer-anomalysubscription.json](./community-costexplorer-anomalysubscription.json) +2. Implement your resource handlers in [handlers.ts](./community-costexplorer-anomalysubscription/handlers.ts) + +> Don't modify [models.ts](./community-costexplorer-anomalysubscription/models.ts) by hand, any modifications will be overwritten when the `generate` or `package` commands are run. + +Implement CloudFormation resource here. Each function must always return a ProgressEvent. + +```typescript +const progress: ProgressEvent = ProgressEvent.builder() + + // Required + // Must be one of OperationStatus.InProgress, OperationStatus.Failed, OperationStatus.Success + .status(OperationStatus.InProgress) + // Required on SUCCESS (except for LIST where resourceModels is required) + // The current resource model after the operation; instance of ResourceModel class + .resourceModel(model) + .resourceModels(null) + // Required on FAILED + // Customer-facing message, displayed in e.g. CloudFormation stack events + .message('') + // Required on FAILED a HandlerErrorCode + .errorCode(HandlerErrorCode.InternalFailure) + // Optional + // Use to store any state between re-invocation via IN_PROGRESS + .callbackContext({}) + // Required on IN_PROGRESS + // The number of seconds to delay before re-invocation + .callbackDelaySeconds(0) + + .build() +``` + +While importing the [cfn-rpdk](https://github.com/eduardomourar/cloudformation-cli-typescript-plugin) library, failures can be passed back to CloudFormation by either raising an exception from `exceptions`, or setting the ProgressEvent's `status` to `OperationStatus.Failed` and `errorCode` to one of `HandlerErrorCode`. There is a static helper function, `ProgressEvent.failed`, for this common case. + +Keep in mind, during runtime all logs will be delivered to CloudWatch except those used with `debug` method. diff --git a/cost-explorer/anomaly-subscription/community-costexplorer-anomalysubscription.json b/cost-explorer/anomaly-subscription/community-costexplorer-anomalysubscription.json new file mode 100644 index 00000000..0f9e359a --- /dev/null +++ b/cost-explorer/anomaly-subscription/community-costexplorer-anomalysubscription.json @@ -0,0 +1,96 @@ +{ + "typeName": "Community::CostExplorer::AnomalySubscription", + "description": "An example resource schema demonstrating some basic constructs and validation rules.", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", + "definitions": { + "Subscriber": { + "type": "object", + "properties": { + "Address": { + "type": "string" + }, + "Type": { + "type": "string", + "enum": [ + "EMAIL", + "SNS" + ] + } + }, + "additionalProperties": false + } + }, + "properties": { + "Arn": { + "type": "string" + }, + "AccountId": { + "type": "string" + }, + "MonitorArnList": { + "type": "array", + "items": { + "type": "string" + } + }, + "Subscribers": { + "type": "array", + "items": { + "$ref": "#/definitions/Subscriber" + } + }, + "Threshold" : { + "type": "number" + }, + "Frequency": { + "type": "string", + "enum": [ + "DAILY", + "IMMEDIATE", + "WEEKLY" + ] + }, + "SubscriptionName": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "SubscriptionName", + "Frequency", + "Threshold" + ], + "readOnlyProperties": [ + "/properties/Arn" + ], + "primaryIdentifier": [ + "/properties/Arn" + ], + "handlers": { + "create": { + "permissions": [ + "costExplorer:createAnomalySubscription" + ] + }, + "read": { + "permissions": [ + + ] + }, + "update": { + "permissions": [ + "costExplorer:updateAnomalySubscription" + ] + }, + "delete": { + "permissions": [ + "costExplorer:deleteAnomalySubscription" + ] + }, + "list": { + "permissions": [ + + ] + } + } +} diff --git a/cost-explorer/anomaly-subscription/docs/README.md b/cost-explorer/anomaly-subscription/docs/README.md new file mode 100644 index 00000000..6ff40c51 --- /dev/null +++ b/cost-explorer/anomaly-subscription/docs/README.md @@ -0,0 +1,107 @@ +# Community::CostExplorer::AnomalySubscription + +An example resource schema demonstrating some basic constructs and validation rules. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Type" : "Community::CostExplorer::AnomalySubscription",
+    "Properties" : {
+        "AccountId" : String,
+        "MonitorArnList" : [ String, ... ],
+        "Subscribers" : [ Subscriber, ... ],
+        "Threshold" : Double,
+        "Frequency" : String,
+        "SubscriptionName" : String
+    }
+}
+
+ +### YAML + +
+Type: Community::CostExplorer::AnomalySubscription
+Properties:
+    AccountId: String
+    MonitorArnList: 
+      - String
+    Subscribers: 
+      - Subscriber
+    Threshold: Double
+    Frequency: String
+    SubscriptionName: String
+
+ +## Properties + +#### AccountId + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### MonitorArnList + +_Required_: No + +_Type_: List of String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Subscribers + +_Required_: No + +_Type_: List of Subscriber + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Threshold + +_Required_: Yes + +_Type_: Double + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Frequency + +_Required_: Yes + +_Type_: String + +_Allowed Values_: DAILY | IMMEDIATE | WEEKLY + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### SubscriptionName + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the Arn. + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### Arn + +Returns the Arn value. + diff --git a/cost-explorer/anomaly-subscription/docs/subscriber.md b/cost-explorer/anomaly-subscription/docs/subscriber.md new file mode 100644 index 00000000..9596e989 --- /dev/null +++ b/cost-explorer/anomaly-subscription/docs/subscriber.md @@ -0,0 +1,42 @@ +# Community::CostExplorer::AnomalySubscription Subscriber + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Address" : String,
+    "Type" : String
+}
+
+ +### YAML + +
+Address: String
+Type: String
+
+ +## Properties + +#### Address + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Type + +_Required_: No + +_Type_: String + +_Allowed Values_: EMAIL | SNS + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + diff --git a/cost-explorer/anomaly-subscription/package-lock.json b/cost-explorer/anomaly-subscription/package-lock.json new file mode 100644 index 00000000..7c2e3bcb --- /dev/null +++ b/cost-explorer/anomaly-subscription/package-lock.json @@ -0,0 +1,394 @@ +{ + "name": "community-costexplorer-anomalysubscription", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@org-formation/tombok": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@org-formation/tombok/-/tombok-0.0.1.tgz", + "integrity": "sha512-6F0zitevY+H3VT3MVsAo4JFlDl5kfqnhGLUwXNc652/HYEBzMru5iLkTIF6+cp/lgvTWxQJQJzH4yoYja2f9Pg==" + }, + "@types/node": { + "version": "12.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.3.tgz", + "integrity": "sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg==", + "dev": true + }, + "@types/uuid": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.4.tgz", + "integrity": "sha512-WGZCqBZZ0mXN2RxvLHL6/7RCu+OWs28jgQMP04LWfpyJlQUMTR6YU9CNJAKDgbw+EV/u687INXuLUc7FuML/4g==", + "dev": true + }, + "autobind-decorator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", + "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==" + }, + "aws-resource-providers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/aws-resource-providers-common/-/aws-resource-providers-common-0.2.3.tgz", + "integrity": "sha512-DVLaqozhBnbrhUX5/OCtiuuHnrB21673bG9UKP33Gmb+2t15sAFQzRnl/IzyrU2kbl5gEW8L+4Jm7PYx+GRByw==", + "requires": { + "aws-sdk": "^2.656.0", + "cfn-rpdk": "npm:@org-formation/cfn-rpdk@0.x" + } + }, + "aws-sdk": { + "version": "2.775.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.775.0.tgz", + "integrity": "sha512-rlej1sgHmfhl+PJqpQ2qOOsbHEEnLBIKBmanMTUNGiEAfuS0MpFjXECXTpJIOrbUzl3OZuAYrGuBkg2qrBwRbQ==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "cfn-rpdk": { + "version": "npm:@org-formation/cfn-rpdk@0.4.0", + "resolved": "https://registry.npmjs.org/@org-formation/cfn-rpdk/-/cfn-rpdk-0.4.0.tgz", + "integrity": "sha512-3JSVoN3OxQrVD68O9H6yUhawUQg7nAITqRydTfs517qALgS5foD4qmZDV9NB3SCBF5XgU0pEwDTY3tqwit1exQ==", + "requires": { + "@org-formation/tombok": "^0.0.1", + "autobind-decorator": "^2.4.0", + "aws-sdk": "~2.712.0", + "class-transformer": "^0.3.1", + "reflect-metadata": "^0.1.13", + "string.prototype.replaceall": "^1.0.3", + "uuid": "^7.0.2" + }, + "dependencies": { + "aws-sdk": { + "version": "2.712.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.712.0.tgz", + "integrity": "sha512-C3SLWanFydoWJwtKNi73BG9uB6UzrUuECaAiplOEVBltO/R4sBsHWhwTBuxS02eTNdRrgulu19bJ5RWt+OuXiA==", + "optional": true, + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "optional": true + } + } + } + } + }, + "class-transformer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", + "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "string.prototype.replaceall": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.3.tgz", + "integrity": "sha512-GF8JS9jtHSDkIsVMsYBPR4dItwaU6xOSPsMcRGTAbBr12ZDfyKMtgxdC2HDFbsMogGel29pmwxioJoXeu9ztIg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-regex": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", + "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "string.prototype.trimstart": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", + "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } +} diff --git a/cost-explorer/anomaly-subscription/package.json b/cost-explorer/anomaly-subscription/package.json new file mode 100644 index 00000000..8e7720cc --- /dev/null +++ b/cost-explorer/anomaly-subscription/package.json @@ -0,0 +1,29 @@ +{ + "name": "community-costexplorer-anomalysubscription", + "version": "0.1.0", + "description": "AWS custom resource provider named Community::CostExplorer::AnomalySubscription.", + "main": "dist/handlers.js", + "files": [ + "dist" + ], + "scripts": { + "build": "npx tsc", + "prepack": "cfn generate && npm run build", + "submit": "npm run prepack && cfn submit -vv --region us-east-1 --set-default", + "package": "npm run prepack && cfn submit --dry-run -vv && cp ${npm_package_name}.zip ${npm_package_name}-${npm_package_version}.zip", + "version": "aws s3 cp ${npm_package_name}-${npm_package_version}.zip s3://community-resource-provider-catalog/${npm_package_name}-${npm_package_version}.zip && aws s3 cp resource-role.yaml s3://community-resource-provider-catalog/${npm_package_name}-resource-role-${npm_package_version}.yml", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "aws-resource-providers-common": "^0.2.0", + "cfn-rpdk": "npm:@org-formation/cfn-rpdk@^0.4.0", + "class-transformer": "^0.3.1", + "uuid": "^7.0.3", + "aws-sdk": "2.775.0" + }, + "devDependencies": { + "@types/node": "^12.0.0", + "@types/uuid": "^7.0.3", + "typescript": "^3.8.3" + } +} diff --git a/cost-explorer/anomaly-subscription/resource-role.yaml b/cost-explorer/anomaly-subscription/resource-role.yaml new file mode 100644 index 00000000..6f7b9c7a --- /dev/null +++ b/cost-explorer/anomaly-subscription/resource-role.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + This CloudFormation template creates a role assumed by CloudFormation + during CRUDL operations to mutate resources on behalf of the customer. + +Resources: + ExecutionRole: + Type: AWS::IAM::Role + Properties: + MaxSessionDuration: 8400 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: resources.cloudformation.amazonaws.com + Action: sts:AssumeRole + Path: "/" + Policies: + - PolicyName: ResourceTypePolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "costExplorer:createAnomalySubscription" + - "costExplorer:deleteAnomalySubscription" + - "costExplorer:updateAnomalySubscription" + Resource: "*" +Outputs: + ExecutionRoleArn: + Value: + Fn::GetAtt: ExecutionRole.Arn diff --git a/cost-explorer/anomaly-subscription/src/handlers.ts b/cost-explorer/anomaly-subscription/src/handlers.ts new file mode 100644 index 00000000..84cd769c --- /dev/null +++ b/cost-explorer/anomaly-subscription/src/handlers.ts @@ -0,0 +1,48 @@ +import { Action, BaseResource, handlerEvent } from 'cfn-rpdk'; +import { commonAws, HandlerArgs } from 'aws-resource-providers-common'; +import { ResourceModel } from './models'; +import { CostExplorer } from 'aws-sdk'; +import { Subscribers } from 'aws-sdk/clients/costexplorer'; + +// Use this logger to forward log messages to CloudWatch Logs. +const LOGGER = console; + +interface CallbackContext extends Record {} + +class Resource extends BaseResource { + + @handlerEvent(Action.Create) + @commonAws({ serviceName: 'CostExplorer', debug: true }) + public async create(action: Action, args: HandlerArgs, service: CostExplorer, model: ResourceModel): Promise { + + const response = await service.createAnomalySubscription({ + AnomalySubscription: { + AccountId: model.accountId, + MonitorArnList: model.monitorArnList, + Subscribers: model.subscribers as Subscribers, + Threshold: model.threshold, + Frequency: model.frequency, + SubscriptionName: model.subscriptionName + } + }).promise(); + + model.arn = response.SubscriptionArn; + return Promise.resolve(model); + } + + @handlerEvent(Action.Delete) + @commonAws({ serviceName: 'CostExplorer', debug: true }) + public async delete(action: Action, args: HandlerArgs, service: CostExplorer): Promise { + const model: ResourceModel = args.request.desiredResourceState; + + await service.deleteAnomalySubscription( { SubscriptionArn: model.arn}).promise(); + + return Promise.resolve(null); + } +} + +const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel); + +export const entrypoint = resource.entrypoint; + +export const testEntrypoint = resource.testEntrypoint; diff --git a/cost-explorer/anomaly-subscription/src/models.ts b/cost-explorer/anomaly-subscription/src/models.ts new file mode 100644 index 00000000..eb439a31 --- /dev/null +++ b/cost-explorer/anomaly-subscription/src/models.ts @@ -0,0 +1,115 @@ +// This is a generated file. Modifications will be overwritten. +import { BaseModel, Dict, integer, Integer, Optional, transformValue } from 'cfn-rpdk'; +import { Exclude, Expose, Type, Transform } from 'class-transformer'; + +export class ResourceModel extends BaseModel { + ['constructor']: typeof ResourceModel; + + @Exclude() + public static readonly TYPE_NAME: string = 'Community::CostExplorer::AnomalySubscription'; + + @Exclude() + protected readonly IDENTIFIER_KEY_ARN: string = '/properties/Arn'; + + @Expose({ name: 'Arn' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'arn', value, obj, []), + { + toClassOnly: true, + } + ) + arn?: Optional; + @Expose({ name: 'AccountId' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'accountId', value, obj, []), + { + toClassOnly: true, + } + ) + accountId?: Optional; + @Expose({ name: 'MonitorArnList' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'monitorArnList', value, obj, [Array]), + { + toClassOnly: true, + } + ) + monitorArnList?: Optional>; + @Expose({ name: 'Subscribers' }) + @Type(() => Subscriber) + subscribers?: Optional>; + @Expose({ name: 'Threshold' }) + @Transform( + (value: any, obj: any) => + transformValue(Number, 'threshold', value, obj, []), + { + toClassOnly: true, + } + ) + threshold?: Optional; + @Expose({ name: 'Frequency' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'frequency', value, obj, []), + { + toClassOnly: true, + } + ) + frequency?: Optional; + @Expose({ name: 'SubscriptionName' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'subscriptionName', value, obj, []), + { + toClassOnly: true, + } + ) + subscriptionName?: Optional; + + @Exclude() + public getPrimaryIdentifier(): Dict { + const identifier: Dict = {}; + if (this.arn != null) { + identifier[this.IDENTIFIER_KEY_ARN] = this.arn; + } + + // only return the identifier if it can be used, i.e. if all components are present + return Object.keys(identifier).length === 1 ? identifier : null; + } + + @Exclude() + public getAdditionalIdentifiers(): Array { + const identifiers: Array = new Array(); + // only return the identifiers if any can be used + return identifiers.length === 0 ? null : identifiers; + } +} + +export class Subscriber extends BaseModel { + ['constructor']: typeof Subscriber; + + + @Expose({ name: 'Address' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'address', value, obj, []), + { + toClassOnly: true, + } + ) + address?: Optional; + @Expose({ name: 'Type' }) + @Transform( + (value: any, obj: any) => + transformValue(String, 'type_', value, obj, []), + { + toClassOnly: true, + } + ) + type_?: Optional; + +} + diff --git a/cost-explorer/anomaly-subscription/template.yml b/cost-explorer/anomaly-subscription/template.yml new file mode 100644 index 00000000..0e8b9e0c --- /dev/null +++ b/cost-explorer/anomaly-subscription/template.yml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: AWS SAM template for the Community::CostExplorer::AnomalySubscription resource type + +Globals: + Function: + Timeout: 180 # docker start-up times can be long for SAM CLI + MemorySize: 256 + +Resources: + TestEntrypoint: + Type: AWS::Serverless::Function + Properties: + Handler: dist/handlers.testEntrypoint + Runtime: nodejs12.x + CodeUri: ./ + + TypeFunction: + Type: AWS::Serverless::Function + Properties: + Handler: dist/handlers.entrypoint + Runtime: nodejs12.x + CodeUri: ./ + diff --git a/cost-explorer/anomaly-subscription/tsconfig.json b/cost-explorer/anomaly-subscription/tsconfig.json new file mode 100644 index 00000000..ffbcc7d2 --- /dev/null +++ b/cost-explorer/anomaly-subscription/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "commonjs", + "noImplicitAny": true, + "alwaysStrict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "allowJs": true, + "experimentalDecorators": true, + "outDir": "dist" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} From 77ed2f9b86775c954f96a5fc18a69cda49818acc Mon Sep 17 00:00:00 2001 From: Olaf Conijn Date: Mon, 2 Nov 2020 12:41:00 +0100 Subject: [PATCH 2/3] chore(cost-explorer): linting --- cost-explorer/anomaly-monitor/src/handlers.ts | 30 +++++++----------- .../anomaly-subscription/src/handlers.ts | 31 ++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/cost-explorer/anomaly-monitor/src/handlers.ts b/cost-explorer/anomaly-monitor/src/handlers.ts index 36fd0f08..97f3896d 100644 --- a/cost-explorer/anomaly-monitor/src/handlers.ts +++ b/cost-explorer/anomaly-monitor/src/handlers.ts @@ -3,26 +3,21 @@ import { commonAws, HandlerArgs } from 'aws-resource-providers-common'; import { ResourceModel } from './models'; import { CostExplorer } from 'aws-sdk'; -// Use this logger to forward log messages to CloudWatch Logs. -const LOGGER = console; - -interface CallbackContext extends Record {} - class Resource extends BaseResource { - @handlerEvent(Action.Create) @commonAws({ serviceName: 'CostExplorer', debug: true }) public async create(action: Action, args: HandlerArgs, service: CostExplorer, model: ResourceModel): Promise { - - const response = await service.createAnomalyMonitor({ - AnomalyMonitor: { - MonitorName: model.monitorName, - MonitorDimension: model.monitorDimension, - MonitorType: model.monitorType, - MonitorSpecification: model.monitorSpecification as CostExplorer.Expression, - DimensionalValueCount: model.dimensionalValueCount - } - }).promise(); + const response = await service + .createAnomalyMonitor({ + AnomalyMonitor: { + MonitorName: model.monitorName, + MonitorDimension: model.monitorDimension, + MonitorType: model.monitorType, + MonitorSpecification: model.monitorSpecification as CostExplorer.Expression, + DimensionalValueCount: model.dimensionalValueCount, + }, + }) + .promise(); model.arn = response.MonitorArn; return Promise.resolve(model); @@ -33,11 +28,10 @@ class Resource extends BaseResource { public async delete(action: Action, args: HandlerArgs, service: CostExplorer): Promise { const model: ResourceModel = args.request.desiredResourceState; - await service.deleteAnomalyMonitor( { MonitorArn: model.arn}).promise(); + await service.deleteAnomalyMonitor({ MonitorArn: model.arn }).promise(); return Promise.resolve(null); } - } const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel); diff --git a/cost-explorer/anomaly-subscription/src/handlers.ts b/cost-explorer/anomaly-subscription/src/handlers.ts index 84cd769c..861fad5c 100644 --- a/cost-explorer/anomaly-subscription/src/handlers.ts +++ b/cost-explorer/anomaly-subscription/src/handlers.ts @@ -4,27 +4,22 @@ import { ResourceModel } from './models'; import { CostExplorer } from 'aws-sdk'; import { Subscribers } from 'aws-sdk/clients/costexplorer'; -// Use this logger to forward log messages to CloudWatch Logs. -const LOGGER = console; - -interface CallbackContext extends Record {} - class Resource extends BaseResource { - @handlerEvent(Action.Create) @commonAws({ serviceName: 'CostExplorer', debug: true }) public async create(action: Action, args: HandlerArgs, service: CostExplorer, model: ResourceModel): Promise { - - const response = await service.createAnomalySubscription({ - AnomalySubscription: { - AccountId: model.accountId, - MonitorArnList: model.monitorArnList, - Subscribers: model.subscribers as Subscribers, - Threshold: model.threshold, - Frequency: model.frequency, - SubscriptionName: model.subscriptionName - } - }).promise(); + const response = await service + .createAnomalySubscription({ + AnomalySubscription: { + AccountId: model.accountId, + MonitorArnList: model.monitorArnList, + Subscribers: model.subscribers as Subscribers, + Threshold: model.threshold, + Frequency: model.frequency, + SubscriptionName: model.subscriptionName, + }, + }) + .promise(); model.arn = response.SubscriptionArn; return Promise.resolve(model); @@ -35,7 +30,7 @@ class Resource extends BaseResource { public async delete(action: Action, args: HandlerArgs, service: CostExplorer): Promise { const model: ResourceModel = args.request.desiredResourceState; - await service.deleteAnomalySubscription( { SubscriptionArn: model.arn}).promise(); + await service.deleteAnomalySubscription({ SubscriptionArn: model.arn }).promise(); return Promise.resolve(null); } From 63ba022ed131f990fc8324d28622ced80aa0e900 Mon Sep 17 00:00:00 2001 From: Olaf Conijn Date: Thu, 12 Nov 2020 12:08:53 +0100 Subject: [PATCH 3/3] chore(cost-explorer): updated tsconfig files --- cost-explorer/anomaly-monitor/tsconfig.json | 12 +----------- cost-explorer/anomaly-subscription/tsconfig.json | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/cost-explorer/anomaly-monitor/tsconfig.json b/cost-explorer/anomaly-monitor/tsconfig.json index ffbcc7d2..f1ab532e 100644 --- a/cost-explorer/anomaly-monitor/tsconfig.json +++ b/cost-explorer/anomaly-monitor/tsconfig.json @@ -1,19 +1,9 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "target": "ES2017", - "module": "commonjs", - "noImplicitAny": true, - "alwaysStrict": true, - "esModuleInterop": true, - "moduleResolution": "node", - "allowJs": true, - "experimentalDecorators": true, "outDir": "dist" }, "include": [ "src/**/*.ts" - ], - "exclude": [ - "node_modules" ] } diff --git a/cost-explorer/anomaly-subscription/tsconfig.json b/cost-explorer/anomaly-subscription/tsconfig.json index ffbcc7d2..f1ab532e 100644 --- a/cost-explorer/anomaly-subscription/tsconfig.json +++ b/cost-explorer/anomaly-subscription/tsconfig.json @@ -1,19 +1,9 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "target": "ES2017", - "module": "commonjs", - "noImplicitAny": true, - "alwaysStrict": true, - "esModuleInterop": true, - "moduleResolution": "node", - "allowJs": true, - "experimentalDecorators": true, "outDir": "dist" }, "include": [ "src/**/*.ts" - ], - "exclude": [ - "node_modules" ] }