diff --git a/package-lock.json b/package-lock.json index 8f8fdeb5..c2b00cf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@adobe/spacecat-shared-http-utils": "1.1.3", "@adobe/spacecat-shared-rum-api-client": "1.4.3", "@adobe/spacecat-shared-utils": "1.7.4", + "@aws-sdk/client-cost-explorer": "3.499.0", "@aws-sdk/client-sqs": "3.504.0", "diff": "5.1.0", "urijs": "1.19.11" @@ -1900,6 +1901,465 @@ } } }, + "node_modules/@aws-sdk/client-cost-explorer": { + "version": "3.499.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cost-explorer/-/client-cost-explorer-3.499.0.tgz", + "integrity": "sha512-FCT1OJsPkzz+SKKm/8XdCAWLBb/CYSFYHKI87dEzfyHek8pBhH+iJpaK9XCfPQXufTxaO+NEkib82SSRlnVpUw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.499.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/credential-provider-node": "3.499.0", + "@aws-sdk/middleware-host-header": "3.496.0", + "@aws-sdk/middleware-logger": "3.496.0", + "@aws-sdk/middleware-recursion-detection": "3.496.0", + "@aws-sdk/middleware-signing": "3.496.0", + "@aws-sdk/middleware-user-agent": "3.496.0", + "@aws-sdk/region-config-resolver": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@aws-sdk/util-endpoints": "3.496.0", + "@aws-sdk/util-user-agent-browser": "3.496.0", + "@aws-sdk/util-user-agent-node": "3.496.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/client-sso": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.496.0.tgz", + "integrity": "sha512-fuaMuxKg7CMUsP9l3kxYWCOxFsBjdA0xj5nlikaDm1661/gB4KkAiGqRY8LsQkpNXvXU8Nj+f7oCFADFyGYzyw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/middleware-host-header": "3.496.0", + "@aws-sdk/middleware-logger": "3.496.0", + "@aws-sdk/middleware-recursion-detection": "3.496.0", + "@aws-sdk/middleware-user-agent": "3.496.0", + "@aws-sdk/region-config-resolver": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@aws-sdk/util-endpoints": "3.496.0", + "@aws-sdk/util-user-agent-browser": "3.496.0", + "@aws-sdk/util-user-agent-node": "3.496.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/client-sts": { + "version": "3.499.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.499.0.tgz", + "integrity": "sha512-Eyj9STw2DXMtXL5V/v0HYHO6+JjGPi257M5IYyxwqlvRchq6jbOsedobfxclB/gBUyBRtZdnyAIS8uCKjb4kpA==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/credential-provider-node": "3.499.0", + "@aws-sdk/middleware-host-header": "3.496.0", + "@aws-sdk/middleware-logger": "3.496.0", + "@aws-sdk/middleware-recursion-detection": "3.496.0", + "@aws-sdk/middleware-user-agent": "3.496.0", + "@aws-sdk/region-config-resolver": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@aws-sdk/util-endpoints": "3.496.0", + "@aws-sdk/util-user-agent-browser": "3.496.0", + "@aws-sdk/util-user-agent-node": "3.496.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/core": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.496.0.tgz", + "integrity": "sha512-yT+ug7Cw/3eJi7x2es0+46x12+cIJm5Xv+GPWsrTFD1TKgqO/VPEgfDtHFagDNbFmjNQA65Ygc/kEdIX9ICX/A==", + "dependencies": { + "@smithy/core": "^1.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.496.0.tgz", + "integrity": "sha512-lukQMJ8SWWP5RqkRNOHi/H+WMhRvSWa3Fc5Jf/VP6xHiPLfF1XafcvthtV91e0VwPCiseI+HqChrcGq8pvnxHw==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.496.0.tgz", + "integrity": "sha512-2nD1jp1sIwcQaWK1y/9ruQOkW16RUxZpzgjbW/gnK3iiUXwx+/FNQWxshud+GTSx3Q4x6eIhqsbjtP4VVPPuUA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.496.0", + "@aws-sdk/credential-provider-process": "3.496.0", + "@aws-sdk/credential-provider-sso": "3.496.0", + "@aws-sdk/credential-provider-web-identity": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.499.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.499.0.tgz", + "integrity": "sha512-EsiSevVmcVSMIq7D9siSH/XVc5I0vMntg1rx6KQdng1Fq8X/RBL5t9wSWEwOl7KFo5HlEsWrLWIpo1WHuzIL/w==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.496.0", + "@aws-sdk/credential-provider-ini": "3.496.0", + "@aws-sdk/credential-provider-process": "3.496.0", + "@aws-sdk/credential-provider-sso": "3.496.0", + "@aws-sdk/credential-provider-web-identity": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.496.0.tgz", + "integrity": "sha512-/YZscCTGOKVmGr916Th4XF8Sz6JDtZ/n2loHG9exok9iy/qIbACsTRNLP9zexPxhPoue/oZqecY5xbVljfY34A==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.496.0.tgz", + "integrity": "sha512-eP7GxpT2QYubSDG7uk1GJW4eNymZCq65IxDyEFCXOP/kfqkxriCY+iVEFG6/Mo3LxvgrgHXU4jxrCAXMAWN43g==", + "dependencies": { + "@aws-sdk/client-sso": "3.496.0", + "@aws-sdk/token-providers": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.496.0.tgz", + "integrity": "sha512-IbP+qLlvJSpNPj+zW6TtFuLRTK5Tf0hW+2pom4vFyi5YSH4pn8UOC136UdewX8vhXGS9BJQ5zBDMasIyl5VeGQ==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.496.0.tgz", + "integrity": "sha512-jUdPpSJeqCYXf6hSjfwsfHway7peIV8Vz51w/BN91bF4vB/bYwAC5o9/iJiK/EoByp5asxA8fg9wFOyGjzdbLg==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/middleware-logger": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.496.0.tgz", + "integrity": "sha512-EwMVSY6iBMeGbVnvwdaFl/ClMS/YWtxCAo+bcEtgk8ltRuo7qgbJem8Km/fvWC1vdWvIbe4ArdJ8iGzq62ffAw==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.496.0.tgz", + "integrity": "sha512-+IuOcFsfqg2WAnaEzH6KhVbicqCxtOq9w3DH2jwTpddRlCx2Kqf6wCzg8luhHRGyjBZdsbIS+OXwyMevoppawA==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/middleware-signing": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.496.0.tgz", + "integrity": "sha512-Oq73Brs4IConvWnRlh8jM1V7LHoTw9SVQklu/QW2FPlNrB3B8fuTdWHHYIWv7ybw1bykXoCY99v865Mmq/Or/g==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.496.0.tgz", + "integrity": "sha512-+iMtRxFk0GmFWNUF4ilxylOQd9PZdR4ZC9jkcPIh1PZlvKtpCyFywKlk5RRZKklSoJ/CttcqwhMvOXTNbWm/0w==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@aws-sdk/util-endpoints": "3.496.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.496.0.tgz", + "integrity": "sha512-URrNVOPHPgEDm6QFu6lDC2cUFs+Jx23mA3jEwCvoKlXiEY/ZoWjH8wlX3OMUlLrF1qoUTuD03jjrJzF6zoCgug==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "@smithy/util-config-provider": "^2.2.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/token-providers": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.496.0.tgz", + "integrity": "sha512-fyi8RcObEa1jNETJdc2H6q9VHrrdKCj/b6+fbLvymb7mUVRd0aWUn+24SNUImnSOnrwYnwaMfyyEC388X4MbFQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.496.0", + "@aws-sdk/middleware-logger": "3.496.0", + "@aws-sdk/middleware-recursion-detection": "3.496.0", + "@aws-sdk/middleware-user-agent": "3.496.0", + "@aws-sdk/region-config-resolver": "3.496.0", + "@aws-sdk/types": "3.496.0", + "@aws-sdk/util-endpoints": "3.496.0", + "@aws-sdk/util-user-agent-browser": "3.496.0", + "@aws-sdk/util-user-agent-node": "3.496.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/types": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.496.0.tgz", + "integrity": "sha512-umkGadK4QuNQaMoDICMm7NKRI/mYSXiyPjcn3d53BhsuArYU/52CebGQKdt4At7SwwsiVJZw9RNBHyN5Mm0HVw==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/util-endpoints": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.496.0.tgz", + "integrity": "sha512-1QzOiWHi383ZwqSi/R2KgKCd7M+6DxkxI5acqLPm8mvDRDP2jRjrnVaC0g9/tlttWousGEemDUWStwrD2mVYSw==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/types": "^2.9.1", + "@smithy/util-endpoints": "^1.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.496.0.tgz", + "integrity": "sha512-4j2spN+h0I0qfSMsGvJXTfQBu1e18rPdekKvzsGJxhaAE1tNgUfUT4nbvc5uVn0sNjZmirskmJ3kfbzVOrqIFg==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/types": "^2.9.1", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cost-explorer/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.496.0.tgz", + "integrity": "sha512-h0Ax0jlDc7UIo3KoSI4C4tVLBFoiAdx3+DhTVfgLS7x93d41dMlziPoBX2RgdcFn37qnzw6AQKTVTMwDbRCGpg==", + "dependencies": { + "@aws-sdk/types": "3.496.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.465.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.465.0.tgz", diff --git a/package.json b/package.json index fc6378f9..19b73d2f 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@adobe/spacecat-shared-http-utils": "1.1.3", "@adobe/spacecat-shared-rum-api-client": "1.4.3", "@adobe/spacecat-shared-utils": "1.7.4", + "@aws-sdk/client-cost-explorer": "3.499.0", "@aws-sdk/client-sqs": "3.504.0", "diff": "5.1.0", "urijs": "1.19.11" diff --git a/src/cogs/handler.js b/src/cogs/handler.js new file mode 100644 index 00000000..fd37929e --- /dev/null +++ b/src/cogs/handler.js @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { CostExplorerClient, GetCostAndUsageCommand } from '@aws-sdk/client-cost-explorer'; +import { internalServerError, noContent, notFound } from '@adobe/spacecat-shared-http-utils'; + +/** + * This function builds the input for AWS Cost Explorer API + * @param {string} startDate start date in YYYY/MM/DD format like 2021/01/01 + * @param {string} endDate end date in YYYY/MM/DD format like 2021/01/31 + * @returns {Object} input + */ +function buildAWSInput(startDate, endDate) { + return { + TimePeriod: { + End: endDate, + Start: startDate, + }, + Granularity: 'MONTHLY', + Filter: { + Tags: { + Key: 'Adobe.ArchPath', + Values: ['EC.SpaceCat.Services'], + MatchOptions: ['EQUALS'], + }, + }, + Metrics: [ + 'UnblendedCost', + ], + GroupBy: [ + { + Key: 'SERVICE', + Type: 'DIMENSION', + }, + { + Key: 'Environment', + Type: 'TAG', + }, + ], + }; +} + +/** + * This function parses the date in YYYY/MM/DD format like 2021/01/01 and return the date in + * @param {string} date format like 2021-01-01 + * @returns string like Jan-21 + */ +const parseYearDate = (date) => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const yrMonth = date.split(/-/); + return `${months[yrMonth[1] - 1]}-${yrMonth[0].substring(2)}`; +}; + +/** + * This function maps the AWS service name to the service name used in SpaceCat + * @param {string} service AWS service name + * @returns string like LAMBDA + */ +const serviceMap = (service) => { + switch (service) { + case 'AWS Lambda': + return 'LAMBDA'; + case 'AWS Secrets Manager': + return 'SECRETS MANAGER'; + case 'Amazon DynamoDB': + return 'DYNAMODB'; + case 'Amazon Simple Storage Service': + return 'S3'; + case 'Amazon Simple Queue Service': + return 'SQS'; + case 'AmazonCloudWatch': + return 'CLOUDWATCH'; + default: + return service; + } +}; + +/** + * This function processes the response from AWS Cost Explorer API + * @param {Object} data response from AWS Cost Explorer API + * @returns {Object} results by month and Year wise. + */ +function processAWSResponse(data) { + if (data && data.ResultsByTime && data.ResultsByTime.length > 0) { + const result = {}; + data.ResultsByTime.forEach((r) => { + if (r.Groups && r.Groups.length > 0) { + const granularity = {}; + r.Groups.forEach((group) => { + const key = group.Keys[0]; + if (group.Metrics && group.Metrics.UnblendedCost) { + // eslint-disable-next-line max-len + granularity[serviceMap(key)] = parseFloat(group.Metrics.UnblendedCost.Amount).toFixed(2); + } + }); + result[parseYearDate(r.TimePeriod.Start)] = granularity; + } + }); + return result; + } + return {}; +} +export default async function auditCOGs(message, context) { + const { type, startDate, endDate } = message; + const { log, sqs } = context; + const { region } = context.runtime; + const { + AUDIT_RESULTS_QUEUE_URL: queueUrl, + } = context.env; + log.info(`Fetching Cost Usage from ${startDate} to ${endDate}`); + + try { + const client = new CostExplorerClient({ region }); + const input = buildAWSInput(startDate, endDate); + const command = new GetCostAndUsageCommand(input); + const response = await client.send(command); + const usageCost = processAWSResponse(response); + if (Object.keys(usageCost).length === 0) { + return notFound('No Cost Usage found'); + } + Object.keys(usageCost).forEach(async (monthYear) => { + await sqs.sendMessage(queueUrl, { + type, + monthYear, + usageCost: usageCost[monthYear], + }); + }); + + log.info(`Successfully fetched Cost Usage from ${startDate} to ${endDate}`); + return noContent(); + } catch (e) { + return internalServerError(e.message); + } +} diff --git a/src/index.js b/src/index.js index 6d1fd3b6..6c68e507 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,7 @@ import apex from './apex/handler.js'; import cwv from './cwv/handler.js'; import lhs from './lhs/handler.js'; import notfound from './notfound/handler.js'; +import cogs from './cogs/handler.js'; import backlinks from './backlinks/handler.js'; const HANDLERS = { @@ -28,6 +29,7 @@ const HANDLERS = { cwv, 'lhs-mobile': lhs, 'lhs-desktop': lhs, + cogs, 404: notfound, 'broken-backlinks': backlinks, }; diff --git a/test/audits/cogs.test.js b/test/audits/cogs.test.js new file mode 100644 index 00000000..f00bf6e8 --- /dev/null +++ b/test/audits/cogs.test.js @@ -0,0 +1,124 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +import sinon from 'sinon'; +import chai from 'chai'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import nock from 'nock'; +import main from '../../src/cogs/handler.js'; +import { expectedCogsResult, expectedCOGSValue } from '../fixtures/cogs-data.js'; + +chai.use(sinonChai); +chai.use(chaiAsPromised); +const { expect } = chai; + +const sandbox = sinon.createSandbox(); + +describe('COGS handler test', () => { + let context; + let messageBodyJson; + beforeEach('setup', () => { + messageBodyJson = { + type: 'cogs', + startDate: '2023-12-01', + endDate: '2024-01-01', + }; + context = { + log: console, + runtime: { + region: 'us-east-1', + }, + env: { + AUDIT_RESULTS_QUEUE_URL: 'queueUrl', + }, + sqs: { + sendMessage: sandbox.stub().resolves(), + }, + }; + }); + afterEach('clean', () => { + sandbox.restore(); + }); + + it('should pass on correct inputs', async () => { + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(200, expectedCogsResult); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(204); + expect(context.sqs.sendMessage).to.have.been.calledOnce; + expect(context.sqs.sendMessage).to.have.been + .calledWith(context.env.AUDIT_RESULTS_QUEUE_URL, expectedCOGSValue); + }); + it('Should test for new service data', async () => { + expectedCogsResult.ResultsByTime[0].Groups.push({ + Keys: [ + 'AmazonNewService', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '0.049616647', + Unit: 'USD', + }, + }, + }); + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(200, expectedCogsResult); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(204); + }); + it('Should test for empty group data set', async () => { + expectedCogsResult.ResultsByTime[0].Groups = []; + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(200, expectedCogsResult); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(404); + }); + it('Should test for empty data set', async () => { + expectedCogsResult.ResultsByTime = []; + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(200, expectedCogsResult); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(404); + }); + it('Should fail, when missing startDate input', async () => { + delete messageBodyJson.startDate; + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(400, { message: 'Start time is invalid. Valid format is: yyyy-MM-dd' }); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(500); + }); + it('Should fail, when missing endDate input', async () => { + delete messageBodyJson.endDate; + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(400, { message: 'End time is invalid. Valid format is: yyyy-MM-dd' }); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(500); + }); + it('Should fail, when both startDate and endDate are missing in input', async () => { + delete messageBodyJson.startDate; + delete messageBodyJson.endDate; + nock('https://ce.us-east-1.amazonaws.com') + .post('/') + .reply(400, { message: 'Time is invalid. Valid format is: yyyy-MM-dd' }); + const result = await main(messageBodyJson, context); + expect(result.status).to.be.equal(500); + }); +}); diff --git a/test/fixtures/cogs-data.js b/test/fixtures/cogs-data.js new file mode 100644 index 00000000..cfd7435e --- /dev/null +++ b/test/fixtures/cogs-data.js @@ -0,0 +1,123 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export const expectedCogsResult = { + DimensionValueAttributes: [], + GroupDefinitions: [ + { + Key: 'SERVICE', + Type: 'DIMENSION', + }, + { + Key: 'Environment', + Type: 'TAG', + }, + ], + NextPageToken: null, + ResultsByTime: [ + { + Estimated: false, + Groups: [ + { + Keys: [ + 'AWS Lambda', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '14.6732180628', + Unit: 'USD', + }, + }, + }, + { + Keys: [ + 'AWS Secrets Manager', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '0.093415', + Unit: 'USD', + }, + }, + }, + { + Keys: [ + 'Amazon DynamoDB', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '0.095706623', + Unit: 'USD', + }, + }, + }, + { + Keys: [ + 'Amazon Simple Queue Service', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '0.17150958', + Unit: 'USD', + }, + }, + }, + { + Keys: [ + 'Amazon Simple Storage Service', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '0.0007143178', + Unit: 'USD', + }, + }, + }, + { + Keys: [ + 'AmazonCloudWatch', + 'Environment$', + ], + Metrics: { + UnblendedCost: { + Amount: '0.049616647', + Unit: 'USD', + }, + }, + }, + ], + TimePeriod: { + End: '2024-01-01', + Start: '2023-12-01', + }, + Total: {}, + }, + ], + ResultMetadata: {}, +}; +export const expectedCOGSValue = { + type: 'cogs', + monthYear: 'Dec-23', + usageCost: { + LAMBDA: '14.67', + 'SECRETS MANAGER': '0.09', + DYNAMODB: '0.10', + SQS: '0.17', + S3: '0.00', + CLOUDWATCH: '0.05', + }, +};