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..97f3896d
--- /dev/null
+++ b/cost-explorer/anomaly-monitor/src/handlers.ts
@@ -0,0 +1,41 @@
+import { Action, BaseResource, handlerEvent } from 'cfn-rpdk';
+import { commonAws, HandlerArgs } from 'aws-resource-providers-common';
+import { ResourceModel } from './models';
+import { CostExplorer } from 'aws-sdk';
+
+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