diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 4f72319..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,236 +0,0 @@ -// ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". -{ - "env": { - "jest": true, - "node": true - }, - "root": true, - "plugins": [ - "@typescript-eslint", - "import", - "@stylistic" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module", - "project": "./tsconfig.dev.json" - }, - "extends": [ - "plugin:import/typescript" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] - }, - "import/resolver": { - "node": {}, - "typescript": { - "project": "./tsconfig.dev.json", - "alwaysTryTypes": true - } - } - }, - "ignorePatterns": [ - "*.js", - "*.d.ts", - "node_modules/", - "*.generated.ts", - "coverage", - "!.projenrc.ts", - "!projenrc/**/*.ts" - ], - "rules": { - "indent": [ - "off" - ], - "@stylistic/indent": [ - "error", - 2 - ], - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "comma-dangle": [ - "error", - "always-multiline" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "array-bracket-spacing": [ - "error", - "never" - ], - "array-bracket-newline": [ - "error", - "consistent" - ], - "object-curly-spacing": [ - "error", - "always" - ], - "object-curly-newline": [ - "error", - { - "multiline": true, - "consistent": true - } - ], - "object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true - } - ], - "keyword-spacing": [ - "error" - ], - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "space-before-blocks": [ - "error" - ], - "curly": [ - "error", - "multi-line", - "consistent" - ], - "@stylistic/member-delimiter-style": [ - "error" - ], - "semi": [ - "error", - "always" - ], - "max-len": [ - "error", - { - "code": 150, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - "ignoreRegExpLiterals": true - } - ], - "quote-props": [ - "error", - "consistent-as-needed" - ], - "@typescript-eslint/no-require-imports": [ - "error" - ], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/test/**", - "**/build-tools/**", - ".projenrc.ts", - "projenrc/**/*.ts" - ], - "optionalDependencies": false, - "peerDependencies": true - } - ], - "import/no-unresolved": [ - "error" - ], - "import/order": [ - "warn", - { - "groups": [ - "builtin", - "external" - ], - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ], - "import/no-duplicates": [ - "error" - ], - "no-shadow": [ - "off" - ], - "@typescript-eslint/no-shadow": [ - "error" - ], - "key-spacing": [ - "error" - ], - "no-multiple-empty-lines": [ - "error" - ], - "@typescript-eslint/no-floating-promises": [ - "error" - ], - "no-return-await": [ - "off" - ], - "@typescript-eslint/return-await": [ - "error" - ], - "no-trailing-spaces": [ - "error" - ], - "dot-notation": [ - "error" - ], - "no-bitwise": [ - "error" - ], - "@typescript-eslint/member-ordering": [ - "error", - { - "default": [ - "public-static-field", - "public-static-method", - "protected-static-field", - "protected-static-method", - "private-static-field", - "private-static-method", - "field", - "constructor", - "method" - ] - } - ] - }, - "overrides": [ - { - "files": [ - ".projenrc.ts" - ], - "rules": { - "@typescript-eslint/no-require-imports": "off", - "import/no-extraneous-dependencies": "off" - } - } - ] -} diff --git a/.gitattributes b/.gitattributes index 4cd269a..edc5148 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,18 +2,20 @@ * text=auto eol=lf *.snap linguist-generated -/.eslintrc.json linguist-generated /.gitattributes linguist-generated /.github/pull_request_template.md linguist-generated /.github/workflows/auto-approve.yml linguist-generated /.github/workflows/auto-queue.yml linguist-generated /.github/workflows/build.yml linguist-generated /.github/workflows/pull-request-lint.yml linguist-generated -/.github/workflows/upgrade-cdklabs-projen-project-types.yml linguist-generated -/.github/workflows/upgrade-dev-deps.yml linguist-generated -/.github/workflows/upgrade.yml linguist-generated +/.github/workflows/release.yml linguist-generated +/.github/workflows/upgrade-cdklabs-projen-project-types-main.yml linguist-generated +/.github/workflows/upgrade-dev-deps-main.yml linguist-generated +/.github/workflows/upgrade-main.yml linguist-generated /.gitignore linguist-generated /.npmignore linguist-generated +/.prettierignore linguist-generated +/.prettierrc.json linguist-generated /.projen/** linguist-generated /.projen/deps.json linguist-generated /.projen/files.json linguist-generated diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index efab190..e5f6ba0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: merge_group: {} jobs: build: - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: write outputs: @@ -23,7 +23,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - name: Install dependencies run: yarn install --check-files - name: build @@ -58,7 +58,7 @@ jobs: overwrite: true self-mutation: needs: build - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: write if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) @@ -89,14 +89,14 @@ jobs: git push origin HEAD:$PULL_REQUEST_REF package-js: needs: build - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: read if: ${{ !needs.build.outputs.self_mutation_happened }} steps: - uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - name: Download build artifacts uses: actions/download-artifact@v4 with: @@ -123,7 +123,7 @@ jobs: run: mv .repo/dist dist package-java: needs: build - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: read if: ${{ !needs.build.outputs.self_mutation_happened }} @@ -134,7 +134,7 @@ jobs: java-version: "11" - uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - name: Download build artifacts uses: actions/download-artifact@v4 with: @@ -161,17 +161,14 @@ jobs: run: mv .repo/dist dist package-python: needs: build - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: read if: ${{ !needs.build.outputs.self_mutation_happened }} steps: - uses: actions/setup-node@v4 with: - node-version: lts/* - - uses: actions/setup-python@v5 - with: - python-version: 3.x + node-version: "20" - name: Download build artifacts uses: actions/download-artifact@v4 with: @@ -198,14 +195,14 @@ jobs: run: mv .repo/dist dist package-dotnet: needs: build - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: read if: ${{ !needs.build.outputs.self_mutation_happened }} steps: - uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - uses: actions/setup-dotnet@v4 with: dotnet-version: 6.x @@ -235,14 +232,14 @@ jobs: run: mv .repo/dist dist package-go: needs: build - runs-on: ubuntu-latest + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} permissions: contents: read if: ${{ !needs.build.outputs.self_mutation_happened }} steps: - uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - uses: actions/setup-go@v5 with: go-version: ^1.18.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e86abf3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,296 @@ +# ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". + +name: release +on: + push: + branches: + - main + workflow_dispatch: {} +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false +jobs: + release: + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: write + outputs: + latest_commit: ${{ steps.git_remote.outputs.latest_commit }} + tag_exists: ${{ steps.check_tag_exists.outputs.exists }} + env: + CI: "true" + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set git identity + run: |- + git config user.name "github-actions" + git config user.email "github-actions@github.com" + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Install dependencies + run: yarn install --check-files --frozen-lockfile + - name: release + run: npx projen release + - name: Check if version has already been tagged + id: check_tag_exists + run: |- + TAG=$(cat dist/releasetag.txt) + ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) + cat $GITHUB_OUTPUT + - name: Check for new commits + id: git_remote + run: |- + echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + - name: Backup artifact permissions + if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} + run: cd dist && getfacl -R . > permissions-backup.acl + continue-on-error: true + - name: Upload artifact + if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} + uses: actions/upload-artifact@v4.4.0 + with: + name: build-artifact + path: dist + overwrite: true + release_github: + name: Publish to GitHub Releases + needs: + - release + - release_npm + - release_maven + - release_pypi + - release_nuget + - release_golang + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: write + if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha + steps: + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifact + path: dist + - name: Restore build artifact permissions + run: cd dist && setfacl --restore=permissions-backup.acl + continue-on-error: true + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_REF: ${{ github.sha }} + run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_REF -p 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi + release_npm: + name: Publish to npm + needs: release + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: read + if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha + steps: + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifact + path: dist + - name: Restore build artifact permissions + run: cd dist && setfacl --restore=permissions-backup.acl + continue-on-error: true + - name: Checkout + uses: actions/checkout@v4 + with: + path: .repo + - name: Install Dependencies + run: cd .repo && yarn install --check-files --frozen-lockfile + - name: Extract build artifact + run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo + - name: Move build artifact out of the way + run: mv dist dist.old + - name: Create js artifact + run: cd .repo && npx projen package:js + - name: Collect js artifact + run: mv .repo/dist dist + - name: Release + env: + NPM_DIST_TAG: latest + NPM_REGISTRY: registry.npmjs.org + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx -p publib@latest publib-npm + release_maven: + name: Publish to Maven Central + needs: release + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: read + if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha + steps: + - uses: actions/setup-java@v4 + with: + distribution: corretto + java-version: "11" + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifact + path: dist + - name: Restore build artifact permissions + run: cd dist && setfacl --restore=permissions-backup.acl + continue-on-error: true + - name: Checkout + uses: actions/checkout@v4 + with: + path: .repo + - name: Install Dependencies + run: cd .repo && yarn install --check-files --frozen-lockfile + - name: Extract build artifact + run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo + - name: Move build artifact out of the way + run: mv dist dist.old + - name: Create java artifact + run: cd .repo && npx projen package:java + - name: Collect java artifact + run: mv .repo/dist dist + - name: Release + env: + MAVEN_ENDPOINT: https://s01.oss.sonatype.org + MAVEN_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + MAVEN_GPG_PRIVATE_KEY_PASSPHRASE: ${{ secrets.MAVEN_GPG_PRIVATE_KEY_PASSPHRASE }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_STAGING_PROFILE_ID: ${{ secrets.MAVEN_STAGING_PROFILE_ID }} + run: npx -p publib@latest publib-maven + release_pypi: + name: Publish to PyPI + needs: release + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: read + if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha + steps: + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifact + path: dist + - name: Restore build artifact permissions + run: cd dist && setfacl --restore=permissions-backup.acl + continue-on-error: true + - name: Checkout + uses: actions/checkout@v4 + with: + path: .repo + - name: Install Dependencies + run: cd .repo && yarn install --check-files --frozen-lockfile + - name: Extract build artifact + run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo + - name: Move build artifact out of the way + run: mv dist dist.old + - name: Create python artifact + run: cd .repo && npx projen package:python + - name: Collect python artifact + run: mv .repo/dist dist + - name: Release + env: + TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} + TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + run: npx -p publib@latest publib-pypi + release_nuget: + name: Publish to NuGet Gallery + needs: release + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: read + if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha + steps: + - uses: actions/setup-node@v4 + with: + node-version: "20" + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.x + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifact + path: dist + - name: Restore build artifact permissions + run: cd dist && setfacl --restore=permissions-backup.acl + continue-on-error: true + - name: Checkout + uses: actions/checkout@v4 + with: + path: .repo + - name: Install Dependencies + run: cd .repo && yarn install --check-files --frozen-lockfile + - name: Extract build artifact + run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo + - name: Move build artifact out of the way + run: mv dist dist.old + - name: Create dotnet artifact + run: cd .repo && npx projen package:dotnet + - name: Collect dotnet artifact + run: mv .repo/dist dist + - name: Release + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: npx -p publib@latest publib-nuget + release_golang: + name: Publish to GitHub Go Module Repository + needs: release + runs-on: codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }} + permissions: + contents: read + if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha + steps: + - uses: actions/setup-node@v4 + with: + node-version: "20" + - uses: actions/setup-go@v5 + with: + go-version: ^1.18.0 + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifact + path: dist + - name: Restore build artifact permissions + run: cd dist && setfacl --restore=permissions-backup.acl + continue-on-error: true + - name: Checkout + uses: actions/checkout@v4 + with: + path: .repo + - name: Install Dependencies + run: cd .repo && yarn install --check-files --frozen-lockfile + - name: Extract build artifact + run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo + - name: Move build artifact out of the way + run: mv dist dist.old + - name: Create go artifact + run: cd .repo && npx projen package:go + - name: Collect go artifact + run: mv .repo/dist dist + - name: Release + env: + GIT_USER_NAME: github-actions + GIT_USER_EMAIL: github-actions@github.com + GITHUB_TOKEN: ${{ secrets.GO_GITHUB_TOKEN }} + run: npx -p publib@latest publib-golang diff --git a/.github/workflows/upgrade-cdklabs-projen-project-types.yml b/.github/workflows/upgrade-cdklabs-projen-project-types-main.yml similarity index 93% rename from .github/workflows/upgrade-cdklabs-projen-project-types.yml rename to .github/workflows/upgrade-cdklabs-projen-project-types-main.yml index b14d204..0d58790 100644 --- a/.github/workflows/upgrade-cdklabs-projen-project-types.yml +++ b/.github/workflows/upgrade-cdklabs-projen-project-types-main.yml @@ -1,6 +1,6 @@ # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". -name: upgrade-cdklabs-projen-project-types +name: upgrade-cdklabs-projen-project-types-main on: workflow_dispatch: {} jobs: @@ -14,6 +14,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Setup Node.js uses: actions/setup-node@v4 - name: Install dependencies @@ -43,6 +45,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Download patch uses: actions/download-artifact@v4 with: @@ -68,8 +72,8 @@ jobs: ------ - *Automatically created by projen via the "upgrade-cdklabs-projen-project-types" workflow* - branch: github-actions/upgrade-cdklabs-projen-project-types + *Automatically created by projen via the "upgrade-cdklabs-projen-project-types-main" workflow* + branch: github-actions/upgrade-cdklabs-projen-project-types-main title: "chore(deps): upgrade cdklabs-projen-project-types" labels: auto-approve body: |- @@ -79,7 +83,7 @@ jobs: ------ - *Automatically created by projen via the "upgrade-cdklabs-projen-project-types" workflow* + *Automatically created by projen via the "upgrade-cdklabs-projen-project-types-main" workflow* author: github-actions committer: github-actions signoff: true diff --git a/.github/workflows/upgrade-dev-deps.yml b/.github/workflows/upgrade-dev-deps-main.yml similarity index 92% rename from .github/workflows/upgrade-dev-deps.yml rename to .github/workflows/upgrade-dev-deps-main.yml index c13dfaf..a575373 100644 --- a/.github/workflows/upgrade-dev-deps.yml +++ b/.github/workflows/upgrade-dev-deps-main.yml @@ -1,6 +1,6 @@ # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". -name: upgrade-dev-deps +name: upgrade-dev-deps-main on: workflow_dispatch: {} schedule: @@ -16,10 +16,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - name: Install dependencies run: yarn install --check-files --frozen-lockfile - name: Upgrade dependencies @@ -47,6 +49,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Download patch uses: actions/download-artifact@v4 with: @@ -72,8 +76,8 @@ jobs: ------ - *Automatically created by projen via the "upgrade-dev-deps" workflow* - branch: github-actions/upgrade-dev-deps + *Automatically created by projen via the "upgrade-dev-deps-main" workflow* + branch: github-actions/upgrade-dev-deps-main title: "chore(deps): upgrade dev dependencies" labels: auto-approve body: |- @@ -83,7 +87,7 @@ jobs: ------ - *Automatically created by projen via the "upgrade-dev-deps" workflow* + *Automatically created by projen via the "upgrade-dev-deps-main" workflow* author: github-actions committer: github-actions signoff: true diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade-main.yml similarity index 89% rename from .github/workflows/upgrade.yml rename to .github/workflows/upgrade-main.yml index bed1920..4991041 100644 --- a/.github/workflows/upgrade.yml +++ b/.github/workflows/upgrade-main.yml @@ -1,6 +1,6 @@ # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". -name: upgrade +name: upgrade-main on: workflow_dispatch: {} schedule: @@ -16,10 +16,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "20" - name: Install dependencies run: yarn install --check-files --frozen-lockfile - name: Upgrade dependencies @@ -47,6 +49,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Download patch uses: actions/download-artifact@v4 with: @@ -72,8 +76,8 @@ jobs: ------ - *Automatically created by projen via the "upgrade" workflow* - branch: github-actions/upgrade + *Automatically created by projen via the "upgrade-main" workflow* + branch: github-actions/upgrade-main title: "fix(deps): upgrade dependencies" labels: auto-approve body: |- @@ -83,7 +87,7 @@ jobs: ------ - *Automatically created by projen via the "upgrade" workflow* + *Automatically created by projen via the "upgrade-main" workflow* author: github-actions committer: github-actions signoff: true diff --git a/.gitignore b/.gitignore index f23d743..6f9ca42 100644 --- a/.gitignore +++ b/.gitignore @@ -24,31 +24,54 @@ coverage *.lcov .nyc_output build/Release -node_modules/ jspm_packages/ *.tsbuildinfo .eslintcache *.tgz .yarn-integrity .cache +*.d.ts +*.js +node_modules/ +lib/ +coverage/ +test-reports/ +.cdk.staging +cdk.out +/cdk/bin/ +/cdk/obj/ +.DS_Store +**/.DS_Store +src/canaries/src/package +src/canaries/src/canary.zip +src/outlier-detection/src/scipy +src/outlier-detection/src/outlier-detection.zip +src/outlier-detection/src/scipy-layer.zip +src/monitoring +tsconfig.tsbuildinfo +package-lock.json /test-reports/ junit.xml /coverage/ !/.github/workflows/build.yml +/dist/changelog.md +/dist/version.txt +!/.github/workflows/release.yml !/.github/pull_request_template.md +!/.prettierignore +!/.prettierrc.json !/test/ !/tsconfig.dev.json !/src/ /lib /dist/ -!/.eslintrc.json .jsii tsconfig.json !/API.md .jsii.tabl.json !/rosetta/default.ts-fixture !/.github/workflows/auto-queue.yml -!/.github/workflows/upgrade-cdklabs-projen-project-types.yml -!/.github/workflows/upgrade.yml -!/.github/workflows/upgrade-dev-deps.yml +!/.github/workflows/upgrade-cdklabs-projen-project-types-main.yml +!/.github/workflows/upgrade-main.yml +!/.github/workflows/upgrade-dev-deps-main.yml !/.projenrc.ts diff --git a/.npmignore b/.npmignore index 56ae386..98db8fc 100644 --- a/.npmignore +++ b/.npmignore @@ -1,16 +1,27 @@ - -# Exclude typescript source and config -*.ts -tsconfig.json -*.tsbuildinfo - -# Include javascript files and typescript declarations -!*.js -!*.d.ts - -# Exclude jsii outdir +# ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". +/.projen/ +/test-reports/ +junit.xml +/coverage/ +permissions-backup.acl +/dist/changelog.md +/dist/version.txt +/.mergify.yml +/test/ +/tsconfig.dev.json +/src/ +!/lib/ +!/lib/**/*.js +!/lib/**/*.d.ts dist - -# Include .jsii and .jsii.gz +/tsconfig.json +/.github/ +/.vscode/ +/.idea/ +/.projenrc.js +tsconfig.tsbuildinfo +/.eslintrc.json !.jsii -!.jsii.gz +/.gitattributes +/.projenrc.ts +/projenrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3385eb8 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +# ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..84c85a3 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "overrides": [] +} diff --git a/.projen/deps.json b/.projen/deps.json index 5bbcad9..7537fd9 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -1,10 +1,5 @@ { "dependencies": [ - { - "name": "@stylistic/eslint-plugin", - "version": "^2", - "type": "build" - }, { "name": "@types/jest", "type": "build" @@ -15,13 +10,12 @@ "type": "build" }, { - "name": "@typescript-eslint/eslint-plugin", - "version": "^8", + "name": "aws-cdk-lib", + "version": "2.173.1", "type": "build" }, { - "name": "@typescript-eslint/parser", - "version": "^8", + "name": "cdk-nag", "type": "build" }, { @@ -29,16 +23,13 @@ "type": "build" }, { - "name": "eslint-import-resolver-typescript", + "name": "commit-and-tag-version", + "version": "^12", "type": "build" }, { - "name": "eslint-plugin-import", - "type": "build" - }, - { - "name": "eslint", - "version": "^9", + "name": "constructs", + "version": "10.0.5", "type": "build" }, { @@ -69,7 +60,11 @@ }, { "name": "jsii", - "version": "~5.2", + "version": "~5.5.0", + "type": "build" + }, + { + "name": "prettier", "type": "build" }, { @@ -100,7 +95,7 @@ }, { "name": "aws-cdk-lib", - "version": "^2.1.0", + "version": "^2.173.1", "type": "peer" }, { diff --git a/.projen/files.json b/.projen/files.json index 9e95fe9..90ef6c5 100644 --- a/.projen/files.json +++ b/.projen/files.json @@ -1,16 +1,18 @@ { "files": [ - ".eslintrc.json", ".gitattributes", ".github/pull_request_template.md", ".github/workflows/auto-approve.yml", ".github/workflows/auto-queue.yml", ".github/workflows/build.yml", ".github/workflows/pull-request-lint.yml", - ".github/workflows/upgrade-cdklabs-projen-project-types.yml", - ".github/workflows/upgrade-dev-deps.yml", - ".github/workflows/upgrade.yml", + ".github/workflows/release.yml", + ".github/workflows/upgrade-cdklabs-projen-project-types-main.yml", + ".github/workflows/upgrade-dev-deps-main.yml", + ".github/workflows/upgrade-main.yml", ".gitignore", + ".prettierignore", + ".prettierrc.json", ".projen/deps.json", ".projen/files.json", ".projen/tasks.json", diff --git a/.projen/tasks.json b/.projen/tasks.json index b688159..5035704 100644 --- a/.projen/tasks.json +++ b/.projen/tasks.json @@ -24,6 +24,149 @@ } ] }, + "build-assets": { + "name": "build-assets", + "steps": [ + { + "exec": "export DOCKER_DEFAULT_PLATFORM=\"linux/arm64\"" + }, + { + "spawn": "build-canary-function" + }, + { + "spawn": "build-outlier-detection-function" + }, + { + "spawn": "build-scipy-layer" + }, + { + "spawn": "build-monitoring-layer" + }, + { + "exec": "rm -rf lib/azmapper/src" + }, + { + "exec": "cp -R src/azmapper/src lib/azmapper" + } + ] + }, + "build-canary-function": { + "name": "build-canary-function", + "steps": [ + { + "exec": "rm -rf src/canaries/src/package" + }, + { + "exec": "rm -rf lib/canaries/src" + }, + { + "exec": "rm -f src/canaries/src/canaries.zip" + }, + { + "exec": "mkdir -p src/canaries/src/package" + }, + { + "exec": "mkdir -p lib/canaries/src" + }, + { + "exec": "docker run --rm --platform \"linux/arm64\" --user \"0:0\" --volume \"$PWD/src/canaries/src:/asset-input:delegated\" --volume \"$PWD/src/canaries/src/package:/asset-output:delegated\" --workdir \"/asset-input\" \"public.ecr.aws/sam/build-python3.12\" bash -c \"pip install --no-cache --requirement requirements.txt --target /asset-output && cp --archive --update index.py /asset-output\"" + }, + { + "exec": "cd src/canaries/src/package && zip -r ../canary.zip ." + }, + { + "exec": "cp src/canaries/src/canary.zip lib/canaries/src/canary.zip" + } + ] + }, + "build-monitoring-layer": { + "name": "build-monitoring-layer", + "steps": [ + { + "exec": "rm -rf src/monitoring/src/monitoring" + }, + { + "exec": "rm -f src/monitoring/src/monitoring-layer.zip" + }, + { + "exec": "mkdir -p src/monitoring/src/monitoring" + }, + { + "exec": "mkdir -p lib/monitoring/src" + }, + { + "exec": "pip3 install aws-embedded-metrics aws-xray-sdk --only-binary=:all: --target src/monitoring/src/monitoring/python/lib/python3.12/site-packages --platform manylinux2014_aarch64" + }, + { + "exec": "cd src/monitoring/src/monitoring && zip -r ../monitoring-layer.zip ." + }, + { + "exec": "cp src/monitoring/src/monitoring-layer.zip lib/monitoring/src/monitoring-layer.zip" + } + ] + }, + "build-outlier-detection-function": { + "name": "build-outlier-detection-function", + "steps": [ + { + "exec": "mkdir -p lib/outlier-detection/src" + }, + { + "exec": "rm -f src/outlier-detection/src/outlier-detection.zip" + }, + { + "exec": "zip src/outlier-detection/src/outlier-detection.zip src/outlier-detection/src/index.py" + }, + { + "exec": "cp src/outlier-detection/src/outlier-detection.zip lib/outlier-detection/src/outlier-detection.zip" + } + ] + }, + "build-scipy-layer": { + "name": "build-scipy-layer", + "steps": [ + { + "exec": "rm -rf src/outlier-detection/src/scipy" + }, + { + "exec": "rm -f src/outlier-detection/src/scipy-layer.zip" + }, + { + "exec": "mkdir src/outlier-detection/src/scipy" + }, + { + "exec": "mkdir -p lib/outlier-detection/src" + }, + { + "exec": "pip3 install scipy --only-binary=:all: --target src/outlier-detection/src/scipy/python/lib/python3.12/site-packages --platform manylinux2014_aarch64" + }, + { + "exec": "cd src/outlier-detection/src/scipy && zip -r ../scipy-layer.zip ." + }, + { + "exec": "cp src/outlier-detection/src/scipy-layer.zip lib/outlier-detection/src/scipy-layer.zip" + } + ] + }, + "bump": { + "name": "bump", + "description": "Bumps version based on latest git tag and generates a changelog entry", + "env": { + "OUTFILE": "package.json", + "CHANGELOG": "dist/changelog.md", + "BUMPFILE": "dist/version.txt", + "RELEASETAG": "dist/releasetag.txt", + "RELEASE_TAG_PREFIX": "", + "BUMP_PACKAGE": "commit-and-tag-version@^12", + "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" + }, + "steps": [ + { + "builtin": "release/bump-version" + } + ], + "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" + }, "clobber": { "name": "clobber", "description": "hard resets to HEAD of origin and cleans the local repo", @@ -71,6 +214,9 @@ "steps": [ { "exec": "jsii --silence-warnings=reserved-word" + }, + { + "spawn": "build-assets" } ] }, @@ -104,19 +250,6 @@ } ] }, - "eslint": { - "name": "eslint", - "description": "Runs eslint against the codebase", - "env": { - "ESLINT_USE_FLAT_CONFIG": "false" - }, - "steps": [ - { - "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", - "receiveArgs": true - } - ] - }, "install": { "name": "install", "description": "Install project dependencies and update lockfile (non-frozen)", @@ -244,6 +377,9 @@ }, { "spawn": "rosetta:extract" + }, + { + "exec": "npx awslint" } ] }, @@ -255,6 +391,32 @@ "name": "pre-compile", "description": "Prepare the project for compilation" }, + "release": { + "name": "release", + "description": "Prepare a release from \"main\" branch", + "env": { + "RELEASE": "true", + "MAJOR": "0", + "PRERELEASE": "alpha" + }, + "steps": [ + { + "exec": "rm -fr dist" + }, + { + "spawn": "bump" + }, + { + "spawn": "build" + }, + { + "spawn": "unbump" + }, + { + "exec": "git diff --ignore-space-at-eol --exit-code ':!tsconfig.json'" + } + ] + }, "rosetta:extract": { "name": "rosetta:extract", "description": "Test rosetta extract", @@ -272,9 +434,6 @@ "exec": "jest --passWithNoTests --updateSnapshot", "receiveArgs": true }, - { - "spawn": "eslint" - }, { "spawn": "integ" } @@ -289,6 +448,24 @@ } ] }, + "unbump": { + "name": "unbump", + "description": "Restores version to 0.0.0", + "env": { + "OUTFILE": "package.json", + "CHANGELOG": "dist/changelog.md", + "BUMPFILE": "dist/version.txt", + "RELEASETAG": "dist/releasetag.txt", + "RELEASE_TAG_PREFIX": "", + "BUMP_PACKAGE": "commit-and-tag-version@^12", + "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" + }, + "steps": [ + { + "builtin": "release/reset-version" + } + ] + }, "upgrade": { "name": "upgrade", "description": "upgrade dependencies", @@ -333,13 +510,13 @@ }, "steps": [ { - "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=@types/jest,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,jsii-pacmak,jsii-rosetta,ts-jest,ts-node,typescript" + "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=@types/jest,cdk-nag,jest,jsii-diff,jsii-pacmak,jsii-rosetta,prettier,ts-jest,ts-node,typescript" }, { "exec": "yarn install --check-files" }, { - "exec": "yarn upgrade @stylistic/eslint-plugin @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii ts-jest ts-node typescript @aws-cdk/integ-runner @aws-cdk/integ-tests-alpha" + "exec": "yarn upgrade @types/jest @types/node aws-cdk-lib cdk-nag commit-and-tag-version constructs jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii prettier ts-jest ts-node typescript @aws-cdk/integ-runner @aws-cdk/integ-tests-alpha" }, { "exec": "npx projen" diff --git a/.projenrc.ts b/.projenrc.ts index f66c80b..b1e816e 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1,14 +1,267 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 import { CdklabsConstructLibrary } from 'cdklabs-projen-project-types'; -import { javascript } from 'projen'; -const project = new CdklabsConstructLibrary({ +import { JsonPatch, javascript } from 'projen'; +import { UpgradeDependenciesSchedule } from 'projen/lib/javascript'; + +const project = new CdklabsConstructLibrary ({ author: 'AWS', authorAddress: 'aws-cdk-dev@amazon.com', - cdkVersion: '2.1.0', + homepage: 'https://github.com/cdklabs/cdk-multi-az-observability', + repositoryUrl: 'https://github.com/cdklabs/cdk-multi-az-observability/', + cdkVersion: '2.173.1', defaultReleaseBranch: 'main', - devDeps: ['cdklabs-projen-project-types'], - name: 'cdk-multi-az-observability', + jsiiVersion: '~5.5.0', + name: '@cdklabs/multi-az-observability', + devDeps: [ + 'cdklabs-projen-project-types', + 'aws-cdk-lib', + 'cdk-nag' + ], npmAccess: javascript.NpmAccess.PUBLIC, + license: 'Apache-2.0', + githubOptions: { + mergify: true, + }, + autoMerge: true, + autoApproveUpgrades: true, + autoApproveOptions: { + allowedUsernames: ['cdklabs-automation'], + }, + eslint: false, + eslintOptions: { + dirs: [ + '**/*.ts', + '**/*.tsx', + ], + fileExtensions: [], + }, + prettier: true, + prerelease: 'alpha', projenrcTs: true, - release: false, + description: + 'A CDK construct for implementing multi-AZ observability to detect single AZ impairments', + dependabot: false, + buildWorkflow: true, + depsUpgrade: true, + release: true, + npmProvenance: false, + releaseToNpm: true, + majorVersion: 0, + depsUpgradeOptions: { + workflowOptions: { + labels: ['auto-approve', 'auto-merge'], + schedule: UpgradeDependenciesSchedule.WEEKLY, + }, + }, + workflowNodeVersion: '20', + workflowRunsOn: [ + 'codebuild-Arm64CdkLabsGithubRunner-${{ github.run_id }}-${{ github.run_attempt }}', + ], + keywords: [ + 'cdk', + 'aws-cdk', + 'cloudwatch', + 'observability', + 'monitoring', + 'resilience', + 'multi-az', + ], + gitignore: [ + '*.d.ts', + '*.js', + 'node_modules/', + 'lib/', + 'coverage/', + 'test-reports/', + '.cdk.staging', + 'cdk.out', + '/cdk/bin/', + '/cdk/obj/', + '.DS_Store', + '**/.DS_Store', + 'src/canaries/src/package', + 'src/canaries/src/canary.zip', + 'src/outlier-detection/src/scipy', + 'src/outlier-detection/src/outlier-detection.zip', + 'src/outlier-detection/src/scipy-layer.zip', + 'src/monitoring', + 'tsconfig.tsbuildinfo', + 'package-lock.json', + '.jsii', + 'tsconfig.json', + ], + publishToNuget: { + dotNetNamespace: 'Cdklabs.MultiAZObservability', + packageId: 'Cdklabs.MultiAZObservability', + }, + publishToGo: { + // The repo where the module will be published + moduleName: 'github.com/cdklabs/cdk-multi-az-observability-go', + // The name of the package which will become the directory in + // the repo where the go module files are placed + packageName: 'multi-az-observability', + }, + publishToPypi: { + distName: 'cdklabs.multi-az-observability', + module: 'cdklabs.multi_az_observability', + }, + publishToMaven: { + mavenGroupId: 'io.github.cdklabs', + javaPackage: 'io.github.cdklabs.multiazobservability', + mavenArtifactId: 'cdk-multi-az-observability', + }, + jest: true, + jestOptions: { + jestConfig: { + roots: ['/test'], + testMatch: ['**/*.test.ts'], + }, + }, }); -project.synth(); \ No newline at end of file + +project.tasks.addTask('build-monitoring-layer', { + steps: [ + { + exec: 'rm -rf src/monitoring/src/monitoring', + }, + { + exec: 'rm -f src/monitoring/src/monitoring-layer.zip', + }, + { + exec: 'mkdir -p src/monitoring/src/monitoring', + }, + { + exec: 'mkdir -p lib/monitoring/src', + }, + { + exec: 'pip3 install aws-embedded-metrics aws-xray-sdk --only-binary=:all: --target src/monitoring/src/monitoring/python/lib/python3.12/site-packages --platform manylinux2014_aarch64', + }, + { + exec: 'cd src/monitoring/src/monitoring && zip -r ../monitoring-layer.zip .', + }, + { + exec: 'cp src/monitoring/src/monitoring-layer.zip lib/monitoring/src/monitoring-layer.zip', + }, + ], +}); + +project.tasks.addTask('build-canary-function', { + steps: [ + { + exec: 'rm -rf src/canaries/src/package', + }, + { + exec: 'rm -rf lib/canaries/src', + }, + { + exec: 'rm -f src/canaries/src/canaries.zip', + }, + { + exec: 'mkdir -p src/canaries/src/package', + }, + { + exec: 'mkdir -p lib/canaries/src', + }, + { + exec: 'docker run --rm --platform "linux/arm64" --user "0:0" --volume "$PWD/src/canaries/src:/asset-input:delegated" --volume "$PWD/src/canaries/src/package:/asset-output:delegated" --workdir "/asset-input" "public.ecr.aws/sam/build-python3.12" bash -c "pip install --no-cache --requirement requirements.txt --target /asset-output && cp --archive --update index.py /asset-output"', + }, + { + exec: 'cd src/canaries/src/package && zip -r ../canary.zip .', + }, + { + exec: 'cp src/canaries/src/canary.zip lib/canaries/src/canary.zip', + }, + ], +}); + +project.tasks.addTask('build-scipy-layer', { + steps: [ + { + exec: 'rm -rf src/outlier-detection/src/scipy', + }, + { + exec: 'rm -f src/outlier-detection/src/scipy-layer.zip', + }, + { + exec: 'mkdir src/outlier-detection/src/scipy', + }, + { + exec: 'mkdir -p lib/outlier-detection/src', + }, + { + exec: 'pip3 install scipy --only-binary=:all: --target src/outlier-detection/src/scipy/python/lib/python3.12/site-packages --platform manylinux2014_aarch64', + }, + { + exec: 'cd src/outlier-detection/src/scipy && zip -r ../scipy-layer.zip .', + }, + { + exec: 'cp src/outlier-detection/src/scipy-layer.zip lib/outlier-detection/src/scipy-layer.zip', + }, + ], +}); + +project.tasks.addTask('build-outlier-detection-function', { + steps: [ + { + exec: 'mkdir -p lib/outlier-detection/src', + }, + { + exec: 'rm -f src/outlier-detection/src/outlier-detection.zip', + }, + { + exec: 'zip src/outlier-detection/src/outlier-detection.zip src/outlier-detection/src/index.py', + }, + { + exec: 'cp src/outlier-detection/src/outlier-detection.zip lib/outlier-detection/src/outlier-detection.zip', + }, + ], +}); + +const buildAssets = project.tasks.addTask('build-assets', { + steps: [ + { + exec: 'export DOCKER_DEFAULT_PLATFORM="linux/arm64"', + }, + { + spawn: 'build-canary-function', + }, + { + spawn: 'build-outlier-detection-function', + }, + { + spawn: 'build-scipy-layer', + }, + { + spawn: 'build-monitoring-layer', + }, + { + exec: 'rm -rf lib/azmapper/src', + }, + { + exec: 'cp -R src/azmapper/src lib/azmapper', + }, + ], +}); + +project.tasks.tryFind('compile')?.spawn(buildAssets); +project.tasks.tryFind('post-compile')?.exec('npx awslint'); + +// tsconfig.json gets the exclude list updated and isn't tracked +project.tasks.tryFind('release')?.updateStep(4, { + exec: "git diff --ignore-space-at-eol --exit-code ':!tsconfig.json'", +}); + +/*project.addFields({ + version: '0.0.1-alpha.1', +});*/ + +project.github + ?.tryFindWorkflow('release') + ?.file?.patch(JsonPatch.remove('/jobs/release_pypi/steps/1')); +project.synth(); + +project.github + ?.tryFindWorkflow('build') + ?.file?.patch(JsonPatch.remove('/jobs/package-python/steps/1')); +project.synth(); diff --git a/API.md b/API.md index 3ee177d..78470d1 100644 --- a/API.md +++ b/API.md @@ -1,21 +1,306 @@ # API Reference +## Constructs +### AvailabilityZoneMapper -## Classes +- *Implements:* IAvailabilityZoneMapper + +A construct that allows you to map AZ names to ids and back. + +#### Initializers + +```typescript +import { AvailabilityZoneMapper } from '@cdklabs/multi-az-observability' + +new AvailabilityZoneMapper(scope: Construct, id: string, props?: AvailabilityZoneMapperProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| scope | constructs.Construct | *No description.* | +| id | string | *No description.* | +| props | AvailabilityZoneMapperProps | *No description.* | + +--- + +##### `scope`Required + +- *Type:* constructs.Construct + +--- + +##### `id`Required + +- *Type:* string + +--- + +##### `props`Optional + +- *Type:* AvailabilityZoneMapperProps + +--- + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| toString | Returns a string representation of this construct. | +| allAvailabilityZoneIdsAsArray | Returns a reference that can be cast to a string array with all of the Availability Zone Ids. | +| allAvailabilityZoneIdsAsCommaDelimitedList | Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. | +| allAvailabilityZoneNamesAsCommaDelimitedList | Gets all of the Availability Zone names in this Region as a comma delimited list. | +| availabilityZoneId | Gets the Availability Zone Id for the given Availability Zone Name in this account. | +| availabilityZoneIdFromAvailabilityZoneLetter | Given a letter like "f" or "a", returns the Availability Zone Id for that Availability Zone name in this account. | +| availabilityZoneIdsAsArray | Returns an array for Availability Zone Ids for the supplied Availability Zone names, they are returned in the same order the names were provided. | +| availabilityZoneIdsAsCommaDelimitedList | Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. | +| availabilityZoneName | Gets the Availability Zone Name for the given Availability Zone Id in this account. | +| regionPrefixForAvailabilityZoneIds | Gets the prefix for the region used with Availability Zone Ids, for example in us-east-1, this returns "use1". | + +--- + +##### `toString` + +```typescript +public toString(): string +``` + +Returns a string representation of this construct. + +##### `allAvailabilityZoneIdsAsArray` + +```typescript +public allAvailabilityZoneIdsAsArray(): Reference +``` + +Returns a reference that can be cast to a string array with all of the Availability Zone Ids. + +##### `allAvailabilityZoneIdsAsCommaDelimitedList` + +```typescript +public allAvailabilityZoneIdsAsCommaDelimitedList(): string +``` + +Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. + +You can use this string with Fn.Select(x, Fn.Split(",", azs)) to +get a specific Availability Zone Id + +##### `allAvailabilityZoneNamesAsCommaDelimitedList` + +```typescript +public allAvailabilityZoneNamesAsCommaDelimitedList(): string +``` + +Gets all of the Availability Zone names in this Region as a comma delimited list. + +You can use this string with Fn.Select(x, Fn.Split(",", azs)) to +get a specific Availability Zone Name + +##### `availabilityZoneId` + +```typescript +public availabilityZoneId(availabilityZoneName: string): string +``` + +Gets the Availability Zone Id for the given Availability Zone Name in this account. + +###### `availabilityZoneName`Required + +- *Type:* string + +--- + +##### `availabilityZoneIdFromAvailabilityZoneLetter` + +```typescript +public availabilityZoneIdFromAvailabilityZoneLetter(letter: string): string +``` + +Given a letter like "f" or "a", returns the Availability Zone Id for that Availability Zone name in this account. + +###### `letter`Required + +- *Type:* string + +--- + +##### `availabilityZoneIdsAsArray` + +```typescript +public availabilityZoneIdsAsArray(availabilityZoneNames: string[]): string[] +``` + +Returns an array for Availability Zone Ids for the supplied Availability Zone names, they are returned in the same order the names were provided. + +###### `availabilityZoneNames`Required + +- *Type:* string[] + +--- + +##### `availabilityZoneIdsAsCommaDelimitedList` + +```typescript +public availabilityZoneIdsAsCommaDelimitedList(availabilityZoneNames: string[]): string +``` + +Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. + +You can use this string with Fn.Select(x, Fn.Split(",", azs)) to +get a specific Availability Zone Id + +###### `availabilityZoneNames`Required + +- *Type:* string[] + +--- + +##### `availabilityZoneName` + +```typescript +public availabilityZoneName(availabilityZoneId: string): string +``` + +Gets the Availability Zone Name for the given Availability Zone Id in this account. + +###### `availabilityZoneId`Required + +- *Type:* string + +--- + +##### `regionPrefixForAvailabilityZoneIds` + +```typescript +public regionPrefixForAvailabilityZoneIds(): string +``` + +Gets the prefix for the region used with Availability Zone Ids, for example in us-east-1, this returns "use1". + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| isConstruct | Checks if `x` is a construct. | + +--- + +##### ~~`isConstruct`~~ + +```typescript +import { AvailabilityZoneMapper } from '@cdklabs/multi-az-observability' + +AvailabilityZoneMapper.isConstruct(x: any) +``` + +Checks if `x` is a construct. + +###### `x`Required + +- *Type:* any + +Any object. + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| function | aws-cdk-lib.aws_lambda.IFunction | The function that does the mapping. | +| logGroup | aws-cdk-lib.aws_logs.ILogGroup | The log group for the function's logs. | +| mapper | aws-cdk-lib.CustomResource | The custom resource that can be referenced to use Fn::GetAtt functions on to retrieve availability zone names and ids. | + +--- + +##### `node`Required -### Hello +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `function`Required + +```typescript +public readonly function: IFunction; +``` + +- *Type:* aws-cdk-lib.aws_lambda.IFunction + +The function that does the mapping. + +--- + +##### `logGroup`Required + +```typescript +public readonly logGroup: ILogGroup; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup + +The log group for the function's logs. + +--- + +##### `mapper`Required + +```typescript +public readonly mapper: CustomResource; +``` + +- *Type:* aws-cdk-lib.CustomResource + +The custom resource that can be referenced to use Fn::GetAtt functions on to retrieve availability zone names and ids. + +--- + + +### BasicServiceMultiAZObservability -#### Initializers +- *Implements:* IBasicServiceMultiAZObservability + +Basic observability for a service using metrics from ALBs and NAT Gateways. + +#### Initializers ```typescript -import { Hello } from 'cdk-multi-az-observability' +import { BasicServiceMultiAZObservability } from '@cdklabs/multi-az-observability' -new Hello() +new BasicServiceMultiAZObservability(scope: Construct, id: string, props: BasicServiceMultiAZObservabilityProps) ``` | **Name** | **Type** | **Description** | | --- | --- | --- | +| scope | constructs.Construct | *No description.* | +| id | string | *No description.* | +| props | BasicServiceMultiAZObservabilityProps | *No description.* | + +--- + +##### `scope`Required + +- *Type:* constructs.Construct + +--- + +##### `id`Required + +- *Type:* string + +--- + +##### `props`Required + +- *Type:* BasicServiceMultiAZObservabilityProps --- @@ -23,17 +308,4812 @@ new Hello() | **Name** | **Description** | | --- | --- | -| sayHello | *No description.* | +| toString | Returns a string representation of this construct. | + +--- + +##### `toString` + +```typescript +public toString(): string +``` + +Returns a string representation of this construct. + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| isConstruct | Checks if `x` is a construct. | + +--- + +##### ~~`isConstruct`~~ + +```typescript +import { BasicServiceMultiAZObservability } from '@cdklabs/multi-az-observability' + +BasicServiceMultiAZObservability.isConstruct(x: any) +``` + +Checks if `x` is a construct. + +###### `x`Required + +- *Type:* any + +Any object. + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| aggregateZonalIsolatedImpactAlarms | {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} | The alarms indicating if an AZ has isolated impact from either ALB or NAT GW metrics. | +| serviceName | string | The name of the service. | +| albZonalIsolatedImpactAlarms | {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} | The alarms indicating if an AZ is an outlier for ALB faults and has isolated impact. | +| applicationLoadBalancers | aws-cdk-lib.aws_elasticloadbalancingv2.IApplicationLoadBalancer[] | The application load balancers being used by the service. | +| dashboard | aws-cdk-lib.aws_cloudwatch.Dashboard | The dashboard that is optionally created. | +| natGateways | {[ key: string ]: aws-cdk-lib.aws_ec2.CfnNatGateway[]} | The NAT Gateways being used in the service, each set of NAT Gateways are keyed by their Availability Zone Id. | +| natGWZonalIsolatedImpactAlarms | {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} | The alarms indicating if an AZ is an outlier for NAT GW packet loss and has isolated impact. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `aggregateZonalIsolatedImpactAlarms`Required + +```typescript +public readonly aggregateZonalIsolatedImpactAlarms: {[ key: string ]: IAlarm}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} + +The alarms indicating if an AZ has isolated impact from either ALB or NAT GW metrics. + +--- + +##### `serviceName`Required + +```typescript +public readonly serviceName: string; +``` + +- *Type:* string + +The name of the service. + +--- + +##### `albZonalIsolatedImpactAlarms`Optional + +```typescript +public readonly albZonalIsolatedImpactAlarms: {[ key: string ]: IAlarm}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} + +The alarms indicating if an AZ is an outlier for ALB faults and has isolated impact. + +--- + +##### `applicationLoadBalancers`Optional + +```typescript +public readonly applicationLoadBalancers: IApplicationLoadBalancer[]; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.IApplicationLoadBalancer[] + +The application load balancers being used by the service. + +--- + +##### `dashboard`Optional + +```typescript +public readonly dashboard: Dashboard; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Dashboard + +The dashboard that is optionally created. --- -##### `sayHello` +##### `natGateways`Optional ```typescript -public sayHello(): string +public readonly natGateways: {[ key: string ]: CfnNatGateway[]}; ``` +- *Type:* {[ key: string ]: aws-cdk-lib.aws_ec2.CfnNatGateway[]} + +The NAT Gateways being used in the service, each set of NAT Gateways are keyed by their Availability Zone Id. + +--- + +##### `natGWZonalIsolatedImpactAlarms`Optional +```typescript +public readonly natGWZonalIsolatedImpactAlarms: {[ key: string ]: IAlarm}; +``` +- *Type:* {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} +The alarms indicating if an AZ is an outlier for NAT GW packet loss and has isolated impact. + +--- + + +### InstrumentedServiceMultiAZObservability + +- *Implements:* IInstrumentedServiceMultiAZObservability + +An service that implements its own instrumentation to record availability and latency metrics that can be used to create alarms, rules, and dashboards from. + +#### Initializers + +```typescript +import { InstrumentedServiceMultiAZObservability } from '@cdklabs/multi-az-observability' + +new InstrumentedServiceMultiAZObservability(scope: Construct, id: string, props: InstrumentedServiceMultiAZObservabilityProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| scope | constructs.Construct | *No description.* | +| id | string | *No description.* | +| props | InstrumentedServiceMultiAZObservabilityProps | *No description.* | + +--- + +##### `scope`Required + +- *Type:* constructs.Construct + +--- + +##### `id`Required + +- *Type:* string + +--- + +##### `props`Required + +- *Type:* InstrumentedServiceMultiAZObservabilityProps + +--- + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| toString | Returns a string representation of this construct. | + +--- + +##### `toString` + +```typescript +public toString(): string +``` + +Returns a string representation of this construct. + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| isConstruct | Checks if `x` is a construct. | + +--- + +##### ~~`isConstruct`~~ + +```typescript +import { InstrumentedServiceMultiAZObservability } from '@cdklabs/multi-az-observability' + +InstrumentedServiceMultiAZObservability.isConstruct(x: any) +``` + +Checks if `x` is a construct. + +###### `x`Required + +- *Type:* any + +Any object. + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| perOperationZonalImpactAlarms | {[ key: string ]: {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm}} | Index into the dictionary by operation name, then by Availability Zone Id to get the alarms that indicate an AZ shows isolated impact from availability or latency as seen by either the server-side or canary. | +| serviceAlarms | IServiceAlarmsAndRules | The alarms and rules for the overall service. | +| canaryLogGroup | aws-cdk-lib.aws_logs.ILogGroup | If the service is configured to have canary tests created, this will be the log group where the canary's logs are stored. | +| operationDashboards | aws-cdk-lib.aws_cloudwatch.Dashboard[] | The dashboards for each operation. | +| serviceDashboard | aws-cdk-lib.aws_cloudwatch.Dashboard | The service level dashboard. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `perOperationZonalImpactAlarms`Required + +```typescript +public readonly perOperationZonalImpactAlarms: {[ key: string ]: {[ key: string ]: IAlarm}}; +``` + +- *Type:* {[ key: string ]: {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm}} + +Index into the dictionary by operation name, then by Availability Zone Id to get the alarms that indicate an AZ shows isolated impact from availability or latency as seen by either the server-side or canary. + +These are the alarms +you would want to use to trigger automation to evacuate an AZ. + +--- + +##### `serviceAlarms`Required + +```typescript +public readonly serviceAlarms: IServiceAlarmsAndRules; +``` + +- *Type:* IServiceAlarmsAndRules + +The alarms and rules for the overall service. + +--- + +##### `canaryLogGroup`Optional + +```typescript +public readonly canaryLogGroup: ILogGroup; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup +- *Default:* No log group is created if the canary is not requested. + +If the service is configured to have canary tests created, this will be the log group where the canary's logs are stored. + +--- + +##### `operationDashboards`Optional + +```typescript +public readonly operationDashboards: Dashboard[]; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Dashboard[] + +The dashboards for each operation. + +--- + +##### `serviceDashboard`Optional + +```typescript +public readonly serviceDashboard: Dashboard; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Dashboard + +The service level dashboard. + +--- + + +## Structs + +### AddCanaryTestProps + +The props for requesting a canary be made for an operation. + +#### Initializer + +```typescript +import { AddCanaryTestProps } from '@cdklabs/multi-az-observability' + +const addCanaryTestProps: AddCanaryTestProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| loadBalancer | aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 | The load balancer that will be tested against. | +| requestCount | number | The number of requests to send on each test. | +| schedule | string | A schedule expression. | +| headers | {[ key: string ]: string} | Any headers to include. | +| httpMethods | string[] | Defining this will override the methods defined in the operation and will use these instead. | +| ignoreTlsErrors | boolean | Whether to ignore TLS validation errors. | +| networkConfiguration | NetworkConfigurationProps | The VPC network configuration. | +| postData | string | Data to supply in a POST, PUT, or PATCH operation. | +| regionalRequestCount | number | Specifies a separate number of request to send to the regional endpoint. | +| timeout | aws-cdk-lib.Duration | The timeout for each individual HTTP request. | + +--- + +##### `loadBalancer`Required + +```typescript +public readonly loadBalancer: ILoadBalancerV2; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 + +The load balancer that will be tested against. + +--- + +##### `requestCount`Required + +```typescript +public readonly requestCount: number; +``` + +- *Type:* number + +The number of requests to send on each test. + +--- + +##### `schedule`Required + +```typescript +public readonly schedule: string; +``` + +- *Type:* string + +A schedule expression. + +--- + +##### `headers`Optional + +```typescript +public readonly headers: {[ key: string ]: string}; +``` + +- *Type:* {[ key: string ]: string} +- *Default:* No additional headers are added to the requests + +Any headers to include. + +--- + +##### `httpMethods`Optional + +```typescript +public readonly httpMethods: string[]; +``` + +- *Type:* string[] +- *Default:* The operation's defined HTTP methods will be used to conduct the canary tests + +Defining this will override the methods defined in the operation and will use these instead. + +--- + +##### `ignoreTlsErrors`Optional + +```typescript +public readonly ignoreTlsErrors: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Whether to ignore TLS validation errors. + +--- + +##### `networkConfiguration`Optional + +```typescript +public readonly networkConfiguration: NetworkConfigurationProps; +``` + +- *Type:* NetworkConfigurationProps +- *Default:* The Lambda function is not run in a VPC + +The VPC network configuration. + +--- + +##### `postData`Optional + +```typescript +public readonly postData: string; +``` + +- *Type:* string +- *Default:* No data is sent in a POST, PUT, or PATCH request + +Data to supply in a POST, PUT, or PATCH operation. + +--- + +##### `regionalRequestCount`Optional + +```typescript +public readonly regionalRequestCount: number; +``` + +- *Type:* number +- *Default:* The same number of requests specified by the requestCount property is used. + +Specifies a separate number of request to send to the regional endpoint. + +--- + +##### `timeout`Optional + +```typescript +public readonly timeout: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* Defaults to 2 seconds + +The timeout for each individual HTTP request. + +--- + +### AvailabilityZoneMapperProps + +Properties for the AZ mapper. + +#### Initializer + +```typescript +import { AvailabilityZoneMapperProps } from '@cdklabs/multi-az-observability' + +const availabilityZoneMapperProps: AvailabilityZoneMapperProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneNames | string[] | The currently in use Availability Zone names which constrains the list of AZ IDs that are returned. | + +--- + +##### `availabilityZoneNames`Optional + +```typescript +public readonly availabilityZoneNames: string[]; +``` + +- *Type:* string[] +- *Default:* No names are provided and the mapper returns all AZs in the region in its lists + +The currently in use Availability Zone names which constrains the list of AZ IDs that are returned. + +--- + +### BasicServiceMultiAZObservabilityProps + +Properties for creating a basic service. + +#### Initializer + +```typescript +import { BasicServiceMultiAZObservabilityProps } from '@cdklabs/multi-az-observability' + +const basicServiceMultiAZObservabilityProps: BasicServiceMultiAZObservabilityProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| createDashboard | boolean | Whether to create a dashboard displaying the metrics and alarms. | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| outlierDetectionAlgorithm | OutlierDetectionAlgorithm | The algorithm to use for performing outlier detection. | +| period | aws-cdk-lib.Duration | The period to evaluate metrics. | +| serviceName | string | The service's name. | +| applicationLoadBalancers | aws-cdk-lib.aws_elasticloadbalancingv2.IApplicationLoadBalancer[] | The application load balancers being used by the service. | +| assetsBucketParameterName | string | If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket whose name is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. | +| assetsBucketPrefixParameterName | string | If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket that uses a prefix that is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. | +| faultCountPercentageThreshold | number | The percentage of faults for a single ALB to consider an AZ to be unhealthy, this should align with your availability goal. | +| interval | aws-cdk-lib.Duration | Dashboard interval. | +| natGateways | {[ key: string ]: aws-cdk-lib.aws_ec2.CfnNatGateway[]} | (Optional) A map of Availability Zone name to the NAT Gateways in that AZ. | +| outlierThreshold | number | The outlier threshold for determining if an AZ is an outlier for latency or faults. | +| packetLossImpactPercentageThreshold | number | The amount of packet loss in a NAT GW to determine if an AZ is actually impacted, recommendation is 0.01%. | + +--- + +##### `createDashboard`Required + +```typescript +public readonly createDashboard: boolean; +``` + +- *Type:* boolean + +Whether to create a dashboard displaying the metrics and alarms. + +--- + +##### `datapointsToAlarm`Required + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Required + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `outlierDetectionAlgorithm`Required + +```typescript +public readonly outlierDetectionAlgorithm: OutlierDetectionAlgorithm; +``` + +- *Type:* OutlierDetectionAlgorithm + +The algorithm to use for performing outlier detection. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period to evaluate metrics. + +--- + +##### `serviceName`Required + +```typescript +public readonly serviceName: string; +``` + +- *Type:* string + +The service's name. + +--- + +##### `applicationLoadBalancers`Optional + +```typescript +public readonly applicationLoadBalancers: IApplicationLoadBalancer[]; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.IApplicationLoadBalancer[] +- *Default:* No alarms for ALBs will be created + +The application load balancers being used by the service. + +--- + +##### `assetsBucketParameterName`Optional + +```typescript +public readonly assetsBucketParameterName: string; +``` + +- *Type:* string +- *Default:* The assets will be uploaded to the default defined asset location. + +If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket whose name is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. + +It will override the bucket location CDK provides by +default for bundled assets. The stack containing this contruct needs +to have a parameter defined that uses this name. The underlying +stacks in this construct that deploy assets will copy the parent stack's +value for this property. + +--- + +##### `assetsBucketPrefixParameterName`Optional + +```typescript +public readonly assetsBucketPrefixParameterName: string; +``` + +- *Type:* string +- *Default:* No object prefix will be added to your custom assets location. However, if you have overridden something like the 'BucketPrefix' property in your stack synthesizer with a variable like "${AssetsBucketPrefix", you will need to define this property so it doesn't cause a reference error even if the prefix value is blank. + +If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket that uses a prefix that is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. + +It will override the bucket prefix CDK provides by +default for bundled assets. This property only takes effect if you +defined the assetsBucketParameterName. The stack containing this contruct needs +to have a parameter defined that uses this name. The underlying +stacks in this construct that deploy assets will copy the parent stack's +value for this property. + +--- + +##### `faultCountPercentageThreshold`Optional + +```typescript +public readonly faultCountPercentageThreshold: number; +``` + +- *Type:* number +- *Default:* 5 (as in 5%) + +The percentage of faults for a single ALB to consider an AZ to be unhealthy, this should align with your availability goal. + +For example +1% or 5%. + +--- + +##### `interval`Optional + +```typescript +public readonly interval: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* 1 hour + +Dashboard interval. + +--- + +##### `natGateways`Optional + +```typescript +public readonly natGateways: {[ key: string ]: CfnNatGateway[]}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_ec2.CfnNatGateway[]} +- *Default:* No alarms for NAT Gateways will be created + +(Optional) A map of Availability Zone name to the NAT Gateways in that AZ. + +--- + +##### `outlierThreshold`Optional + +```typescript +public readonly outlierThreshold: number; +``` + +- *Type:* number +- *Default:* Depends on the outlier detection algorithm selected + +The outlier threshold for determining if an AZ is an outlier for latency or faults. + +This number is interpreted +differently for different outlier algorithms. When used with +STATIC, the number should be between 0 and 1 to represent the +percentage of errors (like .7) that an AZ must be responsible +for to be considered an outlier. When used with CHI_SQUARED, it +represents the p value that indicates statistical significance, like +0.05 which means the skew has less than or equal to a 5% chance of +occuring. When used with Z_SCORE it indicates how many standard +deviations to evaluate for an AZ being an outlier, typically 3 is +standard for Z_SCORE. + +Standard defaults based on the outlier detection algorithm: +STATIC: 0.7 +CHI_SQUARED: 0.05 +Z_SCORE: 2 +IQR: 1.5 +MAD: 3 + +--- + +##### `packetLossImpactPercentageThreshold`Optional + +```typescript +public readonly packetLossImpactPercentageThreshold: number; +``` + +- *Type:* number +- *Default:* 0.01 (as in 0.01%) + +The amount of packet loss in a NAT GW to determine if an AZ is actually impacted, recommendation is 0.01%. + +--- + +### CanaryMetricProps + +Properties for canary metrics in an operation. + +#### Initializer + +```typescript +import { CanaryMetricProps } from '@cdklabs/multi-az-observability' + +const canaryMetricProps: CanaryMetricProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| canaryAvailabilityMetricDetails | IOperationMetricDetails | The canary availability metric details. | +| canaryLatencyMetricDetails | IOperationMetricDetails | The canary latency metric details. | + +--- + +##### `canaryAvailabilityMetricDetails`Required + +```typescript +public readonly canaryAvailabilityMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The canary availability metric details. + +--- + +##### `canaryLatencyMetricDetails`Required + +```typescript +public readonly canaryLatencyMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The canary latency metric details. + +--- + +### CanaryTestMetricsOverrideProps + +The properties for creating an override. + +#### Initializer + +```typescript +import { CanaryTestMetricsOverrideProps } from '@cdklabs/multi-az-observability' + +const canaryTestMetricsOverrideProps: CanaryTestMetricsOverrideProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | + +--- + +##### `alarmStatistic`Optional + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string +- *Default:* This property will use the default defined for the service + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Optional + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number +- *Default:* This property will use the default defined for the service + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Optional + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number +- *Default:* This property will use the default defined for the service + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Optional + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number +- *Default:* This property will use the default defined for the service + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `period`Optional + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* This property will use the default defined for the service + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Optional + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number +- *Default:* This property will use the default defined for the service + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +### ContributorInsightRuleDetailsProps + +The contributor insight rule details properties. + +#### Initializer + +```typescript +import { ContributorInsightRuleDetailsProps } from '@cdklabs/multi-az-observability' + +const contributorInsightRuleDetailsProps: ContributorInsightRuleDetailsProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneIdJsonPath | string | The path in the log files to the field that identifies the Availability Zone Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would have a path of $.AZ-ID. | +| faultMetricJsonPath | string | The path in the log files to the field that identifies if the response resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault. | +| instanceIdJsonPath | string | The JSON path to the instance id field in the log files, only required for server-side rules. | +| logGroups | aws-cdk-lib.aws_logs.ILogGroup[] | The log groups where CloudWatch logs for the operation are located. | +| operationNameJsonPath | string | The path in the log files to the field that identifies the operation the log file is for. | +| successLatencyMetricJsonPath | string | The path in the log files to the field that indicates the latency for the response. | + +--- + +##### `availabilityZoneIdJsonPath`Required + +```typescript +public readonly availabilityZoneIdJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies the Availability Zone Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would have a path of $.AZ-ID. + +--- + +##### `faultMetricJsonPath`Required + +```typescript +public readonly faultMetricJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies if the response resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault. + +--- + +##### `instanceIdJsonPath`Required + +```typescript +public readonly instanceIdJsonPath: string; +``` + +- *Type:* string + +The JSON path to the instance id field in the log files, only required for server-side rules. + +--- + +##### `logGroups`Required + +```typescript +public readonly logGroups: ILogGroup[]; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup[] + +The log groups where CloudWatch logs for the operation are located. + +If +this is not provided, Contributor Insight rules cannot be created. + +--- + +##### `operationNameJsonPath`Required + +```typescript +public readonly operationNameJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies the operation the log file is for. + +--- + +##### `successLatencyMetricJsonPath`Required + +```typescript +public readonly successLatencyMetricJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that indicates the latency for the response. + +This could either be success latency or fault +latency depending on the alarms and rules you are creating. + +--- + +### InstrumentedServiceMultiAZObservabilityProps + +The properties for adding alarms and dashboards for an instrumented service. + +#### Initializer + +```typescript +import { InstrumentedServiceMultiAZObservabilityProps } from '@cdklabs/multi-az-observability' + +const instrumentedServiceMultiAZObservabilityProps: InstrumentedServiceMultiAZObservabilityProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| outlierDetectionAlgorithm | OutlierDetectionAlgorithm | The algorithm to use for performing outlier detection. | +| service | IService | The service that the alarms and dashboards are being crated for. | +| assetsBucketParameterName | string | If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket whose name is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. | +| assetsBucketPrefixParameterName | string | If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket that uses a prefix that is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. | +| createDashboards | boolean | Indicates whether to create per operation and overall service dashboards. | +| interval | aws-cdk-lib.Duration | The interval used in the dashboard, defaults to 60 minutes. | +| outlierThreshold | number | The outlier threshold for determining if an AZ is an outlier for latency or faults. | + +--- + +##### `outlierDetectionAlgorithm`Required + +```typescript +public readonly outlierDetectionAlgorithm: OutlierDetectionAlgorithm; +``` + +- *Type:* OutlierDetectionAlgorithm + +The algorithm to use for performing outlier detection. + +--- + +##### `service`Required + +```typescript +public readonly service: IService; +``` + +- *Type:* IService + +The service that the alarms and dashboards are being crated for. + +--- + +##### `assetsBucketParameterName`Optional + +```typescript +public readonly assetsBucketParameterName: string; +``` + +- *Type:* string +- *Default:* The assets will be uploaded to the default defined asset location. + +If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket whose name is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. + +It will override the bucket location CDK provides by +default for bundled assets. The stack containing this contruct needs +to have a parameter defined that uses this name. The underlying +stacks in this construct that deploy assets will copy the parent stack's +value for this property. + +--- + +##### `assetsBucketPrefixParameterName`Optional + +```typescript +public readonly assetsBucketPrefixParameterName: string; +``` + +- *Type:* string +- *Default:* No object prefix will be added to your custom assets location. However, if you have overridden something like the 'BucketPrefix' property in your stack synthesizer with a variable like "${AssetsBucketPrefix", you will need to define this property so it doesn't cause a reference error even if the prefix value is blank. + +If you are not using a static bucket to deploy assets, for example you are synthing this and it gets uploaded to a bucket that uses a prefix that is unknown to you (maybe used as part of a central CI/CD system) and is provided as a parameter to your stack, specify that parameter name here. + +It will override the bucket prefix CDK provides by +default for bundled assets. This property only takes effect if you +defined the assetsBucketParameterName. The stack containing this contruct needs +to have a parameter defined that uses this name. The underlying +stacks in this construct that deploy assets will copy the parent stack's +value for this property. + +--- + +##### `createDashboards`Optional + +```typescript +public readonly createDashboards: boolean; +``` + +- *Type:* boolean +- *Default:* No dashboards are created + +Indicates whether to create per operation and overall service dashboards. + +--- + +##### `interval`Optional + +```typescript +public readonly interval: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* 60 minutes + +The interval used in the dashboard, defaults to 60 minutes. + +--- + +##### `outlierThreshold`Optional + +```typescript +public readonly outlierThreshold: number; +``` + +- *Type:* number +- *Default:* Depends on the outlier detection algorithm selected + +The outlier threshold for determining if an AZ is an outlier for latency or faults. + +This number is interpreted +differently for different outlier algorithms. When used with +STATIC, the number should be between 0 and 1 to represent the +percentage of errors (like .7) that an AZ must be responsible +for to be considered an outlier. When used with CHI_SQUARED, it +represents the p value that indicates statistical significance, like +0.05 which means the skew has less than or equal to a 5% chance of +occuring. When used with Z_SCORE it indicates how many standard +deviations to evaluate for an AZ being an outlier, typically 3 is +standard for Z_SCORE. + +Standard defaults based on the outlier detection algorithm: +STATIC: 0.7 +CHI_SQUARED: 0.05 +Z_SCORE: 2 +IQR: 1.5 +MAD: 3 + +--- + +### NetworkConfigurationProps + +The network configuration for the canary function. + +#### Initializer + +```typescript +import { NetworkConfigurationProps } from '@cdklabs/multi-az-observability' + +const networkConfigurationProps: NetworkConfigurationProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| subnetSelection | aws-cdk-lib.aws_ec2.SubnetSelection | The subnets the Lambda function will be deployed in the VPC. | +| vpc | aws-cdk-lib.aws_ec2.IVpc | The VPC to run the canary in. | + +--- + +##### `subnetSelection`Required + +```typescript +public readonly subnetSelection: SubnetSelection; +``` + +- *Type:* aws-cdk-lib.aws_ec2.SubnetSelection + +The subnets the Lambda function will be deployed in the VPC. + +--- + +##### `vpc`Required + +```typescript +public readonly vpc: IVpc; +``` + +- *Type:* aws-cdk-lib.aws_ec2.IVpc + +The VPC to run the canary in. + +A security group will be created +that allows the function to communicate with the VPC as well +as the required IAM permissions. + +--- + +### OperationMetricDetailsProps + +The properties for operation metric details. + +#### Initializer + +```typescript +import { OperationMetricDetailsProps } from '@cdklabs/multi-az-observability' + +const operationMetricDetailsProps: OperationMetricDetailsProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| metricDimensions | MetricDimensions | The user implemented functions for providing the metric's dimensions. | +| operationName | string | The operation these metric details are for. | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| faultMetricNames | string[] | The names of fault indicating metrics. | +| graphedFaultStatistics | string[] | The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| graphedSuccessStatistics | string[] | The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| metricNamespace | string | The CloudWatch metric namespace for these metrics. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | +| successMetricNames | string[] | The names of success indicating metrics. | +| unit | aws-cdk-lib.aws_cloudwatch.Unit | The unit used for these metrics. | + +--- + +##### `metricDimensions`Required + +```typescript +public readonly metricDimensions: MetricDimensions; +``` + +- *Type:* MetricDimensions + +The user implemented functions for providing the metric's dimensions. + +--- + +##### `operationName`Required + +```typescript +public readonly operationName: string; +``` + +- *Type:* string + +The operation these metric details are for. + +--- + +##### `alarmStatistic`Optional + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string +- *Default:* The service default is used + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Optional + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number +- *Default:* The service default is used + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Optional + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number +- *Default:* The service default is used + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Optional + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number +- *Default:* The service default is used + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `faultMetricNames`Optional + +```typescript +public readonly faultMetricNames: string[]; +``` + +- *Type:* string[] +- *Default:* The service default is used + +The names of fault indicating metrics. + +--- + +##### `graphedFaultStatistics`Optional + +```typescript +public readonly graphedFaultStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* The service default is used + +The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `graphedSuccessStatistics`Optional + +```typescript +public readonly graphedSuccessStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* The service default is used + +The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `metricNamespace`Optional + +```typescript +public readonly metricNamespace: string; +``` + +- *Type:* string +- *Default:* The service default is used + +The CloudWatch metric namespace for these metrics. + +--- + +##### `period`Optional + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration +- *Default:* The service default is used + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Optional + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number +- *Default:* The service default is used + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +##### `successMetricNames`Optional + +```typescript +public readonly successMetricNames: string[]; +``` + +- *Type:* string[] +- *Default:* The service default is used + +The names of success indicating metrics. + +--- + +##### `unit`Optional + +```typescript +public readonly unit: Unit; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Unit +- *Default:* The service default is used + +The unit used for these metrics. + +--- + +### OperationProps + +Properties for an operation. + +#### Initializer + +```typescript +import { OperationProps } from '@cdklabs/multi-az-observability' + +const operationProps: OperationProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| critical | boolean | Indicates this is a critical operation for the service and will be included in service level metrics and dashboards. | +| httpMethods | string[] | The http methods supported by the operation. | +| operationName | string | The name of the operation. | +| path | string | The HTTP path for the operation for canaries to run against, something like "/products/list". | +| serverSideAvailabilityMetricDetails | IOperationMetricDetails | The server side availability metric details. | +| serverSideLatencyMetricDetails | IOperationMetricDetails | The server side latency metric details. | +| service | IService | The service the operation is associated with. | +| canaryMetricDetails | ICanaryMetrics | Optional metric details if the service has a canary. | +| canaryTestAvailabilityMetricsOverride | ICanaryTestMetricsOverride | The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for availability. | +| canaryTestLatencyMetricsOverride | ICanaryTestMetricsOverride | The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for latency. | +| canaryTestProps | AddCanaryTestProps | If you define this property, a synthetic canary will be provisioned to test the operation. | +| optOutOfServiceCreatedCanary | boolean | Set to true if you have defined CanaryTestProps for your service, which applies to all operations, but you want to opt out of creating the canary test for this operation. | +| serverSideContributorInsightRuleDetails | IContributorInsightRuleDetails | The server side details for contributor insights rules. | + +--- + +##### `critical`Required + +```typescript +public readonly critical: boolean; +``` + +- *Type:* boolean + +Indicates this is a critical operation for the service and will be included in service level metrics and dashboards. + +--- + +##### `httpMethods`Required + +```typescript +public readonly httpMethods: string[]; +``` + +- *Type:* string[] + +The http methods supported by the operation. + +--- + +##### `operationName`Required + +```typescript +public readonly operationName: string; +``` + +- *Type:* string + +The name of the operation. + +--- + +##### `path`Required + +```typescript +public readonly path: string; +``` + +- *Type:* string + +The HTTP path for the operation for canaries to run against, something like "/products/list". + +--- + +##### `serverSideAvailabilityMetricDetails`Required + +```typescript +public readonly serverSideAvailabilityMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The server side availability metric details. + +--- + +##### `serverSideLatencyMetricDetails`Required + +```typescript +public readonly serverSideLatencyMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The server side latency metric details. + +--- + +##### `service`Required + +```typescript +public readonly service: IService; +``` + +- *Type:* IService + +The service the operation is associated with. + +--- + +##### `canaryMetricDetails`Optional + +```typescript +public readonly canaryMetricDetails: ICanaryMetrics; +``` + +- *Type:* ICanaryMetrics +- *Default:* No alarms, rules, or dashboards will be created from canary metrics + +Optional metric details if the service has a canary. + +--- + +##### `canaryTestAvailabilityMetricsOverride`Optional + +```typescript +public readonly canaryTestAvailabilityMetricsOverride: ICanaryTestMetricsOverride; +``` + +- *Type:* ICanaryTestMetricsOverride +- *Default:* No availability metric details will be overridden and the service defaults will be used for the automatically created canaries + +The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for availability. + +--- + +##### `canaryTestLatencyMetricsOverride`Optional + +```typescript +public readonly canaryTestLatencyMetricsOverride: ICanaryTestMetricsOverride; +``` + +- *Type:* ICanaryTestMetricsOverride +- *Default:* No latency metric details will be overridden and the service defaults will be used for the automatically created canaries + +The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for latency. + +--- + +##### `canaryTestProps`Optional + +```typescript +public readonly canaryTestProps: AddCanaryTestProps; +``` + +- *Type:* AddCanaryTestProps +- *Default:* The default for the service will be used, if that is undefined, then no canary will be provisioned for this operation. + +If you define this property, a synthetic canary will be provisioned to test the operation. + +--- + +##### `optOutOfServiceCreatedCanary`Optional + +```typescript +public readonly optOutOfServiceCreatedCanary: boolean; +``` + +- *Type:* boolean +- *Default:* The operation is not opted out + +Set to true if you have defined CanaryTestProps for your service, which applies to all operations, but you want to opt out of creating the canary test for this operation. + +--- + +##### `serverSideContributorInsightRuleDetails`Optional + +```typescript +public readonly serverSideContributorInsightRuleDetails: IContributorInsightRuleDetails; +``` + +- *Type:* IContributorInsightRuleDetails +- *Default:* The default service contributor insight rule details will be used. If those are not defined no Contributor Insight rules will be created and the number of instances contributing to AZ faults or high latency will not be considered, so a single bad instance could make the AZ appear to look impaired. + +The server side details for contributor insights rules. + +--- + +### ServiceMetricDetailsProps + +The properties for default service metric details. + +#### Initializer + +```typescript +import { ServiceMetricDetailsProps } from '@cdklabs/multi-az-observability' + +const serviceMetricDetailsProps: ServiceMetricDetailsProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| faultMetricNames | string[] | The names of fault indicating metrics. | +| metricNamespace | string | The CloudWatch metric namespace for these metrics. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | +| successMetricNames | string[] | The names of success indicating metrics. | +| unit | aws-cdk-lib.aws_cloudwatch.Unit | The unit used for these metrics. | +| graphedFaultStatistics | string[] | The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| graphedSuccessStatistics | string[] | The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | + +--- + +##### `alarmStatistic`Required + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Required + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Required + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Required + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `faultMetricNames`Required + +```typescript +public readonly faultMetricNames: string[]; +``` + +- *Type:* string[] + +The names of fault indicating metrics. + +--- + +##### `metricNamespace`Required + +```typescript +public readonly metricNamespace: string; +``` + +- *Type:* string + +The CloudWatch metric namespace for these metrics. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Required + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +##### `successMetricNames`Required + +```typescript +public readonly successMetricNames: string[]; +``` + +- *Type:* string[] + +The names of success indicating metrics. + +--- + +##### `unit`Required + +```typescript +public readonly unit: Unit; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Unit + +The unit used for these metrics. + +--- + +##### `graphedFaultStatistics`Optional + +```typescript +public readonly graphedFaultStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `graphedSuccessStatistics`Optional + +```typescript +public readonly graphedSuccessStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +### ServiceProps + +Properties to initialize a service. + +#### Initializer + +```typescript +import { ServiceProps } from '@cdklabs/multi-az-observability' + +const serviceProps: ServiceProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneNames | string[] | A list of the Availability Zone names used by this application. | +| baseUrl | string | The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. | +| defaultAvailabilityMetricDetails | IServiceMetricDetails | The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. | +| defaultLatencyMetricDetails | IServiceMetricDetails | The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. | +| faultCountThreshold | number | The fault count threshold that indicates the service is unhealthy. | +| period | aws-cdk-lib.Duration | The period for which metrics for the service should be aggregated. | +| serviceName | string | The name of your service. | +| canaryTestProps | AddCanaryTestProps | Define these settings if you want to automatically add canary tests to your operations. | +| defaultContributorInsightRuleDetails | IContributorInsightRuleDetails | The default settings that are used for contributor insight rules. | +| loadBalancer | aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 | The load balancer this service sits behind. | + +--- + +##### `availabilityZoneNames`Required + +```typescript +public readonly availabilityZoneNames: string[]; +``` + +- *Type:* string[] + +A list of the Availability Zone names used by this application. + +--- + +##### `baseUrl`Required + +```typescript +public readonly baseUrl: string; +``` + +- *Type:* string + +The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. + +--- + +##### `defaultAvailabilityMetricDetails`Required + +```typescript +public readonly defaultAvailabilityMetricDetails: IServiceMetricDetails; +``` + +- *Type:* IServiceMetricDetails + +The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. + +--- + +##### `defaultLatencyMetricDetails`Required + +```typescript +public readonly defaultLatencyMetricDetails: IServiceMetricDetails; +``` + +- *Type:* IServiceMetricDetails + +The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. + +--- + +##### `faultCountThreshold`Required + +```typescript +public readonly faultCountThreshold: number; +``` + +- *Type:* number + +The fault count threshold that indicates the service is unhealthy. + +This is an absolute value of faults +being produced by all critical operations in aggregate. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for which metrics for the service should be aggregated. + +--- + +##### `serviceName`Required + +```typescript +public readonly serviceName: string; +``` + +- *Type:* string + +The name of your service. + +--- + +##### `canaryTestProps`Optional + +```typescript +public readonly canaryTestProps: AddCanaryTestProps; +``` + +- *Type:* AddCanaryTestProps +- *Default:* Automatic canary tests will not be created for operations in this service. + +Define these settings if you want to automatically add canary tests to your operations. + +Operations can individually opt out +of canary test creation if you define this setting. + +--- + +##### `defaultContributorInsightRuleDetails`Optional + +```typescript +public readonly defaultContributorInsightRuleDetails: IContributorInsightRuleDetails; +``` + +- *Type:* IContributorInsightRuleDetails +- *Default:* No defaults are provided and must be specified per operation if the operation has logs that can be queried by contributor insights + +The default settings that are used for contributor insight rules. + +--- + +##### `loadBalancer`Optional + +```typescript +public readonly loadBalancer: ILoadBalancerV2; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 +- *Default:* Load balancer metrics won't be shown on dashboards and its ARN won't be included in top level alarm descriptions that automation can use to implement a zonal shift. + +The load balancer this service sits behind. + +--- + +## Classes + +### CanaryMetrics + +- *Implements:* ICanaryMetrics + +Represents metrics for a canary testing a service. + +#### Initializers + +```typescript +import { CanaryMetrics } from '@cdklabs/multi-az-observability' + +new CanaryMetrics(props: CanaryMetricProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | CanaryMetricProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* CanaryMetricProps + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| canaryAvailabilityMetricDetails | IOperationMetricDetails | The canary availability metric details. | +| canaryLatencyMetricDetails | IOperationMetricDetails | The canary latency metric details. | + +--- + +##### `canaryAvailabilityMetricDetails`Required + +```typescript +public readonly canaryAvailabilityMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The canary availability metric details. + +--- + +##### `canaryLatencyMetricDetails`Required + +```typescript +public readonly canaryLatencyMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The canary latency metric details. + +--- + + +### CanaryTestMetricsOverride + +- *Implements:* ICanaryTestMetricsOverride + +Provides overrides for the default metric settings used for the automatically created canary tests. + +#### Initializers + +```typescript +import { CanaryTestMetricsOverride } from '@cdklabs/multi-az-observability' + +new CanaryTestMetricsOverride(props: CanaryTestMetricsOverrideProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | CanaryTestMetricsOverrideProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* CanaryTestMetricsOverrideProps + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | + +--- + +##### `alarmStatistic`Optional + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Optional + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Optional + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Optional + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `period`Optional + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Optional + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + + +### ContributorInsightRuleDetails + +- *Implements:* IContributorInsightRuleDetails + +The contributor insight rule details for creating an insight rule. + +#### Initializers + +```typescript +import { ContributorInsightRuleDetails } from '@cdklabs/multi-az-observability' + +new ContributorInsightRuleDetails(props: ContributorInsightRuleDetailsProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | ContributorInsightRuleDetailsProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* ContributorInsightRuleDetailsProps + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneIdJsonPath | string | The path in the log files to the field that identifies the Availability Zone Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would have a path of $.AZ-ID. | +| faultMetricJsonPath | string | The path in the log files to the field that identifies if the response resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault. | +| instanceIdJsonPath | string | The JSON path to the instance id field in the log files, only required for server-side rules. | +| logGroups | aws-cdk-lib.aws_logs.ILogGroup[] | The log groups where CloudWatch logs for the operation are located. | +| operationNameJsonPath | string | The path in the log files to the field that identifies the operation the log file is for. | +| successLatencyMetricJsonPath | string | The path in the log files to the field that indicates the latency for the response. | + +--- + +##### `availabilityZoneIdJsonPath`Required + +```typescript +public readonly availabilityZoneIdJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies the Availability Zone Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would have a path of $.AZ-ID. + +--- + +##### `faultMetricJsonPath`Required + +```typescript +public readonly faultMetricJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies if the response resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault. + +--- + +##### `instanceIdJsonPath`Required + +```typescript +public readonly instanceIdJsonPath: string; +``` + +- *Type:* string + +The JSON path to the instance id field in the log files, only required for server-side rules. + +--- + +##### `logGroups`Required + +```typescript +public readonly logGroups: ILogGroup[]; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup[] + +The log groups where CloudWatch logs for the operation are located. + +If +this is not provided, Contributor Insight rules cannot be created. + +--- + +##### `operationNameJsonPath`Required + +```typescript +public readonly operationNameJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies the operation the log file is for. + +--- + +##### `successLatencyMetricJsonPath`Required + +```typescript +public readonly successLatencyMetricJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that indicates the latency for the response. + +This could either be success latency or fault +latency depending on the alarms and rules you are creating. + +--- + + +### MetricDimensions + +Provides the ability to get operation specific metric dimensions for metrics at the regional level as well as Availability Zone level. + +#### Initializers + +```typescript +import { MetricDimensions } from '@cdklabs/multi-az-observability' + +new MetricDimensions(staticDimensions: {[ key: string ]: string}, availabilityZoneIdKey: string, regionKey?: string) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| staticDimensions | {[ key: string ]: string} | *No description.* | +| availabilityZoneIdKey | string | *No description.* | +| regionKey | string | *No description.* | + +--- + +##### `staticDimensions`Required + +- *Type:* {[ key: string ]: string} + +--- + +##### `availabilityZoneIdKey`Required + +- *Type:* string + +--- + +##### `regionKey`Optional + +- *Type:* string + +--- + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| regionalDimensions | Gets the regional dimensions for these metrics by combining the static metric dimensions with the keys provided the optional Region key, expected to return something like { "Region": "us-east-1", "Operation": "ride", "Service": "WildRydes" }. | +| zonalDimensions | Gets the zonal dimensions for these metrics by combining the static metric dimensions with the keys provided for Availability Zone and optional Region, expected to return something like { "Region": "us-east-1", "AZ-ID": "use1-az1", "Operation": "ride", "Service": "WildRydes" }. | + +--- + +##### `regionalDimensions` + +```typescript +public regionalDimensions(region: string): {[ key: string ]: string} +``` + +Gets the regional dimensions for these metrics by combining the static metric dimensions with the keys provided the optional Region key, expected to return something like { "Region": "us-east-1", "Operation": "ride", "Service": "WildRydes" }. + +###### `region`Required + +- *Type:* string + +--- + +##### `zonalDimensions` + +```typescript +public zonalDimensions(availabilityZoneId: string, region: string): {[ key: string ]: string} +``` + +Gets the zonal dimensions for these metrics by combining the static metric dimensions with the keys provided for Availability Zone and optional Region, expected to return something like { "Region": "us-east-1", "AZ-ID": "use1-az1", "Operation": "ride", "Service": "WildRydes" }. + +###### `availabilityZoneId`Required + +- *Type:* string + +--- + +###### `region`Required + +- *Type:* string + +--- + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneIdKey | string | The key used to specify an Availability Zone specific metric dimension, for example: "AZ-ID". | +| staticDimensions | {[ key: string ]: string} | The dimensions that are the same for all Availability Zones for example: { "Operation": "ride", "Service": "WildRydes" }. | +| regionKey | string | The key used for the Region in your dimensions, if you provide one. | + +--- + +##### `availabilityZoneIdKey`Required + +```typescript +public readonly availabilityZoneIdKey: string; +``` + +- *Type:* string + +The key used to specify an Availability Zone specific metric dimension, for example: "AZ-ID". + +--- + +##### `staticDimensions`Required + +```typescript +public readonly staticDimensions: {[ key: string ]: string}; +``` + +- *Type:* {[ key: string ]: string} + +The dimensions that are the same for all Availability Zones for example: { "Operation": "ride", "Service": "WildRydes" }. + +--- + +##### `regionKey`Optional + +```typescript +public readonly regionKey: string; +``` + +- *Type:* string +- *Default:* A region specific key and value is not added to your zonal and regional metric dimensions + +The key used for the Region in your dimensions, if you provide one. + +--- + + +### Operation + +- *Implements:* IOperation + +A single operation that is part of a service. + +#### Initializers + +```typescript +import { Operation } from '@cdklabs/multi-az-observability' + +new Operation(props: OperationProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | OperationProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* OperationProps + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| critical | boolean | Indicates this is a critical operation for the service and will be included in service level metrics and dashboards. | +| httpMethods | string[] | The http methods supported by the operation. | +| operationName | string | The name of the operation. | +| path | string | The HTTP path for the operation for canaries to run against, something like "/products/list". | +| serverSideAvailabilityMetricDetails | IOperationMetricDetails | The server side availability metric details. | +| serverSideLatencyMetricDetails | IOperationMetricDetails | The server side latency metric details. | +| service | IService | The service the operation is associated with. | +| canaryMetricDetails | ICanaryMetrics | Optional metric details if the service has a canary. | +| canaryTestAvailabilityMetricsOverride | ICanaryTestMetricsOverride | The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for availability. | +| canaryTestLatencyMetricsOverride | ICanaryTestMetricsOverride | The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for latency. | +| canaryTestProps | AddCanaryTestProps | If they have been added, the properties for creating new canary tests on this operation. | +| optOutOfServiceCreatedCanary | boolean | Set to true if you have defined CanaryTestProps for your service, which applies to all operations, but you want to opt out of creating the canary test for this operation. | +| serverSideContributorInsightRuleDetails | IContributorInsightRuleDetails | The server side details for contributor insights rules. | + +--- + +##### `critical`Required + +```typescript +public readonly critical: boolean; +``` + +- *Type:* boolean + +Indicates this is a critical operation for the service and will be included in service level metrics and dashboards. + +--- + +##### `httpMethods`Required + +```typescript +public readonly httpMethods: string[]; +``` + +- *Type:* string[] + +The http methods supported by the operation. + +--- + +##### `operationName`Required + +```typescript +public readonly operationName: string; +``` + +- *Type:* string + +The name of the operation. + +--- + +##### `path`Required + +```typescript +public readonly path: string; +``` + +- *Type:* string + +The HTTP path for the operation for canaries to run against, something like "/products/list". + +--- + +##### `serverSideAvailabilityMetricDetails`Required + +```typescript +public readonly serverSideAvailabilityMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The server side availability metric details. + +--- + +##### `serverSideLatencyMetricDetails`Required + +```typescript +public readonly serverSideLatencyMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The server side latency metric details. + +--- + +##### `service`Required + +```typescript +public readonly service: IService; +``` + +- *Type:* IService + +The service the operation is associated with. + +--- + +##### `canaryMetricDetails`Optional + +```typescript +public readonly canaryMetricDetails: ICanaryMetrics; +``` + +- *Type:* ICanaryMetrics + +Optional metric details if the service has a canary. + +--- + +##### `canaryTestAvailabilityMetricsOverride`Optional + +```typescript +public readonly canaryTestAvailabilityMetricsOverride: ICanaryTestMetricsOverride; +``` + +- *Type:* ICanaryTestMetricsOverride + +The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for availability. + +--- + +##### `canaryTestLatencyMetricsOverride`Optional + +```typescript +public readonly canaryTestLatencyMetricsOverride: ICanaryTestMetricsOverride; +``` + +- *Type:* ICanaryTestMetricsOverride + +The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for latency. + +--- + +##### `canaryTestProps`Optional + +```typescript +public readonly canaryTestProps: AddCanaryTestProps; +``` + +- *Type:* AddCanaryTestProps + +If they have been added, the properties for creating new canary tests on this operation. + +--- + +##### `optOutOfServiceCreatedCanary`Optional + +```typescript +public readonly optOutOfServiceCreatedCanary: boolean; +``` + +- *Type:* boolean +- *Default:* The operation is not opted out + +Set to true if you have defined CanaryTestProps for your service, which applies to all operations, but you want to opt out of creating the canary test for this operation. + +--- + +##### `serverSideContributorInsightRuleDetails`Optional + +```typescript +public readonly serverSideContributorInsightRuleDetails: IContributorInsightRuleDetails; +``` + +- *Type:* IContributorInsightRuleDetails + +The server side details for contributor insights rules. + +--- + + +### OperationMetricDetails + +- *Implements:* IOperationMetricDetails + +Generic metric details for an operation. + +#### Initializers + +```typescript +import { OperationMetricDetails } from '@cdklabs/multi-az-observability' + +new OperationMetricDetails(props: OperationMetricDetailsProps, defaultProps: IServiceMetricDetails) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | OperationMetricDetailsProps | *No description.* | +| defaultProps | IServiceMetricDetails | *No description.* | + +--- + +##### `props`Required + +- *Type:* OperationMetricDetailsProps + +--- + +##### `defaultProps`Required + +- *Type:* IServiceMetricDetails + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| faultMetricNames | string[] | The names of fault indicating metrics. | +| metricDimensions | MetricDimensions | The metric dimensions for this operation, must be implemented as a concrete class by the user. | +| metricNamespace | string | The CloudWatch metric namespace for these metrics. | +| operationName | string | The operation these metric details are for. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | +| successMetricNames | string[] | The names of success indicating metrics. | +| unit | aws-cdk-lib.aws_cloudwatch.Unit | The unit used for these metrics. | +| graphedFaultStatistics | string[] | The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| graphedSuccessStatistics | string[] | The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | + +--- + +##### `alarmStatistic`Required + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Required + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Required + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Required + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `faultMetricNames`Required + +```typescript +public readonly faultMetricNames: string[]; +``` + +- *Type:* string[] + +The names of fault indicating metrics. + +--- + +##### `metricDimensions`Required + +```typescript +public readonly metricDimensions: MetricDimensions; +``` + +- *Type:* MetricDimensions + +The metric dimensions for this operation, must be implemented as a concrete class by the user. + +--- + +##### `metricNamespace`Required + +```typescript +public readonly metricNamespace: string; +``` + +- *Type:* string + +The CloudWatch metric namespace for these metrics. + +--- + +##### `operationName`Required + +```typescript +public readonly operationName: string; +``` + +- *Type:* string + +The operation these metric details are for. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Required + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +##### `successMetricNames`Required + +```typescript +public readonly successMetricNames: string[]; +``` + +- *Type:* string[] + +The names of success indicating metrics. + +--- + +##### `unit`Required + +```typescript +public readonly unit: Unit; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Unit + +The unit used for these metrics. + +--- + +##### `graphedFaultStatistics`Optional + +```typescript +public readonly graphedFaultStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `graphedSuccessStatistics`Optional + +```typescript +public readonly graphedSuccessStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + + +### Service + +- *Implements:* IService + +The representation of a service composed of multiple operations. + +#### Initializers + +```typescript +import { Service } from '@cdklabs/multi-az-observability' + +new Service(props: ServiceProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | ServiceProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* ServiceProps + +--- + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| addOperation | Adds an operation to this service and sets the operation's service property. | + +--- + +##### `addOperation` + +```typescript +public addOperation(operation: IOperation): void +``` + +Adds an operation to this service and sets the operation's service property. + +###### `operation`Required + +- *Type:* IOperation + +--- + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneNames | string[] | A list of the Availability Zone names used by this application. | +| baseUrl | string | The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. | +| defaultAvailabilityMetricDetails | IServiceMetricDetails | The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. | +| defaultLatencyMetricDetails | IServiceMetricDetails | The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. | +| faultCountThreshold | number | The fault count threshold that indicates the service is unhealthy. | +| operations | IOperation[] | The operations that are part of this service. | +| period | aws-cdk-lib.Duration | The period for which metrics for the service should be aggregated. | +| serviceName | string | The name of your service. | +| canaryTestProps | AddCanaryTestProps | Define these settings if you want to automatically add canary tests to your operations. | +| defaultContributorInsightRuleDetails | IContributorInsightRuleDetails | The default settings that are used for contributor insight rules. | +| loadBalancer | aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 | The load balancer this service sits behind. | + +--- + +##### `availabilityZoneNames`Required + +```typescript +public readonly availabilityZoneNames: string[]; +``` + +- *Type:* string[] + +A list of the Availability Zone names used by this application. + +--- + +##### `baseUrl`Required + +```typescript +public readonly baseUrl: string; +``` + +- *Type:* string + +The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. + +--- + +##### `defaultAvailabilityMetricDetails`Required + +```typescript +public readonly defaultAvailabilityMetricDetails: IServiceMetricDetails; +``` + +- *Type:* IServiceMetricDetails + +The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. + +--- + +##### `defaultLatencyMetricDetails`Required + +```typescript +public readonly defaultLatencyMetricDetails: IServiceMetricDetails; +``` + +- *Type:* IServiceMetricDetails + +The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. + +--- + +##### `faultCountThreshold`Required + +```typescript +public readonly faultCountThreshold: number; +``` + +- *Type:* number + +The fault count threshold that indicates the service is unhealthy. + +This is an absolute value of faults +being produced by all critical operations in aggregate. + +--- + +##### `operations`Required + +```typescript +public readonly operations: IOperation[]; +``` + +- *Type:* IOperation[] + +The operations that are part of this service. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for which metrics for the service should be aggregated. + +--- + +##### `serviceName`Required + +```typescript +public readonly serviceName: string; +``` + +- *Type:* string + +The name of your service. + +--- + +##### `canaryTestProps`Optional + +```typescript +public readonly canaryTestProps: AddCanaryTestProps; +``` + +- *Type:* AddCanaryTestProps +- *Default:* Automatic canary tests will not be created for operations in this service. + +Define these settings if you want to automatically add canary tests to your operations. + +Operations can individually opt out +of canary test creation if you define this setting. + +--- + +##### `defaultContributorInsightRuleDetails`Optional + +```typescript +public readonly defaultContributorInsightRuleDetails: IContributorInsightRuleDetails; +``` + +- *Type:* IContributorInsightRuleDetails +- *Default:* No defaults are provided and must be specified per operation + +The default settings that are used for contributor insight rules. + +--- + +##### `loadBalancer`Optional + +```typescript +public readonly loadBalancer: ILoadBalancerV2; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 +- *Default:* No load balancer metrics will be included in dashboards and its ARN will not be added to top level AZ alarm descriptions. + +The load balancer this service sits behind. + +--- + + +### ServiceMetricDetails + +- *Implements:* IServiceMetricDetails + +Default metric details for a service. + +#### Initializers + +```typescript +import { ServiceMetricDetails } from '@cdklabs/multi-az-observability' + +new ServiceMetricDetails(props: ServiceMetricDetailsProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| props | ServiceMetricDetailsProps | *No description.* | + +--- + +##### `props`Required + +- *Type:* ServiceMetricDetailsProps + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| faultMetricNames | string[] | The names of fault indicating metrics. | +| metricNamespace | string | The CloudWatch metric namespace for these metrics. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | +| successMetricNames | string[] | The names of success indicating metrics. | +| unit | aws-cdk-lib.aws_cloudwatch.Unit | The unit used for these metrics. | +| graphedFaultStatistics | string[] | The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| graphedSuccessStatistics | string[] | The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | + +--- + +##### `alarmStatistic`Required + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Required + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Required + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Required + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `faultMetricNames`Required + +```typescript +public readonly faultMetricNames: string[]; +``` + +- *Type:* string[] + +The names of fault indicating metrics. + +--- + +##### `metricNamespace`Required + +```typescript +public readonly metricNamespace: string; +``` + +- *Type:* string + +The CloudWatch metric namespace for these metrics. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Required + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +##### `successMetricNames`Required + +```typescript +public readonly successMetricNames: string[]; +``` + +- *Type:* string[] + +The names of success indicating metrics. + +--- + +##### `unit`Required + +```typescript +public readonly unit: Unit; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Unit + +The unit used for these metrics. + +--- + +##### `graphedFaultStatistics`Optional + +```typescript +public readonly graphedFaultStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `graphedSuccessStatistics`Optional + +```typescript +public readonly graphedSuccessStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + + +## Protocols + +### IAvailabilityZoneMapper + +- *Extends:* constructs.IConstruct + +- *Implemented By:* AvailabilityZoneMapper, IAvailabilityZoneMapper + +A wrapper for the Availability Zone mapper construct that allows you to translate Availability Zone names to Availability Zone Ids and vice a versa using the mapping in the AWS account where this is deployed. + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| allAvailabilityZoneIdsAsArray | Returns a reference that can be cast to a string array with all of the Availability Zone Ids. | +| allAvailabilityZoneIdsAsCommaDelimitedList | Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. | +| allAvailabilityZoneNamesAsCommaDelimitedList | Gets all of the Availability Zone names in this Region as a comma delimited list. | +| availabilityZoneId | Gets the Availability Zone Id for the given Availability Zone Name in this account. | +| availabilityZoneIdFromAvailabilityZoneLetter | Given a letter like "f" or "a", returns the Availability Zone Id for that Availability Zone name in this account. | +| availabilityZoneIdsAsArray | Returns an array for Availability Zone Ids for the supplied Availability Zone names, they are returned in the same order the names were provided. | +| availabilityZoneIdsAsCommaDelimitedList | Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. | +| availabilityZoneName | Gets the Availability Zone Name for the given Availability Zone Id in this account. | +| regionPrefixForAvailabilityZoneIds | Gets the prefix for the region used with Availability Zone Ids, for example in us-east-1, this returns "use1". | + +--- + +##### `allAvailabilityZoneIdsAsArray` + +```typescript +public allAvailabilityZoneIdsAsArray(): Reference +``` + +Returns a reference that can be cast to a string array with all of the Availability Zone Ids. + +##### `allAvailabilityZoneIdsAsCommaDelimitedList` + +```typescript +public allAvailabilityZoneIdsAsCommaDelimitedList(): string +``` + +Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. + +You can use this string with Fn.Select(x, Fn.Split(",", azs)) to +get a specific Availability Zone Id + +##### `allAvailabilityZoneNamesAsCommaDelimitedList` + +```typescript +public allAvailabilityZoneNamesAsCommaDelimitedList(): string +``` + +Gets all of the Availability Zone names in this Region as a comma delimited list. + +You can use this string with Fn.Select(x, Fn.Split(",", azs)) to +get a specific Availability Zone Name + +##### `availabilityZoneId` + +```typescript +public availabilityZoneId(availabilityZoneName: string): string +``` + +Gets the Availability Zone Id for the given Availability Zone Name in this account. + +###### `availabilityZoneName`Required + +- *Type:* string + +--- + +##### `availabilityZoneIdFromAvailabilityZoneLetter` + +```typescript +public availabilityZoneIdFromAvailabilityZoneLetter(letter: string): string +``` + +Given a letter like "f" or "a", returns the Availability Zone Id for that Availability Zone name in this account. + +###### `letter`Required + +- *Type:* string + +--- + +##### `availabilityZoneIdsAsArray` + +```typescript +public availabilityZoneIdsAsArray(availabilityZoneNames: string[]): string[] +``` + +Returns an array for Availability Zone Ids for the supplied Availability Zone names, they are returned in the same order the names were provided. + +###### `availabilityZoneNames`Required + +- *Type:* string[] + +--- + +##### `availabilityZoneIdsAsCommaDelimitedList` + +```typescript +public availabilityZoneIdsAsCommaDelimitedList(availabilityZoneNames: string[]): string +``` + +Returns a comma delimited list of Availability Zone Ids for the supplied Availability Zone names. + +You can use this string with Fn.Select(x, Fn.Split(",", azs)) to +get a specific Availability Zone Id + +###### `availabilityZoneNames`Required + +- *Type:* string[] + +--- + +##### `availabilityZoneName` + +```typescript +public availabilityZoneName(availabilityZoneId: string): string +``` + +Gets the Availability Zone Name for the given Availability Zone Id in this account. + +###### `availabilityZoneId`Required + +- *Type:* string + +--- + +##### `regionPrefixForAvailabilityZoneIds` + +```typescript +public regionPrefixForAvailabilityZoneIds(): string +``` + +Gets the prefix for the region used with Availability Zone Ids, for example in us-east-1, this returns "use1". + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| function | aws-cdk-lib.aws_lambda.IFunction | The function that does the mapping. | +| logGroup | aws-cdk-lib.aws_logs.ILogGroup | The log group for the function's logs. | +| mapper | aws-cdk-lib.CustomResource | The custom resource that can be referenced to use Fn::GetAtt functions on to retrieve availability zone names and ids. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `function`Required + +```typescript +public readonly function: IFunction; +``` + +- *Type:* aws-cdk-lib.aws_lambda.IFunction + +The function that does the mapping. + +--- + +##### `logGroup`Required + +```typescript +public readonly logGroup: ILogGroup; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup + +The log group for the function's logs. + +--- + +##### `mapper`Required + +```typescript +public readonly mapper: CustomResource; +``` + +- *Type:* aws-cdk-lib.CustomResource + +The custom resource that can be referenced to use Fn::GetAtt functions on to retrieve availability zone names and ids. + +--- + +### IBasicServiceMultiAZObservability + +- *Extends:* constructs.IConstruct + +- *Implemented By:* BasicServiceMultiAZObservability, IBasicServiceMultiAZObservability + +Properties of a basic service. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| aggregateZonalIsolatedImpactAlarms | {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} | The alarms indicating if an AZ has isolated impact from either ALB or NAT GW metrics. | +| serviceName | string | The name of the service. | +| albZonalIsolatedImpactAlarms | {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} | The alarms indicating if an AZ is an outlier for ALB faults and has isolated impact. | +| applicationLoadBalancers | aws-cdk-lib.aws_elasticloadbalancingv2.IApplicationLoadBalancer[] | The application load balancers being used by the service. | +| natGateways | {[ key: string ]: aws-cdk-lib.aws_ec2.CfnNatGateway[]} | The NAT Gateways being used in the service, each set of NAT Gateways are keyed by their Availability Zone Id. | +| natGWZonalIsolatedImpactAlarms | {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} | The alarms indicating if an AZ is an outlier for NAT GW packet loss and has isolated impact. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `aggregateZonalIsolatedImpactAlarms`Required + +```typescript +public readonly aggregateZonalIsolatedImpactAlarms: {[ key: string ]: IAlarm}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} + +The alarms indicating if an AZ has isolated impact from either ALB or NAT GW metrics. + +--- + +##### `serviceName`Required + +```typescript +public readonly serviceName: string; +``` + +- *Type:* string + +The name of the service. + +--- + +##### `albZonalIsolatedImpactAlarms`Optional + +```typescript +public readonly albZonalIsolatedImpactAlarms: {[ key: string ]: IAlarm}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} + +The alarms indicating if an AZ is an outlier for ALB faults and has isolated impact. + +--- + +##### `applicationLoadBalancers`Optional + +```typescript +public readonly applicationLoadBalancers: IApplicationLoadBalancer[]; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.IApplicationLoadBalancer[] + +The application load balancers being used by the service. + +--- + +##### `natGateways`Optional + +```typescript +public readonly natGateways: {[ key: string ]: CfnNatGateway[]}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_ec2.CfnNatGateway[]} + +The NAT Gateways being used in the service, each set of NAT Gateways are keyed by their Availability Zone Id. + +--- + +##### `natGWZonalIsolatedImpactAlarms`Optional + +```typescript +public readonly natGWZonalIsolatedImpactAlarms: {[ key: string ]: IAlarm}; +``` + +- *Type:* {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm} + +The alarms indicating if an AZ is an outlier for NAT GW packet loss and has isolated impact. + +--- + +### ICanaryMetrics + +- *Implemented By:* CanaryMetrics, ICanaryMetrics + +The metric definitions for metric produced by the canary. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| canaryAvailabilityMetricDetails | IOperationMetricDetails | The canary availability metric details. | +| canaryLatencyMetricDetails | IOperationMetricDetails | The canary latency metric details. | + +--- + +##### `canaryAvailabilityMetricDetails`Required + +```typescript +public readonly canaryAvailabilityMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The canary availability metric details. + +--- + +##### `canaryLatencyMetricDetails`Required + +```typescript +public readonly canaryLatencyMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The canary latency metric details. + +--- + +### ICanaryTestMetricsOverride + +- *Implemented By:* CanaryTestMetricsOverride, ICanaryTestMetricsOverride + +Provides overrides for the default metric settings used for the automatically created canary tests. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | + +--- + +##### `alarmStatistic`Optional + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Optional + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Optional + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Optional + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `period`Optional + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Optional + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +### IContributorInsightRuleDetails + +- *Implemented By:* ContributorInsightRuleDetails, IContributorInsightRuleDetails + +Details for setting up Contributor Insight rules. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneIdJsonPath | string | The path in the log files to the field that identifies the Availability Zone Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would have a path of $.AZ-ID. | +| faultMetricJsonPath | string | The path in the log files to the field that identifies if the response resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault. | +| instanceIdJsonPath | string | The JSON path to the instance id field in the log files, only required for server-side rules. | +| logGroups | aws-cdk-lib.aws_logs.ILogGroup[] | The log groups where CloudWatch logs for the operation are located. | +| operationNameJsonPath | string | The path in the log files to the field that identifies the operation the log file is for. | +| successLatencyMetricJsonPath | string | The path in the log files to the field that indicates the latency for the response. | + +--- + +##### `availabilityZoneIdJsonPath`Required + +```typescript +public readonly availabilityZoneIdJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies the Availability Zone Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would have a path of $.AZ-ID. + +--- + +##### `faultMetricJsonPath`Required + +```typescript +public readonly faultMetricJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies if the response resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault. + +--- + +##### `instanceIdJsonPath`Required + +```typescript +public readonly instanceIdJsonPath: string; +``` + +- *Type:* string + +The JSON path to the instance id field in the log files, only required for server-side rules. + +--- + +##### `logGroups`Required + +```typescript +public readonly logGroups: ILogGroup[]; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup[] + +The log groups where CloudWatch logs for the operation are located. + +If +this is not provided, Contributor Insight rules cannot be created. + +--- + +##### `operationNameJsonPath`Required + +```typescript +public readonly operationNameJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that identifies the operation the log file is for. + +--- + +##### `successLatencyMetricJsonPath`Required + +```typescript +public readonly successLatencyMetricJsonPath: string; +``` + +- *Type:* string + +The path in the log files to the field that indicates the latency for the response. + +This could either be success latency or fault +latency depending on the alarms and rules you are creating. + +--- + +### IInstrumentedServiceMultiAZObservability + +- *Extends:* constructs.IConstruct + +- *Implemented By:* InstrumentedServiceMultiAZObservability, IInstrumentedServiceMultiAZObservability + +Observability for an instrumented service. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | +| perOperationZonalImpactAlarms | {[ key: string ]: {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm}} | Index into the dictionary by operation name, then by Availability Zone Id to get the alarms that indicate an AZ shows isolated impact from availability or latency as seen by either the server-side or canary. | +| serviceAlarms | IServiceAlarmsAndRules | The alarms and rules for the overall service. | +| canaryLogGroup | aws-cdk-lib.aws_logs.ILogGroup | If the service is configured to have canary tests created, this will be the log group where the canary's logs are stored. | +| operationDashboards | aws-cdk-lib.aws_cloudwatch.Dashboard[] | The dashboards for each operation. | +| serviceDashboard | aws-cdk-lib.aws_cloudwatch.Dashboard | The service level dashboard. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + +##### `perOperationZonalImpactAlarms`Required + +```typescript +public readonly perOperationZonalImpactAlarms: {[ key: string ]: {[ key: string ]: IAlarm}}; +``` + +- *Type:* {[ key: string ]: {[ key: string ]: aws-cdk-lib.aws_cloudwatch.IAlarm}} + +Index into the dictionary by operation name, then by Availability Zone Id to get the alarms that indicate an AZ shows isolated impact from availability or latency as seen by either the server-side or canary. + +These are the alarms +you would want to use to trigger automation to evacuate an AZ. + +--- + +##### `serviceAlarms`Required + +```typescript +public readonly serviceAlarms: IServiceAlarmsAndRules; +``` + +- *Type:* IServiceAlarmsAndRules + +The alarms and rules for the overall service. + +--- + +##### `canaryLogGroup`Optional + +```typescript +public readonly canaryLogGroup: ILogGroup; +``` + +- *Type:* aws-cdk-lib.aws_logs.ILogGroup +- *Default:* No log group is created if the canary is not requested. + +If the service is configured to have canary tests created, this will be the log group where the canary's logs are stored. + +--- + +##### `operationDashboards`Optional + +```typescript +public readonly operationDashboards: Dashboard[]; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Dashboard[] + +The dashboards for each operation. + +--- + +##### `serviceDashboard`Optional + +```typescript +public readonly serviceDashboard: Dashboard; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Dashboard + +The service level dashboard. + +--- + +### IOperation + +- *Implemented By:* Operation, IOperation + +Represents an operation in a service. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| critical | boolean | Indicates this is a critical operation for the service and will be included in service level metrics and dashboards. | +| httpMethods | string[] | The http methods supported by the operation. | +| operationName | string | The name of the operation. | +| path | string | The HTTP path for the operation for canaries to run against, something like "/products/list". | +| serverSideAvailabilityMetricDetails | IOperationMetricDetails | The server side availability metric details. | +| serverSideLatencyMetricDetails | IOperationMetricDetails | The server side latency metric details. | +| service | IService | The service the operation is associated with. | +| canaryMetricDetails | ICanaryMetrics | Optional metric details if the service has an existing canary. | +| canaryTestAvailabilityMetricsOverride | ICanaryTestMetricsOverride | The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for availability. | +| canaryTestLatencyMetricsOverride | ICanaryTestMetricsOverride | The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for latency. | +| canaryTestProps | AddCanaryTestProps | If they have been added, the properties for creating new canary tests on this operation. | +| optOutOfServiceCreatedCanary | boolean | Set to true if you have defined CanaryTestProps for your service, which applies to all operations, but you want to opt out of creating the canary test for this operation. | +| serverSideContributorInsightRuleDetails | IContributorInsightRuleDetails | The server side details for contributor insights rules. | + +--- + +##### `critical`Required + +```typescript +public readonly critical: boolean; +``` + +- *Type:* boolean + +Indicates this is a critical operation for the service and will be included in service level metrics and dashboards. + +--- + +##### `httpMethods`Required + +```typescript +public readonly httpMethods: string[]; +``` + +- *Type:* string[] + +The http methods supported by the operation. + +--- + +##### `operationName`Required + +```typescript +public readonly operationName: string; +``` + +- *Type:* string + +The name of the operation. + +--- + +##### `path`Required + +```typescript +public readonly path: string; +``` + +- *Type:* string + +The HTTP path for the operation for canaries to run against, something like "/products/list". + +--- + +##### `serverSideAvailabilityMetricDetails`Required + +```typescript +public readonly serverSideAvailabilityMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The server side availability metric details. + +--- + +##### `serverSideLatencyMetricDetails`Required + +```typescript +public readonly serverSideLatencyMetricDetails: IOperationMetricDetails; +``` + +- *Type:* IOperationMetricDetails + +The server side latency metric details. + +--- + +##### `service`Required + +```typescript +public readonly service: IService; +``` + +- *Type:* IService + +The service the operation is associated with. + +--- + +##### `canaryMetricDetails`Optional + +```typescript +public readonly canaryMetricDetails: ICanaryMetrics; +``` + +- *Type:* ICanaryMetrics + +Optional metric details if the service has an existing canary. + +--- + +##### `canaryTestAvailabilityMetricsOverride`Optional + +```typescript +public readonly canaryTestAvailabilityMetricsOverride: ICanaryTestMetricsOverride; +``` + +- *Type:* ICanaryTestMetricsOverride + +The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for availability. + +--- + +##### `canaryTestLatencyMetricsOverride`Optional + +```typescript +public readonly canaryTestLatencyMetricsOverride: ICanaryTestMetricsOverride; +``` + +- *Type:* ICanaryTestMetricsOverride + +The override values for automatically created canary tests so you can use values other than the service defaults to define the thresholds for latency. + +--- + +##### `canaryTestProps`Optional + +```typescript +public readonly canaryTestProps: AddCanaryTestProps; +``` + +- *Type:* AddCanaryTestProps + +If they have been added, the properties for creating new canary tests on this operation. + +--- + +##### `optOutOfServiceCreatedCanary`Optional + +```typescript +public readonly optOutOfServiceCreatedCanary: boolean; +``` + +- *Type:* boolean +- *Default:* The operation is not opted out + +Set to true if you have defined CanaryTestProps for your service, which applies to all operations, but you want to opt out of creating the canary test for this operation. + +--- + +##### `serverSideContributorInsightRuleDetails`Optional + +```typescript +public readonly serverSideContributorInsightRuleDetails: IContributorInsightRuleDetails; +``` + +- *Type:* IContributorInsightRuleDetails + +The server side details for contributor insights rules. + +--- + +### IOperationMetricDetails + +- *Implemented By:* OperationMetricDetails, IOperationMetricDetails + +Details for operation metrics in one perspective, such as server side latency. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| faultMetricNames | string[] | The names of fault indicating metrics. | +| metricDimensions | MetricDimensions | The metric dimensions for this operation, must be implemented as a concrete class by the user. | +| metricNamespace | string | The CloudWatch metric namespace for these metrics. | +| operationName | string | The operation these metric details are for. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | +| successMetricNames | string[] | The names of success indicating metrics. | +| unit | aws-cdk-lib.aws_cloudwatch.Unit | The unit used for these metrics. | +| graphedFaultStatistics | string[] | The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| graphedSuccessStatistics | string[] | The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | + +--- + +##### `alarmStatistic`Required + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Required + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Required + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Required + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `faultMetricNames`Required + +```typescript +public readonly faultMetricNames: string[]; +``` + +- *Type:* string[] + +The names of fault indicating metrics. + +--- + +##### `metricDimensions`Required + +```typescript +public readonly metricDimensions: MetricDimensions; +``` + +- *Type:* MetricDimensions + +The metric dimensions for this operation, must be implemented as a concrete class by the user. + +--- + +##### `metricNamespace`Required + +```typescript +public readonly metricNamespace: string; +``` + +- *Type:* string + +The CloudWatch metric namespace for these metrics. + +--- + +##### `operationName`Required + +```typescript +public readonly operationName: string; +``` + +- *Type:* string + +The operation these metric details are for. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Required + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +##### `successMetricNames`Required + +```typescript +public readonly successMetricNames: string[]; +``` + +- *Type:* string[] + +The names of success indicating metrics. + +--- + +##### `unit`Required + +```typescript +public readonly unit: Unit; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Unit + +The unit used for these metrics. + +--- + +##### `graphedFaultStatistics`Optional + +```typescript +public readonly graphedFaultStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `graphedSuccessStatistics`Optional + +```typescript +public readonly graphedSuccessStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +### IService + +- *Implemented By:* Service, IService + +Represents a complete service composed of one or more operations. + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| addOperation | Adds an operation to this service. | + +--- + +##### `addOperation` + +```typescript +public addOperation(operation: IOperation): void +``` + +Adds an operation to this service. + +###### `operation`Required + +- *Type:* IOperation + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| availabilityZoneNames | string[] | A list of the Availability Zone names used by this application. | +| baseUrl | string | The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. | +| defaultAvailabilityMetricDetails | IServiceMetricDetails | The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. | +| defaultLatencyMetricDetails | IServiceMetricDetails | The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. | +| faultCountThreshold | number | The fault count threshold that indicates the service is unhealthy. | +| operations | IOperation[] | The operations that are part of this service. | +| period | aws-cdk-lib.Duration | The period for which metrics for the service should be aggregated. | +| serviceName | string | The name of your service. | +| canaryTestProps | AddCanaryTestProps | Define these settings if you want to automatically add canary tests to your operations. | +| defaultContributorInsightRuleDetails | IContributorInsightRuleDetails | The default settings that are used for contributor insight rules. | +| loadBalancer | aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 | The load balancer this service sits behind. | + +--- + +##### `availabilityZoneNames`Required + +```typescript +public readonly availabilityZoneNames: string[]; +``` + +- *Type:* string[] + +A list of the Availability Zone names used by this application. + +--- + +##### `baseUrl`Required + +```typescript +public readonly baseUrl: string; +``` + +- *Type:* string + +The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. + +--- + +##### `defaultAvailabilityMetricDetails`Required + +```typescript +public readonly defaultAvailabilityMetricDetails: IServiceMetricDetails; +``` + +- *Type:* IServiceMetricDetails + +The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. + +--- + +##### `defaultLatencyMetricDetails`Required + +```typescript +public readonly defaultLatencyMetricDetails: IServiceMetricDetails; +``` + +- *Type:* IServiceMetricDetails + +The default settings that are used for availability metrics for all operations unless specifically overridden in an operation definition. + +--- + +##### `faultCountThreshold`Required + +```typescript +public readonly faultCountThreshold: number; +``` + +- *Type:* number + +The fault count threshold that indicates the service is unhealthy. + +This is an absolute value of faults +being produced by all critical operations in aggregate. + +--- + +##### `operations`Required + +```typescript +public readonly operations: IOperation[]; +``` + +- *Type:* IOperation[] + +The operations that are part of this service. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for which metrics for the service should be aggregated. + +--- + +##### `serviceName`Required + +```typescript +public readonly serviceName: string; +``` + +- *Type:* string + +The name of your service. + +--- + +##### `canaryTestProps`Optional + +```typescript +public readonly canaryTestProps: AddCanaryTestProps; +``` + +- *Type:* AddCanaryTestProps +- *Default:* Automatic canary tests will not be created for operations in this service. + +Define these settings if you want to automatically add canary tests to your operations. + +Operations can individually opt out +of canary test creation if you define this setting. + +--- + +##### `defaultContributorInsightRuleDetails`Optional + +```typescript +public readonly defaultContributorInsightRuleDetails: IContributorInsightRuleDetails; +``` + +- *Type:* IContributorInsightRuleDetails +- *Default:* No defaults are provided and must be specified per operation + +The default settings that are used for contributor insight rules. + +--- + +##### `loadBalancer`Optional + +```typescript +public readonly loadBalancer: ILoadBalancerV2; +``` + +- *Type:* aws-cdk-lib.aws_elasticloadbalancingv2.ILoadBalancerV2 +- *Default:* No load balancer metrics are included in dashboards and its ARN is not added to top level AZ alarm descriptions. + +The load balancer this service sits behind. + +--- + +### IServiceAlarmsAndRules + +- *Implemented By:* IServiceAlarmsAndRules + +Service level alarms and rules using critical operations. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| regionalAvailabilityOrLatencyServerSideAlarm | aws-cdk-lib.aws_cloudwatch.IAlarm | An alarm for regional availability or latency impact of any critical operation as measured by the server-side. | +| regionalAvailabilityServerSideAlarm | aws-cdk-lib.aws_cloudwatch.IAlarm | An alarm for regional availability impact of any critical operation as measured by the server-side. | +| regionalFaultCountServerSideAlarm | aws-cdk-lib.aws_cloudwatch.IAlarm | An alarm for fault count exceeding a regional threshold for all critical operations. | +| service | IService | The service these alarms and rules are for. | +| zonalAggregateIsolatedImpactAlarms | aws-cdk-lib.aws_cloudwatch.IAlarm[] | The zonal aggregate isolated impact alarms. | +| zonalServerSideIsolatedImpactAlarms | aws-cdk-lib.aws_cloudwatch.IAlarm[] | The zonal server-side isolated impact alarms. | +| regionalAvailabilityCanaryAlarm | aws-cdk-lib.aws_cloudwatch.IAlarm | An alarm for regional availability impact of any critical operation as measured by the canary. | +| regionalAvailabilityOrLatencyCanaryAlarm | aws-cdk-lib.aws_cloudwatch.IAlarm | An alarm for regional availability or latency impact of any critical operation as measured by the canary. | + +--- + +##### `regionalAvailabilityOrLatencyServerSideAlarm`Required + +```typescript +public readonly regionalAvailabilityOrLatencyServerSideAlarm: IAlarm; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm + +An alarm for regional availability or latency impact of any critical operation as measured by the server-side. + +--- + +##### `regionalAvailabilityServerSideAlarm`Required + +```typescript +public readonly regionalAvailabilityServerSideAlarm: IAlarm; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm + +An alarm for regional availability impact of any critical operation as measured by the server-side. + +--- + +##### `regionalFaultCountServerSideAlarm`Required + +```typescript +public readonly regionalFaultCountServerSideAlarm: IAlarm; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm + +An alarm for fault count exceeding a regional threshold for all critical operations. + +--- + +##### `service`Required + +```typescript +public readonly service: IService; +``` + +- *Type:* IService + +The service these alarms and rules are for. + +--- + +##### `zonalAggregateIsolatedImpactAlarms`Required + +```typescript +public readonly zonalAggregateIsolatedImpactAlarms: IAlarm[]; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm[] + +The zonal aggregate isolated impact alarms. + +There is 1 alarm per AZ that +triggers for availability or latency impact to any critical operation in that AZ +that indicates it has isolated impact as measured by canaries or server-side. + +--- + +##### `zonalServerSideIsolatedImpactAlarms`Required + +```typescript +public readonly zonalServerSideIsolatedImpactAlarms: IAlarm[]; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm[] + +The zonal server-side isolated impact alarms. + +There is 1 alarm per AZ that triggers +on availability or atency impact to any critical operation in that AZ. These are useful +for deployment monitoring to not inadvertently fail when a canary can't contact an AZ +during a deployment. + +--- + +##### `regionalAvailabilityCanaryAlarm`Optional + +```typescript +public readonly regionalAvailabilityCanaryAlarm: IAlarm; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm + +An alarm for regional availability impact of any critical operation as measured by the canary. + +--- + +##### `regionalAvailabilityOrLatencyCanaryAlarm`Optional + +```typescript +public readonly regionalAvailabilityOrLatencyCanaryAlarm: IAlarm; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.IAlarm + +An alarm for regional availability or latency impact of any critical operation as measured by the canary. + +--- + +### IServiceMetricDetails + +- *Implemented By:* ServiceMetricDetails, IServiceMetricDetails + +Details for the defaults used in a service for metrics in one perspective, such as server side latency. + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| alarmStatistic | string | The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". | +| datapointsToAlarm | number | The number of datapoints to alarm on for latency and availability alarms. | +| evaluationPeriods | number | The number of evaluation periods for latency and availabiltiy alarms. | +| faultAlarmThreshold | number | The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. | +| faultMetricNames | string[] | The names of fault indicating metrics. | +| metricNamespace | string | The CloudWatch metric namespace for these metrics. | +| period | aws-cdk-lib.Duration | The period for the metrics. | +| successAlarmThreshold | number | The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. | +| successMetricNames | string[] | The names of success indicating metrics. | +| unit | aws-cdk-lib.aws_cloudwatch.Unit | The unit used for these metrics. | +| graphedFaultStatistics | string[] | The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | +| graphedSuccessStatistics | string[] | The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. | + +--- + +##### `alarmStatistic`Required + +```typescript +public readonly alarmStatistic: string; +``` + +- *Type:* string + +The statistic used for alarms, for availability metrics this should be "Sum", for latency metrics it could something like "p99" or "p99.9". + +--- + +##### `datapointsToAlarm`Required + +```typescript +public readonly datapointsToAlarm: number; +``` + +- *Type:* number + +The number of datapoints to alarm on for latency and availability alarms. + +--- + +##### `evaluationPeriods`Required + +```typescript +public readonly evaluationPeriods: number; +``` + +- *Type:* number + +The number of evaluation periods for latency and availabiltiy alarms. + +--- + +##### `faultAlarmThreshold`Required + +```typescript +public readonly faultAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with fault metrics, for example if measuring fault rate, the threshold may be 1, meaning you would want an alarm that triggers if the fault rate goes above 1%. + +--- + +##### `faultMetricNames`Required + +```typescript +public readonly faultMetricNames: string[]; +``` + +- *Type:* string[] + +The names of fault indicating metrics. + +--- + +##### `metricNamespace`Required + +```typescript +public readonly metricNamespace: string; +``` + +- *Type:* string + +The CloudWatch metric namespace for these metrics. + +--- + +##### `period`Required + +```typescript +public readonly period: Duration; +``` + +- *Type:* aws-cdk-lib.Duration + +The period for the metrics. + +--- + +##### `successAlarmThreshold`Required + +```typescript +public readonly successAlarmThreshold: number; +``` + +- *Type:* number + +The threshold for alarms associated with success metrics, for example if measuring success rate, the threshold may be 99, meaning you would want an alarm that triggers if success drops below 99%. + +--- + +##### `successMetricNames`Required + +```typescript +public readonly successMetricNames: string[]; +``` + +- *Type:* string[] + +The names of success indicating metrics. + +--- + +##### `unit`Required + +```typescript +public readonly unit: Unit; +``` + +- *Type:* aws-cdk-lib.aws_cloudwatch.Unit + +The unit used for these metrics. + +--- + +##### `graphedFaultStatistics`Optional + +```typescript +public readonly graphedFaultStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for faults you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +##### `graphedSuccessStatistics`Optional + +```typescript +public readonly graphedSuccessStatistics: string[]; +``` + +- *Type:* string[] +- *Default:* For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + +The statistics for successes you want to appear on dashboards, for example, with latency metrics, you might want p50, p99, and tm99. + +For availability +metrics this will typically just be "Sum". + +--- + +## Enums + +### OutlierDetectionAlgorithm + +Available algorithms for performing outlier detection. + +#### Members + +| **Name** | **Description** | +| --- | --- | +| STATIC | Defines using a static value to compare skew in faults or high latency responses. | +| CHI_SQUARED | Uses the chi squared statistic to determine if there is a statistically significant skew in fault rate or high latency distribution. | +| Z_SCORE | Uses z-score to determine if the skew in faults or high latency respones exceeds a defined number of standard devations. | +| IQR | Uses Interquartile Range Method to determine an outlier for faults or latency. | +| MAD | Median Absolute Deviation (MAD) to determine an outlier for faults or latency. | + +--- + +##### `STATIC` + +Defines using a static value to compare skew in faults or high latency responses. + +A good default threshold for this is .7 meaning one AZ +is responsible for 70% of the total errors or high latency responses + +--- + + +##### `CHI_SQUARED` + +Uses the chi squared statistic to determine if there is a statistically significant skew in fault rate or high latency distribution. + +A normal default threshold for this is 0.05, which means there is a 5% or +less chance of the skew in errors or high latency responses occuring + +--- + + +##### `Z_SCORE` + +Uses z-score to determine if the skew in faults or high latency respones exceeds a defined number of standard devations. + +A good default threshold value for this is 2, meaning the outlier value is outside +95% of the normal distribution. Using 3 means the outlier is outside 99.7% of +the normal distribution. + +--- + + +##### `IQR` + +Uses Interquartile Range Method to determine an outlier for faults or latency. + +No threshold is required for this method and will be ignored + +--- + + +##### `MAD` + +Median Absolute Deviation (MAD) to determine an outlier for faults or latency. + +A common default value threshold 3 + +--- diff --git a/README.md b/README.md index b3fa7dd..3d80f19 100644 --- a/README.md +++ b/README.md @@ -1 +1,175 @@ -# replace this \ No newline at end of file +# multi-az-observability +This is a CDK construct for multi-AZ observability to help detect single-AZ impairments. This is currently an `alpha` version, but is being used in the AWS [Advanced Multi-AZ Resilience Patterns](https://catalog.workshops.aws/multi-az-gray-failures/en-US) workshop. + +There is a lot of available information to think through and combine to provide signals about single-AZ impact. To simplify the setup and use reasonable defaults, this construct (available in TypeScript, Go, Python, and .NET [Java coming soon]) sets up the necessary observability. To use the CDK construct, you first define your service like this: + +```csharp +var wildRydesService = new Service(new ServiceProps(){ + ServiceName = "WildRydes", + BaseUrl = "http://www.example.com", + FaultCountThreshold = 25, + AvailabilityZoneNames = vpc.AvailabilityZones, + Period = Duration.Seconds(60), + LoadBalancer = loadBalancer, + DefaultAvailabilityMetricDetails = new ServiceMetricDetails(new ServiceMetricDetailsProps() { + AlarmStatistic = "Sum", + DatapointsToAlarm = 3, + EvaluationPeriods = 5, + FaultAlarmThreshold = 1, + FaultMetricNames = new string[] { "Fault", "Error" }, + GraphedFaultStatistics = new string[] { "Sum" }, + GraphedSuccessStatistics = new string[] { "Sum" }, + MetricNamespace = metricsNamespace, + Period = Duration.Seconds(60), + SuccessAlarmThreshold = 99, + SuccessMetricNames = new string[] {"Success"}, + Unit = Unit.COUNT, + }), + DefaultLatencyMetricDetails = new ServiceMetricDetails(new ServiceMetricDetailsProps(){ + AlarmStatistic = "p99", + DatapointsToAlarm = 3, + EvaluationPeriods = 5, + FaultAlarmThreshold = 1, + FaultMetricNames = new string[] { "FaultLatency" }, + GraphedFaultStatistics = new string[] { "p50" }, + GraphedSuccessStatistics = new string[] { "p50", "p99", "tm50", "tm99" }, + MetricNamespace = metricsNamespace, + Period = Duration.Seconds(60), + SuccessAlarmThreshold = 100, + SuccessMetricNames = new string[] {"SuccessLatency"}, + Unit = Unit.MILLISECONDS, + }), + DefaultContributorInsightRuleDetails = new ContributorInsightRuleDetails(new ContributorInsightRuleDetailsProps() { + AvailabilityZoneIdJsonPath = azIdJsonPath, + FaultMetricJsonPath = faultMetricJsonPath, + InstanceIdJsonPath = instanceIdJsonPath, + LogGroups = serverLogGroups, + OperationNameJsonPath = operationNameJsonPath, + SuccessLatencyMetricJsonPath = successLatencyMetricJsonPath + }), + CanaryTestProps = new AddCanaryTestProps() { + RequestCount = 10, + LoadBalancer = loadBalancer, + Schedule = "rate(1 minute)", + NetworkConfiguration = new NetworkConfigurationProps() { + Vpc = vpc, + SubnetSelection = new SubnetSelection() { SubnetType = SubnetType.PRIVATE_ISOLATED } + } + } +}); +wildRydesService.AddOperation(new Operation(new OperationProps() { + OperationName = "Signin", + Path = "/signin", + Service = wildRydesService, + Critical = true, + HttpMethods = new string[] { "GET" }, + ServerSideAvailabilityMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Signin", + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Signin"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultAvailabilityMetricDetails), + ServerSideLatencyMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Signin", + SuccessAlarmThreshold = 150, + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Signin"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultLatencyMetricDetails), + CanaryTestLatencyMetricsOverride = new CanaryTestMetricsOverride(new CanaryTestMetricsOverrideProps() { + SuccessAlarmThreshold = 250 + }) +})); +wildRydesService.AddOperation(new Operation(new OperationProps() { + OperationName = "Pay", + Path = "/pay", + Service = wildRydesService, + HttpMethods = new string[] { "GET" }, + Critical = true, + ServerSideAvailabilityMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Pay", + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Pay"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultAvailabilityMetricDetails), + ServerSideLatencyMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Pay", + SuccessAlarmThreshold = 200, + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Pay"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultLatencyMetricDetails), + CanaryTestLatencyMetricsOverride = new CanaryTestMetricsOverride(new CanaryTestMetricsOverrideProps() { + SuccessAlarmThreshold = 300 + }) +})); +wildRydesService.AddOperation(new Operation(new OperationProps() { + OperationName = "Ride", + Path = "/ride", + Service = wildRydesService, + HttpMethods = new string[] { "GET" }, + Critical = true, + ServerSideAvailabilityMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Ride", + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Ride"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultAvailabilityMetricDetails), + ServerSideLatencyMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Ride", + SuccessAlarmThreshold = 350, + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Ride"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultLatencyMetricDetails), + CanaryTestLatencyMetricsOverride = new CanaryTestMetricsOverride(new CanaryTestMetricsOverrideProps() { + SuccessAlarmThreshold = 550 + }) +})); +wildRydesService.AddOperation(new Operation(new OperationProps() { + OperationName = "Home", + Path = "/home", + Service = wildRydesService, + HttpMethods = new string[] { "GET" }, + Critical = true, + ServerSideAvailabilityMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Home", + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Ride"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultAvailabilityMetricDetails), + ServerSideLatencyMetricDetails = new OperationMetricDetails(new OperationMetricDetailsProps() { + OperationName = "Home", + SuccessAlarmThreshold = 100, + MetricDimensions = new MetricDimensions(new Dictionary {{ "Operation", "Ride"}}, "AZ-ID", "Region") + }, wildRydesService.DefaultLatencyMetricDetails), + CanaryTestLatencyMetricsOverride = new CanaryTestMetricsOverride(new CanaryTestMetricsOverrideProps() { + SuccessAlarmThreshold = 200 + }) +})); +``` + +Then you provide that service definition to the CDK construct. + +```csharp +InstrumentedServiceMultiAZObservability multiAvailabilityZoneObservability = new InstrumentedServiceMultiAZObservability(this, "MultiAZObservability", new InstrumentedServiceMultiAZObservabilityProps() { + Service = wildRydesService, + CreateDashboards = true, + Interval = Duration.Minutes(60), // The interval for the dashboard + OutlierDetectionAlgorithm = OutlierDetectionAlgorithm.STATIC +}); +``` + +You define some characteristics of the service, default values for metrics and alarms, and then add operations as well as any overrides for default values that you need. The construct can also automatically create synthetic canaries that test each operation with a very simple HTTP check, or you can configure your own synthetics and just tell the construct about the metric details and optionally log files. This creates metrics, alarms, and dashboards that can be used to detect single-AZ impact. + +If you don't have service specific logs and custom metrics with per-AZ dimensions, you can still use the construct to evaluate ALB and NAT Gateway metrics to find single AZ faults. + +```csharp +BasicServiceMultiAZObservability multiAvailabilityZoneObservability = new BasicServiceMultiAZObservability(this, "MultiAZObservability", new BasicServiceMultiAZObservabilityProps() { + ApplicationLoadBalancers = new IApplicationLoadBalancer[] { loadBalancer }, + NatGateways = new Dictionary() { + { "us-east-1a", natGateway1}, + { "us-east-1b", natGateway2}, + { "us-east-1c", natGateway3}, + }, + CreateDashboard = true, + OutlierDetectionAlgorithm = OutlierDetectionAlgorithm.STATIC, + FaultCountPercentageThreshold = 1.0, // The fault rate to alarm on for errors seen from the ALBs in the same AZ + PacketLossImpactPercentageThreshold = 0.01, // The percentage of packet loss to alarm on for the NAT Gateways in the same AZ + ServiceName = "WildRydes", + Period = Duration.Seconds(60), // The period for metric evaluation + Interval = Duration.Minutes(60) // The interval for the dashboards + EvaluationPeriods = 5, + DatapointsToAlarm = 3 +}); +``` + +If you provide a load balancer, the construct assumes it is deployed in each AZ of the VPC the load balancer is associated with and will look for HTTP metrics using those AZs as dimensions. + +Both options support running workloads on EC2, ECS, Lambda, and EKS. diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..2bd7694 --- /dev/null +++ b/cdk.json @@ -0,0 +1,68 @@ +{ + "app": "npx ts-node --prefer-ts-exts test/synth-test.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true + } +} diff --git a/package.json b/package.json index 8069370..7bd483f 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,24 @@ { - "name": "cdk-multi-az-observability", + "name": "@cdklabs/multi-az-observability", + "description": "A CDK construct for implementing multi-AZ observability to detect single AZ impairments", "repository": { "type": "git", - "url": "https://github.com/cdklabs/cdk-multi-az-observability.git" + "url": "https://github.com/cdklabs/cdk-multi-az-observability/" }, "scripts": { "build": "npx projen build", + "build-assets": "npx projen build-assets", + "build-canary-function": "npx projen build-canary-function", + "build-monitoring-layer": "npx projen build-monitoring-layer", + "build-outlier-detection-function": "npx projen build-outlier-detection-function", + "build-scipy-layer": "npx projen build-scipy-layer", + "bump": "npx projen bump", "clobber": "npx projen clobber", "compat": "npx projen compat", "compile": "npx projen compile", "default": "npx projen default", "docgen": "npx projen docgen", "eject": "npx projen eject", - "eslint": "npx projen eslint", "integ": "npx projen integ", "integ:update": "npx projen integ:update", "package": "npx projen package", @@ -25,9 +31,11 @@ "post-compile": "npx projen post-compile", "post-upgrade": "npx projen post-upgrade", "pre-compile": "npx projen pre-compile", + "release": "npx projen release", "rosetta:extract": "npx projen rosetta:extract", "test": "npx projen test", "test:watch": "npx projen test:watch", + "unbump": "npx projen unbump", "upgrade": "npx projen upgrade", "upgrade-cdklabs-projen-project-types": "npx projen upgrade-cdklabs-projen-project-types", "upgrade-dev-deps": "npx projen upgrade-dev-deps", @@ -42,51 +50,61 @@ "devDependencies": { "@aws-cdk/integ-runner": "latest", "@aws-cdk/integ-tests-alpha": "latest", - "@stylistic/eslint-plugin": "^2", "@types/jest": "^29.5.14", "@types/node": "^18", - "@typescript-eslint/eslint-plugin": "^8", - "@typescript-eslint/parser": "^8", - "aws-cdk-lib": "2.1.0", + "aws-cdk-lib": "2.173.1", + "cdk-nag": "^2.34.23", "cdklabs-projen-project-types": "^0.1.211", + "commit-and-tag-version": "^12", "constructs": "10.0.5", - "eslint": "^9", - "eslint-import-resolver-typescript": "^3.7.0", - "eslint-plugin-import": "^2.31.0", "jest": "^29.7.0", "jest-junit": "^15", - "jsii": "~5.2", + "jsii": "~5.5.0", "jsii-diff": "^1.105.0", "jsii-docgen": "^10.5.0", "jsii-pacmak": "^1.105.0", - "jsii-rosetta": "^5.6.2", + "jsii-rosetta": "~5.5.0", + "prettier": "^3.3.3", "projen": "^0.90.5", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", - "typescript": "^5.7.2" + "typescript": "^5.6.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.1.0", + "aws-cdk-lib": "^2.173.1", "constructs": "^10.0.5" }, "keywords": [ - "cdk" + "aws-cdk", + "cdk", + "cloudwatch", + "monitoring", + "multi-az", + "observability", + "resilience" ], "engines": { "node": ">= 18.12.0" }, "main": "lib/index.js", "license": "Apache-2.0", + "homepage": "https://github.com/cdklabs/cdk-multi-az-observability", "publishConfig": { "access": "public" }, "version": "0.0.0", "jest": { "coverageProvider": "v8", + "roots": [ + "/test" + ], "testMatch": [ + "**/*.test.ts", "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", "/@(src|test)/**/__tests__/**/*.ts?(x)", "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", + "/@(projenrc)/**/__tests__/**/*.ts?(x)", + "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", "/@(projenrc)/**/__tests__/**/*.ts?(x)" ], "clearMocks": true, @@ -132,22 +150,23 @@ "outdir": "dist", "targets": { "java": { - "package": "io.github.cdklabs.cdk.multi.az.observability", + "package": "io.github.cdklabs.multiazobservability", "maven": { "groupId": "io.github.cdklabs", "artifactId": "cdk-multi-az-observability" } }, "python": { - "distName": "cdk-multi-az-observability", - "module": "cdk_multi_az_observability" + "distName": "cdklabs.multi-az-observability", + "module": "cdklabs.multi_az_observability" }, "dotnet": { - "namespace": "CdklabsCdkMultiAzObservability", - "packageId": "CdklabsCdkMultiAzObservability" + "namespace": "Cdklabs.MultiAZObservability", + "packageId": "Cdklabs.MultiAZObservability" }, "go": { - "moduleName": "github.com/cdklabs/cdk-multi-az-observability-go" + "moduleName": "github.com/cdklabs/cdk-multi-az-observability-go", + "packageName": "multi-az-observability" } }, "tsc": { diff --git a/src/alarmsandrules/AvailabilityAndLatencyAlarmsAndRules.ts b/src/alarmsandrules/AvailabilityAndLatencyAlarmsAndRules.ts new file mode 100644 index 0000000..02081e6 --- /dev/null +++ b/src/alarmsandrules/AvailabilityAndLatencyAlarmsAndRules.ts @@ -0,0 +1,1125 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration, Fn } from 'aws-cdk-lib'; +import { + IAlarm, + Alarm, + IMetric, + CompositeAlarm, + AlarmRule, + MathExpression, + CfnInsightRule, + ComparisonOperator, + TreatMissingData, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { CfnNatGateway } from 'aws-cdk-lib/aws-ec2'; +import { + BaseLoadBalancer, + IApplicationLoadBalancer, + ILoadBalancerV2, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { Construct, IConstruct } from 'constructs'; +import { IContributionDefinition, InsightRuleBody } from './InsightRuleBody'; +import { IAvailabilityZoneMapper } from '../azmapper/IAvailabilityZoneMapper'; +import { RegionalAvailabilityMetrics } from '../metrics/RegionalAvailabilityMetrics'; +import { RegionalLatencyMetrics } from '../metrics/RegionalLatencyMetrics'; +import { ZonalAvailabilityMetrics } from '../metrics/ZonalAvailabilityMetrics'; +import { ZonalLatencyMetrics } from '../metrics/ZonalLatencyMetrics'; +import { IContributorInsightRuleDetails } from '../services/IContributorInsightRuleDetails'; +import { IOperation } from '../services/IOperation'; +import { IOperationMetricDetails } from '../services/IOperationMetricDetails'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; +import { LatencyMetricType } from '../utilities/LatencyMetricType'; +import { OutlierDetectionAlgorithm } from '../utilities/OutlierDetectionAlgorithm'; + +/** + * Class used to create availability and latency alarms and Contributor Insight rules + */ +export class AvailabilityAndLatencyAlarmsAndRules { + /** + * Creates a zonal availability alarm + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @returns + */ + static createZonalAvailabilityAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + counter: number, + nameSuffix?: string, + ): IAlarm { + return new Alarm( + scope, + metricDetails.operationName + 'AZ' + counter + 'AvailabilityAlarm', + { + alarmName: + availabilityZoneId + + '-' + + metricDetails.operationName.toLowerCase() + + '-success-rate' + + nameSuffix, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD, + threshold: metricDetails.successAlarmThreshold, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + metric: ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + label: availabilityZoneId + ' availability', + metricDetails: metricDetails, + metricType: AvailabilityMetricType.SUCCESS_RATE, + }), + }, + ); + } + + /** + * Creates a zonal latency alarm + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @returns + */ + static createZonalLatencyAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + counter: number, + nameSuffix?: string, + ): IAlarm { + return new Alarm( + scope, + metricDetails.operationName + 'AZ' + counter + 'LatencyAlarm', + { + alarmName: + availabilityZoneId + + '-' + + metricDetails.operationName.toLowerCase() + + '-success-latency' + + nameSuffix, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: metricDetails.successAlarmThreshold, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + metric: ZonalLatencyMetrics.createZonalAverageLatencyMetric({ + availabilityZoneId: availabilityZoneId, + label: + availabilityZoneId + + ' ' + + metricDetails.alarmStatistic + + ' latency', + metricDetails: metricDetails, + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: metricDetails.alarmStatistic, + }), + }, + ); + } + + /** + * Creates a composite alarm when either latency or availability is breached in the Availabiltiy Zone + * @param scope + * @param operation + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @param zonalAvailabilityAlarm + * @param zonalLatencyAlarm + * @returns + */ + static createZonalAvailabilityOrLatencyCompositeAlarm( + scope: Construct, + operationName: string, + availabilityZoneId: string, + counter: number, + zonalAvailabilityAlarm: IAlarm, + zonalLatencyAlarm: IAlarm, + nameSuffix?: string, + ): IAlarm { + return new CompositeAlarm(scope, 'AZ' + counter + 'ZonalImpactAlarm', { + actionsEnabled: false, + alarmDescription: + availabilityZoneId + + ' has latency or availability impact. This does not indicate it is an outlier and shows isolated impact.', + compositeAlarmName: + availabilityZoneId + + `-${operationName.toLowerCase()}-impact-aggregate-alarm` + + nameSuffix, + alarmRule: AlarmRule.anyOf(zonalAvailabilityAlarm, zonalLatencyAlarm), + }); + } + + /** + * An alarm that compares error rate in this AZ to the overall region error based only on metric data + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @param outlierThreshold + * @returns + */ + static createZonalFaultRateStaticOutlierAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + counter: number, + outlierThreshold: number, + nameSuffix?: string, + ): IAlarm { + let zonalFaults: IMetric = + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + metricDetails: metricDetails, + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: 'a', + }); + + let regionalFaults: IMetric = + RegionalAvailabilityMetrics.createRegionalAvailabilityMetric({ + metricDetails: metricDetails, + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: 'b', + }); + + return new Alarm(scope, 'AZ' + counter + 'IsolatedImpactAlarmStatic', { + alarmName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-static-majority-errors-impact` + + nameSuffix, + metric: new MathExpression({ + expression: 'IF(m2 > 0, (m1 / m2), 0)', + usingMetrics: { + m1: zonalFaults, + m2: regionalFaults, + }, + period: metricDetails.period, + label: availabilityZoneId + ' percent faults', + }), + threshold: outlierThreshold, + comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + treatMissingData: TreatMissingData.NOT_BREACHING, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + }); + } + + static createZonalFaultRateOutlierAlarm( + scope: IConstruct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + allAvailabilityZoneIds: string[], + outlierThreshold: number, + outlierDetectionFunction: IFunction, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm, + counter: number, + nameSuffix?: string, + ): IAlarm { + let metricDimensions: { [key: string]: { [key: string]: string }[] } = {}; + + allAvailabilityZoneIds.forEach((azId: string) => { + metricDimensions[azId] = [ + metricDetails.metricDimensions.zonalDimensions( + azId, + Fn.ref('AWS::Region'), + ), + ]; + }); + + let str: string = JSON.stringify(metricDimensions) + .replace(/[\\]/g, '\\\\') + .replace(/[\"]/g, '\\"') + .replace(/[\/]/g, '\\/') + .replace(/[\b]/g, '\\b') + .replace(/[\f]/g, '\\f') + .replace(/[\n]/g, '\\n') + .replace(/[\r]/g, '\\r') + .replace(/[\t]/g, '\\t'); + + let outlierMetrics: IMetric = new MathExpression({ + expression: + `MAX(LAMBDA("${outlierDetectionFunction.functionName}",` + + `"${outlierDetectionAlgorithm.toString()}",` + + `"${outlierThreshold}",` + + `"${availabilityZoneId}",` + + `"${str}",` + + `"${metricDetails.metricNamespace}",` + + `"${metricDetails.faultMetricNames.join(':')}",` + + '"Sum",' + + '"Count"' + + '))', + period: Duration.seconds(60), + }); + + return new Alarm( + scope, + 'AZ' + counter + 'FaultIsolatedImpactAlarmOutlier', + { + alarmName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-majority-errors-impact` + + nameSuffix, + metric: outlierMetrics, + threshold: 1, + comparisonOperator: + ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + treatMissingData: TreatMissingData.NOT_BREACHING, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + }, + ); + } + + static createZonalFaultRateOutlierAlarmForAlb( + scope: IConstruct, + loadBalancers: IApplicationLoadBalancer[], + availabilityZoneId: string, + outlierThreshold: number, + outlierDetectionFunction: IFunction, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm, + azMapper: IAvailabilityZoneMapper, + counter: number, + evaluationPeriods: number, + datapointsToAlarm: number, + nameSuffix?: string, + ): IAlarm { + let metricDimensions: { [key: string]: { [key: string]: string }[] } = {}; + + loadBalancers.forEach((x) => { + x.vpc?.availabilityZones.forEach((az) => { + let azId = azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + az.substring(az.length - 1), + ); + if (!(azId in metricDimensions)) { + metricDimensions[azId] = []; + } + + metricDimensions[azId].push({ + AvailabilityZone: az, + LoadBalancer: (x as ILoadBalancerV2 as BaseLoadBalancer) + .loadBalancerFullName, + }); + }); + }); + + let str: string = JSON.stringify(metricDimensions) + .replace(/[\\]/g, '\\\\') + .replace(/[\"]/g, '\\"') + .replace(/[\/]/g, '\\/') + .replace(/[\b]/g, '\\b') + .replace(/[\f]/g, '\\f') + .replace(/[\n]/g, '\\n') + .replace(/[\r]/g, '\\r') + .replace(/[\t]/g, '\\t'); + + let outlierMetrics: IMetric = new MathExpression({ + expression: + `MAX(LAMBDA("${outlierDetectionFunction.functionName}",` + + `"${outlierDetectionAlgorithm.toString()}",` + + `"${outlierThreshold}",` + + `"${availabilityZoneId}",` + + `"${str}",` + + '"AWS/ApplicationELB",' + + '"HTTPCode_ELB_5XX_Count:HTTPCode_Target_5XX_Count",' + + '"Sum",' + + '"Count"' + + '))', + period: Duration.seconds(60), + }); + + return new Alarm(scope, 'AZ' + counter + 'AlbIsolatedImpactAlarmOutlier', { + alarmName: + availabilityZoneId + '-alb-majority-errors-impact' + nameSuffix, + metric: outlierMetrics, + threshold: 1, + comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + treatMissingData: TreatMissingData.NOT_BREACHING, + evaluationPeriods: evaluationPeriods, + datapointsToAlarm: datapointsToAlarm, + }); + } + + static createZonalFaultRateOutlierAlarmForNatGW( + scope: IConstruct, + natGateways: { [key: string]: CfnNatGateway[] }, + availabilityZoneId: string, + outlierThreshold: number, + outlierDetectionFunction: IFunction, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm, + azMapper: IAvailabilityZoneMapper, + counter: number, + evaluationPeriods: number, + datapointsToAlarm: number, + nameSuffix?: string, + ): IAlarm { + let metricDimensions: { [key: string]: { [key: string]: string }[] } = {}; + + Object.keys(natGateways).forEach((az) => { + let azId = azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + az.substring(az.length - 1), + ); + + if (!(azId in metricDimensions)) { + metricDimensions[azId] = []; + } + + natGateways[az].forEach((natgw) => { + metricDimensions[azId].push({ + NatGatewayId: natgw.attrNatGatewayId, + }); + }); + }); + + let str: string = JSON.stringify(metricDimensions) + .replace(/[\\]/g, '\\\\') + .replace(/[\"]/g, '\\"') + .replace(/[\/]/g, '\\/') + .replace(/[\b]/g, '\\b') + .replace(/[\f]/g, '\\f') + .replace(/[\n]/g, '\\n') + .replace(/[\r]/g, '\\r') + .replace(/[\t]/g, '\\t'); + + let outlierMetrics: IMetric = new MathExpression({ + expression: + `MAX(LAMBDA("${outlierDetectionFunction.functionName}",` + + `"${outlierDetectionAlgorithm.toString()}",` + + `"${outlierThreshold}",` + + `"${availabilityZoneId}",` + + `"${str}",` + + '"AWS/NATGateway",' + + '"PacketsDropCount",' + + '"Sum",' + + '"Count"' + + '))', + period: Duration.seconds(60), + }); + + return new Alarm( + scope, + 'AZ' + counter + 'NatGWIsolatedImpactAlarmOutlier', + { + alarmName: + availabilityZoneId + '-nat-gw-majority-errors-impact' + nameSuffix, + metric: outlierMetrics, + threshold: 1, + comparisonOperator: + ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + treatMissingData: TreatMissingData.NOT_BREACHING, + evaluationPeriods: evaluationPeriods, + datapointsToAlarm: datapointsToAlarm, + }, + ); + } + + static createZonalHighLatencyOutlierAlarm( + scope: IConstruct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + allAvailabilityZoneIds: string[], + outlierThreshold: number, + outlierDetectionFunction: IFunction, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm, + counter: number, + nameSuffix?: string, + ): IAlarm { + let metricDimensions: { [key: string]: { [key: string]: string }[] } = {}; + + allAvailabilityZoneIds.forEach((azId: string) => { + metricDimensions[azId] = [ + metricDetails.metricDimensions.zonalDimensions( + azId, + Fn.ref('AWS::Region'), + ), + ]; + }); + + let str: string = JSON.stringify(metricDimensions) + .replace(/[\\]/g, '\\\\') + .replace(/[\"]/g, '\\"') + .replace(/[\/]/g, '\\/') + .replace(/[\b]/g, '\\b') + .replace(/[\f]/g, '\\f') + .replace(/[\n]/g, '\\n') + .replace(/[\r]/g, '\\r') + .replace(/[\t]/g, '\\t'); + + let outlierMetrics: IMetric = new MathExpression({ + expression: + `MAX(LAMBDA("${outlierDetectionFunction.functionName}",` + + `"${outlierDetectionAlgorithm.toString()}",` + + `"${outlierThreshold}",` + + `"${availabilityZoneId}",` + + `"${str}",` + + `"${metricDetails.metricNamespace}",` + + `"${metricDetails.successMetricNames.join(':')}",` + + `"TC(${metricDetails.successAlarmThreshold}:)",` + + '"Milliseconds"' + + '))', + period: Duration.seconds(60), + }); + + return new Alarm( + scope, + metricDetails.operationName + + 'AZ' + + counter + + 'LatencyIsolatedImpactAlarmOutlier', + { + alarmName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-majority-high-latency-impact` + + nameSuffix, + metric: outlierMetrics, + threshold: 1, + comparisonOperator: + ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + treatMissingData: TreatMissingData.NOT_BREACHING, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + }, + ); + } + + static createZonalHighLatencyStaticOutlierAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + counter: number, + outlierThreshold: number, + nameSuffix?: string, + ): IAlarm { + let zonalLatency: IMetric = + ZonalLatencyMetrics.createZonalCountLatencyMetric({ + availabilityZoneId: availabilityZoneId, + label: + availabilityZoneId + + '-' + + metricDetails.operationName + + '-high-latency-requests', + metricDetails: metricDetails, + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: `TC(${metricDetails.successAlarmThreshold}:)`, + keyPrefix: 'a', + }); + + let regionalLatency: IMetric = + RegionalLatencyMetrics.createRegionalLatencyCountMetric({ + label: + Fn.ref('AWS::Region') + + '-' + + metricDetails.operationName + + '-high-latency-requests', + metricDetails: metricDetails, + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: `TC(${metricDetails.successAlarmThreshold}:)`, + keyPrefix: 'b', + }); + + return new Alarm( + scope, + metricDetails.operationName + + 'AZ' + + counter + + 'IsolatedImpactAlarmStatic', + { + alarmName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-static-majority-high-latency-impact` + + nameSuffix, + metric: new MathExpression({ + expression: 'IF(m2 > 0, (m1 / m2), 0)', + usingMetrics: { + m1: zonalLatency, + m2: regionalLatency, + }, + period: metricDetails.period, + label: availabilityZoneId + ' percent high latency requests', + }), + threshold: outlierThreshold, + comparisonOperator: + ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + treatMissingData: TreatMissingData.NOT_BREACHING, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + }, + ); + } + + /** + * An insight rule that calculates how many instances are responding to requests in + * the specified AZ. Only useful for server-side metrics since the canary doesn't record instance id metrics. + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param logGroups + * @param nameSuffix + * @param counter + * @param instanceIdPath + * @param operationNamePath + * @param availabilityZoneIdPath + * @returns + */ + static createServerSideInstancesHandlingRequestsInThisAZRule( + scope: Construct, + operationName: string, + availabilityZoneId: string, + ruleDetails: IContributorInsightRuleDetails, + counter: number, + nameSuffix?: string, + ): CfnInsightRule { + let ruleBody = new InsightRuleBody(); + ruleBody.logGroupNames = ruleDetails.logGroups.map((x) => x.logGroupName); + ruleBody.aggregateOn = 'Count'; + ruleBody.logFormat = 'JSON'; + + ruleBody.contribution = { + keys: [ruleDetails.instanceIdJsonPath], + filters: [ + { + Match: ruleDetails.availabilityZoneIdJsonPath, + In: [availabilityZoneId], + }, + { + Match: ruleDetails.operationNameJsonPath, + In: [operationName], + }, + ], + } as unknown as IContributionDefinition; + + return new CfnInsightRule(scope, 'AZ' + counter + 'InstancesInTheAZRule', { + ruleName: + availabilityZoneId + + `-${operationName.toLowerCase()}-instances-in-the-az` + + nameSuffix, + ruleState: 'ENABLED', + ruleBody: ruleBody.toJson(), + }); + } + + /** + * An insight rule that calculates the instances contributing to errors + * in this AZ. Only useful for server-side metrics since the canary doesn't record instance id metrics. + * @param scope + * @param operation + * @param availabilityZoneId + * @param logGroups + * @param nameSuffix + * @param counter + * @param instanceIdPath + * @param operationNamePath + * @param availabilityZoneIdPath + * @param errorMetricPath + * @returns + */ + static createServerSideInstanceFaultContributorsInThisAZRule( + scope: Construct, + operationName: string, + availabilityZoneId: string, + ruleDetails: IContributorInsightRuleDetails, + counter: number, + nameSuffix?: string, + ): CfnInsightRule { + let ruleBody = new InsightRuleBody(); + ruleBody.logGroupNames = ruleDetails.logGroups.map((x) => x.logGroupName); + ruleBody.aggregateOn = 'Count'; + ruleBody.logFormat = 'JSON'; + ruleBody.contribution = { + keys: [ruleDetails.instanceIdJsonPath], + filters: [ + { + Match: ruleDetails.availabilityZoneIdJsonPath, + In: [availabilityZoneId], + }, + { + Match: ruleDetails.operationNameJsonPath, + In: [operationName], + }, + { + Match: ruleDetails.faultMetricJsonPath, + GreaterThan: 0, + }, + ], + } as unknown as IContributionDefinition; + + return new CfnInsightRule( + scope, + 'AZ' + counter + 'InstanceErrorContributionRule', + { + ruleName: + availabilityZoneId + + `-${operationName.toLowerCase()}-per-instance-faults` + + nameSuffix, + ruleState: 'ENABLED', + ruleBody: ruleBody.toJson(), + }, + ); + } + + /** + * An insight rule that calculates instances contributing to high latency in this AZ. Only + * useful for server-side metrics since the canary doesn't record instance id metrics. + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param logGroups + * @param nameSuffix + * @param counter + * @returns + */ + static createServerSideInstanceHighLatencyContributorsInThisAZRule( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + ruleDetails: IContributorInsightRuleDetails, + counter: number, + nameSuffix?: string, + ): CfnInsightRule { + let ruleBody = new InsightRuleBody(); + ruleBody.logGroupNames = ruleDetails.logGroups.map((x) => x.logGroupName); + ruleBody.aggregateOn = 'Count'; + ruleBody.logFormat = 'JSON'; + ruleBody.contribution = { + keys: [ruleDetails.instanceIdJsonPath], + filters: [ + { + Match: ruleDetails.availabilityZoneIdJsonPath, + In: [availabilityZoneId], + }, + { + Match: ruleDetails.operationNameJsonPath, + In: [metricDetails.operationName], + }, + { + Match: ruleDetails.successLatencyMetricJsonPath, + GreaterThan: metricDetails.successAlarmThreshold, + }, + ], + } as unknown as IContributionDefinition; + + return new CfnInsightRule( + scope, + 'AZ' + counter + 'LatencyContributorsRule', + { + ruleName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-per-instance-high-latency` + + nameSuffix, + ruleState: 'ENABLED', + ruleBody: ruleBody.toJson(), + }, + ); + } + + /** + * An alarm that indicates some percentage of the instances in this AZ are producing errors. Only + * useful for server-side metrics since the canary doesn't record instance id metrics. + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @param outlierThreshold + * @param instanceFaultRateContributorsInThisAZ + * @param instancesHandlingRequestsInThisAZ + * @returns + */ + static createServerSideZonalMoreThanOneInstanceProducingFaultsAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + counter: number, + outlierThreshold: number, + instanceFaultRateContributorsInThisAZ: CfnInsightRule, + instancesHandlingRequestsInThisAZ: CfnInsightRule, + nameSuffix?: string, + ): IAlarm { + return new Alarm(scope, 'AZ' + counter + 'MoreThanOneAlarmForErrors', { + alarmName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-multiple-instances-faults` + + nameSuffix, + metric: new MathExpression({ + expression: `INSIGHT_RULE_METRIC(\"${instanceFaultRateContributorsInThisAZ.attrRuleName}\", \"UniqueContributors\") / INSIGHT_RULE_METRIC(\"${instancesHandlingRequestsInThisAZ.attrRuleName}\", \"UniqueContributors\")`, + period: metricDetails.period, + }), + evaluationPeriods: metricDetails.evaluationPeriods, + threshold: outlierThreshold, + comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + datapointsToAlarm: metricDetails.datapointsToAlarm, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + }); + } + + /** + * An alarm indicating more than some percentage of instances in this AZ + * are contributing to high latency. Only useful for server-side metrics since + * the canary doesn't record instance id metrics. + * @param scope + * @param metricDetails + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @param outlierThreshold + * @param instanceHighLatencyContributorsInThisAZ + * @param instancesHandlingRequestsInThisAZ + * @returns + */ + static createServerSideZonalMoreThanOneInstanceProducingHighLatencyAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + availabilityZoneId: string, + counter: number, + outlierThreshold: number, + instanceHighLatencyContributorsInThisAZ: CfnInsightRule, + instancesHandlingRequestsInThisAZ: CfnInsightRule, + nameSuffix?: string, + ): IAlarm { + return new Alarm(scope, 'AZ' + counter + 'MoreThanOneAlarmForHighLatency', { + alarmName: + availabilityZoneId + + `-${metricDetails.operationName.toLowerCase()}-multiple-instances-high-latency` + + nameSuffix, + metric: new MathExpression({ + expression: `INSIGHT_RULE_METRIC(\"${instanceHighLatencyContributorsInThisAZ.attrRuleName}\", \"UniqueContributors\") / INSIGHT_RULE_METRIC(\"${instancesHandlingRequestsInThisAZ.attrRuleName}\", \"UniqueContributors\")`, + period: metricDetails.period, + }), + evaluationPeriods: metricDetails.evaluationPeriods, + threshold: outlierThreshold, + comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + datapointsToAlarm: metricDetails.datapointsToAlarm, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + }); + } + + /** + * An alarm that indicates this AZ as an outlier + * for availability or latency. This does not ensure that the errors + * or latency originate from more than one instance. + * @param scope + * @param operation + * @param availabilityZoneId + * @param logGroups + * @param nameSuffix + * @param counter + * @param azIsOutlierForFaultsAlarm + * @param availabilityImpactAlarm + * @param azIsOutlierForLatencyAlarm + * @param latencyImpactAlarm + * @returns + */ + static createCanaryIsolatedAZImpactAlarm( + scope: Construct, + operationName: string, + availabilityZoneId: string, + counter: number, + azIsOutlierForFaultsAlarm: IAlarm, + availabilityImpactAlarm: IAlarm, + azIsOutlierForLatencyAlarm: IAlarm, + latencyImpactAlarm: IAlarm, + nameSuffix?: string, + ): IAlarm { + return new CompositeAlarm( + scope, + operationName + 'AZ' + counter + 'IsolatedImpactAlarm' + nameSuffix, + { + compositeAlarmName: + availabilityZoneId + + `-${operationName.toLowerCase()}-isolated-impact-alarm` + + nameSuffix, + alarmRule: AlarmRule.anyOf( + AlarmRule.allOf(azIsOutlierForFaultsAlarm, availabilityImpactAlarm), + AlarmRule.allOf(azIsOutlierForLatencyAlarm, latencyImpactAlarm), + ), + actionsEnabled: false, + }, + ); + } + + /** + * Creates the server side alarm to identify isolated single AZ + * impact meaning that this one AZ is affected and the others aren't + * @param scope + * @param operation + * @param availabilityZoneId + * @param nameSuffix + * @param counter + * @param azIsOutlierForFaultsAlarm + * @param availabilityImpactAlarm + * @param moreThanOneInstanceContributingToFaults + * @param azIsOutlierForLatencyAlarm + * @param latencyImpactAlarm + * @param moreThanOneInstanceContributingToLatency + * @returns + */ + static createServerSideIsolatedAZImpactAlarm( + scope: Construct, + operationName: string, + availabilityZoneId: string, + counter: number, + azIsOutlierForFaultsAlarm: IAlarm, + availabilityImpactAlarm: IAlarm, + moreThanOneInstanceContributingToFaults: IAlarm, + azIsOutlierForLatencyAlarm: IAlarm, + latencyImpactAlarm: IAlarm, + moreThanOneInstanceContributingToLatency: IAlarm, + nameSuffix?: string, + ): IAlarm { + return new CompositeAlarm( + scope, + operationName + 'AZ' + counter + 'IsolatedImpactAlarm' + nameSuffix, + { + compositeAlarmName: + availabilityZoneId + + `-${operationName.toLowerCase()}-isolated-impact-alarm` + + nameSuffix, + alarmRule: AlarmRule.anyOf( + moreThanOneInstanceContributingToFaults === undefined || + moreThanOneInstanceContributingToFaults == null + ? AlarmRule.allOf( + azIsOutlierForFaultsAlarm, + availabilityImpactAlarm, + ) + : AlarmRule.allOf( + azIsOutlierForFaultsAlarm, + availabilityImpactAlarm, + moreThanOneInstanceContributingToFaults, + ), + moreThanOneInstanceContributingToLatency === undefined || + moreThanOneInstanceContributingToLatency == null + ? AlarmRule.allOf(azIsOutlierForLatencyAlarm, latencyImpactAlarm) + : AlarmRule.allOf( + azIsOutlierForLatencyAlarm, + latencyImpactAlarm, + moreThanOneInstanceContributingToLatency, + ), + ), + actionsEnabled: false, + }, + ); + } + + /** + * Creates an alarm that fires if either the canary or the + * server side detect single AZ isolated impact + * @param scope + * @param operation + * @param availabilityZoneId + * @param counter + * @param serverSideAlarm + * @param canaryAlarm + * @returns + */ + static createAggregateIsolatedAZImpactAlarm( + scope: Construct, + operation: IOperation, + availabilityZoneId: string, + counter: number, + serverSideAlarm: IAlarm, + canaryAlarm: IAlarm, + ): IAlarm { + return new CompositeAlarm( + scope, + operation.operationName + 'AZ' + counter + 'AggregateIsolatedImpactAlarm', + { + compositeAlarmName: + availabilityZoneId + + `-${operation.operationName.toLowerCase()}-aggregate-isolated-impact-alarm`, + alarmRule: AlarmRule.anyOf(serverSideAlarm, canaryAlarm), + actionsEnabled: false, + }, + ); + } + + /** + * Creates a regional availability alarm for the operation + * @param scope + * @param metricDetails + * @param nameSuffix + * @param counter + * @returns + */ + static createRegionalAvailabilityAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + nameSuffix: string, + ): IAlarm { + return new Alarm( + scope, + metricDetails.operationName + 'RegionalAvailabilityAlarm', + { + alarmName: + Fn.ref('AWS::Region') + + '-' + + metricDetails.operationName.toLowerCase() + + '-success-rate' + + nameSuffix, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD, + threshold: metricDetails.successAlarmThreshold, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + metric: RegionalAvailabilityMetrics.createRegionalAvailabilityMetric({ + label: Fn.ref('AWS::Region') + ' availability', + metricDetails: metricDetails, + metricType: AvailabilityMetricType.SUCCESS_RATE, + }), + }, + ); + } + + /** + * Creates a regional latency alarm for the operation + * @param scope + * @param metricDetails + * @param nameSuffix + * @param counter + * @returns + */ + static createRegionalLatencyAlarm( + scope: Construct, + metricDetails: IOperationMetricDetails, + nameSuffix: string, + ): IAlarm { + return new Alarm( + scope, + metricDetails.operationName + 'RegionalLatencyAlarm', + { + alarmName: + Fn.ref('AWS::Region') + + '-' + + metricDetails.operationName.toLowerCase() + + '-success-latency' + + nameSuffix, + evaluationPeriods: metricDetails.evaluationPeriods, + datapointsToAlarm: metricDetails.datapointsToAlarm, + comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: metricDetails.successAlarmThreshold, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + metric: RegionalLatencyMetrics.createRegionalAverageLatencyMetric({ + label: + Fn.ref('AWS::Region') + + ' ' + + metricDetails.alarmStatistic + + ' latency', + metricDetails: metricDetails, + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: metricDetails.alarmStatistic, + }), + }, + ); + } + + /** + * A composite alarm combining latency and availability alarms for this operation in the region + * as measured from either the server side or canary + * @param scope + * @param operation + * @param nameSuffix + * @param regionalAvailabilityAlarm + * @param regionalLatencyAlarm + * @returns + */ + static createRegionalCustomerExperienceAlarm( + scope: Construct, + operationName: string, + nameSuffix: string, + regionalAvailabilityAlarm: IAlarm, + regionalLatencyAlarm: IAlarm, + ): IAlarm { + return new CompositeAlarm( + scope, + operationName + 'RegionalCustomerExperienceAlarm', + { + compositeAlarmName: + Fn.ref('AWS::Region') + + '-' + + operationName.toLowerCase() + + '-customer-experience-imact' + + nameSuffix, + alarmRule: AlarmRule.anyOf( + regionalAvailabilityAlarm, + regionalLatencyAlarm, + ), + }, + ); + } + + static createRegionalInstanceContributorsToHighLatency( + scope: Construct, + metricDetails: IOperationMetricDetails, + ruleDetails: IContributorInsightRuleDetails, + ): CfnInsightRule { + let ruleBody = new InsightRuleBody(); + ruleBody.logGroupNames = ruleDetails.logGroups.map((x) => x.logGroupName); + ruleBody.aggregateOn = 'Count'; + ruleBody.logFormat = 'JSON'; + ruleBody.contribution = { + keys: [ruleDetails.instanceIdJsonPath], + filters: [ + { + Match: ruleDetails.successLatencyMetricJsonPath, + GreaterThan: metricDetails.successAlarmThreshold, + }, + { + Match: ruleDetails.operationNameJsonPath, + In: [metricDetails.operationName], + }, + ], + } as unknown as IContributionDefinition; + + return new CfnInsightRule(scope, 'RegionPerInstanceHighLatencyRule', { + ruleName: + Fn.ref('AWS::Region') + + `-${metricDetails.operationName.toLowerCase()}-per-instance-high-latency-server`, + ruleState: 'ENABLED', + ruleBody: ruleBody.toJson(), + }); + } + + static createRegionalInstanceContributorsToFaults( + scope: Construct, + metricDetails: IOperationMetricDetails, + ruleDetails: IContributorInsightRuleDetails, + ): CfnInsightRule { + let ruleBody = new InsightRuleBody(); + ruleBody.logGroupNames = ruleDetails.logGroups.map((x) => x.logGroupName); + ruleBody.aggregateOn = 'Count'; + ruleBody.logFormat = 'JSON'; + ruleBody.contribution = { + keys: [ruleDetails.instanceIdJsonPath], + filters: [ + { + Match: ruleDetails.faultMetricJsonPath, + GreaterThan: 0, + }, + { + Match: ruleDetails.operationNameJsonPath, + In: [metricDetails.operationName], + }, + ], + } as unknown as IContributionDefinition; + + return new CfnInsightRule(scope, 'RegionPerInstanceErrorRule', { + ruleName: + Fn.ref('AWS::Region') + + `-${metricDetails.operationName.toLowerCase()}-per-instance-faults-server`, + ruleState: 'ENABLED', + ruleBody: ruleBody.toJson(), + }); + } +} diff --git a/src/alarmsandrules/BaseOperationRegionalAlarmsAndRules.ts b/src/alarmsandrules/BaseOperationRegionalAlarmsAndRules.ts new file mode 100644 index 0000000..e37f874 --- /dev/null +++ b/src/alarmsandrules/BaseOperationRegionalAlarmsAndRules.ts @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { AvailabilityAndLatencyAlarmsAndRules } from './AvailabilityAndLatencyAlarmsAndRules'; +import { IBaseOperationRegionalAlarmsAndRules } from './IBaseOperationRegionalAlarmsAndRules'; +import { BaseOperationRegionalAlarmsAndRulesProps } from './props/BaseOperationRegionalAlarmsAndRulesProps'; + +/** + * Base operation regional alarms and rules + */ +export abstract class BaseOperationRegionalAlarmsAndRules + extends Construct + implements IBaseOperationRegionalAlarmsAndRules { + /** + * Availability alarm for this operation + */ + availabilityAlarm: IAlarm; + + /** + * Latency alarm for this operation + */ + latencyAlarm: IAlarm; + + /** + * Composite alarm for either availabiltiy or latency impact to this operation + */ + availabilityOrLatencyAlarm: IAlarm; + + constructor( + scope: Construct, + id: string, + props: BaseOperationRegionalAlarmsAndRulesProps, + ) { + super(scope, id); + + this.availabilityAlarm = + AvailabilityAndLatencyAlarmsAndRules.createRegionalAvailabilityAlarm( + this, + props.availabilityMetricDetails, + props.nameSuffix, + ); + this.latencyAlarm = + AvailabilityAndLatencyAlarmsAndRules.createRegionalLatencyAlarm( + this, + props.latencyMetricDetails, + props.nameSuffix, + ); + this.availabilityOrLatencyAlarm = + AvailabilityAndLatencyAlarmsAndRules.createRegionalCustomerExperienceAlarm( + this, + props.availabilityMetricDetails.operationName, + props.nameSuffix, + this.availabilityAlarm, + this.latencyAlarm, + ); + } +} diff --git a/src/alarmsandrules/BaseOperationZonalAlarmsAndRules.ts b/src/alarmsandrules/BaseOperationZonalAlarmsAndRules.ts new file mode 100644 index 0000000..64f9e9a --- /dev/null +++ b/src/alarmsandrules/BaseOperationZonalAlarmsAndRules.ts @@ -0,0 +1,134 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { AvailabilityAndLatencyAlarmsAndRules } from './AvailabilityAndLatencyAlarmsAndRules'; +import { IBaseOperationZonalAlarmsAndRules } from './IBaseOperationZonalAlarmsAndRules'; +import { BaseOperationZonalAlarmsAndRulesProps } from './props/BaseOperationZonalAlarmsAndRulesProps'; +import { OutlierDetectionAlgorithm } from '../utilities/OutlierDetectionAlgorithm'; + +/** + * The base operation regional alarms and rules + */ +export abstract class BaseOperationZonalAlarmsAndRules + extends Construct + implements IBaseOperationZonalAlarmsAndRules { + /** + * Composite alarm for either availabiltiy or latency impact to this operation + */ + availabilityOrLatencyAlarm: IAlarm; + + /** + * Availability alarm for this operation + */ + availabilityAlarm: IAlarm; + + /** + * Latency alarm for this operation + */ + latencyAlarm: IAlarm; + + /** + * Alarm that indicates that this AZ is an outlier for fault rate + */ + availabilityZoneIsOutlierForFaults: IAlarm; + + /** + * Alarm that indicates this AZ is an outlier for high latency + */ + availabilityZoneIsOutlierForLatency: IAlarm; + + /** + * The Availability Zone Id for the alarms and rules + */ + availabilityZoneId: string; + + constructor( + scope: Construct, + id: string, + props: BaseOperationZonalAlarmsAndRulesProps, + ) { + super(scope, id); + this.availabilityZoneId = props.availabilityZoneId; + this.availabilityAlarm = + AvailabilityAndLatencyAlarmsAndRules.createZonalAvailabilityAlarm( + this, + props.availabilityMetricDetails, + props.availabilityZoneId, + props.counter, + props.nameSuffix, + ); + this.latencyAlarm = + AvailabilityAndLatencyAlarmsAndRules.createZonalLatencyAlarm( + this, + props.latencyMetricDetails, + props.availabilityZoneId, + props.counter, + props.nameSuffix, + ); + this.availabilityOrLatencyAlarm = + AvailabilityAndLatencyAlarmsAndRules.createZonalAvailabilityOrLatencyCompositeAlarm( + this, + props.availabilityMetricDetails.operationName, + props.availabilityZoneId, + props.counter, + this.availabilityAlarm, + this.latencyAlarm, + props.nameSuffix, + ); + + if (props.outlierDetectionAlgorithm == OutlierDetectionAlgorithm.STATIC) { + this.availabilityZoneIsOutlierForFaults = + AvailabilityAndLatencyAlarmsAndRules.createZonalFaultRateStaticOutlierAlarm( + this, + props.availabilityMetricDetails, + props.availabilityZoneId, + props.counter, + props.outlierThreshold, + props.nameSuffix, + ); + this.availabilityZoneIsOutlierForLatency = + AvailabilityAndLatencyAlarmsAndRules.createZonalHighLatencyStaticOutlierAlarm( + this, + props.latencyMetricDetails, + props.availabilityZoneId, + props.counter, + props.outlierThreshold, + props.nameSuffix, + ); + } else { + this.availabilityZoneIsOutlierForFaults = + AvailabilityAndLatencyAlarmsAndRules.createZonalFaultRateOutlierAlarm( + this, + props.availabilityMetricDetails, + props.availabilityZoneId, + props.operation.service.availabilityZoneNames.map((az) => { + return props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + az.substring(az.length - 1), + ); + }), + props.outlierThreshold, + props.outlierDetectionFunction!, + props.outlierDetectionAlgorithm, + props.counter, + props.nameSuffix, + ); + this.availabilityZoneIsOutlierForLatency = + AvailabilityAndLatencyAlarmsAndRules.createZonalHighLatencyOutlierAlarm( + this, + props.latencyMetricDetails, + props.availabilityZoneId, + props.operation.service.availabilityZoneNames.map((az) => { + return props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + az.substring(az.length - 1), + ); + }), + props.outlierThreshold, + props.outlierDetectionFunction!, + props.outlierDetectionAlgorithm, + props.counter, + props.nameSuffix, + ); + } + } +} diff --git a/src/alarmsandrules/CanaryOperationRegionalAlarmsAndRules.ts b/src/alarmsandrules/CanaryOperationRegionalAlarmsAndRules.ts new file mode 100644 index 0000000..39994b3 --- /dev/null +++ b/src/alarmsandrules/CanaryOperationRegionalAlarmsAndRules.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Construct } from 'constructs'; +import { BaseOperationRegionalAlarmsAndRules } from './BaseOperationRegionalAlarmsAndRules'; +import { ICanaryOperationRegionalAlarmsAndRules } from './ICanaryOperationRegionalAlarmsAndRules'; +import { CanaryOperationRegionalAlarmsAndRulesProps } from './props/CanaryOperationRegionalAlarmsAndRulesProps'; + +export class CanaryOperationRegionalAlarmsAndRules + extends BaseOperationRegionalAlarmsAndRules + implements ICanaryOperationRegionalAlarmsAndRules { + constructor( + scope: Construct, + id: string, + props: CanaryOperationRegionalAlarmsAndRulesProps, + ) { + super(scope, id, props); + } +} diff --git a/src/alarmsandrules/CanaryOperationZonalAlarmsAndRules.ts b/src/alarmsandrules/CanaryOperationZonalAlarmsAndRules.ts new file mode 100644 index 0000000..282b3df --- /dev/null +++ b/src/alarmsandrules/CanaryOperationZonalAlarmsAndRules.ts @@ -0,0 +1,42 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { AvailabilityAndLatencyAlarmsAndRules } from './AvailabilityAndLatencyAlarmsAndRules'; +import { BaseOperationZonalAlarmsAndRules } from './BaseOperationZonalAlarmsAndRules'; +import { ICanaryOperationZonalAlarmsAndRules } from './ICanaryOperationZonalAlarmsAndRules'; +import { CanaryOperationZonalAlarmsAndRulesProps } from './props/CanaryOperationZonalAlarmsAndRulesProps'; + +/** + * Creates the alarms and rules for a particular operation as measured by the canary + */ +export class CanaryOperationZonalAlarmsAndRules + extends BaseOperationZonalAlarmsAndRules + implements ICanaryOperationZonalAlarmsAndRules { + /** + * Alarm that triggers if either latency or availability breach the specified + * threshold in this AZ and the AZ is an outlier for faults or latency + */ + isolatedImpactAlarm: IAlarm; + + constructor( + scope: Construct, + id: string, + props: CanaryOperationZonalAlarmsAndRulesProps, + ) { + super(scope, id, props); + + this.isolatedImpactAlarm = + AvailabilityAndLatencyAlarmsAndRules.createCanaryIsolatedAZImpactAlarm( + this, + props.availabilityMetricDetails.operationName, + props.availabilityZoneId, + props.counter, + this.availabilityZoneIsOutlierForFaults, + this.availabilityAlarm, + this.availabilityZoneIsOutlierForLatency, + this.latencyAlarm, + props.nameSuffix, + ); + } +} diff --git a/src/alarmsandrules/IBaseOperationRegionalAlarmsAndRules.ts b/src/alarmsandrules/IBaseOperationRegionalAlarmsAndRules.ts new file mode 100644 index 0000000..06381ae --- /dev/null +++ b/src/alarmsandrules/IBaseOperationRegionalAlarmsAndRules.ts @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * Base regional alarms and rules + */ +export interface IBaseOperationRegionalAlarmsAndRules { + /** + * Availability alarm for this operation + */ + availabilityAlarm: IAlarm; + + /** + * Latency alarm for this operation + */ + latencyAlarm: IAlarm; + + /** + * Composite alarm for either availabiltiy or latency impact to this operation + */ + availabilityOrLatencyAlarm: IAlarm; +} diff --git a/src/alarmsandrules/IBaseOperationZonalAlarmsAndRules.ts b/src/alarmsandrules/IBaseOperationZonalAlarmsAndRules.ts new file mode 100644 index 0000000..573588a --- /dev/null +++ b/src/alarmsandrules/IBaseOperationZonalAlarmsAndRules.ts @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * The base operation zonal alarms and rules + */ +export interface IBaseOperationZonalAlarmsAndRules { + /** + * Composite alarm for either availabiltiy or latency impact to this operation + */ + availabilityOrLatencyAlarm: IAlarm; + + /** + * Availability alarm for this operation + */ + availabilityAlarm: IAlarm; + + /** + * Latency alarm for this operation + */ + latencyAlarm: IAlarm; + + /** + * Alarm that indicates that this AZ is an outlier for fault rate + */ + availabilityZoneIsOutlierForFaults: IAlarm; + + /** + * Alarm that indicates this AZ is an outlier for high latency + */ + availabilityZoneIsOutlierForLatency: IAlarm; + + /** + * The Availability Zone Id for the alarms and rules + */ + availabilityZoneId: string; +} diff --git a/src/alarmsandrules/ICanaryOperationRegionalAlarmsAndRules.ts b/src/alarmsandrules/ICanaryOperationRegionalAlarmsAndRules.ts new file mode 100644 index 0000000..900b6e4 --- /dev/null +++ b/src/alarmsandrules/ICanaryOperationRegionalAlarmsAndRules.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IBaseOperationRegionalAlarmsAndRules } from './IBaseOperationRegionalAlarmsAndRules'; + +/** + * The canary operation regional alarms and rules + */ +export interface ICanaryOperationRegionalAlarmsAndRules + extends IBaseOperationRegionalAlarmsAndRules {} diff --git a/src/alarmsandrules/ICanaryOperationZonalAlarmsAndRules.ts b/src/alarmsandrules/ICanaryOperationZonalAlarmsAndRules.ts new file mode 100644 index 0000000..d307976 --- /dev/null +++ b/src/alarmsandrules/ICanaryOperationZonalAlarmsAndRules.ts @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { IBaseOperationZonalAlarmsAndRules } from './IBaseOperationZonalAlarmsAndRules'; + +/** + * Alarms and rules for canary metrics + */ +export interface ICanaryOperationZonalAlarmsAndRules + extends IBaseOperationZonalAlarmsAndRules { + /** + * Alarm that triggers if either latency or availability breach the specified + * threshold in this AZ and the AZ is an outlier for faults or latency + */ + isolatedImpactAlarm: IAlarm; +} diff --git a/src/alarmsandrules/IOperationAlarmsAndRules.ts b/src/alarmsandrules/IOperationAlarmsAndRules.ts new file mode 100644 index 0000000..1c12ad8 --- /dev/null +++ b/src/alarmsandrules/IOperationAlarmsAndRules.ts @@ -0,0 +1,62 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { ICanaryOperationRegionalAlarmsAndRules } from './ICanaryOperationRegionalAlarmsAndRules'; +import { ICanaryOperationZonalAlarmsAndRules } from './ICanaryOperationZonalAlarmsAndRules'; +import { IServerSideOperationRegionalAlarmsAndRules } from './IServerSideOperationRegionalAlarmsAndRules'; +import { IServerSideOperationZonalAlarmsAndRules } from './IServerSideOperationZonalAlarmsAndRules'; +import { IOperation } from '../services/IOperation'; + +/** + * Creates alarms and rules for an operation for both regional and zonal metrics + */ +export interface IOperationAlarmsAndRules { + /** + * The operation the alarms and rules are created for + */ + operation: IOperation; + + /** + * The server side regional alarms and rules + */ + serverSideRegionalAlarmsAndRules: IServerSideOperationRegionalAlarmsAndRules; + + /** + * The canary regional alarms and rules + */ + canaryRegionalAlarmsAndRules?: ICanaryOperationRegionalAlarmsAndRules; + + /** + * The aggregate regional alarm that looks at both canary and server + * side impact alarms for latency and availability + */ + aggregateRegionalAlarm: IAlarm; + + /** + * The server side zonal alarms and rules + */ + serverSideZonalAlarmsAndRules: IServerSideOperationZonalAlarmsAndRules[]; + + /** + * The canary zonal alarms and rules + */ + canaryZonalAlarmsAndRules?: ICanaryOperationZonalAlarmsAndRules[]; + + /** + * The aggregate zonal alarms, one per AZ. Each alarm indicates there is either + * latency or availability impact in that AZ, and the AZ is an outlier for + * availability or latency impact. Both server side and canary metrics are + * evaluated + */ + aggregateZonalAlarms: IAlarm[]; + + /** + * The aggregate zonal alarm indexed by Availability Zone Id. + */ + aggregateZonalAlarmsMap: { [key: string]: IAlarm }; + + /** + * Just the server side zonal alarms + */ + serverSideZonalAlarmsMap: { [key: string]: IAlarm }; +} diff --git a/src/alarmsandrules/IServerSideOperationRegionalAlarmsAndRules.ts b/src/alarmsandrules/IServerSideOperationRegionalAlarmsAndRules.ts new file mode 100644 index 0000000..41b1b6a --- /dev/null +++ b/src/alarmsandrules/IServerSideOperationRegionalAlarmsAndRules.ts @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { CfnInsightRule } from 'aws-cdk-lib/aws-cloudwatch'; +import { IBaseOperationRegionalAlarmsAndRules } from './IBaseOperationRegionalAlarmsAndRules'; + +/** + * The server side operation regional alarms and rules + */ +export interface IServerSideOperationRegionalAlarmsAndRules + extends IBaseOperationRegionalAlarmsAndRules { + /** + * A rule that shows which instances are contributing to high latency responses + */ + instanceContributorsToRegionalHighLatency?: CfnInsightRule; + + /** + * A rule that shows which instances are contributing to faults + */ + instanceContributorsToRegionalFaults?: CfnInsightRule; +} diff --git a/src/alarmsandrules/IServerSideOperationZonalAlarmsAndRules.ts b/src/alarmsandrules/IServerSideOperationZonalAlarmsAndRules.ts new file mode 100644 index 0000000..671486c --- /dev/null +++ b/src/alarmsandrules/IServerSideOperationZonalAlarmsAndRules.ts @@ -0,0 +1,45 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { CfnInsightRule, IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { IBaseOperationZonalAlarmsAndRules } from './IBaseOperationZonalAlarmsAndRules'; + +/** + * Server side opertaion zonal alarms and rules + */ +export interface IServerSideOperationZonalAlarmsAndRules + extends IBaseOperationZonalAlarmsAndRules { + /** + * Alarm that triggers if either latency or availability breach the specified + * threshold in this AZ and the AZ is an outlier for faults or latency + */ + isolatedImpactAlarm: IAlarm; + + /** + * Alarm indicating that there are multiple instances producing faults in + * this AZ indicating the fault rate is not being caused by a single instance + */ + multipleInstancesProducingFaultsInThisAvailabilityZone?: IAlarm; + + /** + * Alarm indicating that there are multiple instances producing high + * latency responses in this AZ indicating the latency is not being + * caused by a single instance + */ + multipleInstancesProducingHighLatencyInThisAZ?: IAlarm; + + /** + * Insight rule that measures the number of instances contributing to high latency in this AZ + */ + instanceContributorsToHighLatencyInThisAZ?: CfnInsightRule; + + /** + * Insight rule that measures the number of instances contributing to faults in this AZ + */ + instanceContributorsToFaultsInThisAZ?: CfnInsightRule; + + /** + * Insight rule that is used to calculate the number of instances in this particular AZ that is used with metric math to calculate + * the percent of instances contributing to latency or faults + */ + instancesHandlingRequestsInThisAZ?: CfnInsightRule; +} diff --git a/src/alarmsandrules/IServiceAlarmsAndRules.ts b/src/alarmsandrules/IServiceAlarmsAndRules.ts new file mode 100644 index 0000000..1b38c08 --- /dev/null +++ b/src/alarmsandrules/IServiceAlarmsAndRules.ts @@ -0,0 +1,54 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { IService } from '../services/IService'; + +/** + * Service level alarms and rules using critical operations + */ +export interface IServiceAlarmsAndRules { + /** + * The service these alarms and rules are for + */ + service: IService; + + /** + * The zonal aggregate isolated impact alarms. There is 1 alarm per AZ that + * triggers for availability or latency impact to any critical operation in that AZ + * that indicates it has isolated impact as measured by canaries or server-side. + */ + zonalAggregateIsolatedImpactAlarms: IAlarm[]; + + /** + * The zonal server-side isolated impact alarms. There is 1 alarm per AZ that triggers + * on availability or atency impact to any critical operation in that AZ. These are useful + * for deployment monitoring to not inadvertently fail when a canary can't contact an AZ + * during a deployment. + */ + zonalServerSideIsolatedImpactAlarms: IAlarm[]; + + /** + * An alarm for regional availability or latency impact of any critical operation as measured by the canary. + */ + regionalAvailabilityOrLatencyCanaryAlarm?: IAlarm; + + /** + * An alarm for regional availability impact of any critical operation as measured by the canary. + */ + regionalAvailabilityCanaryAlarm?: IAlarm; + + /** + * An alarm for regional availability or latency impact of any critical operation as measured by the server-side. + */ + regionalAvailabilityOrLatencyServerSideAlarm: IAlarm; + + /** + * An alarm for regional availability impact of any critical operation as measured by the server-side. + */ + regionalAvailabilityServerSideAlarm: IAlarm; + + /** + * An alarm for fault count exceeding a regional threshold for all critical operations. + */ + regionalFaultCountServerSideAlarm: IAlarm; +} diff --git a/src/alarmsandrules/InsightRuleBody.ts b/src/alarmsandrules/InsightRuleBody.ts new file mode 100644 index 0000000..f82400f --- /dev/null +++ b/src/alarmsandrules/InsightRuleBody.ts @@ -0,0 +1,111 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/** + * The insight rule body to be included in a CfnInsightRule construct + */ +export class InsightRuleBody { + /** + * The value of Schema for a rule that analyzes CloudWatch Logs data must always be {"Name": "CloudWatchLogRule", "Version": 1} + */ + schema: IRuleSchema; + + /** + * An array of strings. For each element in the array, you can optionally use * at the end of a string to include all log groups with names that start with that prefix. + */ + logGroupNames: string[]; + + /** + * Valid values are JSON and CLF. + */ + logFormat: string; + + /** + * Valid values are Count and Sum. Specifies whether to aggregate the report based on a count of occurrences or a sum of the values of the field that is specified in the ValueOf field. + */ + aggregateOn: string; + + /** + * This object includes a Keys array with as many as four members, optionally a single ValueOf, and optionally an array of as many as four Filters. + */ + contribution: IContributionDefinition; + + constructor() { + this.schema = { + name: 'CloudWatchLogRule', + version: 1, + }; + this.aggregateOn = ''; + this.contribution = { + keys: [] as string[], + } as IContributionDefinition; + this.logFormat = ''; + this.logGroupNames = []; + } + + /** + * Converts the rule to a JSON string + * @returns + */ + toJson(): string { + let objectKeysToUpperCase = function (input: { [key: string]: any }): { + [key: string]: any; + } { + if (typeof input !== 'object') return input; + if (Array.isArray(input)) return input.map(objectKeysToUpperCase); + return Object.keys(input).reduce(function ( + newObj: { [key: string]: any }, + key: string, + ) { + let val = input[key]; + let newVal = + typeof val === 'object' && val !== null + ? objectKeysToUpperCase(val) + : val; + let newKey: string = key.slice(0, 1).toUpperCase() + key.substring(1); + newObj[newKey] = newVal; + return newObj; + }, {}); + }; + + return JSON.stringify(objectKeysToUpperCase(this)); + } +} + +export interface IRuleSchema { + /** + * The name of the rule schema, this should bre CloudWatchLogRule + */ + readonly name: string; + + /** + * The version number of the schema, this should be 1 + */ + readonly version: number; +} + +export interface IContributionDefinition { + /** + * An array of up to four log fields that are used as dimensions to classify contributors. + * If you enter more than one key, each unique combination of values for the keys is counted + * as a unique contributor. The fields must be specified using JSON property format notation. + */ + keys: string[]; + + /** + * (Optional) Specify this only when you are specifying Sum as the value of AggregateOn. + * ValueOf specifies a log field with numerical values. In this type of rule, the contributors + * are ranked by their sum of the value of this field, instead of their number of occurrences + * in the log entries. For example, if you want to sort contributors by their total BytesSent + * over a period, you would set ValueOf to BytesSent and specify Sum for AggregateOn. If this + * value is not set, it must not be included in the JSON string representation of the rule body. + */ + valueOf?: string; + + /** + * (Optional) Specifies an array of as many as four filters to narrow the log events + * that are included in the report. If you specify multiple filters, Contributor Insights + * evaluates them with a logical AND operator. You can use this to filter out irrelevant + * log events in your search or you can use it to select a single contributor to analyze their behavior. + */ + filters?: { [key: string]: any }[]; +} diff --git a/src/alarmsandrules/OperationAlarmsAndRules.ts b/src/alarmsandrules/OperationAlarmsAndRules.ts new file mode 100644 index 0000000..03f1fac --- /dev/null +++ b/src/alarmsandrules/OperationAlarmsAndRules.ts @@ -0,0 +1,261 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { AlarmRule, CompositeAlarm, IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { BaseLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { Construct } from 'constructs'; +import { CanaryOperationRegionalAlarmsAndRules } from './CanaryOperationRegionalAlarmsAndRules'; +import { CanaryOperationZonalAlarmsAndRules } from './CanaryOperationZonalAlarmsAndRules'; +import { ICanaryOperationRegionalAlarmsAndRules } from './ICanaryOperationRegionalAlarmsAndRules'; +import { ICanaryOperationZonalAlarmsAndRules } from './ICanaryOperationZonalAlarmsAndRules'; +import { IOperationAlarmsAndRules } from './IOperationAlarmsAndRules'; +import { IServerSideOperationRegionalAlarmsAndRules } from './IServerSideOperationRegionalAlarmsAndRules'; +import { IServerSideOperationZonalAlarmsAndRules } from './IServerSideOperationZonalAlarmsAndRules'; +import { OperationAlarmsAndRulesProps } from './props/OperationAlarmsAndRulesProps'; +import { ServerSideOperationRegionalAlarmsAndRules } from './ServerSideOperationRegionalAlarmsAndRules'; +import { ServerSideOperationZonalAlarmsAndRules } from './ServerSideOperationZonalAlarmsAndRules'; +import { IOperation } from '../services/IOperation'; + +/** + * Creates alarms and rules for an operation for both regional and zonal metrics + */ +export class OperationAlarmsAndRules + extends Construct + implements IOperationAlarmsAndRules { + /** + * The operation the alarms and rules are created for + */ + readonly operation: IOperation; + + /** + * The server side regional alarms and rules + */ + readonly serverSideRegionalAlarmsAndRules: IServerSideOperationRegionalAlarmsAndRules; + + /** + * The canary regional alarms and rules + */ + readonly canaryRegionalAlarmsAndRules?: ICanaryOperationRegionalAlarmsAndRules; + + /** + * The aggregate regional alarm that looks at both canary and server + * side impact alarms for latency and availability + */ + readonly aggregateRegionalAlarm: IAlarm; + + /** + * The server side zonal alarms and rules + */ + readonly serverSideZonalAlarmsAndRules: IServerSideOperationZonalAlarmsAndRules[]; + + /** + * The canary zonal alarms and rules + */ + readonly canaryZonalAlarmsAndRules?: ICanaryOperationZonalAlarmsAndRules[]; + + /** + * The aggregate zonal alarms, one per AZ. Each alarm indicates there is either + * latency or availability impact in that AZ, and the AZ is an outlier for + * availability or latency impact. Both server side and canary metrics are + * evaluated + */ + readonly aggregateZonalAlarms: IAlarm[]; + + /** + * The aggregate zonal alarm indexed by Availability Zone Id. + */ + readonly aggregateZonalAlarmsMap: { [key: string]: IAlarm }; + + /** + * Just the server side zonal alarms + */ + readonly serverSideZonalAlarmsMap: { [key: string]: IAlarm }; + + constructor( + scope: Construct, + id: string, + props: OperationAlarmsAndRulesProps, + ) { + super(scope, id); + this.serverSideZonalAlarmsAndRules = []; + this.canaryZonalAlarmsAndRules = []; + this.aggregateZonalAlarms = []; + this.operation = props.operation; + this.aggregateZonalAlarmsMap = {}; + this.serverSideZonalAlarmsMap = {}; + + let availabilityZoneIds: string[] = + props.operation.service.availabilityZoneNames.map((x) => { + return props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + x.substring(x.length - 1), + ); + }); + + let loadBalancerArn: string = ''; + + if (props.loadBalancer !== undefined) { + loadBalancerArn = (props.loadBalancer as BaseLoadBalancer) + .loadBalancerArn; + } + + this.serverSideRegionalAlarmsAndRules = + new ServerSideOperationRegionalAlarmsAndRules( + this, + props.operation.operationName + 'ServerSideRegionalAlarms', + { + availabilityMetricDetails: + props.operation.serverSideAvailabilityMetricDetails, + latencyMetricDetails: props.operation.serverSideLatencyMetricDetails, + contributorInsightRuleDetails: props.operation + .serverSideContributorInsightRuleDetails + ? props.operation.serverSideContributorInsightRuleDetails + : props.operation.service.defaultContributorInsightRuleDetails, + nameSuffix: '-server', + }, + ); + + if ( + props.operation.canaryMetricDetails !== undefined && + props.operation.canaryMetricDetails != null + ) { + this.canaryRegionalAlarmsAndRules = + new CanaryOperationRegionalAlarmsAndRules( + this, + props.operation.operationName + 'CanaryRegionalAlarms', + { + availabilityMetricDetails: + props.operation.canaryMetricDetails + .canaryAvailabilityMetricDetails, + latencyMetricDetails: + props.operation.canaryMetricDetails.canaryLatencyMetricDetails, + nameSuffix: '-canary', + }, + ); + + this.aggregateRegionalAlarm = new CompositeAlarm( + this, + props.operation.operationName + 'AggregateRegionalAlarm', + { + actionsEnabled: false, + compositeAlarmName: + Fn.ref('AWS::Region') + + '-' + + props.operation.operationName.toLowerCase() + + '-' + + 'aggregate-alarm', + alarmRule: AlarmRule.anyOf( + this.serverSideRegionalAlarmsAndRules.availabilityOrLatencyAlarm, + this.canaryRegionalAlarmsAndRules.availabilityOrLatencyAlarm, + ), + }, + ); + } else { + this.aggregateRegionalAlarm = + this.serverSideRegionalAlarmsAndRules.availabilityOrLatencyAlarm; + } + + let counter: number = 1; + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId: string = availabilityZoneIds[i]; + + this.serverSideZonalAlarmsAndRules.push( + new ServerSideOperationZonalAlarmsAndRules( + this, + props.operation.operationName + + 'AZ' + + counter + + 'ServerSideZonalAlarmsAndRules', + { + availabilityZoneId: availabilityZoneId, + availabilityMetricDetails: + props.operation.serverSideAvailabilityMetricDetails, + latencyMetricDetails: + props.operation.serverSideLatencyMetricDetails, + contributorInsightRuleDetails: props.operation + .serverSideContributorInsightRuleDetails + ? props.operation.serverSideContributorInsightRuleDetails + : props.operation.service.defaultContributorInsightRuleDetails, + counter: counter, + outlierThreshold: props.outlierThreshold, + outlierDetectionAlgorithm: props.outlierDetectionAlgorithm, + nameSuffix: '-server', + operation: props.operation, + azMapper: props.azMapper, + outlierDetectionFunction: props.outlierDetectionFunction, + }, + ), + ); + + this.serverSideZonalAlarmsMap[availabilityZoneId] = + this.serverSideZonalAlarmsAndRules[i].isolatedImpactAlarm; + + if ( + props.operation.canaryMetricDetails !== undefined && + props.operation.canaryMetricDetails != null + ) { + this.canaryZonalAlarmsAndRules.push( + new CanaryOperationZonalAlarmsAndRules( + this, + props.operation.operationName + + 'AZ' + + counter + + 'CanaryZonalAlarmsAndRules', + { + availabilityZoneId: availabilityZoneId, + availabilityMetricDetails: + props.operation.canaryMetricDetails + .canaryAvailabilityMetricDetails, + latencyMetricDetails: + props.operation.canaryMetricDetails.canaryLatencyMetricDetails, + counter: counter, + outlierThreshold: props.outlierThreshold, + outlierDetectionAlgorithm: props.outlierDetectionAlgorithm, + nameSuffix: '-canary', + operation: props.operation, + azMapper: props.azMapper, + outlierDetectionFunction: props.outlierDetectionFunction, + }, + ), + ); + + this.aggregateZonalAlarms.push( + new CompositeAlarm( + this, + props.operation.operationName + + 'AZ' + + counter + + 'AggregateZonalIsolatedImpactAlarm', + { + compositeAlarmName: + availabilityZoneId + + '-' + + props.operation.operationName.toLowerCase() + + '-aggregate-isolated-az-impact', + alarmRule: AlarmRule.anyOf( + this.canaryZonalAlarmsAndRules[i].isolatedImpactAlarm, + this.serverSideZonalAlarmsAndRules[i].isolatedImpactAlarm, + ), + actionsEnabled: false, + alarmDescription: + '{"loadBalancer":"' + + loadBalancerArn + + '","az-id":"' + + availabilityZoneId + + '"}', + }, + ), + ); + } else { + this.aggregateZonalAlarms.push( + this.serverSideZonalAlarmsAndRules[i].isolatedImpactAlarm, + ); + } + + this.aggregateZonalAlarmsMap[availabilityZoneId] = + this.aggregateZonalAlarms[-1]; + + counter++; + } + } +} diff --git a/src/alarmsandrules/ServerSideOperationRegionalAlarmsAndRules.ts b/src/alarmsandrules/ServerSideOperationRegionalAlarmsAndRules.ts new file mode 100644 index 0000000..a09c53c --- /dev/null +++ b/src/alarmsandrules/ServerSideOperationRegionalAlarmsAndRules.ts @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { CfnInsightRule } from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { AvailabilityAndLatencyAlarmsAndRules } from './AvailabilityAndLatencyAlarmsAndRules'; +import { BaseOperationRegionalAlarmsAndRules } from './BaseOperationRegionalAlarmsAndRules'; +import { IServerSideOperationRegionalAlarmsAndRules } from './IServerSideOperationRegionalAlarmsAndRules'; +import { ServerSideOperationRegionalAlarmsAndRulesProps } from './props/ServerSideOperationRegionalAlarmsAndRulesProps'; + +/** + * The server side regional alarms and rules for an operation + */ +export class ServerSideOperationRegionalAlarmsAndRules + extends BaseOperationRegionalAlarmsAndRules + implements IServerSideOperationRegionalAlarmsAndRules { + /** + * A rule that shows which instances are contributing to high latency responses + */ + instanceContributorsToRegionalHighLatency?: CfnInsightRule; + + /** + * A rule that shows which instances are contributing to faults + */ + instanceContributorsToRegionalFaults?: CfnInsightRule; + + constructor( + scope: Construct, + id: string, + props: ServerSideOperationRegionalAlarmsAndRulesProps, + ) { + super(scope, id, props); + + if ( + props.contributorInsightRuleDetails !== undefined && + props.contributorInsightRuleDetails != null + ) { + this.instanceContributorsToRegionalFaults = + AvailabilityAndLatencyAlarmsAndRules.createRegionalInstanceContributorsToFaults( + this, + props.availabilityMetricDetails, + props.contributorInsightRuleDetails, + ); + this.instanceContributorsToRegionalHighLatency = + AvailabilityAndLatencyAlarmsAndRules.createRegionalInstanceContributorsToHighLatency( + this, + props.latencyMetricDetails, + props.contributorInsightRuleDetails, + ); + } + } +} diff --git a/src/alarmsandrules/ServerSideOperationZonalAlarmsAndRules.ts b/src/alarmsandrules/ServerSideOperationZonalAlarmsAndRules.ts new file mode 100644 index 0000000..e3ac44c --- /dev/null +++ b/src/alarmsandrules/ServerSideOperationZonalAlarmsAndRules.ts @@ -0,0 +1,159 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { + IAlarm, + CfnInsightRule, + CompositeAlarm, + AlarmRule, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { AvailabilityAndLatencyAlarmsAndRules } from './AvailabilityAndLatencyAlarmsAndRules'; +import { BaseOperationZonalAlarmsAndRules } from './BaseOperationZonalAlarmsAndRules'; +import { IServerSideOperationZonalAlarmsAndRules } from './IServerSideOperationZonalAlarmsAndRules'; +import { ServerSideOperationZonalAlarmsAndRulesProps } from './props/ServerSideOperationZonalAlarmsAndRulesProps'; + +/** + * The server side alarms and rules for an operation in an Availability Zone + */ +export class ServerSideOperationZonalAlarmsAndRules + extends BaseOperationZonalAlarmsAndRules + implements IServerSideOperationZonalAlarmsAndRules { + /** + * Alarm that triggers if either latency or availability breach the specified + * threshold in this AZ and the AZ is an outlier for faults or latency + */ + isolatedImpactAlarm: IAlarm; + + /** + * Alarm indicating that there are multiple instances producing faults in + * this AZ indicating the fault rate is not being caused by a single instance + */ + multipleInstancesProducingFaultsInThisAvailabilityZone?: IAlarm; + + /** + * Alarm indicating that there are multiple instances producing high + * latency responses in this AZ indicating the latency is not being + * caused by a single instance + */ + multipleInstancesProducingHighLatencyInThisAZ?: IAlarm; + + /** + * Insight rule that measures the number of instances contributing to high latency in this AZ + */ + instanceContributorsToHighLatencyInThisAZ?: CfnInsightRule; + + /** + * Insight rule that measures the number of instances contributing to faults in this AZ + */ + instanceContributorsToFaultsInThisAZ?: CfnInsightRule; + + /** + * Insight rule that is used to calculate the number of instances in this particular AZ that is used with metric math to calculate + * the percent of instances contributing to latency or faults + */ + instancesHandlingRequestsInThisAZ?: CfnInsightRule; + + constructor( + scope: Construct, + id: string, + props: ServerSideOperationZonalAlarmsAndRulesProps, + ) { + super(scope, id, props); + + if ( + props.contributorInsightRuleDetails !== undefined && + props.contributorInsightRuleDetails != null + ) { + this.instancesHandlingRequestsInThisAZ = + AvailabilityAndLatencyAlarmsAndRules.createServerSideInstancesHandlingRequestsInThisAZRule( + this, + props.availabilityMetricDetails.operationName, + props.availabilityZoneId, + props.contributorInsightRuleDetails, + props.counter, + props.nameSuffix, + ); + this.instanceContributorsToFaultsInThisAZ = + AvailabilityAndLatencyAlarmsAndRules.createServerSideInstanceFaultContributorsInThisAZRule( + this, + props.availabilityMetricDetails.operationName, + props.availabilityZoneId, + props.contributorInsightRuleDetails, + props.counter, + props.nameSuffix, + ); + this.multipleInstancesProducingFaultsInThisAvailabilityZone = + AvailabilityAndLatencyAlarmsAndRules.createServerSideZonalMoreThanOneInstanceProducingFaultsAlarm( + this, + props.availabilityMetricDetails, + props.availabilityZoneId, + props.counter, + props.outlierThreshold, + this.instanceContributorsToFaultsInThisAZ, + this.instancesHandlingRequestsInThisAZ, + props.nameSuffix, + ); + this.instanceContributorsToHighLatencyInThisAZ = + AvailabilityAndLatencyAlarmsAndRules.createServerSideInstanceHighLatencyContributorsInThisAZRule( + this, + props.latencyMetricDetails, + props.availabilityZoneId, + props.contributorInsightRuleDetails, + props.counter, + props.nameSuffix, + ); + this.multipleInstancesProducingHighLatencyInThisAZ = + AvailabilityAndLatencyAlarmsAndRules.createServerSideZonalMoreThanOneInstanceProducingHighLatencyAlarm( + this, + props.latencyMetricDetails, + props.availabilityZoneId, + props.counter, + props.outlierThreshold, + this.instanceContributorsToHighLatencyInThisAZ, + this.instancesHandlingRequestsInThisAZ, + props.nameSuffix, + ); + + this.isolatedImpactAlarm = + AvailabilityAndLatencyAlarmsAndRules.createServerSideIsolatedAZImpactAlarm( + this, + props.availabilityMetricDetails.operationName, + props.availabilityZoneId, + props.counter, + this.availabilityZoneIsOutlierForFaults, + this.availabilityAlarm, + this.multipleInstancesProducingFaultsInThisAvailabilityZone, + this.availabilityZoneIsOutlierForLatency, + this.latencyAlarm, + this.multipleInstancesProducingHighLatencyInThisAZ, + props.nameSuffix, + ); + } else { + this.isolatedImpactAlarm = new CompositeAlarm( + scope, + props.operation.operationName + + 'AZ' + + props.counter + + 'IsolatedImpactAlarm' + + props.nameSuffix, + { + compositeAlarmName: + props.availabilityZoneId + + `-${props.operation.operationName.toLowerCase()}-isolated-impact-alarm` + + props.nameSuffix, + alarmRule: AlarmRule.anyOf( + AlarmRule.allOf( + this.availabilityZoneIsOutlierForFaults, + this.availabilityAlarm, + ), + AlarmRule.allOf( + this.availabilityZoneIsOutlierForLatency, + this.latencyAlarm, + ), + ), + actionsEnabled: false, + }, + ); + } + } +} diff --git a/src/alarmsandrules/ServiceAlarmsAndRules.ts b/src/alarmsandrules/ServiceAlarmsAndRules.ts new file mode 100644 index 0000000..6542ae8 --- /dev/null +++ b/src/alarmsandrules/ServiceAlarmsAndRules.ts @@ -0,0 +1,332 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { + Alarm, + AlarmRule, + ComparisonOperator, + CompositeAlarm, + IAlarm, + IMetric, + MathExpression, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { ICanaryOperationRegionalAlarmsAndRules } from './ICanaryOperationRegionalAlarmsAndRules'; +import { IOperationAlarmsAndRules } from './IOperationAlarmsAndRules'; +import { IServiceAlarmsAndRules } from './IServiceAlarmsAndRules'; +import { ServiceAlarmsAndRulesProps } from './props/ServiceAlarmsAndRulesProps'; +import { AvailabilityAndLatencyMetrics } from '../metrics/AvailabilityAndLatencyMetrics'; +import { RegionalAvailabilityMetrics } from '../metrics/RegionalAvailabilityMetrics'; +import { IService } from '../services/IService'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; + +/** + * Service level alarms and rules using critical operations + */ +export class ServiceAlarmsAndRules + extends Construct + implements IServiceAlarmsAndRules { + /** + * The service these alarms and rules are for + */ + service: IService; + + /** + * The zonal aggregate isolated impact alarms. There is 1 alarm per AZ that + * triggers for availability or latency impact to any critical operation in that AZ + * that indicates it has isolated impact as measured by canaries or server-side. + */ + zonalAggregateIsolatedImpactAlarms: IAlarm[]; + + /** + * The zonal server-side isolated impact alarms. There is 1 alarm per AZ that triggers + * on availability or atency impact to any critical operation in that AZ. These are useful + * for deployment monitoring to not inadvertently fail when a canary can't contact an AZ + * during a deployment. + */ + zonalServerSideIsolatedImpactAlarms: IAlarm[]; + + /** + * An alarm for regional availability or latency impact of any critical operation as measured by the canary. + */ + regionalAvailabilityOrLatencyCanaryAlarm?: IAlarm; + + /** + * An alarm for regional availability impact of any critical operation as measured by the canary. + */ + regionalAvailabilityCanaryAlarm?: IAlarm; + + /** + * An alarm for regional availability or latency impact of any critical operation as measured by the server-side. + */ + regionalAvailabilityOrLatencyServerSideAlarm: IAlarm; + + /** + * An alarm for regional availability impact of any critical operation as measured by the server-side. + */ + regionalAvailabilityServerSideAlarm: IAlarm; + + /** + * An alarm for fault count exceeding a regional threshold for all critical operations. + */ + regionalFaultCountServerSideAlarm: IAlarm; + + constructor(scope: Construct, id: string, props: ServiceAlarmsAndRulesProps) { + super(scope, id); + this.service = props.service; + + let criticalOperations: string[] = props.service.operations + .filter((x) => x.critical == true) + .map((x) => x.operationName); + let counter: number = 1; + this.zonalAggregateIsolatedImpactAlarms = []; + this.zonalServerSideIsolatedImpactAlarms = []; + + let availabilityZoneIds: string[] = props.service.availabilityZoneNames.map( + (x) => { + return props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + x.substring(x.length - 1), + ); + }, + ); + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZonedId: string = availabilityZoneIds[i]; + + this.zonalAggregateIsolatedImpactAlarms.push( + new CompositeAlarm( + this, + 'AZ' + counter + 'ServiceAggregateIsolatedImpactAlarm', + { + compositeAlarmName: + availabilityZonedId + + '-' + + props.service.serviceName.toLowerCase() + + '-isolated-impact-aggregate-alarm', + alarmRule: AlarmRule.anyOf( + ...Object.values( + Object.entries(props.perOperationAlarmsAndRules).reduce( + (filtered, [key, value]) => { + if (criticalOperations.indexOf(key) > -1) { + filtered[key] = value; + } + + return filtered; + }, + {} as { [key: string]: IOperationAlarmsAndRules }, + ), + ).map((x) => x.aggregateZonalAlarms[i]), + ), + }, + ), + ); + + this.zonalServerSideIsolatedImpactAlarms.push( + new CompositeAlarm( + this, + 'AZ' + counter + 'ServiceServerSideIsolatedImpactAlarm', + { + compositeAlarmName: + availabilityZonedId + + '-' + + props.service.serviceName.toLowerCase() + + '-isolated-impact-server-side-alarm', + alarmRule: AlarmRule.anyOf( + ...Object.values( + Object.entries(props.perOperationAlarmsAndRules).reduce( + (filtered, [key, value]) => { + if (criticalOperations.indexOf(key) > -1) { + filtered[key] = value; + } + + return filtered; + }, + {} as { [key: string]: IOperationAlarmsAndRules }, + ), + ).map((x) => x.serverSideZonalAlarmsMap[availabilityZonedId]), + ), + }, + ), + ); + + counter++; + } + + let keyPrefix: string = ''; + + let regionalOperationFaultCountMetrics: { [key: string]: IMetric } = {}; + + props.service.operations + .filter((x) => x.critical == true) + .forEach((x) => { + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + + regionalOperationFaultCountMetrics[keyPrefix] = + RegionalAvailabilityMetrics.createRegionalAvailabilityMetric({ + label: x.operationName + ' fault count', + metricDetails: x.serverSideAvailabilityMetricDetails, + metricType: AvailabilityMetricType.FAULT_COUNT, + }); + }); + + let regionalFaultCount: IMetric = new MathExpression({ + usingMetrics: regionalOperationFaultCountMetrics, + expression: Object.keys(regionalOperationFaultCountMetrics).join('+'), + label: props.service.serviceName + ' fault count', + period: props.service.period, + }); + + this.regionalFaultCountServerSideAlarm = new Alarm( + this, + 'RegionalFaultCount', + { + alarmName: + Fn.ref('AWS::Region') + + '-' + + props.service.serviceName.toLowerCase() + + '-fault-count', + datapointsToAlarm: 3, + evaluationPeriods: 5, + comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: props.service.faultCountThreshold, + alarmDescription: + 'Counts faults from all critical operation in the service', + metric: regionalFaultCount, + }, + ); + + let canaryAlarms: IAlarm[] = Object.values( + Object.entries(props.perOperationAlarmsAndRules).reduce( + (filtered, [key, value]) => { + if (criticalOperations.indexOf(key) > -1) { + filtered[key] = value; + } + + return filtered; + }, + {} as { [key: string]: IOperationAlarmsAndRules }, + ), + ) + .reduce((filtered, value) => { + if (value.canaryRegionalAlarmsAndRules !== undefined) { + filtered.push(value.canaryRegionalAlarmsAndRules); + } + return filtered; + }, [] as ICanaryOperationRegionalAlarmsAndRules[]) + .map((x) => x.availabilityOrLatencyAlarm); + + if ( + canaryAlarms !== undefined && + canaryAlarms !== null && + canaryAlarms.length > 0 + ) { + this.regionalAvailabilityOrLatencyCanaryAlarm = new CompositeAlarm( + this, + 'ServiceCanaryAvailabilityOrLatencyAggregateAlarm', + { + compositeAlarmName: + Fn.ref('AWS::Region') + + '-' + + props.service.serviceName.toLowerCase() + + '-canary-availability-or-latency-aggregate-alarm', + alarmRule: AlarmRule.anyOf(...canaryAlarms), + }, + ); + } + + let canaryAvailabilityAlarms: IAlarm[] = Object.values( + Object.entries(props.perOperationAlarmsAndRules).reduce( + (filtered, [key, value]) => { + if (criticalOperations.indexOf(key) > -1) { + filtered[key] = value; + } + + return filtered; + }, + {} as { [key: string]: IOperationAlarmsAndRules }, + ), + ) + .reduce((filtered, value) => { + if (value.canaryRegionalAlarmsAndRules !== undefined) { + filtered.push(value.canaryRegionalAlarmsAndRules); + } + return filtered; + }, [] as ICanaryOperationRegionalAlarmsAndRules[]) + .map((x) => x.availabilityAlarm); + + if ( + canaryAvailabilityAlarms !== undefined && + canaryAvailabilityAlarms !== null && + canaryAvailabilityAlarms.length > 0 + ) { + this.regionalAvailabilityCanaryAlarm = new CompositeAlarm( + this, + 'ServiceCanaryAvailabilityAggregateAlarm', + { + compositeAlarmName: + Fn.ref('AWS::Region') + + '-' + + props.service.serviceName.toLowerCase() + + '-canary-availability-aggregate-alarm', + alarmRule: AlarmRule.anyOf(...canaryAvailabilityAlarms), + }, + ); + } + + this.regionalAvailabilityOrLatencyServerSideAlarm = new CompositeAlarm( + this, + 'ServiceServerSideAggregateIsolatedImpactAlarm', + { + compositeAlarmName: + Fn.ref('AWS::Region') + + '-' + + props.service.serviceName.toLowerCase() + + '-server-side-aggregate-alarm', + alarmRule: AlarmRule.anyOf( + ...Object.values( + Object.entries(props.perOperationAlarmsAndRules).reduce( + (filtered, [key, value]) => { + if (criticalOperations.indexOf(key) > -1) { + filtered[key] = value; + } + + return filtered; + }, + {} as { [key: string]: IOperationAlarmsAndRules }, + ), + ) + .map((x) => x.serverSideRegionalAlarmsAndRules) + .map((x) => x.availabilityOrLatencyAlarm), + ), + }, + ); + + this.regionalAvailabilityServerSideAlarm = new CompositeAlarm( + this, + 'ServiceServerSideAvailabilityAlarm', + { + compositeAlarmName: + Fn.ref('AWS::Region') + + '-' + + props.service.serviceName.toLowerCase() + + '-server-side-availability-alarm', + alarmRule: AlarmRule.anyOf( + ...Object.values( + Object.entries(props.perOperationAlarmsAndRules).reduce( + (filtered, [key, value]) => { + if (criticalOperations.indexOf(key) > -1) { + filtered[key] = value; + } + + return filtered; + }, + {} as { [key: string]: IOperationAlarmsAndRules }, + ), + ) + .map((x) => x.serverSideRegionalAlarmsAndRules) + .map((x) => x.availabilityAlarm), + ), + }, + ); + } +} diff --git a/src/alarmsandrules/props/BaseOperationRegionalAlarmsAndRulesProps.ts b/src/alarmsandrules/props/BaseOperationRegionalAlarmsAndRulesProps.ts new file mode 100644 index 0000000..fb2628a --- /dev/null +++ b/src/alarmsandrules/props/BaseOperationRegionalAlarmsAndRulesProps.ts @@ -0,0 +1,30 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IContributorInsightRuleDetails } from '../../services/IContributorInsightRuleDetails'; +import { IOperationMetricDetails } from '../../services/IOperationMetricDetails'; + +/** + * The base props for an operation regional alarms and rules configuration + */ +export interface BaseOperationRegionalAlarmsAndRulesProps { + /** + * The metric details for availability metrics + */ + readonly availabilityMetricDetails: IOperationMetricDetails; + + /** + * The metric details for latency metrics + */ + readonly latencyMetricDetails: IOperationMetricDetails; + + /** + * (Optional) A suffix to be appended to alarm and rule names + */ + readonly nameSuffix: string; + + /** + * (Optional) Details for creating contributor insight rules, which help + * make the server-side alarms for detecting single AZ failures more accurate + */ + readonly contributorInsightRuleDetails?: IContributorInsightRuleDetails; +} diff --git a/src/alarmsandrules/props/BaseOperationZonalAlarmsAndRulesProps.ts b/src/alarmsandrules/props/BaseOperationZonalAlarmsAndRulesProps.ts new file mode 100644 index 0000000..ed89021 --- /dev/null +++ b/src/alarmsandrules/props/BaseOperationZonalAlarmsAndRulesProps.ts @@ -0,0 +1,75 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { IAvailabilityZoneMapper } from '../../azmapper/IAvailabilityZoneMapper'; +import { IContributorInsightRuleDetails } from '../../services/IContributorInsightRuleDetails'; +import { IOperationMetricDetails } from '../../services/IOperationMetricDetails'; +import { Operation } from '../../services/Operation'; +import { OutlierDetectionAlgorithm } from '../../utilities/OutlierDetectionAlgorithm'; + +/** + * The base properties for an operation zonal alarms and rules configuration + */ +export interface BaseOperationZonalAlarmsAndRulesProps { + /** + * The operation for these alarms and rules + */ + readonly operation: Operation; + + /** + * The availability metric details to create alarms and rules from + */ + readonly availabilityMetricDetails: IOperationMetricDetails; + + /** + * The latency metric details to create alarms and rules from + */ + readonly latencyMetricDetails: IOperationMetricDetails; + + /** + * The Availability Zone Id the alarms and rules are being created for + */ + readonly availabilityZoneId: string; + + /** + * A counter used to name the CDK constructs uniquely + */ + readonly counter: number; + + /** + * Used when the OutlierDetectionAlgorithm is set to STATIC, should be a + * number between 0 and 1, non-inclusive, representing the percentage + * or faults or high latency responses that an AZ must have to be considered + * an outlier. + */ + readonly outlierThreshold: number; + + /** + * (Optional) Details for creating contributor insight rules, which help + * make the server-side alarms for detecting single AZ failures more accurate + */ + readonly contributorInsightRuleDetails?: IContributorInsightRuleDetails; + + /** + * The outlier detection algorithm used to determine if Availability Zones + * or instances are outliers for latency or availability impact. Currently this property + * is ignored and only STATIC is used. + */ + readonly outlierDetectionAlgorithm: OutlierDetectionAlgorithm; + + /** + * (Optional) A suffix to apply to alarm and rules names, like "-server" for server + * side metrics and alarms + */ + readonly nameSuffix?: string; + + /** + * The AZ mapper + */ + readonly azMapper: IAvailabilityZoneMapper; + + /** + * A function that is used to perform outlier detection + */ + readonly outlierDetectionFunction?: IFunction; +} diff --git a/src/alarmsandrules/props/CanaryOperationRegionalAlarmsAndRulesProps.ts b/src/alarmsandrules/props/CanaryOperationRegionalAlarmsAndRulesProps.ts new file mode 100644 index 0000000..e63d691 --- /dev/null +++ b/src/alarmsandrules/props/CanaryOperationRegionalAlarmsAndRulesProps.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { BaseOperationRegionalAlarmsAndRulesProps } from './BaseOperationRegionalAlarmsAndRulesProps'; + +/** + * Canary metric properties for an operation at the regional level + */ +export interface CanaryOperationRegionalAlarmsAndRulesProps + extends BaseOperationRegionalAlarmsAndRulesProps {} diff --git a/src/alarmsandrules/props/CanaryOperationZonalAlarmsAndRulesProps.ts b/src/alarmsandrules/props/CanaryOperationZonalAlarmsAndRulesProps.ts new file mode 100644 index 0000000..c4d1579 --- /dev/null +++ b/src/alarmsandrules/props/CanaryOperationZonalAlarmsAndRulesProps.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { BaseOperationZonalAlarmsAndRulesProps } from './BaseOperationZonalAlarmsAndRulesProps'; + +/** + * Canary metric properties for an operation at the zonal level + */ +export interface CanaryOperationZonalAlarmsAndRulesProps + extends BaseOperationZonalAlarmsAndRulesProps {} diff --git a/src/alarmsandrules/props/OperationAlarmsAndRulesProps.ts b/src/alarmsandrules/props/OperationAlarmsAndRulesProps.ts new file mode 100644 index 0000000..550c590 --- /dev/null +++ b/src/alarmsandrules/props/OperationAlarmsAndRulesProps.ts @@ -0,0 +1,54 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { IAvailabilityZoneMapper } from '../../azmapper/IAvailabilityZoneMapper'; +import { ContributorInsightRuleDetails } from '../../services/ContributorInsightRuleDetails'; +import { Operation } from '../../services/Operation'; +import { OutlierDetectionAlgorithm } from '../../utilities/OutlierDetectionAlgorithm'; + +/** + * The properties for the operation alarms and rules + */ +export interface OperationAlarmsAndRulesProps { + /** + * The operation the alarms and rules are for + */ + readonly operation: Operation; + + /** + * The load balancer associated with this operation. + * + * @default - If not provided, its ARN will not be included + * in top level alarm descriptions that can be referenced by + * automation to identify which load balancers should execute + * a zonal shift. + */ + readonly loadBalancer?: ILoadBalancerV2; + + /** + * Rule details for contributor insight rules + */ + readonly contributorInsightRuleDetails?: ContributorInsightRuleDetails; + + /** + * The outlier threshold used with the STATIC outlier detection algorithm + */ + readonly outlierThreshold: number; + + /** + * The outlier detection algorithm + */ + readonly outlierDetectionAlgorithm: OutlierDetectionAlgorithm; + + /** + * The AZ Mapper + */ + readonly azMapper: IAvailabilityZoneMapper; + + /** + * An optional Lambda function used to perform outlier detection + * for chi-squared or z-score algorithms + */ + readonly outlierDetectionFunction?: IFunction; +} diff --git a/src/alarmsandrules/props/ServerSideOperationRegionalAlarmsAndRulesProps.ts b/src/alarmsandrules/props/ServerSideOperationRegionalAlarmsAndRulesProps.ts new file mode 100644 index 0000000..96753a8 --- /dev/null +++ b/src/alarmsandrules/props/ServerSideOperationRegionalAlarmsAndRulesProps.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { BaseOperationRegionalAlarmsAndRulesProps } from './BaseOperationRegionalAlarmsAndRulesProps'; + +/** + * The server side rules and alarms properties for an operation + */ +export interface ServerSideOperationRegionalAlarmsAndRulesProps + extends BaseOperationRegionalAlarmsAndRulesProps {} diff --git a/src/alarmsandrules/props/ServerSideOperationZonalAlarmsAndRulesProps.ts b/src/alarmsandrules/props/ServerSideOperationZonalAlarmsAndRulesProps.ts new file mode 100644 index 0000000..b4f129a --- /dev/null +++ b/src/alarmsandrules/props/ServerSideOperationZonalAlarmsAndRulesProps.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { BaseOperationZonalAlarmsAndRulesProps } from './BaseOperationZonalAlarmsAndRulesProps'; + +/** + * The server side zonal alarms and rules properties for an operation + */ +export interface ServerSideOperationZonalAlarmsAndRulesProps + extends BaseOperationZonalAlarmsAndRulesProps {} diff --git a/src/alarmsandrules/props/ServiceAlarmsAndRulesProps.ts b/src/alarmsandrules/props/ServiceAlarmsAndRulesProps.ts new file mode 100644 index 0000000..063e408 --- /dev/null +++ b/src/alarmsandrules/props/ServiceAlarmsAndRulesProps.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAvailabilityZoneMapper } from '../../azmapper/IAvailabilityZoneMapper'; +import { IService } from '../../services/IService'; +import { IOperationAlarmsAndRules } from '../IOperationAlarmsAndRules'; + +export interface ServiceAlarmsAndRulesProps { + readonly service: IService; + + readonly perOperationAlarmsAndRules: { + [key: string]: IOperationAlarmsAndRules; + }; + + /** + * The AZ Mapper + */ + readonly azMapper: IAvailabilityZoneMapper; +} diff --git a/src/azmapper/AvailabilityZoneMapper.ts b/src/azmapper/AvailabilityZoneMapper.ts new file mode 100644 index 0000000..0d15dde --- /dev/null +++ b/src/azmapper/AvailabilityZoneMapper.ts @@ -0,0 +1,276 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as fs from 'fs'; +import * as path from 'path'; +import { + CustomResource, + Duration, + Fn, + Reference, + RemovalPolicy, +} from 'aws-cdk-lib'; +import { + Effect, + IManagedPolicy, + IRole, + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal, +} from 'aws-cdk-lib/aws-iam'; +import { + Architecture, + Code, + Function, + IFunction, + Runtime, + Tracing, +} from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup, LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { Construct } from 'constructs'; +import { IAvailabilityZoneMapper } from './IAvailabilityZoneMapper'; +import { AvailabilityZoneMapperProps } from './props/AvailabilityZoneMapperProps'; + +/** + * A construct that allows you to map AZ names to ids and back + */ +export class AvailabilityZoneMapper + extends Construct + implements IAvailabilityZoneMapper { + /** + * The function that does the mapping + */ + function: IFunction; + + /** + * The log group for the function's logs + */ + logGroup: ILogGroup; + + /** + * The custom resource that can be referenced to use + * Fn::GetAtt functions on to retrieve availability zone + * names and ids + */ + mapper: CustomResource; + + constructor( + scope: Construct, + id: string, + props?: AvailabilityZoneMapperProps, + ) { + super(scope, id); + + /* + let currentNode: Construct | undefined = this.node.scope; + + while (currentNode !== undefined && !(currentNode instanceof Stack)) { + currentNode = currentNode.node.scope; + } + + if (currentNode !== undefined) { + (currentNode as Stack).addTransform('AWS::LanguageExtensions'); + } + */ + + let xrayManagedPolicy: IManagedPolicy = new ManagedPolicy( + this, + 'XrayManagedPolicy', + { + path: '/azmapper/', + statements: [ + new PolicyStatement({ + actions: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + 'xray:GetSamplingRules', + 'xray:GetSamplingTargets', + 'xray:GetSamplingStatisticSummaries', + ], + effect: Effect.ALLOW, + resources: ['*'], + }), + ], + }, + ); + + let ec2ManagedPolicy: IManagedPolicy = new ManagedPolicy( + this, + 'EC2ManagedPolicy', + { + path: '/azmapper/', + statements: [ + new PolicyStatement({ + actions: ['ec2:DescribeAvailabilityZones'], + effect: Effect.ALLOW, + resources: ['*'], + }), + ], + }, + ); + + let executionRole: IRole = new Role(this, 'executionRole', { + assumedBy: new ServicePrincipal('lambda.amazonaws.com'), + path: '/azmapper/', + managedPolicies: [xrayManagedPolicy, ec2ManagedPolicy], + }); + + const file: string = fs.readFileSync( + path.resolve(__dirname, './../azmapper/src/index.py'), + 'utf-8', + ); + + this.function = new Function(this, 'AvailabilityZoneMapperFunction', { + runtime: Runtime.PYTHON_3_12, + code: Code.fromInline(file), + handler: 'index.handler', + role: executionRole, + architecture: Architecture.ARM_64, + tracing: Tracing.ACTIVE, + timeout: Duration.seconds(20), + memorySize: 512, + environment: { + REGION: Fn.ref('AWS::Region'), + PARTITION: Fn.ref('AWS::Partition'), + }, + }); + + this.logGroup = new LogGroup(this, 'LogGroup', { + logGroupName: `/aws/lambda/${this.function.functionName}`, + retention: RetentionDays.ONE_DAY, + removalPolicy: RemovalPolicy.DESTROY, + }); + + new ManagedPolicy(this, 'CloudWatchManagedPolicy', { + path: '/azmapper/', + statements: [ + new PolicyStatement({ + actions: ['cloudwatch:PutMetricData'], + effect: Effect.ALLOW, + resources: ['*'], + }), + new PolicyStatement({ + actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], + effect: Effect.ALLOW, + resources: [ + Fn.sub( + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:', + ) + + this.logGroup.logGroupName + + ':*', + ], + }), + ], + roles: [executionRole], + }); + + this.mapper = new CustomResource(this, 'AvailabilityZoneMapper', { + serviceToken: this.function.functionArn, + properties: + props?.availabilityZoneNames !== undefined + ? { AvailabilityZones: props.availabilityZoneNames } + : {}, + }); + } + + /** + * Gets the Availability Zone Id for the given Availability Zone Name in this account + * @param availabilityZoneName + * @returns + */ + availabilityZoneId(availabilityZoneName: string): string { + return this.mapper.getAttString(availabilityZoneName); + } + + /** + * Gets the Availability Zone Name for the given Availability Zone Id in this account + * @param availabilityZoneId + * @returns + */ + availabilityZoneName(availabilityZoneId: string): string { + return this.mapper.getAttString(availabilityZoneId); + } + + /** + * Gets the prefix for the region used with Availability Zone Ids, for example + * in us-east-1, this returns "use1" + * @returns + */ + regionPrefixForAvailabilityZoneIds(): string { + return this.mapper.getAttString(Fn.ref('AWS::Region')); + } + + /** + * Returns an array for Availability Zone Ids for the supplied Availability Zone names, + * they are returned in the same order the names were provided + * @param availabilityZoneNames + * @returns + */ + availabilityZoneIdsAsArray(availabilityZoneNames: string[]): string[] { + let ids: string[] = []; + + for (let i = 0; i < availabilityZoneNames.length; i++) { + ids.push(this.availabilityZoneId(availabilityZoneNames[i])); + } + + return ids; + } + + /** + * Returns a comma delimited list of Availability Zone Ids for the supplied + * Availability Zone names. You can use this string with Fn.Select(x, Fn.Split(",", azs)) to + * get a specific Availability Zone Id + * @param availabilityZoneNames + * @returns + */ + availabilityZoneIdsAsCommaDelimitedList( + availabilityZoneNames: string[], + ): string { + let ids: string[] = []; + + for (let i = 0; i < availabilityZoneNames.length; i++) { + ids.push(this.availabilityZoneId(availabilityZoneNames[i])); + } + + return ids.join(','); + } + + /** + * Returns a comma delimited list of Availability Zone Ids for the supplied + * Availability Zone names. You can use this string with Fn.Select(x, Fn.Split(",", azs)) to + * get a specific Availability Zone Id + * @returns + */ + allAvailabilityZoneIdsAsCommaDelimitedList(): string { + return this.mapper.getAttString('AllAvailabilityZoneIds'); + } + + /** + * Returns a reference that can be cast to a string array with all of the + * Availability Zone Ids + * @returns + */ + allAvailabilityZoneIdsAsArray(): Reference { + return this.mapper.getAtt('AllAvailabilityZoneIdsArray'); + } + + /** + * Given a letter like "f" or "a", returns the Availability Zone Id for that + * Availability Zone name in this account + * @param letter + * @returns + */ + availabilityZoneIdFromAvailabilityZoneLetter(letter: string): string { + return this.mapper.getAttString(letter); + } + + /** + * Gets all of the Availability Zone names in this Region as a comma delimited + * list. You can use this string with Fn.Select(x, Fn.Split(",", azs)) to + * get a specific Availability Zone Name + * @returns + */ + allAvailabilityZoneNamesAsCommaDelimitedList(): string { + return this.mapper.getAttString('AllAvailabilityZoneNames'); + } +} diff --git a/src/azmapper/IAvailabilityZoneMapper.ts b/src/azmapper/IAvailabilityZoneMapper.ts new file mode 100644 index 0000000..ce1c9a5 --- /dev/null +++ b/src/azmapper/IAvailabilityZoneMapper.ts @@ -0,0 +1,102 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { CustomResource, Reference } from 'aws-cdk-lib'; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; +import { IConstruct } from 'constructs'; + +/** + * A wrapper for the Availability Zone mapper construct + * that allows you to translate Availability Zone names + * to Availability Zone Ids and vice a versa using the + * mapping in the AWS account where this is deployed. + */ +export interface IAvailabilityZoneMapper extends IConstruct { + /** + * The function that does the mapping + */ + function: IFunction; + + /** + * The log group for the function's logs + */ + logGroup: ILogGroup; + + /** + * The custom resource that can be referenced to use + * Fn::GetAtt functions on to retrieve availability zone + * names and ids + */ + mapper: CustomResource; + + /** + * Gets the Availability Zone Id for the given Availability Zone Name in this account + * @param availabilityZoneName + * @returns + */ + availabilityZoneId(availabilityZoneName: string): string; + + /** + * Gets the Availability Zone Name for the given Availability Zone Id in this account + * @param availabilityZoneId + * @returns + */ + availabilityZoneName(availabilityZoneId: string): string; + + /** + * Gets the prefix for the region used with Availability Zone Ids, for example + * in us-east-1, this returns "use1" + * @returns + */ + regionPrefixForAvailabilityZoneIds(): string; + + /** + * Returns an array for Availability Zone Ids for the supplied Availability Zone names, + * they are returned in the same order the names were provided + * @param availabilityZoneNames + * @returns + */ + availabilityZoneIdsAsArray(availabilityZoneNames: string[]): string[]; + + /** + * Returns a comma delimited list of Availability Zone Ids for the supplied + * Availability Zone names. You can use this string with Fn.Select(x, Fn.Split(",", azs)) to + * get a specific Availability Zone Id + * @param availabilityZoneNames + * @returns + */ + availabilityZoneIdsAsCommaDelimitedList( + availabilityZoneNames: string[], + ): string; + + /** + * Returns a comma delimited list of Availability Zone Ids for the supplied + * Availability Zone names. You can use this string with Fn.Select(x, Fn.Split(",", azs)) to + * get a specific Availability Zone Id + * @returns + */ + allAvailabilityZoneIdsAsCommaDelimitedList(): string; + + /** + * Returns a reference that can be cast to a string array with all of the + * Availability Zone Ids + * @returns + */ + allAvailabilityZoneIdsAsArray(): Reference; + + /** + * Given a letter like "f" or "a", returns the Availability Zone Id for that + * Availability Zone name in this account + * @param letter + * @returns + */ + availabilityZoneIdFromAvailabilityZoneLetter(letter: string): string; + + /** + * Gets all of the Availability Zone names in this Region as a comma delimited + * list. You can use this string with Fn.Select(x, Fn.Split(",", azs)) to + * get a specific Availability Zone Name + * @returns + */ + allAvailabilityZoneNamesAsCommaDelimitedList(): string; +} diff --git a/src/azmapper/props/AvailabilityZoneMapperProps.ts b/src/azmapper/props/AvailabilityZoneMapperProps.ts new file mode 100644 index 0000000..72b458c --- /dev/null +++ b/src/azmapper/props/AvailabilityZoneMapperProps.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/** + * Properties for the AZ mapper + */ +export interface AvailabilityZoneMapperProps { + /** + * The currently in use Availability Zone names which + * constrains the list of AZ IDs that are returned + * + * @default - No names are provided and the mapper returns + * all AZs in the region in its lists + */ + readonly availabilityZoneNames?: string[]; +} diff --git a/src/azmapper/src/index.py b/src/azmapper/src/index.py new file mode 100644 index 0000000..a5f76c1 --- /dev/null +++ b/src/azmapper/src/index.py @@ -0,0 +1,107 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import urllib3 +import json +import os +import boto3 + +SUCCESS = "SUCCESS" +FAILED = "FAILED" +http = urllib3.PoolManager() +region = os.environ.get("AWS_REGION") +ec2 = boto3.client('ec2', region_name = region) + +def handler(event, context): + try: + print(event) + if event['RequestType'] == 'Delete': + sendResponse(event, context, SUCCESS, {}) + return + + zone_names=[] + if 'ResourceProperties' in event and 'AvailabilityZones' in event['ResourceProperties'] and event['ResourceProperties']['AvailabilityZones'] != '': + zone_names = event['ResourceProperties']['AvailabilityZones'] + + in_use_azs = ec2.describe_availability_zones(Filters=[ { 'Name': 'zone-type', 'Values': [ 'availability-zone'] } ], ZoneNames = zone_names) + all_azs = ec2.describe_availability_zones(Filters=[ { 'Name': 'zone-type', 'Values': [ 'availability-zone'] } ]) + + az_map = {} + + biggest_letter = sorted([x["ZoneName"][-1] for x in all_azs["AvailabilityZones"]], reverse=True)[0] + biggest_number = int(sorted([x["ZoneId"][-1] for x in all_azs["AvailabilityZones"]], reverse=True)[0]) + + for code in range(ord('a'), ord(biggest_letter) + 1): + az_map[chr(code)] = "" + + for num in range(1, biggest_number + 1): + az_map["az" + str(num)] = "" + + # Supply the region and get its shorthand + az_map[region] = all_azs["AvailabilityZones"][0]["ZoneId"].split("-")[0] + + # Get the in use availability zone Ids as comma delimited list + az_map["InUseAvailabilityZoneIds"] = ",".join(x["ZoneId"] for x in in_use_azs["AvailabilityZones"]) + + # Get the in use availability zone Ids as an array + az_map["InUseAvailabilityZoneIdsArray"] = [x["ZoneId"] for x in in_use_azs["AvailabilityZones"]] + + # Get the in use availability zone names as comma delimited list + az_map["InUseAvailabilityZoneNames"] = ",".join(x["ZoneName"] for x in in_use_azs["AvailabilityZones"]) + + # Get the in use availability zone names as an array + az_map["InUseAvailabilityZoneNames"] = ",".join(x["ZoneName"] for x in in_use_azs["AvailabilityZones"]) + + # Make a comma delimited list of name to id joined with a colon + az_map["InUseAvailabilityZoneNameToIdMap"] = ",".join(x["ZoneName"] + ":" + x["ZoneId"] for x in in_use_azs["AvailabilityZones"]) + + # Make a comma delimited list of name to id joined with a colon for all AZs + az_map["AllAvailabilityZoneNameToIdMap"] = ",".join(x["ZoneName"] + ":" + x["ZoneId"] for x in all_azs["AvailabilityZones"]) + + # All availability zones in the region + az_map["AllAvailabilityZoneIds"] = ",".join(x["ZoneId"] for x in all_azs["AvailabilityZones"]) + az_map["AllAvailabilityZoneIdsArray"] = [x["ZoneId"] for x in all_azs["AvailabilityZones"]] + az_map["AllAvailabilityZoneNames"] = ",".join(x["ZoneName"] for x in all_azs["AvailabilityZones"]) + az_map["AllAvailabilityZoneNamesArray"] = [x["ZoneName"] for x in all_azs["AvailabilityZones"]] + + # Allow lookups for individual AZ names to Ids + for item in all_azs['AvailabilityZones']: + az_map[item['ZoneName']] = item['ZoneId'] + az_map[item['ZoneId']] = item['ZoneName'] + az_id = item['ZoneId'].split('-')[1].lower() + az_map[az_id] = item['ZoneName'] + az_letter = item['ZoneName'][-1] + az_map[az_letter] = item['ZoneId'] + + print(json.dumps(az_map)) + + sendResponse(event, context, SUCCESS, az_map) + except Exception as e: + print(e) + sendResponse(event, context, FAILED, str(e)) + return + +def sendResponse(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False, reason=None): + responseUrl = event['ResponseURL'] + print(responseUrl) + responseBody = { + 'Status' : responseStatus, + 'Reason' : reason or "See the details in CloudWatch Log Stream: {}".format(context.log_stream_name), + 'PhysicalResourceId' : physicalResourceId or context.log_stream_name, + 'StackId' : event['StackId'], + 'RequestId' : event['RequestId'], + 'LogicalResourceId' : event['LogicalResourceId'], + 'NoEcho' : noEcho, + 'Data' : responseData + } + json_responseBody = json.dumps(responseBody, default=str) + print("Response body:") + print(json_responseBody) + headers = { + 'content-type' : '', + 'content-length' : str(len(json_responseBody)) + } + try: + response = http.request('PUT', responseUrl, headers=headers, body=json_responseBody) + print("Status code:", response.status) + except Exception as e: + print("send(...) failed executing http.request(..):", e) \ No newline at end of file diff --git a/src/canaries/CanaryFunction.ts b/src/canaries/CanaryFunction.ts new file mode 100644 index 0000000..f01ff8a --- /dev/null +++ b/src/canaries/CanaryFunction.ts @@ -0,0 +1,202 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as path from 'path'; +import { Duration, Fn, RemovalPolicy } from 'aws-cdk-lib'; +import { ISecurityGroup, SecurityGroup } from 'aws-cdk-lib/aws-ec2'; +import { + Effect, + IManagedPolicy, + IRole, + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal, +} from 'aws-cdk-lib/aws-iam'; +import { + Architecture, + Code, + Function, + IFunction, + ILayerVersion, + LayerVersion, + Runtime, + Tracing, +} from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup, LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { Construct } from 'constructs'; +import { ICanaryFunction } from './ICanaryFunction'; +import { CanaryFunctionProps } from './props/CanaryFunctionProps'; + +export class CanaryFunction extends Construct implements ICanaryFunction { + /** + * The canary function + */ + function: IFunction; + + /** + * The log group where the canarty logs will be sent + */ + logGroup: ILogGroup; + + constructor(scope: Construct, id: string, props: CanaryFunctionProps) { + super(scope, id); + + let xrayManagedPolicy: IManagedPolicy = new ManagedPolicy( + this, + 'xrayManagedPolicy', + { + path: '/canary/', + statements: [ + new PolicyStatement({ + actions: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + 'xray:GetSamplingRules', + 'xray:GetSamplingTargets', + 'xray:GetSamplingStatisticSummaries', + ], + effect: Effect.ALLOW, + resources: ['*'], + }), + ], + }, + ); + let ec2ManagedPolicy = new ManagedPolicy(this, 'ec2ManagedPolicy', { + path: '/canary/', + statements: [ + new PolicyStatement({ + actions: [ + 'ec2:CreateNetworkInterface', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DeleteNetworkInterface', + ], + effect: Effect.ALLOW, + resources: ['*'], + }), + ], + }); + + let executionRole: IRole = new Role(this, 'executionRole', { + assumedBy: new ServicePrincipal('lambda.amazonaws.com'), + path: '/canary/', + managedPolicies: [xrayManagedPolicy, ec2ManagedPolicy], + }); + + /* + let code: AssetCode = Code.fromAsset(path.join(__dirname, "src/"), { + bundling: { + //image: new Runtime('python3.12:latest-arm64', RuntimeFamily.PYTHON).bundlingImage, + image: Runtime.PYTHON_3_12.bundlingImage, + command: [ + 'bash', '-c', + 'pip install --no-cache -r requirements.txt -t /asset-output && cp --archive --update . /asset-output', + ], + platform: 'linux/arm64', + }, + }); + */ + let monitoringLayer: ILayerVersion = new LayerVersion( + this, + 'MonitoringLayer', + { + code: Code.fromAsset( + path.join(__dirname, '../monitoring/src/monitoring-layer.zip'), + ), + compatibleArchitectures: [Architecture.ARM_64], + compatibleRuntimes: [Runtime.PYTHON_3_12], + }, + ); + + if (props.vpc !== undefined && props.vpc != null) { + let sg: ISecurityGroup = new SecurityGroup(this, 'canarySecurityGroup', { + description: 'Allow canary to communicate with load balancer', + vpc: props.vpc, + allowAllOutbound: true, + }); + + this.function = new Function(this, 'canary', { + runtime: Runtime.PYTHON_3_12, + code: Code.fromAsset(path.join(__dirname, 'src/canary.zip')), + handler: 'index.handler', + role: executionRole, + architecture: Architecture.ARM_64, + tracing: Tracing.ACTIVE, + timeout: Duration.seconds(240), + memorySize: 512, + layers: [monitoringLayer], + environment: { + REGION: Fn.ref('AWS::Region'), + PARTITION: Fn.ref('AWS::Partition'), + TIMEOUT: + props.httpTimeout !== undefined + ? props.httpTimeout.toSeconds().toString() + : '2', + IGNORE_SSL_ERRORS: ( + props.ignoreTlsErrors !== undefined && props.ignoreTlsErrors == true + ) + .toString() + .toLowerCase(), + }, + vpc: props.vpc, + securityGroups: [sg], + vpcSubnets: props.subnetSelection, + }); + } else { + this.function = new Function(this, 'canary', { + runtime: Runtime.PYTHON_3_12, + code: Code.fromAsset(path.join(__dirname, 'src/canary.zip')), + handler: 'index.handler', + role: executionRole, + architecture: Architecture.ARM_64, + tracing: Tracing.ACTIVE, + timeout: Duration.seconds(240), + memorySize: 512, + layers: [monitoringLayer], + environment: { + REGION: Fn.ref('AWS::Region'), + PARTITION: Fn.ref('AWS::Partition'), + TIMEOUT: + props.httpTimeout !== undefined + ? props.httpTimeout.toSeconds().toString() + : '2', + IGNORE_SSL_ERRORS: ( + props.ignoreTlsErrors !== undefined && props.ignoreTlsErrors == true + ) + .toString() + .toLowerCase(), + }, + }); + } + + this.function.addPermission('invokePermission', { + action: 'lambda:InvokeFunction', + principal: new ServicePrincipal('events.amazonaws.com'), + sourceArn: Fn.sub( + 'arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/*', + ), + }); + + this.logGroup = new LogGroup(this, 'logGroup', { + logGroupName: `/aws/lambda/${this.function.functionName}`, + retention: RetentionDays.ONE_WEEK, + removalPolicy: RemovalPolicy.DESTROY, + }); + + new ManagedPolicy(this, 'cwManagedPolicy', { + path: '/canary/', + statements: [ + new PolicyStatement({ + actions: ['cloudwatch:PutMetricData'], + effect: Effect.ALLOW, + resources: ['*'], + }), + new PolicyStatement({ + actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], + effect: Effect.ALLOW, + resources: [this.logGroup.logGroupArn], + }), + ], + roles: [executionRole], + }); + } +} diff --git a/src/canaries/CanaryTest.ts b/src/canaries/CanaryTest.ts new file mode 100644 index 0000000..ff6a992 --- /dev/null +++ b/src/canaries/CanaryTest.ts @@ -0,0 +1,110 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { IRule, Rule, RuleTargetInput, Schedule } from 'aws-cdk-lib/aws-events'; +import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets'; +import { Construct } from 'constructs'; +import { CanaryTestProps } from './props/CanaryTestProps'; + +export class CanaryTest extends Construct { + timedEventRules: { [key: string]: IRule }; + + metricNamespace: string; + + constructor(scope: Construct, id: string, props: CanaryTestProps) { + super(scope, id); + this.timedEventRules = {}; + + this.metricNamespace = props.operation.canaryMetricDetails + ? props.operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .metricNamespace + : 'canary/metrics'; + + props.operation.service.availabilityZoneNames.forEach( + (availabilityZoneName, index) => { + let availabilityZoneId: string = + props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + availabilityZoneName.substring(availabilityZoneName.length - 1), + ); + + let scheme: string = props.operation.service.baseUrl.split(':')[0]; + let url: string = + scheme + + '://' + + availabilityZoneName + + '.' + + props.loadBalancer.loadBalancerDnsName + + props.operation.path; + + let data: { [key: string]: any } = { + parameters: { + methods: + props.httpMethods !== undefined + ? props.httpMethods + : props.operation.httpMethods, + url: url, + postData: props.postData === undefined ? '' : props.postData, + headers: props.headers === undefined ? {} : props.headers, + operation: props.operation.operationName, + faultBoundaryId: availabilityZoneId, + faultBoundary: 'az', + metricNamespace: this.metricNamespace, + requestCount: props.requestCount, + }, + }; + + this.timedEventRules[availabilityZoneId] = new Rule( + this, + 'AZ' + index + props.operation.operationName + 'TimedEvent', + { + schedule: Schedule.expression(props.schedule), + enabled: true, + targets: [ + new LambdaFunction(props.function, { + event: RuleTargetInput.fromObject(data), + }), + ], + }, + ); + }, + ); + + let scheme: string = props.operation.service.baseUrl.split(':')[0]; + let url: string = + scheme + + '://' + + props.loadBalancer.loadBalancerDnsName + + props.operation.path; + + let data: { [key: string]: any } = { + parameters: { + methods: + props.httpMethods !== undefined + ? props.httpMethods + : props.operation.httpMethods, + url: url, + postData: props.postData === undefined ? '' : props.postData, + headers: props.headers === undefined ? {} : props.headers, + operation: props.operation.operationName, + faultBoundaryId: Fn.ref('AWS::Region'), + faultBoundary: 'region', + metricNamespace: this.metricNamespace, + requestCount: props.regionalRequestCount, + }, + }; + + this.timedEventRules[Fn.ref('AWS::Region')] = new Rule( + this, + 'RegionalTimedEvent', + { + schedule: Schedule.expression(props.schedule), + enabled: true, + targets: [ + new LambdaFunction(props.function, { + event: RuleTargetInput.fromObject(data), + }), + ], + }, + ); + } +} diff --git a/src/canaries/ICanaryFunction.ts b/src/canaries/ICanaryFunction.ts new file mode 100644 index 0000000..423d973 --- /dev/null +++ b/src/canaries/ICanaryFunction.ts @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; + +export interface ICanaryFunction { + function: IFunction; + + logGroup: ILogGroup; +} diff --git a/src/canaries/props/AddCanaryTestProps.ts b/src/canaries/props/AddCanaryTestProps.ts new file mode 100644 index 0000000..f4431ce --- /dev/null +++ b/src/canaries/props/AddCanaryTestProps.ts @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { NetworkConfigurationProps } from './NetworkConfigurationProps'; + +/** + * The props for requesting a canary be made for an operation + */ +export interface AddCanaryTestProps { + /** + * The number of requests to send on each test + */ + readonly requestCount: number; + + /** + * Specifies a separate number of request to send to the regional endpoint. + * + * @default - The same number of requests specified by the requestCount property is used. + */ + readonly regionalRequestCount?: number; + + /** + * A schedule expression + */ + readonly schedule: string; + + /** + * Data to supply in a POST, PUT, or PATCH operation + * + * @default - No data is sent in a POST, PUT, or PATCH request + */ + readonly postData?: string; + + /** + * Any headers to include + * + * @default - No additional headers are added to the requests + */ + readonly headers?: { [key: string]: string }; + + /** + * The load balancer that will be tested against + */ + readonly loadBalancer: ILoadBalancerV2; + + /** + * Defining this will override the methods defined in the operation + * and will use these instead. + * + * @default - The operation's defined HTTP methods will be used to + * conduct the canary tests + */ + readonly httpMethods?: string[]; + + /** + * The VPC network configuration. + * + * @default - The Lambda function is not run in a VPC + */ + readonly networkConfiguration?: NetworkConfigurationProps; + + /** + * The timeout for each individual HTTP request + * + * @default - Defaults to 2 seconds + */ + readonly timeout?: Duration; + + /** + * Whether to ignore TLS validation errors + * + * @default - false + */ + readonly ignoreTlsErrors?: boolean; +} diff --git a/src/canaries/props/CanaryFunctionProps.ts b/src/canaries/props/CanaryFunctionProps.ts new file mode 100644 index 0000000..729c504 --- /dev/null +++ b/src/canaries/props/CanaryFunctionProps.ts @@ -0,0 +1,35 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { IVpc, SubnetSelection } from 'aws-cdk-lib/aws-ec2'; + +/** + * Properties for the canary function + */ +export interface CanaryFunctionProps { + /** + * If you want the function to run in your VPC, provide + * the VPC object. + * + * @default - The function is not run in a customer VPC + */ + readonly vpc?: IVpc; + + /** + * The subnets to use in the VPC + */ + readonly subnetSelection?: SubnetSelection; + + /** + * Set to true to ignore TLS certificate errors, default is to + * not ignore them + */ + readonly ignoreTlsErrors?: boolean; + + /** + * Specify the timeout for each http request + * + * @default - 2 seconds + */ + readonly httpTimeout?: Duration; +} diff --git a/src/canaries/props/CanaryTestProps.ts b/src/canaries/props/CanaryTestProps.ts new file mode 100644 index 0000000..a7c5e2a --- /dev/null +++ b/src/canaries/props/CanaryTestProps.ts @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { AddCanaryTestProps } from './AddCanaryTestProps'; +import { IAvailabilityZoneMapper } from '../../azmapper/IAvailabilityZoneMapper'; +import { IOperation } from '../../services/IOperation'; + +/** + * The props for creating a canary test on a single operation + */ +export interface CanaryTestProps extends AddCanaryTestProps { + /** + * The function that will run the canary requests + */ + readonly function: IFunction; + + /** + * The operation for the canary test + */ + readonly operation: IOperation; + + /** + * The AZ Mapper + */ + readonly azMapper: IAvailabilityZoneMapper; +} diff --git a/src/canaries/props/NetworkConfigurationProps.ts b/src/canaries/props/NetworkConfigurationProps.ts new file mode 100644 index 0000000..c320f7d --- /dev/null +++ b/src/canaries/props/NetworkConfigurationProps.ts @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IVpc, SubnetSelection } from 'aws-cdk-lib/aws-ec2'; + +/** + * The network configuration for the canary function + */ +export interface NetworkConfigurationProps { + /** + * The VPC to run the canary in. A security group will be created + * that allows the function to communicate with the VPC as well + * as the required IAM permissions. + */ + readonly vpc: IVpc; + + /** + * The subnets the Lambda function will be deployed in the VPC. + */ + readonly subnetSelection: SubnetSelection; +} diff --git a/src/canaries/src/index.py b/src/canaries/src/index.py new file mode 100644 index 0000000..a6646d9 --- /dev/null +++ b/src/canaries/src/index.py @@ -0,0 +1,255 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import json +import http.client +import urllib.parse +import urllib +import time +import functools +import copy +import os +import traceback +import requests +from inspect import getfullargspec +from dateutil import parser +from datetime import datetime, timedelta, timezone +from aws_embedded_metrics import metric_scope +from aws_xray_sdk.core import xray_recorder +from aws_xray_sdk.core import patch_all +from requests.packages.urllib3.util.retry import Retry +from requests.adapters import HTTPAdapter + +ignore_ssl_errors = os.environ.get("IGNORE_SSL_ERRORS") +region = os.environ.get("REGION") +#session = requests.Session() +#session.mount("http://", HTTPAdapter(max_retries = 0)) + +try: + timeout = float(os.environ.get("TIMEOUT")) +except (TypeError, ValueError): + timeout = 2.0 + +#patch_all(double_patch=True) +patch_all() + +def parameterized(dec): + def layer(*args, **kwargs): + def repl(f): + return dec(f, *args, **kwargs) + return repl + return layer + +@parameterized +def latency_timer(method, metric_parameter_name): + """Measure method latency""" + argspec = getfullargspec(method) + argument_index = argspec.args.index(metric_parameter_name) + + @functools.wraps(method) + def timed(*args, **kwargs): + start = time.perf_counter() + code = method(*args, **kwargs) + end = time.perf_counter() + + metrics = None + if metric_parameter_name in kwargs: + metrics = kwargs.get(metric_parameter_name, None) + elif argument_index > -1: + metrics = args[argument_index] + + if metrics is not None: + if code is not None and code >= 200 and code <= 399: + metrics.put_metric("SuccessLatency", (end - start) * 1000, "Milliseconds") + else: + metrics.put_metric("FaultLatency", (end - start) * 1000, "Milliseconds") + else: + print("Metrics parameter not found.") + return code + return timed + +@metric_scope +@latency_timer("metrics") +@xray_recorder.capture('url_check') +def verify_request(context, item, method, metrics = None): + method_start = (time.time() * 1000) + metrics.set_property("MethodStartTime", round(method_start)) + code = None + + if "postData" in item: + post_data = item["postData"] + else: + post_data = "" + + if "headers" in item: + headers = item["headers"] + else: + headers = {} + + url = item["url"] + operation = item["operation"] + fault_boundary_id = item["faultBoundaryId"] + fault_boundary = item["faultBoundary"] + metric_namespace = item["metricNamespace"] + + xray_recorder.put_annotation("Source", "canary") + xray_recorder.put_annotation("Url", url) + xray_recorder.put_annotation("Operation", operation) + + try: + metrics.context.properties.clear() + metrics.set_namespace(metric_namespace) + + metrics.set_property("Method", method) + metrics.set_property("Url", url) + metrics.set_property("PostData", post_data) + metrics.set_property("RequestId", context.aws_request_id) + + parsed_url = urllib.parse.urlparse(url) + user_agent = "lambda-canary-python3.12" + h = copy.deepcopy(headers) + if "User-Agent" in h: + h["User-Agent"] = " ".join([user_agent, h["User-Agent"]]) + else: + h["User-Agent"] = "{}".format(user_agent) + + metrics.set_property("Headers", h) + if fault_boundary == "az": + metrics.set_dimensions({"Operation": operation, "AZ-ID": fault_boundary_id, "Region": region }) + else: + metrics.set_dimensions({"Operation": operation, "Region": region }) + + verify = not (parsed_url.scheme == "https" and ignore_ssl_errors == True) + error = False + + try: + start = (time.time() * 1000) + metrics.set_property("RequestStartTime", round(start)) + + response = requests.request(method = method, headers = h, url = url, data = str(post_data), verify = verify, timeout = timeout, stream = True) + data = response.text + response_end = time.time() * 1000 + if response.raw._connection and response.raw._connection.sock: + sock = response.raw._connection.sock.getsockname() + metrics.set_property("RemoteIpAddress", str(sock[0]) + ":" + str(sock[1])) + else: + metrics.set_property("RemoteIpAddress", "Unknown") + metrics.set_property("ResponseReceivedTime", round(response_end)) + metrics.put_metric("TimeToResponseReceived", response_end - start, "Milliseconds") + except http.client.RemoteDisconnected as e: + metrics.set_property("RemoteDisconnected", str(e)) + error = True + except http.client.HTTPException as e: + metrics.set_property("HTTPException", str(e)) + error = True + except ConnectionError as e: # networking problem, DNS failure, refused connection + metrics.set_property("ConnectionError", str(e)) + error = True + except TimeoutError as e: + metrics.set_property("TimeoutError", str(e)) + error = True + except Exception as e: + metrics.set_property("GenericError", str(e)) + metrics.set_property("Traceback", traceback.format_exc()) + error = True + + if error == True: + metrics.put_metric("Failure", 1, "Count") + metrics.put_metric("Fault", 0, "Count") + metrics.put_metric("Error", 0, "Count") + metrics.put_metric("Success", 0, "Count") + return None + + code = response.status_code + response_headers = dict(response.headers) + + metrics.set_property("HttpStatusCode", code) + metrics.set_property("ResponseHeaders", response_headers) + + if "X-RequestId" in response_headers: + metrics.set_property("RequestId", response_headers["X-RequestId"]) + xray_recorder.put_metadata("RequestId", response_headers["X-RequestId"]) + else: + metrics.set_property("RequestId", "") + xray_recorder.put_metadata("RequestId", "") + + if "X-Server-Side-Latency" in response_headers: + metrics.put_metric("ServerSideLatency", float(response_headers["X-Server-Side-Latency"]), "Milliseconds") + xray_recorder.put_metadata("ServerSideLatency", float(response_headers["X-Server-Side-Latency"])) + + if response.reason: + metrics.set_property("Reason", response.reason) + + try: + data = response.text + json_obj = json.loads(data) + metrics.set_property("Response", json_obj) + response_decode_end = time.time() * 1000 + metrics.set_property("ResponseDecodedTime", round(response_decode_end)) + metrics.put_metric("TimeToResponseBodyRead", response_decode_end - start, "Milliseconds") + except Exception as e: + metrics.set_property("ResponseDecodeError", str(e)) + + metrics.put_metric("Success", (1 if code >= 200 and code <= 399 else 0), "Count") + metrics.put_metric("Error", (1 if code >= 400 and code <= 499 else 0), "Count") + metrics.put_metric("Fault", (1 if code >= 500 and code <= 599 else 0), "Count") + metrics.put_metric("Failure", 0, "Count") + + method_end = (time.time() * 1000) + metrics.set_property("MethodStartTime", round(method_end)) + metrics.set_property("MethodInvocationTime", method_end - method_start) + return code + + except Exception as e: + metrics.set_property("GenericError", str(e)) + metrics.set_property("Traceback", traceback.format_exc()) + + if code is None: + metrics.put_metric("Success", 0, "Count") + metrics.put_metric("Error", 0, "Count") + metrics.put_metric("Fault", 0, "Count") + metrics.put_metric("Failure", 1, "Count") + else: + metrics.put_metric("Success", (1 if code >= 200 and code <= 399 else 0), "Count") + metrics.put_metric("Error", (1 if code >= 400 and code <= 499 else 0), "Count") + metrics.put_metric("Fault", (1 if code >= 500 and code <= 599 else 0), "Count") + metrics.put_metric("Failure", 0, "Count") + +@metric_scope +@xray_recorder.capture('handler') +def handler(event, context, metrics): + start = time.perf_counter() + metrics.set_namespace("Canaries") + metrics.set_dimensions({"FunctionName": context.function_name, "Region": region }) + metrics.set_property("CanaryName", context.function_name) + metrics.set_property("Event", event) + + xray_recorder.put_annotation("Source", "canary") + + errors = [] + tracebacks = [] + + if "parameters" in event and event["parameters"] is not None: + request_count = 10 + if "requestCount" in event["parameters"] and event["parameters"]["requestCount"] is not None: + request_count = int(event["parameters"]["requestCount"]) + + methods = event["parameters"]["methods"] + + if methods is None or len(methods) == 0: + methods = [ "GET" ] + + for method in methods: + for x in range(0, request_count): + try: + verify_request(context, event["parameters"], method) + time.sleep(1) + except Exception as e: + errors.append(str(e)) + tracebacks.append(traceback.format_exc()) + + metrics.set_property("Errors", errors) + metrics.set_property("Tracebacks", tracebacks) + end = time.perf_counter() + metrics.put_metric("TotalProcessingLatency", (end - start) * 1000, "Milliseconds") + + return None \ No newline at end of file diff --git a/src/canaries/src/requirements.txt b/src/canaries/src/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/src/canaries/src/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/src/dashboards/BasicServiceDashboard.ts b/src/dashboards/BasicServiceDashboard.ts new file mode 100644 index 0000000..535e6f1 --- /dev/null +++ b/src/dashboards/BasicServiceDashboard.ts @@ -0,0 +1,217 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { + AlarmStatusWidget, + Dashboard, + GraphWidget, + IAlarm, + IMetric, + IWidget, + PeriodOverride, + TextWidget, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { BasicServiceDashboardProps } from './props/BasicServiceDashboardProps'; +import { AvailabilityZoneMapper } from '../azmapper/AvailabilityZoneMapper'; + +export class BasicServiceDashboard extends Construct { + + private static createLoadBalancerWidgets( + alarms: { [key: string]: IAlarm }, + metrics: { [key: string]: IMetric }, + azMapper: AvailabilityZoneMapper, + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new TextWidget({ + markdown: 'Load Balancer Fault Count Metrics', + height: 2, + width: 24, + }), + ); + + let rowTracker: number = 0; + + Object.keys(alarms).forEach((azLetter, index) => { + let availabilityZoneId: string = azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + widgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Load Balancer Faults', + region: Fn.sub('${AWS::Region}'), + left: [metrics[azLetter]], + statistic: 'Sum', + leftYAxis: { + min: 0, + label: 'Fault Count', + showUnits: false, + }, + }), + ); + + //We're on the third one for this set, add 3 alarms + //or if we're at the end, at the necessary amount + //of alarms, 1, 2, or 3 + if (index % 3 == 2 || index - 1 == Object.keys(alarms).length) { + for (let k = rowTracker; k <= index; k++) { + let azId: string = Object.keys(alarms).at(k)!; + widgets.push( + new AlarmStatusWidget({ + height: 2, + width: 8, + alarms: [alarms[azId]], + }), + ); + } + + rowTracker += index + 1; + } + }); + + return widgets; + } + + private static createNatGatewayWidgets( + alarms: { [key: string]: IAlarm }, + metrics: { [key: string]: IMetric }, + azMapper: AvailabilityZoneMapper, + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new TextWidget({ + markdown: 'NAT Gateway Dropped Packet Metrics', + height: 2, + width: 24, + }), + ); + + let rowTracker: number = 0; + + Object.keys(alarms).forEach((azLetter, index) => { + + let availabilityZoneId: string = azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + widgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' NAT Gateway Dropped Packets', + region: Fn.sub('${AWS::Region}'), + left: [metrics[azLetter]], + statistic: 'Sum', + leftYAxis: { + min: 0, + label: 'Dropped packets', + showUnits: false, + }, + }), + ); + + //We're on the third one for this set, add 3 alarms + //or if we're at the end, at the necessary amount + //of alarms, 1, 2, or 3 + if (index % 3 == 2 || index - 1 == Object.keys(alarms).length) { + for (let k = rowTracker; k <= index; k++) { + let azId: string = Object.keys(alarms).at(k)!; + widgets.push( + new AlarmStatusWidget({ + height: 2, + width: 8, + alarms: [alarms[azId]], + }), + ); + } + + rowTracker += index + 1; + } + }); + + return widgets; + } + + private static createTopLevelAlarmWidgets(alarms: { + [key: string]: IAlarm; + }, + azMapper: AvailabilityZoneMapper, + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new TextWidget({ + markdown: 'Availability Zone Isolated Impact Alarms', + height: 2, + width: 24, + }), + ); + + Object.keys(alarms).forEach((azLetter) => { + let availabilityZoneId: string = azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + widgets.push( + new AlarmStatusWidget({ + alarms: [alarms[azLetter]], + height: 2, + width: 8, + title: availabilityZoneId + ' Aggregate Isolated Impact', + }), + ); + }); + + return widgets; + } + + dashboard: Dashboard; + + constructor(scope: Construct, id: string, props: BasicServiceDashboardProps) { + super(scope, id); + + let widgets: IWidget[][] = []; + + widgets.push( + BasicServiceDashboard.createTopLevelAlarmWidgets( + props.zonalAggregateIsolatedImpactAlarms, + props.azMapper, + ), + ); + + if ( + props.zonalLoadBalancerIsolatedImpactAlarms !== undefined && + props.zonalLoadBalancerFaultRateMetrics !== undefined + ) { + widgets.push( + BasicServiceDashboard.createLoadBalancerWidgets( + props.zonalLoadBalancerIsolatedImpactAlarms, + props.zonalLoadBalancerFaultRateMetrics, + props.azMapper, + ), + ); + } + + if ( + props.zonalNatGatewayIsolatedImpactAlarms !== undefined && + props.zonalNatGatewayPacketDropMetrics !== undefined + ) { + widgets.push( + BasicServiceDashboard.createNatGatewayWidgets( + props.zonalNatGatewayIsolatedImpactAlarms, + props.zonalNatGatewayPacketDropMetrics, + props.azMapper, + ), + ); + } + + this.dashboard = new Dashboard(this, 'BasicServiceDashboard', { + dashboardName: + props.serviceName.toLowerCase() + + Fn.sub('-service-availability-${AWS::Region}'), + defaultInterval: props.interval, + periodOverride: PeriodOverride.INHERIT, + widgets: widgets, + }); + } +} diff --git a/src/dashboards/ContributorInsightsWidget.ts b/src/dashboards/ContributorInsightsWidget.ts new file mode 100644 index 0000000..624ab2f --- /dev/null +++ b/src/dashboards/ContributorInsightsWidget.ts @@ -0,0 +1,64 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { ConcreteWidget, IWidget } from 'aws-cdk-lib/aws-cloudwatch'; +import { ContributorInsightWidgetProps } from './props/ContributorInsightWidgetProps'; + +/** + * A Contributor Insight dashboard widget + */ +export class ContributorInsightsWidget + extends ConcreteWidget + implements IWidget { + /** + * The widget properties + */ + properties: ContributorInsightWidgetProps; + + /** + * Creates the widget + * @param props + */ + constructor(props: ContributorInsightWidgetProps) { + super( + props.width === undefined ? 6 : props.width, + props.height === undefined ? 6 : props.height, + ); + this.properties = props; + } + + /** + * Converts the widget into an array of JSON objects (not string), this returns + * a single item in the array + * @returns An array of dictionaries + */ + toJson(): any[] { + return [ + { + type: 'metric', + width: this.width, + height: this.height, + x: this.x, + y: this.y, + properties: { + insightRule: { + maxContributorCount: this.properties.topContributors, + orderBy: this.properties.orderStatistic, + ruleName: this.properties.insightRule.attrRuleName, + }, + region: + this.properties.region !== undefined + ? this.properties.region + : Fn.ref('AWS::Region'), + legend: { + position: this.properties.legendPosition, + }, + view: 'timeSeries', + period: this.properties.period.toSeconds(), + title: this.properties.title, + accountId: this.properties.accountId, + }, + }, + ]; + } +} diff --git a/src/dashboards/IOperationAvailabilityAndLatencyDashboard.ts b/src/dashboards/IOperationAvailabilityAndLatencyDashboard.ts new file mode 100644 index 0000000..3d3321b --- /dev/null +++ b/src/dashboards/IOperationAvailabilityAndLatencyDashboard.ts @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Dashboard } from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * An operation level availability and latency dashboard + */ +export interface IOperationAvailabilityAndLatencyDashboard { + /** + * The operation level dashboard + */ + dashboard: Dashboard; +} diff --git a/src/dashboards/IServiceAvailabilityAndLatencyDashboard.ts b/src/dashboards/IServiceAvailabilityAndLatencyDashboard.ts new file mode 100644 index 0000000..bf75485 --- /dev/null +++ b/src/dashboards/IServiceAvailabilityAndLatencyDashboard.ts @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Dashboard } from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * A service-level availability and latency dashboard + */ +export interface IServiceAvailabilityAndLatencyDashboard { + /** + * The service availability and latency dashboard + */ + dashboard: Dashboard; +} diff --git a/src/dashboards/OperationAvailabilityAndLatencyDashboard.ts b/src/dashboards/OperationAvailabilityAndLatencyDashboard.ts new file mode 100644 index 0000000..7f524b1 --- /dev/null +++ b/src/dashboards/OperationAvailabilityAndLatencyDashboard.ts @@ -0,0 +1,860 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { + Dashboard, + IMetric, + PeriodOverride, + IWidget, + TextWidget, + AlarmStatusWidget, + GraphWidget, + Color, + AlarmWidget, + LegendPosition, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { + BaseLoadBalancer, + CfnLoadBalancer, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { Construct } from 'constructs'; +import { ContributorInsightsWidget } from './ContributorInsightsWidget'; +import { IOperationAvailabilityAndLatencyDashboard } from './IOperationAvailabilityAndLatencyDashboard'; +import { OperationAvailabilityAndLatencyDashboardProps } from './props/OperationAvailabilityAndLatencyDashboardProps'; +import { OperationAvailabilityWidgetProps } from './props/OperationAvailabilityWidgetProps'; +import { OperationLatencyWidgetProps } from './props/OperationLatencyWidgetProps'; +import { IAvailabilityZoneMapper } from '../azmapper/IAvailabilityZoneMapper'; +import { ApplicationLoadBalancerMetrics } from '../metrics/ApplicationLoadBalancerMetrics'; +import { AvailabilityAndLatencyMetrics } from '../metrics/AvailabilityAndLatencyMetrics'; +import { NetworkLoadBalancerMetrics } from '../metrics/NetworkLoadBalancerMetrics'; +import { RegionalAvailabilityMetrics } from '../metrics/RegionalAvailabilityMetrics'; +import { RegionalLatencyMetrics } from '../metrics/RegionalLatencyMetrics'; +import { ZonalAvailabilityMetrics } from '../metrics/ZonalAvailabilityMetrics'; +import { ZonalLatencyMetrics } from '../metrics/ZonalLatencyMetrics'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; +import { LatencyMetricType } from '../utilities/LatencyMetricType'; + +/** + * Creates an operation level availability and latency dashboard + */ +export class OperationAvailabilityAndLatencyDashboard + extends Construct + implements IOperationAvailabilityAndLatencyDashboard { + private static createTopLevelAggregateAlarmWidgets( + props: OperationAvailabilityAndLatencyDashboardProps, + title: string, + availabilityZoneIds: string[], + ): IWidget[] { + let topLevelAggregateAlarms: IWidget[] = [ + new TextWidget({ height: 2, width: 24, markdown: title }), + new AlarmStatusWidget({ + height: 2, + width: 24, + alarms: [props.regionalImpactAlarm], + title: props.operation.operationName + ' Regional Impact', + }), + ]; + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId = availabilityZoneIds[i]; + + topLevelAggregateAlarms.push( + new AlarmStatusWidget({ + height: 2, + width: 8, + alarms: [props.isolatedAZImpactAlarms[i]], + title: availabilityZoneId + ' Isolated Impact', + }), + ); + } + + topLevelAggregateAlarms.push( + new TextWidget({ height: 2, width: 24, markdown: '**AZ Contributors**' }), + ); + + let zonalServerSideHighLatencyMetrics: IMetric[] = []; + let zonalServerSideFaultCountMetrics: IMetric[] = []; + + let zonalCanaryHighLatencyMetrics: IMetric[] = []; + let zonalCanaryFaultCountMetrics: IMetric[] = []; + + let keyPrefix: string = AvailabilityAndLatencyMetrics.nextChar(''); + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId: string = availabilityZoneIds[i]; + + zonalServerSideHighLatencyMetrics.push( + ZonalLatencyMetrics.createZonalCountLatencyMetric({ + availabilityZoneId: availabilityZoneId, + metricDetails: props.operation.serverSideLatencyMetricDetails, + label: availabilityZoneId + ' high latency responses', + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: `TC(${props.operation.serverSideLatencyMetricDetails.successAlarmThreshold}:)`, + keyPrefix: keyPrefix, + }), + ); + + zonalServerSideFaultCountMetrics.push( + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + metricDetails: props.operation.serverSideAvailabilityMetricDetails, + label: availabilityZoneId + ' fault count', + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: keyPrefix, + }), + ); + + if ( + props.operation.canaryMetricDetails !== undefined && + props.operation.canaryMetricDetails != null + ) { + zonalCanaryHighLatencyMetrics.push( + ZonalLatencyMetrics.createZonalCountLatencyMetric({ + availabilityZoneId: availabilityZoneId, + metricDetails: + props.operation.canaryMetricDetails.canaryLatencyMetricDetails, + label: availabilityZoneId + ' high latency responses', + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: `TC(${props.operation.canaryMetricDetails.canaryLatencyMetricDetails.successAlarmThreshold}:)`, + keyPrefix: keyPrefix, + }), + ); + + zonalCanaryFaultCountMetrics.push( + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + metricDetails: + props.operation.canaryMetricDetails + .canaryAvailabilityMetricDetails, + label: availabilityZoneId + ' fault count', + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: keyPrefix, + }), + ); + } + + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + } + + topLevelAggregateAlarms.push( + new GraphWidget({ + height: 6, + width: 24, + title: 'Server-side AZ Fault Contributors', + left: zonalServerSideFaultCountMetrics, + }), + ); + + if (zonalCanaryFaultCountMetrics.length > 0) { + topLevelAggregateAlarms.push( + new GraphWidget({ + height: 6, + width: 24, + title: 'Canary AZ Fault Contributors', + left: zonalCanaryFaultCountMetrics, + }), + ); + } + + topLevelAggregateAlarms.push( + new GraphWidget({ + height: 6, + width: 24, + title: 'Server-side High Latency Contributors', + left: zonalServerSideHighLatencyMetrics, + }), + ); + + if (zonalCanaryHighLatencyMetrics.length > 0) { + topLevelAggregateAlarms.push( + new GraphWidget({ + height: 6, + width: 24, + title: 'Canary High Latency Contributors', + left: zonalCanaryHighLatencyMetrics, + }), + ); + } + + topLevelAggregateAlarms.push( + new TextWidget({ height: 2, width: 24, markdown: '**TPS Metrics**' }), + ); + + topLevelAggregateAlarms.push( + new GraphWidget({ + height: 6, + width: 24, + title: Fn.sub('${AWS::Region} TPS'), + region: Fn.sub('${AWS::Region}'), + left: [ + RegionalAvailabilityMetrics.createRegionalAvailabilityMetric({ + label: Fn.ref('AWS::Region') + ' tps', + metricDetails: props.operation.serverSideAvailabilityMetricDetails, + metricType: AvailabilityMetricType.REQUEST_COUNT, + }), + ], + statistic: 'Sum', + leftYAxis: { + label: 'TPS', + showUnits: false, + }, + }), + ); + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId: string = availabilityZoneIds[i]; + + topLevelAggregateAlarms.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' TPS', + region: Fn.sub('${AWS::Region}'), + left: [ + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + label: availabilityZoneId + ' tps', + metricDetails: + props.operation.serverSideAvailabilityMetricDetails, + metricType: AvailabilityMetricType.REQUEST_COUNT, + }), + ], + statistic: 'Sum', + leftYAxis: { + label: 'TPS', + showUnits: false, + }, + }), + ); + } + + return topLevelAggregateAlarms; + } + + private static createAvailabilityWidgets( + props: OperationAvailabilityWidgetProps, + title: string, + ): IWidget[] { + let availabilityWidgets: IWidget[] = []; + availabilityWidgets.push( + new TextWidget({ height: 2, width: 24, markdown: title }), + ); + + let rowTracker: number = 0; + let keyPrefix1: string = AvailabilityAndLatencyMetrics.nextChar(''); + let keyPrefix2: string = AvailabilityAndLatencyMetrics.nextChar(keyPrefix1); + + // Create regional availability and fault metrics and availability alarm widgets + availabilityWidgets.push( + new GraphWidget({ + height: 8, + width: 24, + title: Fn.sub('${AWS::Region} Availability'), + region: Fn.sub('${AWS::Region}'), + left: [ + RegionalAvailabilityMetrics.createRegionalAvailabilityMetric({ + label: Fn.ref('AWS::Region') + ' availability', + metricDetails: props.availabilityMetricDetails, + metricType: AvailabilityMetricType.SUCCESS_RATE, + keyPrefix: keyPrefix1, + }), + ], + statistic: 'Sum', + leftYAxis: { + max: 100, + min: 95, + label: 'Availability', + showUnits: false, + }, + leftAnnotations: [ + { + value: props.availabilityMetricDetails.successAlarmThreshold, + visible: true, + color: Color.RED, + label: 'High Severity', + }, + ], + right: [ + RegionalAvailabilityMetrics.createRegionalAvailabilityMetric({ + label: Fn.ref('AWS::Region') + ' fault count', + metricDetails: props.availabilityMetricDetails, + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: keyPrefix2, + }), + ], + rightYAxis: { + label: 'Fault Count', + showUnits: false, + }, + }), + ); + + availabilityWidgets.push( + new AlarmWidget({ + height: 2, + width: 24, + region: Fn.sub('${AWS::Region}'), + alarm: props.regionalEndpointAvailabilityAlarm, + title: 'Success Rate', + }), + ); + + for (let i = 0; i < props.availabilityZoneIds.length; i++) { + let availabilityZoneId: string = props.availabilityZoneIds[i]; + + let keyPrefix3: string = AvailabilityAndLatencyMetrics.nextChar(''); + let keyPrefix4: string = + AvailabilityAndLatencyMetrics.nextChar(keyPrefix3); + + availabilityWidgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Availability', + region: Fn.sub('${AWS::Region}'), + left: [ + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + label: availabilityZoneId + ' availability', + metricDetails: props.availabilityMetricDetails, + metricType: AvailabilityMetricType.SUCCESS_RATE, + keyPrefix: keyPrefix3, + }), + ], + statistic: 'Sum', + leftYAxis: { + max: 100, + min: 95, + label: 'Availability', + showUnits: false, + }, + leftAnnotations: [ + { + value: props.availabilityMetricDetails.successAlarmThreshold, + visible: true, + color: Color.RED, + label: 'High Severity', + }, + ], + right: [ + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + label: availabilityZoneId + ' fault count', + metricDetails: props.availabilityMetricDetails, + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: keyPrefix4, + }), + ], + rightYAxis: { + label: 'Fault Count', + showUnits: false, + }, + }), + ); + + //We're on the third one for this set, add 3 alarms + //or if we're at the end, at the necessary amount + //of alarms, 1, 2, or 3 + if (i % 3 == 2 || i - 1 == props.availabilityZoneIds.length) { + for (let k = rowTracker; k <= i; k++) { + availabilityWidgets.push( + new AlarmWidget({ + height: 2, + width: 8, + region: Fn.sub('${AWS::Region}'), + alarm: props.zonalEndpointAvailabilityAlarms[k], + title: 'Success Rate', + }), + ); + } + + rowTracker += i + 1; + } + } + + if ( + !props.isCanary && + props.instanceContributorsToFaults !== undefined && + props.instanceContributorsToFaults != null + ) { + availabilityWidgets.push( + new ContributorInsightsWidget({ + height: 6, + width: 24, + title: 'Individual Instance Contributors to Fault Count', + insightRule: props.instanceContributorsToFaults, + period: props.availabilityMetricDetails.period, + legendPosition: LegendPosition.BOTTOM, + orderStatistic: 'Sum', + accountId: Fn.ref('AWS::AccountId'), + topContributors: 10, + }), + ); + } + + return availabilityWidgets; + } + + private static createLatencyWidgets( + props: OperationLatencyWidgetProps, + title: string, + ): IWidget[] { + let latencyWidgets: IWidget[] = []; + latencyWidgets.push( + new TextWidget({ height: 2, width: 24, markdown: title }), + ); + + let rowTracker: number = 0; + let keyPrefix: string = ''; + + let latencyMetrics: IMetric[] = []; + + let stats: string[] = + props.latencyMetricDetails.graphedSuccessStatistics !== undefined + ? props.latencyMetricDetails.graphedSuccessStatistics + : ['p99']; + + let latencySuccessMetrics: IMetric[] = stats.map((x) => { + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + return RegionalLatencyMetrics.createRegionalAverageLatencyMetric({ + label: x + ' Success Latency', + metricDetails: props.latencyMetricDetails, + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: x, + keyPrefix: keyPrefix, + }); + }); + + stats = + props.latencyMetricDetails.graphedFaultStatistics !== undefined + ? props.latencyMetricDetails.graphedFaultStatistics + : ['p99']; + + let latencyFaultMetrics: IMetric[] = stats.map((x) => { + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + return RegionalLatencyMetrics.createRegionalAverageLatencyMetric({ + label: x + ' Fault Latency', + metricDetails: props.latencyMetricDetails, + metricType: LatencyMetricType.FAULT_LATENCY, + statistic: x, + keyPrefix: keyPrefix, + }); + }); + + latencyMetrics = latencySuccessMetrics.concat(latencyFaultMetrics); + + if (latencyMetrics.length > 0) { + latencyWidgets.push( + new GraphWidget({ + height: 8, + width: 24, + title: Fn.sub('${AWS::Region} Latency'), + region: Fn.sub('${AWS::Region}'), + left: latencyMetrics, + leftYAxis: { + max: props.latencyMetricDetails.successAlarmThreshold * 1.5, + min: 0, + label: 'Latency', + showUnits: false, + }, + leftAnnotations: [ + { + value: props.latencyMetricDetails.successAlarmThreshold, + visible: true, + color: Color.RED, + label: 'High Severity', + }, + ], + }), + ); + } + + latencyWidgets.push( + new AlarmWidget({ + height: 2, + width: 24, + region: Fn.sub('${AWS::Region}'), + alarm: props.regionalEndpointLatencyAlarm, + }), + ); + + keyPrefix = ''; + + for (let i = 0; i < props.availabilityZoneIds.length; i++) { + let availabilityZoneId: string = props.availabilityZoneIds[i]; + + let latencyMetrics2: IMetric[] = []; + + let stats2: string[] = props.latencyMetricDetails.graphedSuccessStatistics + ? props.latencyMetricDetails.graphedSuccessStatistics + : ['p99']; + + let zonalSuccessLatencyMetrics: IMetric[] = stats2.map((x) => { + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + return ZonalLatencyMetrics.createZonalAverageLatencyMetric({ + label: x + ' Success Latency', + metricDetails: props.latencyMetricDetails, + metricType: LatencyMetricType.SUCCESS_LATENCY, + statistic: x, + availabilityZoneId: availabilityZoneId, + keyPrefix: keyPrefix, + }); + }); + + stats2 = props.latencyMetricDetails.graphedFaultStatistics + ? props.latencyMetricDetails.graphedFaultStatistics + : ['p99']; + + let zonalFaultLatencyMetrics: IMetric[] = stats2.map((x) => { + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + return ZonalLatencyMetrics.createZonalAverageLatencyMetric({ + label: x + ' Fault Latency', + metricDetails: props.latencyMetricDetails, + metricType: LatencyMetricType.FAULT_LATENCY, + statistic: x, + availabilityZoneId: availabilityZoneId, + keyPrefix: keyPrefix, + }); + }); + + latencyMetrics2 = zonalSuccessLatencyMetrics.concat( + zonalFaultLatencyMetrics, + ); + + if (latencyMetrics2.length > 0) { + latencyWidgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Latency', + region: Fn.sub('${AWS::Region}'), + left: latencyMetrics2, + leftAnnotations: [ + { + value: props.latencyMetricDetails.successAlarmThreshold, + visible: true, + color: Color.RED, + label: 'High Severity', + }, + ], + leftYAxis: { + max: props.latencyMetricDetails.successAlarmThreshold * 1.5, + min: 0, + label: 'Latency', + showUnits: false, + }, + }), + ); + } + + //We're on the third one for this set, add 3 alarms + //or if we're at the end, at the necessary amount + //of alarms, 1, 2, or 3 + if (i % 3 == 2 || i - 1 == props.availabilityZoneIds.length) { + for (let k = rowTracker; k <= i; k++) { + latencyWidgets.push( + new AlarmWidget({ + height: 2, + width: 8, + region: Fn.sub('${AWS::Region}'), + alarm: props.zonalEndpointLatencyAlarms[k], + }), + ); + } + rowTracker += i + 1; + } + } + + if ( + !props.isCanary && + props.instanceContributorsToHighLatency !== undefined && + props.instanceContributorsToHighLatency != null + ) { + latencyWidgets.push( + new ContributorInsightsWidget({ + height: 6, + width: 24, + title: 'Individual Instance Contributors to High Latency', + insightRule: props.instanceContributorsToHighLatency, + period: props.latencyMetricDetails.period, + legendPosition: LegendPosition.BOTTOM, + orderStatistic: 'Sum', + accountId: Fn.ref('AWS::AccountId'), + topContributors: 10, + }), + ); + } + + return latencyWidgets; + } + + private static createApplicationLoadBalancerWidgets( + props: OperationAvailabilityAndLatencyDashboardProps, + title: string, + availabilityZoneNames: string[], + ): IWidget[] { + let albWidgets: IWidget[] = []; + let loadBalancerFullName: string = (props.loadBalancer as BaseLoadBalancer) + .loadBalancerFullName; + + albWidgets.push(new TextWidget({ height: 2, width: 24, markdown: title })); + + albWidgets.push( + new GraphWidget({ + height: 8, + width: 24, + title: Fn.sub('${AWS::Region} Processed Bytes'), + region: Fn.sub('${AWS::Region}'), + left: [ + ApplicationLoadBalancerMetrics.createRegionalApplicationLoadBalancerProcessedBytesMetric( + loadBalancerFullName, + props.operation.serverSideAvailabilityMetricDetails.period, + ), + ], + leftYAxis: { + label: 'Processed Bytes', + showUnits: true, + }, + }), + ); + + availabilityZoneNames.forEach((availabilityZoneName) => { + let availabilityZoneId: string = + props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + availabilityZoneName.substring(availabilityZoneName.length - 1), + ); + + albWidgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Processed Bytes', + region: Fn.sub('${AWS::Region}'), + left: [ + ApplicationLoadBalancerMetrics.createZonalApplicationLoadBalancerProcessedBytesMetric( + loadBalancerFullName, + availabilityZoneName, + props.operation.serverSideAvailabilityMetricDetails.period, + ), + ], + leftYAxis: { + label: 'Processed Bytes', + showUnits: true, + }, + }), + ); + }); + + return albWidgets; + } + + private static createNetworkLoadBalancerWidgets( + props: OperationAvailabilityAndLatencyDashboardProps, + title: string, + availabilityZoneNames: string[], + ): IWidget[] { + let nlbWidgets: IWidget[] = []; + let loadBalancerFullName: string = (props.loadBalancer as BaseLoadBalancer) + .loadBalancerFullName; + + nlbWidgets.push(new TextWidget({ height: 2, width: 24, markdown: title })); + + nlbWidgets.push( + new GraphWidget({ + height: 8, + width: 24, + title: Fn.sub('${AWS::Region} Processed Bytes'), + region: Fn.sub('${AWS::Region}'), + left: [ + NetworkLoadBalancerMetrics.createRegionalNetworkLoadBalancerProcessedBytesMetric( + loadBalancerFullName, + props.operation.serverSideAvailabilityMetricDetails.period, + ), + ], + leftYAxis: { + label: 'Processed Bytes', + showUnits: true, + }, + }), + ); + + availabilityZoneNames.forEach((availabilityZoneName) => { + let availabilityZoneId: string = + props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + availabilityZoneName.substring(availabilityZoneName.length - 1), + ); + + nlbWidgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Processed Bytes', + region: Fn.sub('${AWS::Region}'), + left: [ + NetworkLoadBalancerMetrics.createZonalNetworkLoadBalancerProcessedBytesMetric( + loadBalancerFullName, + availabilityZoneName, + props.operation.serverSideAvailabilityMetricDetails.period, + ), + ], + leftYAxis: { + label: 'Processed Bytes', + showUnits: true, + }, + }), + ); + }); + + return nlbWidgets; + } + + /** + * The operation level dashboard + */ + dashboard: Dashboard; + + private azMapper: IAvailabilityZoneMapper; + + constructor( + scope: Construct, + id: string, + props: OperationAvailabilityAndLatencyDashboardProps, + ) { + super(scope, id); + + let widgets: IWidget[][] = []; + + this.azMapper = props.azMapper; + + let availabilityZoneIds: string[] = + props.operation.service.availabilityZoneNames.map((x) => { + return this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + x.substring(x.length - 1), + ); + }); + + widgets.push( + OperationAvailabilityAndLatencyDashboard.createTopLevelAggregateAlarmWidgets( + props, + '**Top Level Aggregate Alarms**', + availabilityZoneIds, + ), + ); + + widgets.push( + OperationAvailabilityAndLatencyDashboard.createAvailabilityWidgets( + { + operation: props.operation, + availabilityMetricDetails: + props.operation.serverSideAvailabilityMetricDetails, + availabilityZoneIds: availabilityZoneIds, + isCanary: false, + zonalEndpointAvailabilityAlarms: + props.zonalEndpointServerAvailabilityAlarms, + regionalEndpointAvailabilityAlarm: + props.regionalEndpointServerAvailabilityAlarm, + instanceContributorsToFaults: props.instanceContributorsToFaults, + }, + '**Server-side Availability**', + ), + ); + + widgets.push( + OperationAvailabilityAndLatencyDashboard.createLatencyWidgets( + { + operation: props.operation, + latencyMetricDetails: props.operation.serverSideLatencyMetricDetails, + availabilityZoneIds: availabilityZoneIds, + isCanary: false, + zonalEndpointLatencyAlarms: props.zonalEndpointServerLatencyAlarms, + regionalEndpointLatencyAlarm: + props.regionalEndpointServerLatencyAlarm, + instanceContributorsToHighLatency: + props.instanceContributorsToHighLatency, + }, + '**Server-side Latency**', + ), + ); + + let lb: CfnLoadBalancer = props.loadBalancer?.node + .defaultChild as CfnLoadBalancer; + + if (lb !== undefined && lb != null) { + if (lb.type == 'application') { + widgets.push( + OperationAvailabilityAndLatencyDashboard.createApplicationLoadBalancerWidgets( + props, + '**Application Load Balancer Metrics**', + props.operation.service.availabilityZoneNames, + ), + ); + } else if (lb.type == 'network') { + widgets.push( + OperationAvailabilityAndLatencyDashboard.createNetworkLoadBalancerWidgets( + props, + '**Network Load Balancer Metrics**', + props.operation.service.availabilityZoneNames, + ), + ); + } + } + + if ( + props.operation.canaryMetricDetails !== undefined && + props.operation.canaryMetricDetails != null + ) { + if ( + props.zonalEndpointCanaryAvailabilityAlarms && + props.zonalEndpointCanaryLatencyAlarms && + props.regionalEndpointCanaryAvailabilityAlarm && + props.regionalEndpointCanaryLatencyAlarm + ) { + widgets.push( + OperationAvailabilityAndLatencyDashboard.createAvailabilityWidgets( + { + operation: props.operation, + availabilityMetricDetails: + props.operation.canaryMetricDetails + .canaryAvailabilityMetricDetails, + availabilityZoneIds: availabilityZoneIds, + isCanary: true, + zonalEndpointAvailabilityAlarms: + props.zonalEndpointCanaryAvailabilityAlarms, + regionalEndpointAvailabilityAlarm: + props.regionalEndpointCanaryAvailabilityAlarm, + }, + '**Canary Measured Availability**', + ), + ); + + widgets.push( + OperationAvailabilityAndLatencyDashboard.createLatencyWidgets( + { + operation: props.operation, + latencyMetricDetails: + props.operation.canaryMetricDetails.canaryLatencyMetricDetails, + availabilityZoneIds: availabilityZoneIds, + isCanary: true, + zonalEndpointLatencyAlarms: + props.zonalEndpointCanaryLatencyAlarms, + regionalEndpointLatencyAlarm: + props.regionalEndpointCanaryLatencyAlarm, + }, + '**Canary Measured Latency**', + ), + ); + } + } + + this.dashboard = new Dashboard(this, 'Dashboard', { + dashboardName: + props.operation.service.serviceName.toLowerCase() + + '-' + + props.operation.operationName.toLowerCase() + + Fn.sub('-operation-availability-and-latency-${AWS::Region}'), + defaultInterval: props.interval, + periodOverride: PeriodOverride.INHERIT, + widgets: widgets, + }); + } +} diff --git a/src/dashboards/ServiceAvailabilityAndLatencyDashboard.ts b/src/dashboards/ServiceAvailabilityAndLatencyDashboard.ts new file mode 100644 index 0000000..8a0fdb3 --- /dev/null +++ b/src/dashboards/ServiceAvailabilityAndLatencyDashboard.ts @@ -0,0 +1,743 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { + AlarmStatusWidget, + Color, + Dashboard, + GraphWidget, + IMetric, + IWidget, + MathExpression, + PeriodOverride, + TextWidget, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { + BaseLoadBalancer, + CfnLoadBalancer, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { Construct } from 'constructs'; +import { IServiceAvailabilityAndLatencyDashboard } from './IServiceAvailabilityAndLatencyDashboard'; +import { ServiceAvailabilityAndLatencyDashboardProps } from './props/ServiceAvailabilityAndLatencyDashboardProps'; +import { ApplicationLoadBalancerMetrics } from '../metrics/ApplicationLoadBalancerMetrics'; +import { AvailabilityAndLatencyMetrics } from '../metrics/AvailabilityAndLatencyMetrics'; +import { AvailabilityMetricProps } from '../metrics/props/AvailabilityMetricProps'; +import { LatencyMetricProps } from '../metrics/props/LatencyMetricProps'; +import { RegionalAvailabilityMetrics } from '../metrics/RegionalAvailabilityMetrics'; +import { RegionalLatencyMetrics } from '../metrics/RegionalLatencyMetrics'; +import { ZonalAvailabilityMetrics } from '../metrics/ZonalAvailabilityMetrics'; +import { ZonalLatencyMetrics } from '../metrics/ZonalLatencyMetrics'; +import { IOperation } from '../services/IOperation'; +import { IOperationMetricDetails } from '../services/IOperationMetricDetails'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; +import { LatencyMetricType } from '../utilities/LatencyMetricType'; + +/** + * Creates a service level availability and latency dashboard + */ +export class ServiceAvailabilityAndLatencyDashboard + extends Construct + implements IServiceAvailabilityAndLatencyDashboard { + private static generateTPSWidgets( + props: ServiceAvailabilityAndLatencyDashboardProps, + availabilityZoneIds: string[], + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new TextWidget({ height: 2, width: 24, markdown: '**TPS Metrics**' }), + ); + + widgets.push( + new GraphWidget({ + height: 6, + width: 24, + title: Fn.ref('AWS::Region') + ' TPS', + region: Fn.ref('AWS::Region'), + left: RegionalAvailabilityMetrics.createRegionalServiceAvailabilityMetrics( + { + label: Fn.ref('AWS::Region') + ' tps', + period: props.service.period, + availabilityMetricProps: props.service.operations + .filter((x) => x.critical) + .map((x) => { + return { + label: x.operationName, + metricDetails: x.serverSideAvailabilityMetricDetails, + metricType: AvailabilityMetricType.REQUEST_COUNT, + }; + }), + }, + ), + statistic: 'Sum', + leftYAxis: { + label: 'TPS', + showUnits: false, + }, + }), + ); + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId: string = availabilityZoneIds[i]; + + let zonalMetricProps = { + availabilityMetricProps: props.service.operations + .filter((x) => x.critical) + .map((x) => { + return { + availabilityZoneId: availabilityZoneId, + label: x.operationName, + metricDetails: x.serverSideAvailabilityMetricDetails, + metricType: AvailabilityMetricType.REQUEST_COUNT, + }; + }), + period: props.service.period, + label: availabilityZoneId + 'tps', + }; + + widgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' TPS', + region: Fn.ref('AWS::Region'), + left: ZonalAvailabilityMetrics.createZonalServiceAvailabilityMetrics( + zonalMetricProps, + ), + statistic: 'Sum', + leftYAxis: { + label: 'TPS', + showUnits: false, + }, + }), + ); + } + + return widgets; + } + + private static generateServerSideAndCanaryAvailabilityWidgets( + props: ServiceAvailabilityAndLatencyDashboardProps, + availabilityZoneIds: string[], + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new TextWidget({ + height: 2, + width: 24, + markdown: + '**Server-side Availability**\n(Each critical operation is equally weighted regardless of request volume)', + }), + ); + + widgets = widgets.concat( + ServiceAvailabilityAndLatencyDashboard.generateAvailabilityWidgets( + props, + false, + availabilityZoneIds, + ), + ); + + if ( + props.service.operations.filter( + (x) => x.critical && x.canaryMetricDetails !== undefined, + ).length > 0 + ) { + widgets.push( + new TextWidget({ + height: 2, + width: 24, + markdown: + '**Canary Measured Availability**\n(Each operation is equally weighted regardless of request volume)', + }), + ); + + widgets = widgets.concat( + ServiceAvailabilityAndLatencyDashboard.generateAvailabilityWidgets( + props, + true, + availabilityZoneIds, + ), + ); + } + + return widgets; + } + + private static generateServerSideAndCanaryLatencyWidgets( + props: ServiceAvailabilityAndLatencyDashboardProps, + availabilityZoneIds: string[], + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new TextWidget({ + height: 2, + width: 24, + markdown: + '**Server-side Latency**\n(Each critical operation is equally weighted regardless of request volume)', + }), + ); + + widgets = widgets.concat( + ServiceAvailabilityAndLatencyDashboard.generateLatencyMetricWidgets( + props, + false, + availabilityZoneIds, + ), + ); + + if ( + props.service.operations.filter( + (x) => x.critical && x.canaryMetricDetails !== undefined, + ).length > 0 + ) { + widgets.push( + new TextWidget({ + height: 2, + width: 24, + markdown: + '**Canary Measured Latency**\n(Each operation is equally weighted regardless of request volume)', + }), + ); + + widgets = widgets.concat( + ServiceAvailabilityAndLatencyDashboard.generateLatencyMetricWidgets( + props, + true, + availabilityZoneIds, + ), + ); + } + + return widgets; + } + + private static generateAvailabilityWidgets( + props: ServiceAvailabilityAndLatencyDashboardProps, + isCanary: boolean, + availabilityZoneIds: string[], + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new GraphWidget({ + height: 6, + width: 24, + title: Fn.ref('AWS::Region') + ' Availability', + region: Fn.ref('AWS::Region'), + left: RegionalAvailabilityMetrics.createRegionalServiceAvailabilityMetrics( + { + label: Fn.ref('AWS::Region') + ' availability', + period: props.service.period, + availabilityMetricProps: this.createRegionalAvailabilityMetricProps( + props.service.operations.filter((x) => x.critical), + isCanary, + AvailabilityMetricType.SUCCESS_RATE, + ), + }, + ), + statistic: 'Sum', + leftYAxis: { + max: 100, + min: 95, + label: 'Availability', + showUnits: false, + }, + right: + RegionalAvailabilityMetrics.createRegionalServiceAvailabilityMetrics({ + label: Fn.ref('AWS::Region') + ' faults', + period: props.service.period, + availabilityMetricProps: this.createRegionalAvailabilityMetricProps( + props.service.operations.filter((x) => x.critical), + isCanary, + AvailabilityMetricType.FAULT_COUNT, + ), + }), + rightYAxis: { + label: 'Faults', + showUnits: false, + min: 0, + max: Math.ceil(props.service.faultCountThreshold * 1.5), + }, + rightAnnotations: [ + { + color: Color.RED, + label: 'High severity', + value: props.service.faultCountThreshold, + }, + ], + }), + ); + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId = availabilityZoneIds[i]; + + widgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Availability', + region: Fn.ref('AWS::Region'), + left: ZonalAvailabilityMetrics.createZonalServiceAvailabilityMetrics({ + label: availabilityZoneId + ' availability', + period: props.service.period, + availabilityMetricProps: this.createZonalAvailabilityMetricProps( + props.service.operations.filter((x) => x.critical), + availabilityZoneId, + isCanary, + AvailabilityMetricType.SUCCESS_RATE, + ), + }), + statistic: 'Sum', + leftYAxis: { + max: 100, + min: 95, + label: 'Availability', + showUnits: false, + }, + right: ZonalAvailabilityMetrics.createZonalServiceAvailabilityMetrics( + { + label: availabilityZoneId + ' faults', + period: props.service.period, + availabilityMetricProps: this.createZonalAvailabilityMetricProps( + props.service.operations.filter((x) => x.critical), + availabilityZoneId, + isCanary, + AvailabilityMetricType.FAULT_COUNT, + ), + }, + ), + rightYAxis: { + label: 'Faults', + showUnits: false, + min: 0, + max: Math.ceil(props.service.faultCountThreshold * 1.5), + }, + rightAnnotations: [ + { + color: Color.RED, + label: 'High severity', + value: props.service.faultCountThreshold, + }, + ], + }), + ); + } + + return widgets; + } + + private static generateLatencyMetricWidgets( + props: ServiceAvailabilityAndLatencyDashboardProps, + isCanary: boolean, + availabilityZoneIds: string[], + ): IWidget[] { + let widgets: IWidget[] = []; + + widgets.push( + new GraphWidget({ + height: 6, + width: 24, + title: Fn.ref('AWS::Region') + ' Latency', + region: Fn.ref('AWS::Region'), + left: RegionalLatencyMetrics.createRegionalServiceLatencyCountMetrics({ + label: Fn.ref('AWS::Region') + ' latency', + period: props.service.period, + latencyMetricProps: this.createRegionalLatencyMetricProps( + props.service.operations.filter((x) => x.critical), + isCanary, + LatencyMetricType.SUCCESS_LATENCY, + ), + }), + statistic: 'Sum', + leftYAxis: { + max: props.service.faultCountThreshold * 1.5, + min: 0, + label: 'High latency count', + showUnits: false, + }, + leftAnnotations: [ + { + color: Color.RED, + label: 'High severity', + value: props.service.faultCountThreshold, + }, + ], + }), + ); + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let availabilityZoneId = availabilityZoneIds[i]; + + widgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Latency', + region: Fn.ref('AWS::Region'), + left: ZonalLatencyMetrics.createZonalServiceLatencyMetrics({ + label: availabilityZoneId + ' latency', + period: props.service.period, + latencyMetricProps: this.createZonalLatencyMetricProps( + props.service.operations.filter((x) => x.critical), + availabilityZoneId, + isCanary, + LatencyMetricType.SUCCESS_LATENCY, + ), + }), + statistic: 'Sum', + leftYAxis: { + max: props.service.faultCountThreshold * 1.5, + min: 0, + label: 'High latency count', + showUnits: false, + }, + leftAnnotations: [ + { + color: Color.RED, + label: 'High severity', + value: props.service.faultCountThreshold, + }, + ], + }), + ); + } + + return widgets; + } + + private static createRegionalAvailabilityMetricProps( + criticalOperations: IOperation[], + isCanary: boolean, + metricType: AvailabilityMetricType, + ): AvailabilityMetricProps[] { + return criticalOperations + .reduce((filtered, value) => { + if (isCanary && value.canaryMetricDetails) { + filtered.push( + value.canaryMetricDetails.canaryAvailabilityMetricDetails, + ); + } else if (!isCanary) { + filtered.push(value.serverSideAvailabilityMetricDetails); + } + return filtered; + }, [] as IOperationMetricDetails[]) + .map((x) => { + return { + label: + x.operationName + ' ' + metricType.toString().replace('_', ' '), + metricDetails: x, + metricType: metricType, + }; + }); + } + + private static createRegionalLatencyMetricProps( + criticalOperations: IOperation[], + isCanary: boolean, + metricType: LatencyMetricType, + ): LatencyMetricProps[] { + return criticalOperations + .reduce((filtered, value) => { + if (isCanary && value.canaryMetricDetails) { + filtered.push(value.canaryMetricDetails.canaryLatencyMetricDetails); + } else if (!isCanary) { + filtered.push(value.serverSideLatencyMetricDetails); + } + return filtered; + }, [] as IOperationMetricDetails[]) + .map((x) => { + return { + label: + x.operationName + ' ' + metricType.toString().replace('_', ' '), + metricDetails: x, + metricType: metricType, + statistic: 'TC(' + x.successAlarmThreshold + ':)', + }; + }); + } + + private static createZonalAvailabilityMetricProps( + criticalOperations: IOperation[], + availabilityZoneId: string, + isCanary: boolean, + metricType: AvailabilityMetricType, + ): AvailabilityMetricProps[] { + return criticalOperations + .reduce((filtered, value) => { + if ( + isCanary && + value.canaryMetricDetails !== undefined && + value.canaryMetricDetails != null + ) { + filtered.push( + value.canaryMetricDetails.canaryAvailabilityMetricDetails, + ); + } else if (!isCanary) { + filtered.push(value.serverSideAvailabilityMetricDetails); + } + return filtered; + }, [] as IOperationMetricDetails[]) + .map((x) => { + return { + label: + x.operationName + ' ' + metricType.toString().replace('_', ' '), + metricDetails: x, + metricType: metricType, + availabilityZoneId: availabilityZoneId, + }; + }); + } + + private static createZonalLatencyMetricProps( + criticalOperations: IOperation[], + availabilityZoneId: string, + isCanary: boolean, + metricType: LatencyMetricType, + ): LatencyMetricProps[] { + return criticalOperations + .reduce((filtered, value) => { + if ( + isCanary && + value.canaryMetricDetails !== undefined && + value.canaryMetricDetails != null + ) { + filtered.push(value.canaryMetricDetails.canaryLatencyMetricDetails); + } else if (!isCanary) { + filtered.push(value.serverSideLatencyMetricDetails); + } + return filtered; + }, [] as IOperationMetricDetails[]) + .map((x) => { + return { + label: + x.operationName + ' ' + metricType.toString().replace('_', ' '), + metricDetails: x, + metricType: metricType, + availabilityZoneId: availabilityZoneId, + statistic: 'TC(' + x.successAlarmThreshold + ':)', + }; + }); + } + + private static generateLoadBalancerWidgets( + props: ServiceAvailabilityAndLatencyDashboardProps, + title: string, + availabilityZoneNames: string[], + ): IWidget[] { + let albWidgets: IWidget[] = []; + let loadBalancerFullName: string = ( + props.service.loadBalancer as BaseLoadBalancer + ).loadBalancerFullName; + + albWidgets.push(new TextWidget({ height: 2, width: 24, markdown: title })); + albWidgets.push( + new GraphWidget({ + height: 8, + width: 24, + title: Fn.sub('${AWS::Region} Fault Rate'), + region: Fn.sub('${AWS::Region}'), + left: [ + ApplicationLoadBalancerMetrics.createRegionalApplicationLoadBalancerFaultRateMetric( + loadBalancerFullName, + props.service.period, + ), + ], + leftYAxis: { + max: 35, + min: 0, + label: 'Fault Rate', + showUnits: false, + }, + leftAnnotations: [ + { + value: 1, + visible: true, + color: Color.RED, + label: 'High severity', + }, + ], + }), + ); + + availabilityZoneNames.forEach((availabilityZoneName) => { + let availabilityZoneId: string = + props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + availabilityZoneName.substring(availabilityZoneName.length - 1), + ); + + albWidgets.push( + new GraphWidget({ + height: 6, + width: 8, + title: availabilityZoneId + ' Fault Rate', + region: Fn.sub('${AWS::Region}'), + left: [ + ApplicationLoadBalancerMetrics.createZonalApplicationLoadBalancerFaultRateMetric( + loadBalancerFullName, + availabilityZoneName, + props.service.period, + ), + ], + leftYAxis: { + max: 35, + min: 0, + label: 'Fault Rate', + showUnits: false, + }, + leftAnnotations: [ + { + value: 1, + visible: true, + color: Color.RED, + label: 'High severity', + }, + ], + }), + ); + }); + + return albWidgets; + } + /** + * The service level dashboard + */ + dashboard: Dashboard; + + constructor( + scope: Construct, + id: string, + props: ServiceAvailabilityAndLatencyDashboardProps, + ) { + super(scope, id); + + let topLevelAggregateAlarmWidgets: IWidget[] = []; + + let availabilityZoneIds: string[] = props.service.availabilityZoneNames.map( + (x) => { + return props.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + x.substring(x.length - 1), + ); + }, + ); + + topLevelAggregateAlarmWidgets.push( + new TextWidget({ + height: 2, + width: 24, + markdown: '***Availability and Latency Alarms***', + }), + ); + + topLevelAggregateAlarmWidgets.push( + new AlarmStatusWidget({ + height: 2, + width: 24, + alarms: [props.aggregateRegionalAlarm], + title: + 'Customer Experience - Regional Aggregate Impact Alarm (measures fault count in aggregate across all critical operations)', + }), + ); + + let keyPrefix: string = AvailabilityAndLatencyMetrics.nextChar(''); + let perOperationAZFaultsMetrics: IMetric[] = []; + + for (let i = 0; i < availabilityZoneIds.length; i++) { + let counter: number = 1; + let availabilityZoneId: string = availabilityZoneIds[i]; + + topLevelAggregateAlarmWidgets.push( + new AlarmStatusWidget({ + height: 2, + width: 8, + alarms: [props.zonalAggregateAlarms[i]], + title: + availabilityZoneId + + ' Zonal Isolated Impact Alarm (any critical operation in this AZ shows impact from server-side or canary)', + }), + ); + + let usingMetrics: { [key: string]: IMetric } = {}; + + props.service.operations + .filter((x) => x.critical == true) + .forEach((x) => { + usingMetrics[`${keyPrefix}${counter++}`] = + ZonalAvailabilityMetrics.createZonalAvailabilityMetric({ + availabilityZoneId: availabilityZoneId, + metricDetails: x.serverSideAvailabilityMetricDetails, + label: + availabilityZoneId + ' ' + x.operationName + ' fault count', + metricType: AvailabilityMetricType.FAULT_COUNT, + keyPrefix: keyPrefix, + }); + }); + + let zonalFaultCount: IMetric = new MathExpression({ + expression: Object.keys(usingMetrics).join('+'), + label: availabilityZoneId + ' fault count', + usingMetrics: usingMetrics, + }); + + perOperationAZFaultsMetrics.push(zonalFaultCount); + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + } + + let azContributorWidgets: IWidget[] = [ + new TextWidget({ + height: 2, + width: 24, + markdown: '**AZ Contributors To Faults**', + }), + new GraphWidget({ + height: 6, + width: 24, + title: 'AZ Fault Count', + period: props.service.period, + left: perOperationAZFaultsMetrics, + }), + ]; + + topLevelAggregateAlarmWidgets.concat( + ServiceAvailabilityAndLatencyDashboard.generateTPSWidgets( + props, + availabilityZoneIds, + ), + ); + + this.dashboard = new Dashboard(this, 'TopLevelDashboard', { + dashboardName: + props.service.serviceName.toLowerCase() + + Fn.sub('-service-availability-and-latency-${AWS::Region}'), + defaultInterval: props.interval, + periodOverride: PeriodOverride.INHERIT, + widgets: [ + topLevelAggregateAlarmWidgets, + azContributorWidgets, + ServiceAvailabilityAndLatencyDashboard.generateServerSideAndCanaryAvailabilityWidgets( + props, + availabilityZoneIds, + ), + ServiceAvailabilityAndLatencyDashboard.generateServerSideAndCanaryLatencyWidgets( + props, + availabilityZoneIds, + ), + ], + }); + + let lb: CfnLoadBalancer = props.service.loadBalancer?.node + .defaultChild as CfnLoadBalancer; + + if (lb !== undefined && lb != null && lb.type == 'application') { + this.dashboard.addWidgets( + ...ServiceAvailabilityAndLatencyDashboard.generateLoadBalancerWidgets( + props, + 'Application Load Balancer Metrics', + props.service.availabilityZoneNames, + ), + ); + } + } +} diff --git a/src/dashboards/props/BasicServiceDashboardProps.ts b/src/dashboards/props/BasicServiceDashboardProps.ts new file mode 100644 index 0000000..f000dc7 --- /dev/null +++ b/src/dashboards/props/BasicServiceDashboardProps.ts @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { IAlarm, IMetric } from 'aws-cdk-lib/aws-cloudwatch'; +import { AvailabilityZoneMapper } from '../../azmapper/AvailabilityZoneMapper'; + +export interface BasicServiceDashboardProps { + readonly serviceName: string; + + readonly zonalLoadBalancerIsolatedImpactAlarms?: { [key: string]: IAlarm }; + + readonly zonalNatGatewayIsolatedImpactAlarms?: { [key: string]: IAlarm }; + + readonly zonalAggregateIsolatedImpactAlarms: { [key: string]: IAlarm }; + + readonly zonalLoadBalancerFaultRateMetrics?: { [key: string]: IMetric }; + + readonly zonalNatGatewayPacketDropMetrics?: { [key: string]: IMetric }; + + readonly interval?: Duration; + + readonly azMapper: AvailabilityZoneMapper; +} diff --git a/src/dashboards/props/ContributorInsightWidgetProps.ts b/src/dashboards/props/ContributorInsightWidgetProps.ts new file mode 100644 index 0000000..21adfa5 --- /dev/null +++ b/src/dashboards/props/ContributorInsightWidgetProps.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { + MetricWidgetProps, + CfnInsightRule, + LegendPosition, +} from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * Properties for creating a contributor insight dashboard widget + */ +export interface ContributorInsightWidgetProps extends MetricWidgetProps { + /** + * The account id for the widget + */ + readonly accountId: string; + + /** + * The number of top contributors to graph + */ + readonly topContributors: number; + + /** + * The insight rule for the widget + */ + readonly insightRule: CfnInsightRule; + + /** + * The legend position in the widget + */ + readonly legendPosition: LegendPosition; + + /** + * The order statistic used + */ + readonly orderStatistic: string; + + /** + * The period for the widget data points + */ + readonly period: Duration; +} diff --git a/src/dashboards/props/OperationAvailabilityAndLatencyDashboardProps.ts b/src/dashboards/props/OperationAvailabilityAndLatencyDashboardProps.ts new file mode 100644 index 0000000..20e16f3 --- /dev/null +++ b/src/dashboards/props/OperationAvailabilityAndLatencyDashboardProps.ts @@ -0,0 +1,102 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { IAlarm, CfnInsightRule } from 'aws-cdk-lib/aws-cloudwatch'; +import { ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IAvailabilityZoneMapper } from '../../azmapper/IAvailabilityZoneMapper'; +import { IOperation } from '../../services/IOperation'; + +/** + * Properties for creating an availability and latency dashboard for + * a single operation + */ +export interface OperationAvailabilityAndLatencyDashboardProps { + /** + * The operation for this dashboard + */ + readonly operation: IOperation; + + /** + * The interval of the dashboard + */ + readonly interval: Duration; + + /** + * The load balancer supporting this operation, if this is not + * provided, no load balancer metrics will be shown + * + * @default - No load balancer metrics will be shown on the dashboard + */ + readonly loadBalancer?: ILoadBalancerV2; + + /** + * Per AZ server-side availability alarms + */ + readonly zonalEndpointServerAvailabilityAlarms: IAlarm[]; + + /** + * Per AZ server-side latency alarms + */ + readonly zonalEndpointServerLatencyAlarms: IAlarm[]; + + /** + * Per AZ canary availability alarms + */ + readonly zonalEndpointCanaryAvailabilityAlarms?: IAlarm[]; + + /** + * Per AZ canary latency alarms + */ + readonly zonalEndpointCanaryLatencyAlarms?: IAlarm[]; + + /** + * Regional server-side availability alarm + */ + readonly regionalEndpointServerAvailabilityAlarm: IAlarm; + + /** + * Regional server-side latency alarm + */ + readonly regionalEndpointServerLatencyAlarm: IAlarm; + + /** + * Regional canary availability alarm + */ + readonly regionalEndpointCanaryAvailabilityAlarm?: IAlarm; + + /** + * Regional canary latency alarm + */ + readonly regionalEndpointCanaryLatencyAlarm?: IAlarm; + + /** + * Per AZ alarms that indicate isolated single AZ impact + */ + readonly isolatedAZImpactAlarms: IAlarm[]; + + /** + * Alarm that indicates regional impact + */ + readonly regionalImpactAlarm: IAlarm; + + /** + * Insight rule that shows instance contributors to + * high latency for this operation + * + * @default - Insight rule will not be shown on the dashboard + */ + readonly instanceContributorsToHighLatency?: CfnInsightRule; + + /** + * Insight rule that shows instance contributors to + * faults for this operation + * + * @default - Insight rule will not be shown on the dashboard + */ + readonly instanceContributorsToFaults?: CfnInsightRule; + + /** + * The AZ Mapper + */ + readonly azMapper: IAvailabilityZoneMapper; +} diff --git a/src/dashboards/props/OperationAvailabilityWidgetProps.ts b/src/dashboards/props/OperationAvailabilityWidgetProps.ts new file mode 100644 index 0000000..f7eb695 --- /dev/null +++ b/src/dashboards/props/OperationAvailabilityWidgetProps.ts @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm, CfnInsightRule } from 'aws-cdk-lib/aws-cloudwatch'; +import { IOperation } from '../../services/IOperation'; +import { IOperationMetricDetails } from '../../services/IOperationMetricDetails'; + +/** + * Props for creating operation dashboard availability and latency widgets + */ +export interface OperationAvailabilityWidgetProps { + /** + * The operation for this widget + */ + readonly operation: IOperation; + + /** + * The availability metric details + */ + readonly availabilityMetricDetails: IOperationMetricDetails; + + /** + * The number of AZs being used + */ + readonly availabilityZoneIds: string[]; + + /** + * An alarm per AZ for availability + */ + readonly zonalEndpointAvailabilityAlarms: IAlarm[]; + + /** + * The regional endpoint availability alarm + */ + readonly regionalEndpointAvailabilityAlarm: IAlarm; + + /** + * Instance contributors to faults, only set for + * server-side widgets + */ + readonly instanceContributorsToFaults?: CfnInsightRule; + + /** + * Is this widget for the canary metrics + */ + readonly isCanary: boolean; +} diff --git a/src/dashboards/props/OperationLatencyWidgetProps.ts b/src/dashboards/props/OperationLatencyWidgetProps.ts new file mode 100644 index 0000000..f70b65b --- /dev/null +++ b/src/dashboards/props/OperationLatencyWidgetProps.ts @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm, CfnInsightRule } from 'aws-cdk-lib/aws-cloudwatch'; +import { IOperation } from '../../services/IOperation'; +import { IOperationMetricDetails } from '../../services/IOperationMetricDetails'; + +/** + * Props for creating operation dashboard availability and latency widgets + */ +export interface OperationLatencyWidgetProps { + /** + * The operation for this widget + */ + readonly operation: IOperation; + + /** + * The latency metric details + */ + readonly latencyMetricDetails: IOperationMetricDetails; + + /** + * The number of AZs being used + */ + readonly availabilityZoneIds: string[]; + + /** + * An alarm per AZ for latency + */ + readonly zonalEndpointLatencyAlarms: IAlarm[]; + + /** + * The regional endpoint latency alarm + */ + readonly regionalEndpointLatencyAlarm: IAlarm; + + /** + * Instance contributors to high latency, only set for + * server-side widgets + */ + readonly instanceContributorsToHighLatency?: CfnInsightRule; + + /** + * Is this widget for the canary metrics + */ + readonly isCanary: boolean; +} diff --git a/src/dashboards/props/ServiceAvailabilityAndLatencyDashboardProps.ts b/src/dashboards/props/ServiceAvailabilityAndLatencyDashboardProps.ts new file mode 100644 index 0000000..8d0941c --- /dev/null +++ b/src/dashboards/props/ServiceAvailabilityAndLatencyDashboardProps.ts @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { IAvailabilityZoneMapper } from '../../azmapper/IAvailabilityZoneMapper'; +import { IService } from '../../services/IService'; + +/** + * Properties for creating a service level dashboard + */ +export interface ServiceAvailabilityAndLatencyDashboardProps { + /** + * The service for the dashboard + */ + readonly service: IService; + + /** + * The AZ isolated impact alarms, one for each AZ + */ + readonly zonalAggregateAlarms: IAlarm[]; + + /** + * The aggregate regional impact alarm, typically a fault + * count across all critical operations + */ + readonly aggregateRegionalAlarm: IAlarm; + + /** + * The interval for the dashboard + */ + readonly interval: Duration; + + /** + * The AZ Mapper + */ + readonly azMapper: IAvailabilityZoneMapper; +} diff --git a/src/index.ts b/src/index.ts index 92c94b8..83f9540 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,37 @@ -export class Hello { - public sayHello() { - return 'hello, world!'; - } -} \ No newline at end of file +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +export { OutlierDetectionAlgorithm } from './utilities/OutlierDetectionAlgorithm'; +export { BasicServiceMultiAZObservabilityProps } from './services/props/BasicServiceMultiAZObservabilityProps'; +export { InstrumentedServiceMultiAZObservabilityProps } from './services/props/InstrumentedServiceMultiAZObservabilityProps'; +export { IService } from './services/IService'; +export { Service } from './services/Service'; +export { IOperation } from './services/IOperation'; +export { Operation } from './services/Operation'; +export { OperationMetricDetails } from './services/OperationMetricDetails'; +export { IOperationMetricDetails } from './services/IOperationMetricDetails'; +export { ServiceMetricDetails } from './services/ServiceMetricDetails'; +export { IServiceMetricDetails } from './services/IServiceMetricDetails'; +export { AddCanaryTestProps } from './canaries/props/AddCanaryTestProps'; +export { ICanaryMetrics } from './services/ICanaryMetrics'; +export { IContributorInsightRuleDetails } from './services/IContributorInsightRuleDetails'; +export { IAvailabilityZoneMapper } from './azmapper/IAvailabilityZoneMapper'; +export { AvailabilityZoneMapper } from './azmapper/AvailabilityZoneMapper'; +export { AvailabilityZoneMapperProps } from './azmapper/props/AvailabilityZoneMapperProps'; +export { ServiceProps } from './services/props/ServiceProps'; +export { CanaryMetrics } from './services/CanaryMetrics'; +export { CanaryMetricProps } from './services/props/CanaryMetricProps'; +export { OperationProps } from './services/props/OperationProps'; +export { OperationMetricDetailsProps } from './services/props/OperationMetricDetailsProps'; +export { ContributorInsightRuleDetails } from './services/ContributorInsightRuleDetails'; +export { ContributorInsightRuleDetailsProps } from './services/props/ContributorInsightRuleDetailsProps'; +export { MetricDimensions } from './services/props/MetricDimensions'; +export { NetworkConfigurationProps } from './canaries/props/NetworkConfigurationProps'; +export { ServiceMetricDetailsProps } from './services/props/ServiceMetricDetailsProps'; +export { InstrumentedServiceMultiAZObservability } from './services/InstrumentedServiceMultiAZObservability'; +export { BasicServiceMultiAZObservability } from './services/BasicServiceMultiAZObservability'; +export { IBasicServiceMultiAZObservability } from './services/IBasicServiceMultiAZObservability'; +export { IInstrumentedServiceMultiAZObservability } from './services/IInstrumentedServiceMultiAZObservability'; +export { IServiceAlarmsAndRules } from './alarmsandrules/IServiceAlarmsAndRules'; +export { ICanaryTestMetricsOverride } from './services/ICanaryTestMetricsOverride'; +export { CanaryTestMetricsOverride } from './services/CanaryTestMetricsOverride'; +export { CanaryTestMetricsOverrideProps } from './services/props/CanaryTestMetricsOverrideProps'; diff --git a/src/metrics/ApplicationLoadBalancerMetrics.ts b/src/metrics/ApplicationLoadBalancerMetrics.ts new file mode 100644 index 0000000..16bb60b --- /dev/null +++ b/src/metrics/ApplicationLoadBalancerMetrics.ts @@ -0,0 +1,171 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { + IMetric, + MathExpression, + Metric, + Unit, +} from 'aws-cdk-lib/aws-cloudwatch'; + +export class ApplicationLoadBalancerMetrics { + /** + * Creates a regional fault count metric using 5xx target and load balancer + * metrics against total requests for the specified load balancer + * @param period + * @param loadBalancerFullName + * @returns + */ + static createRegionalApplicationLoadBalancerFaultRateMetric( + loadBalancerFullName: string, + period: Duration, + ): IMetric { + return new MathExpression({ + expression: '((m1 + m2) / m3) * 100', + label: 'Fault Rate', + period: period, + usingMetrics: { + m1: new Metric({ + metricName: 'HTTPCode_Target_5XX_Count', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + }, + label: '5xxTarget', + }), + m2: new Metric({ + metricName: 'HTTPCode_ELB_5XX_Count', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + }, + label: '5xxELB', + }), + m3: new Metric({ + metricName: 'RequestCount', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + }, + label: 'Requests', + }), + }, + }); + } + + /** + * Creates a zonal fault count metric using 5xx target and load balancer + * metrics against total requests for the specified load balancer + * @param loadBalancerFullName + * @param availabilityZoneName + * @param period + * @returns + */ + static createZonalApplicationLoadBalancerFaultRateMetric( + loadBalancerFullName: string, + availabilityZoneName: string, + period: Duration, + ): IMetric { + return new MathExpression({ + expression: '((m1 + m2) / m3) * 100', + label: 'Fault Rate', + period: period, + usingMetrics: { + m1: new Metric({ + metricName: 'HTTPCode_Target_5XX_Count', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + AvailabilityZone: availabilityZoneName, + }, + label: '5xxTarget', + }), + m2: new Metric({ + metricName: 'HTTPCode_ELB_5XX_Count', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + AvailabilityZone: availabilityZoneName, + }, + label: '5xxELB', + }), + m3: new Metric({ + metricName: 'RequestCount', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + AvailabilityZone: availabilityZoneName, + }, + label: 'Requests', + }), + }, + }); + } + + /** + * Creates a regional processed bytes metric for the specified load balancer + * @param loadBalancerFullName + * @param period + * @returns + */ + static createRegionalApplicationLoadBalancerProcessedBytesMetric( + loadBalancerFullName: string, + period: Duration, + ): IMetric { + return new Metric({ + metricName: 'ProcessedBytes', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + }, + label: 'ProcessedBytes', + }); + } + + /** + * Creates a zonal processed bytes metric for the specified load balancer + * @param loadBalancerFullName + * @param availabilityZoneName + * @param period + * @returns IMetric + */ + static createZonalApplicationLoadBalancerProcessedBytesMetric( + loadBalancerFullName: string, + availabilityZoneName: string, + period: Duration, + ): IMetric { + return new Metric({ + metricName: 'ProcessedBytes', + namespace: 'AWS/ApplicationELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + AvailabilityZone: availabilityZoneName, + }, + label: 'ProcessedBytes', + }); + } +} diff --git a/src/metrics/AvailabilityAndLatencyMetrics.ts b/src/metrics/AvailabilityAndLatencyMetrics.ts new file mode 100644 index 0000000..13f9ee2 --- /dev/null +++ b/src/metrics/AvailabilityAndLatencyMetrics.ts @@ -0,0 +1,266 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IMetric, Metric, MathExpression } from 'aws-cdk-lib/aws-cloudwatch'; +import { AvailabilityMetricProps } from './props/AvailabilityMetricProps'; +import { LatencyMetricProps } from './props/LatencyMetricProps'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; +import { LatencyMetricType } from '../utilities/LatencyMetricType'; + +/** + * Class for creating availability and latency metrics that can be used in alarms and graphs + */ +export class AvailabilityAndLatencyMetrics { + /** + * Increments a str by one char, for example + * a -> b + * z -> aa + * ad -> ae + * + * This wraps at z and adds a new 'a' + * @param str + * @returns + */ + static nextChar(str: string): string { + if (str.length == 0) { + return 'a'; + } + let charA: string[] = str.split(''); + + if (charA[charA.length - 1] === 'z') { + return ( + AvailabilityAndLatencyMetrics.nextChar( + str.substring(0, charA.length - 1), + ) + 'a' + ); + } else { + return ( + str.substring(0, charA.length - 1) + + String.fromCharCode(charA[charA.length - 1].charCodeAt(0) + 1) + ); + } + } + + /** + * General purpose method to create availability metrics + * @param props + * @param dimensions + * @returns + */ + static createAvailabilityMetric( + props: AvailabilityMetricProps, + dimensions: { [key: string]: string }, + ): IMetric { + let counter: number = 0; + let key: string = ''; + + let usingMetrics: { [key: string]: IMetric } = {}; + + let successKeys: string[] = []; + let faultKeys: string[] = []; + + if ( + props.metricDetails.successMetricNames !== undefined && + props.metricType != AvailabilityMetricType.FAULT_COUNT + ) { + props.metricDetails.successMetricNames.forEach( + (successMetric: string) => { + let keyPrefix = + (props.keyPrefix === undefined || props.keyPrefix == '' + ? '' + : props.keyPrefix.toLowerCase() + '_') + + props.metricDetails.operationName.toLowerCase() + + '_' + + successMetric.toLowerCase(); + + key = keyPrefix + '_' + counter++; + successKeys.push(key); + + usingMetrics[key] = new Metric({ + namespace: props.metricDetails.metricNamespace, + metricName: successMetric, + unit: props.metricDetails.unit, + period: props.metricDetails.period, + statistic: props.metricDetails.alarmStatistic, + dimensionsMap: dimensions, + label: successMetric, + }); + }, + ); + } + + if ( + props.metricDetails.faultMetricNames !== undefined && + props.metricType != AvailabilityMetricType.SUCCESS_COUNT + ) { + props.metricDetails.faultMetricNames.forEach((faultMetric) => { + let keyPrefix = + (props.keyPrefix === undefined || props.keyPrefix == '' + ? '' + : props.keyPrefix.toLowerCase() + '_') + + props.metricDetails.operationName.toLowerCase() + + '_' + + faultMetric.toLowerCase(); + + key = keyPrefix + '_' + counter++; + faultKeys.push(key); + + usingMetrics[key] = new Metric({ + namespace: props.metricDetails.metricNamespace, + metricName: faultMetric, + unit: props.metricDetails.unit, + period: props.metricDetails.period, + statistic: props.metricDetails.alarmStatistic, + dimensionsMap: dimensions, + label: faultMetric, + }); + }); + } + + let expression: string = ''; + + switch (props.metricType) { + case AvailabilityMetricType.SUCCESS_RATE: + expression = `((${successKeys.join('+')}) / (${successKeys.join('+')}+${faultKeys.join('+')})) * 100`; + break; + case AvailabilityMetricType.REQUEST_COUNT: + expression = `${successKeys.join('+')}+${faultKeys.join('+')}`; + break; + case AvailabilityMetricType.FAULT_COUNT: + expression = `(${faultKeys.join('+')})`; + break; + case AvailabilityMetricType.FAULT_RATE: + expression = `((${faultKeys.join('+')}) / (${successKeys.join('+')}+${faultKeys.join('+')})) * 100`; + break; + case AvailabilityMetricType.SUCCESS_COUNT: + expression = `(${successKeys.join('+')})`; + break; + } + + return new MathExpression({ + expression: expression, + label: props.label, + period: props.metricDetails.period, + usingMetrics: usingMetrics, + }); + } + + /** + * General purpose method to create latency metrics, the reason this creates an array of metrics while the + * equivalent availability metric method doesn't is because in availability, we can just sum the count of different + * metric names while for latency we can't sum the count because that's not what's being measured. It allows the + * caller to decide if they only want to take the first name, or average all of the names + * (like SuccessLatency and BigItemSuccessLatency). + * + * @param props + * @param dimensions + * @returns + */ + static createLatencyMetrics( + props: LatencyMetricProps, + dimensions: { [key: string]: string }, + ): IMetric[] { + let names: string[]; + + switch (props.metricType) { + default: + case LatencyMetricType.SUCCESS_LATENCY: + names = props.metricDetails.successMetricNames; + break; + case LatencyMetricType.FAULT_LATENCY: + names = props.metricDetails.faultMetricNames; + break; + } + + return names.map( + (x) => + new Metric({ + metricName: x, + namespace: props.metricDetails.metricNamespace, + unit: props.metricDetails.unit, + period: props.metricDetails.period, + statistic: props.statistic, + dimensionsMap: dimensions, + label: props.label, + }), + ); + } + + /** + * Takes all of the success or failure latency metric names and creates an average of those + * names, if there's only 1 name, it just returns that metric + * @param props + * @param dimensions + */ + static createAverageLatencyMetric( + props: LatencyMetricProps, + dimensions: { [key: string]: string }, + ): IMetric { + let latencyMetrics: IMetric[] = + AvailabilityAndLatencyMetrics.createLatencyMetrics(props, dimensions); + + if (latencyMetrics.length == 1) { + return latencyMetrics[0]; + } else { + let usingMetrics: { [key: string]: IMetric } = {}; + + latencyMetrics.forEach((metric: IMetric, index: number) => { + let keyPrefix: string = + (props.keyPrefix === undefined || props.keyPrefix == '' + ? '' + : props.keyPrefix.toLowerCase() + '_') + + props.metricDetails.operationName.toLowerCase() + + '_' + + props.metricType.toString().toLowerCase(); + + usingMetrics[keyPrefix + index] = metric; + }); + + return new MathExpression({ + expression: `(${Object.keys(usingMetrics).join('+')})/${Object.keys(usingMetrics).length}`, + label: props.label, + period: props.metricDetails.period, + usingMetrics: usingMetrics, + }); + } + } + + /** + * Creates a count of high latency metrics for either SuccessLatency or FaultLatency, will total + * the count of requests that exceed a threshold you define in your statistic, like TC(200:) across + * all metric names that are part of either Success or Fault latency. + * @param props + * @returns + */ + static createLatencyCountMetric( + props: LatencyMetricProps, + dimensions: { [key: string]: string }, + ): IMetric { + let latencyMetrics: IMetric[] = + AvailabilityAndLatencyMetrics.createLatencyMetrics(props, dimensions); + + if (latencyMetrics.length == 1) { + return latencyMetrics[0]; + } else { + let usingMetrics: { [key: string]: IMetric } = {}; + + latencyMetrics.forEach((metric: IMetric, index: number) => { + let keyPrefix: string = + (props.keyPrefix === undefined || props.keyPrefix == '' + ? '' + : props.keyPrefix.toLowerCase() + '_') + + props.metricDetails.operationName.toLowerCase() + + '_' + + props.metricType.toString().toLowerCase(); + + usingMetrics[keyPrefix + index] = metric; + }); + + return new MathExpression({ + expression: Object.keys(usingMetrics).join('+'), + label: props.label, + period: props.metricDetails.period, + usingMetrics: usingMetrics, + }); + } + } +} diff --git a/src/metrics/NetworkLoadBalancerMetrics.ts b/src/metrics/NetworkLoadBalancerMetrics.ts new file mode 100644 index 0000000..e0f8575 --- /dev/null +++ b/src/metrics/NetworkLoadBalancerMetrics.ts @@ -0,0 +1,55 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { IMetric, Metric, Unit } from 'aws-cdk-lib/aws-cloudwatch'; + +export class NetworkLoadBalancerMetrics { + /** + * Creates a regional processed bytes metric for the specified load balancer + * @param loadBalancerFullName + * @param period + * @returns + */ + static createRegionalNetworkLoadBalancerProcessedBytesMetric( + loadBalancerFullName: string, + period: Duration, + ): IMetric { + return new Metric({ + metricName: 'ProcessedBytes', + namespace: 'AWS/NetworkELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + }, + label: 'ProcessedBytes', + }); + } + + /** + * Creates a zonal processed bytes metric for the specified load balancer + * @param loadBalancerFullName + * @param availabilityZoneName + * @param period + * @returns + */ + static createZonalNetworkLoadBalancerProcessedBytesMetric( + loadBalancerFullName: string, + availabilityZoneName: string, + period: Duration, + ): IMetric { + return new Metric({ + metricName: 'ProcessedBytes', + namespace: 'AWS/NetworkELB', + unit: Unit.COUNT, + period: period, + statistic: 'Sum', + dimensionsMap: { + LoadBalancer: loadBalancerFullName, + AvailabilityZone: availabilityZoneName, + }, + label: 'ProcessedBytes', + }); + } +} diff --git a/src/metrics/RegionalAvailabilityMetrics.ts b/src/metrics/RegionalAvailabilityMetrics.ts new file mode 100644 index 0000000..5c78ff2 --- /dev/null +++ b/src/metrics/RegionalAvailabilityMetrics.ts @@ -0,0 +1,91 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { IMetric, MathExpression } from 'aws-cdk-lib/aws-cloudwatch'; +import { AvailabilityAndLatencyMetrics } from './AvailabilityAndLatencyMetrics'; +import { RegionalAvailabilityMetricProps } from './props/RegionalAvailabilityMetricProps'; +import { ServiceAvailabilityMetricProps } from './props/ServiceAvailabilityMetricProps'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; + +export class RegionalAvailabilityMetrics { + /** + * Creates a regional availability metric + * @param props + * @returns + */ + static createRegionalAvailabilityMetric( + props: RegionalAvailabilityMetricProps, + ): IMetric { + return AvailabilityAndLatencyMetrics.createAvailabilityMetric( + props, + props.metricDetails.metricDimensions.regionalDimensions( + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates a regional service level availability metrics, one metric for + * each operation at the regional level and the service. + * @param props + * @returns The metric at index 0 is the metric math expression for the whole service. The following metrics + * are the metrics for each operation included in the request availability metric props. + */ + static createRegionalServiceAvailabilityMetrics( + props: ServiceAvailabilityMetricProps, + ): IMetric[] { + let usingMetrics: { [key: string]: IMetric } = {}; + let operationMetrics: IMetric[] = []; + let counter: number = 0; + + props.availabilityMetricProps.forEach((prop) => { + let keyPrefix: string = + (prop.keyPrefix === undefined || prop.keyPrefix == '' + ? '' + : prop.keyPrefix.toLowerCase() + '_') + + prop.metricDetails.operationName.toLowerCase() + + '_' + + prop.metricType.toString().toLowerCase(); + + let regionalOperationAvailabilityMetric: IMetric = + this.createRegionalAvailabilityMetric( + prop as RegionalAvailabilityMetricProps, + ); + + operationMetrics.push(regionalOperationAvailabilityMetric); + usingMetrics[`${keyPrefix}${counter++}`] = + regionalOperationAvailabilityMetric; + }); + + let expression: string = ''; + + switch (props.availabilityMetricProps[0].metricType) { + case AvailabilityMetricType.SUCCESS_RATE: + expression = `(${Object.keys(usingMetrics).join('+')}) / ${props.availabilityMetricProps.length}`; + break; + case AvailabilityMetricType.REQUEST_COUNT: + expression = `${Object.keys(usingMetrics).join('+')}`; + break; + case AvailabilityMetricType.FAULT_COUNT: + expression = `${Object.keys(usingMetrics).join('+')}`; + break; + case AvailabilityMetricType.FAULT_RATE: + expression = `(${Object.keys(usingMetrics).join('+')}) / ${props.availabilityMetricProps.length}`; + break; + case AvailabilityMetricType.SUCCESS_COUNT: + expression = `${Object.keys(usingMetrics).join('+')}`; + break; + } + + let math: IMetric = new MathExpression({ + usingMetrics: usingMetrics, + period: props.period, + label: props.label, + expression: expression, + }); + + operationMetrics.splice(0, 0, math); + + return operationMetrics; + } +} diff --git a/src/metrics/RegionalLatencyMetrics.ts b/src/metrics/RegionalLatencyMetrics.ts new file mode 100644 index 0000000..75896ef --- /dev/null +++ b/src/metrics/RegionalLatencyMetrics.ts @@ -0,0 +1,101 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { IMetric, MathExpression } from 'aws-cdk-lib/aws-cloudwatch'; +import { AvailabilityAndLatencyMetrics } from './AvailabilityAndLatencyMetrics'; +import { LatencyMetricProps } from './props/LatencyMetricProps'; +import { RegionalLatencyMetricProps } from './props/RegionalLatencyMetricProps'; +import { ServiceLatencyMetricProps } from './props/ServiceLatencyMericProps'; + +export class RegionalLatencyMetrics { + /** + * Creates a metrics for regional latency, one metric per metric name for the + * specified type of latency metric. You will need to perform some aggregation + * of these metrics if there is more than 1 metric name that correponds to + * SuccessLatency or FaultLatency, like doing an average. + * @param props + * @returns + */ + static createRegionalLatencyMetrics( + props: RegionalLatencyMetricProps, + ): IMetric[] { + return AvailabilityAndLatencyMetrics.createLatencyMetrics( + props, + props.metricDetails.metricDimensions.regionalDimensions( + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates a regional average latency metric, averages the values from all of the + * metric names that represent either SuccessLatency or FaultLatency + * @param props + * @returns + */ + static createRegionalAverageLatencyMetric( + props: RegionalLatencyMetricProps, + ): IMetric { + return AvailabilityAndLatencyMetrics.createAverageLatencyMetric( + props, + props.metricDetails.metricDimensions.regionalDimensions( + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates a count of high latency metrics for either SuccessLatency or FaultLatency + * @param props + * @returns + */ + static createRegionalLatencyCountMetric( + props: RegionalLatencyMetricProps, + ): IMetric { + return AvailabilityAndLatencyMetrics.createLatencyCountMetric( + props, + props.metricDetails.metricDimensions.regionalDimensions( + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates a count of high latency responses for all critical operations + * @param props + * @returns + */ + static createRegionalServiceLatencyCountMetrics( + props: ServiceLatencyMetricProps, + ): IMetric[] { + let usingMetrics: { [key: string]: IMetric } = {}; + let operationMetrics: IMetric[] = []; + let keyPrefix: string = AvailabilityAndLatencyMetrics.nextChar(''); + + props.latencyMetricProps.forEach( + (prop: LatencyMetricProps, index: number) => { + let operationRegionalMetric: IMetric = + this.createRegionalLatencyCountMetric(prop); + + operationMetrics.push(operationRegionalMetric); + usingMetrics[`${keyPrefix}${index}`] = operationRegionalMetric; + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + }, + ); + + if (Object.keys(usingMetrics).length == 1) { + operationMetrics.push(Object.values(usingMetrics)[0]); + } else { + let math: IMetric = new MathExpression({ + usingMetrics: usingMetrics, + period: props.period, + label: props.label, + expression: Object.keys(usingMetrics).join('+'), + }); + + operationMetrics.splice(0, 0, math); + } + + return operationMetrics; + } +} diff --git a/src/metrics/ZonalAvailabilityMetrics.ts b/src/metrics/ZonalAvailabilityMetrics.ts new file mode 100644 index 0000000..5df2dc8 --- /dev/null +++ b/src/metrics/ZonalAvailabilityMetrics.ts @@ -0,0 +1,91 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { IMetric, MathExpression } from 'aws-cdk-lib/aws-cloudwatch'; +import { AvailabilityAndLatencyMetrics } from './AvailabilityAndLatencyMetrics'; +import { ServiceAvailabilityMetricProps } from './props/ServiceAvailabilityMetricProps'; +import { ZonalAvailabilityMetricProps } from './props/ZonalAvailabilityMetricProps'; +import { AvailabilityMetricType } from '../utilities/AvailabilityMetricType'; + +export class ZonalAvailabilityMetrics { + /** + * Creates a zonal service level availability metrics, one metric for + * each operation at the zonal level and the service. + * @param props + * @returns The metric at index 0 is the metric math expression for the whole service. The following metrics + * are the metrics for each operation included in the request availability metric props. + */ + static createZonalServiceAvailabilityMetrics( + props: ServiceAvailabilityMetricProps, + ): IMetric[] { + let usingMetrics: { [key: string]: IMetric } = {}; + let operationMetrics: IMetric[] = []; + let counter: number = 0; + + props.availabilityMetricProps.forEach((prop) => { + let keyPrefix: string = + (prop.keyPrefix === undefined || prop.keyPrefix == '' + ? '' + : prop.keyPrefix.toLowerCase() + '_') + + prop.metricDetails.operationName.toLowerCase() + + '_' + + prop.metricType.toString().toLowerCase(); + + let zonalOperationAvailabilityMetric: IMetric = + this.createZonalAvailabilityMetric( + prop as ZonalAvailabilityMetricProps, + ); + + operationMetrics.push(zonalOperationAvailabilityMetric); + usingMetrics[`${keyPrefix}${counter++}`] = + zonalOperationAvailabilityMetric; + }); + + let expression: string = ''; + + switch (props.availabilityMetricProps[0].metricType) { + case AvailabilityMetricType.SUCCESS_RATE: + expression = `(${Object.keys(usingMetrics).join('+')}) / ${props.availabilityMetricProps.length}`; + break; + case AvailabilityMetricType.REQUEST_COUNT: + expression = `${Object.keys(usingMetrics).join('+')}`; + break; + case AvailabilityMetricType.FAULT_COUNT: + expression = `${Object.keys(usingMetrics).join('+')}`; + break; + case AvailabilityMetricType.FAULT_RATE: + expression = `(${Object.keys(usingMetrics).join('+')}) / ${props.availabilityMetricProps.length}`; + break; + case AvailabilityMetricType.SUCCESS_COUNT: + expression = `${Object.keys(usingMetrics).join('+')}`; + break; + } + let math: IMetric = new MathExpression({ + usingMetrics: usingMetrics, + period: props.period, + label: props.label, + expression: expression, + }); + + operationMetrics.splice(0, 0, math); + + return operationMetrics; + } + + /** + * Creates a zonal availability metric + * @param props + * @returns + */ + static createZonalAvailabilityMetric( + props: ZonalAvailabilityMetricProps, + ): IMetric { + return AvailabilityAndLatencyMetrics.createAvailabilityMetric( + props, + props.metricDetails.metricDimensions.zonalDimensions( + props.availabilityZoneId, + Fn.ref('AWS::Region'), + ), + ); + } +} diff --git a/src/metrics/ZonalLatencyMetrics.ts b/src/metrics/ZonalLatencyMetrics.ts new file mode 100644 index 0000000..fcb11b5 --- /dev/null +++ b/src/metrics/ZonalLatencyMetrics.ts @@ -0,0 +1,99 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { IMetric, MathExpression } from 'aws-cdk-lib/aws-cloudwatch'; +import { AvailabilityAndLatencyMetrics } from './AvailabilityAndLatencyMetrics'; +import { LatencyMetricProps } from './props/LatencyMetricProps'; +import { ServiceLatencyMetricProps } from './props/ServiceLatencyMericProps'; +import { ZonalLatencyMetricProps } from './props/ZonalLatencyMetricProps'; + +export class ZonalLatencyMetrics { + /** + * Creates a zonal latency metric + * @param props + * @returns + */ + static createZonalLatencyMetrics(props: ZonalLatencyMetricProps): IMetric[] { + return AvailabilityAndLatencyMetrics.createLatencyMetrics( + props, + props.metricDetails.metricDimensions.zonalDimensions( + props.availabilityZoneId, + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates an average zonal latency metric + * @param props + * @returns + */ + static createZonalAverageLatencyMetric( + props: ZonalLatencyMetricProps, + ): IMetric { + return AvailabilityAndLatencyMetrics.createAverageLatencyMetric( + props, + props.metricDetails.metricDimensions.zonalDimensions( + props.availabilityZoneId, + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates a count of high latency metric + * @param props + * @returns + */ + static createZonalCountLatencyMetric( + props: ZonalLatencyMetricProps, + ): IMetric { + return AvailabilityAndLatencyMetrics.createLatencyCountMetric( + props, + props.metricDetails.metricDimensions.zonalDimensions( + props.availabilityZoneId, + Fn.ref('AWS::Region'), + ), + ); + } + + /** + * Creates a count of high latency responses for all critical operations + * @param props + * @returns + */ + static createZonalServiceLatencyMetrics( + props: ServiceLatencyMetricProps, + ): IMetric[] { + let usingMetrics: { [key: string]: IMetric } = {}; + let operationMetrics: IMetric[] = []; + let keyPrefix: string = AvailabilityAndLatencyMetrics.nextChar(''); + + props.latencyMetricProps.forEach( + (prop: LatencyMetricProps, index: number) => { + let operationZonalMetric: IMetric = this.createZonalCountLatencyMetric( + prop as ZonalLatencyMetricProps, + ); + + operationMetrics.push(operationZonalMetric); + usingMetrics[`${keyPrefix}${index}`] = operationZonalMetric; + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + }, + ); + + if (Object.keys(usingMetrics).length == 1) { + operationMetrics.push(Object.values(usingMetrics)[0]); + } else { + let math: IMetric = new MathExpression({ + usingMetrics: usingMetrics, + period: props.period, + label: props.label, + expression: Object.keys(usingMetrics).join('+'), + }); + + operationMetrics.splice(0, 0, math); + } + + return operationMetrics; + } +} diff --git a/src/metrics/props/AvailabilityAndLatencyMetricProps.ts b/src/metrics/props/AvailabilityAndLatencyMetricProps.ts new file mode 100644 index 0000000..c8cc169 --- /dev/null +++ b/src/metrics/props/AvailabilityAndLatencyMetricProps.ts @@ -0,0 +1,29 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IOperationMetricDetails } from '../../services/IOperationMetricDetails'; + +/** + * Common availability and latency metric props + */ +export interface AvailabilityAndLatencyMetricProps { + /** + * The metric details to create metrics from + */ + readonly metricDetails: IOperationMetricDetails; + + /** + * The metric label + */ + readonly label?: string; + + /** + * (Optional) A key prefix for the metric name to make it unique + * in alarms and graphs + */ + readonly keyPrefix?: string; + + /** + * The Region for the metrics + */ + readonly region?: string; +} diff --git a/src/metrics/props/AvailabilityMetricProps.ts b/src/metrics/props/AvailabilityMetricProps.ts new file mode 100644 index 0000000..f90211e --- /dev/null +++ b/src/metrics/props/AvailabilityMetricProps.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AvailabilityAndLatencyMetricProps } from './AvailabilityAndLatencyMetricProps'; +import { AvailabilityMetricType } from '../../utilities/AvailabilityMetricType'; + +/** + * Metric properties for availability metrics + */ +export interface AvailabilityMetricProps + extends AvailabilityAndLatencyMetricProps { + /** + * The type of availability metric + */ + readonly metricType: AvailabilityMetricType; +} diff --git a/src/metrics/props/LatencyMetricProps.ts b/src/metrics/props/LatencyMetricProps.ts new file mode 100644 index 0000000..baa2434 --- /dev/null +++ b/src/metrics/props/LatencyMetricProps.ts @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AvailabilityAndLatencyMetricProps } from './AvailabilityAndLatencyMetricProps'; +import { LatencyMetricType } from '../../utilities/LatencyMetricType'; + +/** + * Metric properties for latency metrics + */ +export interface LatencyMetricProps extends AvailabilityAndLatencyMetricProps { + /** + * The type of latency metric + */ + readonly metricType: LatencyMetricType; + + /** + * The latency statistic like p99, tm99, or TC(100:) + */ + readonly statistic: string; +} diff --git a/src/metrics/props/RegionalAvailabilityMetricProps.ts b/src/metrics/props/RegionalAvailabilityMetricProps.ts new file mode 100644 index 0000000..1b07651 --- /dev/null +++ b/src/metrics/props/RegionalAvailabilityMetricProps.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AvailabilityMetricProps } from './AvailabilityMetricProps'; + +/** + * Metric properties for regional availability metrics + */ +export interface RegionalAvailabilityMetricProps + extends AvailabilityMetricProps {} diff --git a/src/metrics/props/RegionalLatencyMetricProps.ts b/src/metrics/props/RegionalLatencyMetricProps.ts new file mode 100644 index 0000000..f1d7c20 --- /dev/null +++ b/src/metrics/props/RegionalLatencyMetricProps.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { LatencyMetricProps } from './LatencyMetricProps'; + +/** + * Metric properties for regional latency metrics + */ +export interface RegionalLatencyMetricProps extends LatencyMetricProps {} diff --git a/src/metrics/props/ServiceAvailabilityMetricProps.ts b/src/metrics/props/ServiceAvailabilityMetricProps.ts new file mode 100644 index 0000000..1d9ef02 --- /dev/null +++ b/src/metrics/props/ServiceAvailabilityMetricProps.ts @@ -0,0 +1,29 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { AvailabilityMetricProps } from './AvailabilityMetricProps'; + +/** + * Availability metric properties for a service + */ +export interface ServiceAvailabilityMetricProps { + /** + * The availability metric props for each operation in this service + */ + readonly availabilityMetricProps: AvailabilityMetricProps[]; + + /** + * The metric label + */ + readonly label: string; + + /** + * The period for the availability metrics + */ + readonly period: Duration; + + /** + * (Optional) A key prefix for the metric id to make it unique in a graph or alarm + */ + readonly keyPrefix?: string; +} diff --git a/src/metrics/props/ServiceLatencyMericProps.ts b/src/metrics/props/ServiceLatencyMericProps.ts new file mode 100644 index 0000000..8b5a680 --- /dev/null +++ b/src/metrics/props/ServiceLatencyMericProps.ts @@ -0,0 +1,29 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { LatencyMetricProps } from './LatencyMetricProps'; + +/** + * Latency metric properties for a service + */ +export interface ServiceLatencyMetricProps { + /** + * The latency metric props for each operation in this service + */ + readonly latencyMetricProps: LatencyMetricProps[]; + + /** + * The metric label + */ + readonly label: string; + + /** + * The period for the availability metrics + */ + readonly period: Duration; + + /** + * (Optional) A key prefix for the metric id to make it unique in a graph or alarm + */ + readonly keyPrefix?: string; +} diff --git a/src/metrics/props/ZonalAvailabilityMetricProps.ts b/src/metrics/props/ZonalAvailabilityMetricProps.ts new file mode 100644 index 0000000..491a9f1 --- /dev/null +++ b/src/metrics/props/ZonalAvailabilityMetricProps.ts @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AvailabilityMetricProps } from './AvailabilityMetricProps'; + +/** + * The Availability Zone availability metric properties + */ +export interface ZonalAvailabilityMetricProps extends AvailabilityMetricProps { + /** + * The Availability Zone Id for the metrics + */ + readonly availabilityZoneId: string; +} diff --git a/src/metrics/props/ZonalLatencyMetricProps.ts b/src/metrics/props/ZonalLatencyMetricProps.ts new file mode 100644 index 0000000..1dddf65 --- /dev/null +++ b/src/metrics/props/ZonalLatencyMetricProps.ts @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { LatencyMetricProps } from './LatencyMetricProps'; + +/** + * The latency metric properties for an Availability Zone + */ +export interface ZonalLatencyMetricProps extends LatencyMetricProps { + /** + * The Availability Zone Id for the metrics + */ + readonly availabilityZoneId: string; +} diff --git a/src/outlier-detection/IOutlierDetectionFunction.ts b/src/outlier-detection/IOutlierDetectionFunction.ts new file mode 100644 index 0000000..d6378af --- /dev/null +++ b/src/outlier-detection/IOutlierDetectionFunction.ts @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; + +export interface IOutlierDetectionFunction { + /** + * The outlier detection function + */ + function: IFunction; + + /** + * The log group where the function logs will be sent + */ + logGroup: ILogGroup; +} diff --git a/src/outlier-detection/OutlierDetectionFunction.ts b/src/outlier-detection/OutlierDetectionFunction.ts new file mode 100644 index 0000000..6676248 --- /dev/null +++ b/src/outlier-detection/OutlierDetectionFunction.ts @@ -0,0 +1,183 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as path from 'path'; +import { Duration, Fn, RemovalPolicy, Tags } from 'aws-cdk-lib'; +import { ISecurityGroup, SecurityGroup } from 'aws-cdk-lib/aws-ec2'; +import { + Effect, + IManagedPolicy, + IRole, + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal, +} from 'aws-cdk-lib/aws-iam'; +import { + Architecture, + Code, + Function, + IFunction, + ILayerVersion, + LayerVersion, + Runtime, + Tracing, +} from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup, LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { Construct } from 'constructs'; +import { IOutlierDetectionFunction } from './IOutlierDetectionFunction'; +import { OutlierDetectionFunctionProps } from './props/OutlierDetectionFunctionProps'; + +export class OutlierDetectionFunction + extends Construct + implements IOutlierDetectionFunction { + /** + * The z-score function + */ + function: IFunction; + + /** + * The log group where the canarty logs will be sent + */ + logGroup: ILogGroup; + + constructor( + scope: Construct, + id: string, + props: OutlierDetectionFunctionProps, + ) { + super(scope, id); + + let xrayManagedPolicy: IManagedPolicy = new ManagedPolicy( + this, + 'xrayManagedPolicy', + { + path: '/outlier-detection/', + statements: [ + new PolicyStatement({ + actions: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + 'xray:GetSamplingRules', + 'xray:GetSamplingTargets', + 'xray:GetSamplingStatisticSummaries', + ], + effect: Effect.ALLOW, + resources: ['*'], + }), + ], + }, + ); + let cwManagedPolicy = new ManagedPolicy(this, 'CWManagedPolicy', { + path: '/outlier-detection/', + statements: [ + new PolicyStatement({ + actions: ['cloudwatch:GetMetricData', 'cloduwatch:PutMetricData'], + effect: Effect.ALLOW, + resources: ['*'], + }), + ], + }); + + let executionRole: IRole = new Role(this, 'executionRole', { + assumedBy: new ServicePrincipal('lambda.amazonaws.com'), + path: '/outlier-detection/', + managedPolicies: [xrayManagedPolicy, cwManagedPolicy], + }); + + let sciPyLayer: ILayerVersion = new LayerVersion(this, 'SciPyLayer', { + code: Code.fromAsset(path.join(__dirname, 'src/scipy-layer.zip')), + compatibleArchitectures: [Architecture.ARM_64], + compatibleRuntimes: [Runtime.PYTHON_3_12], + }); + + let monitoringLayer: ILayerVersion = new LayerVersion( + this, + 'MonitoringLayer', + { + code: Code.fromAsset( + path.join(__dirname, '../monitoring/src/monitoring-layer.zip'), + ), + compatibleArchitectures: [Architecture.ARM_64], + compatibleRuntimes: [Runtime.PYTHON_3_12], + }, + ); + + if (props.vpc !== undefined && props.vpc != null) { + let sg: ISecurityGroup = new SecurityGroup( + this, + 'OutlierDetectionSecurityGroup', + { + description: + 'Allow outlier detection function to communicate with CW', + vpc: props.vpc, + allowAllOutbound: true, + }, + ); + + this.function = new Function(this, 'OutlierDetection', { + runtime: Runtime.PYTHON_3_12, + code: Code.fromAsset(path.join(__dirname, 'src/outlier-detection.zip')), + handler: 'index.handler', + role: executionRole, + architecture: Architecture.ARM_64, + tracing: Tracing.ACTIVE, + timeout: Duration.seconds(5), + memorySize: 512, + layers: [sciPyLayer, monitoringLayer], + environment: { + REGION: Fn.ref('AWS::Region'), + PARTITION: Fn.ref('AWS::Partition'), + }, + vpc: props.vpc, + securityGroups: [sg], + vpcSubnets: props.subnetSelection, + }); + } else { + this.function = new Function(this, 'OutlierDetection', { + runtime: Runtime.PYTHON_3_12, + code: Code.fromAsset(path.join(__dirname, 'src/outlier-detection.zip')), + handler: 'index.handler', + role: executionRole, + architecture: Architecture.ARM_64, + tracing: Tracing.ACTIVE, + timeout: Duration.seconds(5), + memorySize: 512, + layers: [sciPyLayer, monitoringLayer], + environment: { + REGION: Fn.ref('AWS::Region'), + PARTITION: Fn.ref('AWS::Partition'), + }, + }); + } + + Tags.of(this.function).add('cloudwatch:datasource', 'custom', { + includeResourceTypes: ['AWS::Lambda::Function'], + }); + + this.function.addPermission('invokePermission', { + action: 'lambda:InvokeFunction', + principal: new ServicePrincipal( + 'lambda.datasource.cloudwatch.amazonaws.com', + ), + sourceAccount: Fn.ref('AWS::AccountId'), + }); + + this.logGroup = new LogGroup(this, 'logGroup', { + logGroupName: `/aws/lambda/${this.function.functionName}`, + retention: RetentionDays.ONE_WEEK, + removalPolicy: RemovalPolicy.DESTROY, + }); + + new ManagedPolicy(this, 'cwLogsManagedPolicy', { + path: '/outlier-detection/', + statements: [ + new PolicyStatement({ + actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], + effect: Effect.ALLOW, + resources: [this.logGroup.logGroupArn], + }), + ], + roles: [executionRole], + }); + } +} diff --git a/src/outlier-detection/props/OutlierDetectionFunctionProps.ts b/src/outlier-detection/props/OutlierDetectionFunctionProps.ts new file mode 100644 index 0000000..6a5374e --- /dev/null +++ b/src/outlier-detection/props/OutlierDetectionFunctionProps.ts @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IVpc, SubnetSelection } from 'aws-cdk-lib/aws-ec2'; + +export interface OutlierDetectionFunctionProps { + /** + * If you want the function to run in your VPC, provide + * the VPC object + */ + readonly vpc?: IVpc; + + /** + * The subnets to use in the VPC + */ + readonly subnetSelection?: SubnetSelection; +} diff --git a/src/outlier-detection/src/index.py b/src/outlier-detection/src/index.py new file mode 100644 index 0000000..a2e2d34 --- /dev/null +++ b/src/outlier-detection/src/index.py @@ -0,0 +1,383 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import os +import boto3 +import json +import time +import traceback +import sys +import numpy +from numpy import float64 +from scipy.stats import chisquare +from aws_embedded_metrics import metric_scope + +cw_client = boto3.client("cloudwatch", os.environ.get("AWS_REGION", "us-east-1")) + +# +#{ +# "EventType": "GetMetricData", +# "GetMetricDataRequest": { +# "StartTime": 1697060700, +# "EndTime": 1697061600, +# "Period": 300, +# "Arguments": ["serviceregistry_external_http_requests{host_cluster!=\"prod\"}"] +# } +#} +# + +@metric_scope +def handler(event, context, metrics): + start = time.perf_counter() + metrics.set_dimensions( + { + "Operation": "OutlierDetection", + "EventType": event["EventType"], + "Region": os.environ.get("AWS_REGION", "us-east-1") + } + ) + metrics.set_namespace("OutlierDetection") + metrics.set_property("Event", json.loads(json.dumps(event, default = str))) + + event_type = event["EventType"] + + if event_type == "GetMetricData": + try: + result = get_metric_data(event["GetMetricDataRequest"], metrics) + metrics.put_metric("Success", 1, "Count") + + end = time.perf_counter() + metrics.put_metric("SuccessLatency", (end - start) * 1000, "Milliseconds") + return result + except Exception as e: + + end = time.perf_counter() + metrics.put_metric("FaultLatency", (end - start) * 1000, "Milliseconds") + metrics.put_metric("Fault", 1, "Count") + + info = sys.exc_info() + + exc_info = sys.exc_info() + details = ''.join(traceback.format_exception(*exc_info)) + exc_type, exc_value, exc_context = sys.exc_info() + + metrics.set_property("Exception", { + "type": exc_type.__name__, + "description": str(exc_value), + "details": details + }) + + return { + "Error": { + "Code": "Exception", + "Value": str(e) + } + } + elif event_type == "DescribeGetMetricData": + end = time.perf_counter() + metrics.put_metric("SuccessLatency", (end - start) * 1000, "Milliseconds") + metrics.put_metric("Success", 1, "Count") + return { + "Description": "Outlier detection metric calculator" + } + else: + metrics.set_property("Error", "Unknown event type") + return {} + +# +#{ +# "EventType": "GetMetricData", +# "GetMetricDataRequest": { +# "StartTime": 1697060700, +# "EndTime": 1697061600, +# "Period": 300, +# "Arguments": ["serviceregistry_external_http_requests{host_cluster!=\"prod\"}"] +# } +#} +# +def get_metric_data(event, metrics): + start = event["StartTime"] + end = event["EndTime"] + period = event["Period"] + args: list = event["Arguments"] + algorithm: str = str(args[0]) + threshold: float = float(args[1]) + metrics.set_property("Threshold", threshold) + az_id: str= args[2] + metrics.set_property("AZ-ID", az_id) + # + # { + # "use1-az1": [ + # { + # "Operation": "Ride", + # "AZ-ID": "use-az1", + # "Region": "us-east-1" + # } + # ], + # "use1-az2": [ + # ] + # } + # + dimensions_per_az: dict = json.loads(args[3]) + metric_namespace: str = args[4] + metrics.set_property("Namespace", metric_namespace) + metric_names: list = args[5].split(":") + metric_stat: str = args[6] + unit: str = args[7] + metrics.set_property("Algorithm", algorithm) + + metric_query = { + "StartTime": start, + "EndTime": end, + "MetricDataQueries": [], + } + + operation = "" + + for az in dimensions_per_az: + + for dimension_set in dimensions_per_az[az]: + + index = 0 + az_query_keys = [] + + dimensions = [] + + for dim in dimension_set: + dimensions.append({ + "Name": dim, + "Value": dimension_set[dim] + }) + + if dim == "Operation": + operation = dimension_set[dim] + + for metric in metric_names: + query = { + "Id": az.replace("-", "_") + str(index), + "Label": az + ' ' + metric, + "ReturnData": False, + "MetricStat": { + "Metric": { + "Namespace": metric_namespace, + "MetricName": metric, + "Dimensions": dimensions + }, + "Period": 60, + "Stat": metric_stat, + "Unit": unit, + } + } + + az_query_keys.append(az.replace("-", "_") + str(index)) + index += 1 + + metric_query["MetricDataQueries"].append(query) + + metric_query["MetricDataQueries"].append({ + "Id": az.replace("-", "_"), + "Label": az, + "ReturnData": True, + "Expression": "+".join(az_query_keys) + }) + + metrics.set_property("Query", json.loads(json.dumps(metric_query, default = str))) + + if operation != "": + metrics.set_property("ServiceOperation", operation) + + next_token: str = None + + az_counts: dict = {} + + while True: + if next_token is not None: + metric_query["NextToken"] = next_token + + data = cw_client.get_metric_data(**metric_query) + + if next_token is not None: + metrics.set_property("GetMetricResult::" + next_token, json.loads(json.dumps(data, default = str))) + else: + metrics.set_property("GetMetricResult", json.loads(json.dumps(data, default = str))) + + for item in data["MetricDataResults"]: + az_id = item["Id"].replace("_", "-") + + for index, timestamp in enumerate(item["Timestamps"]): + epoch_timestamp = int(timestamp.timestamp()) + if epoch_timestamp not in az_counts: + az_counts[epoch_timestamp] = {az:0 for az in dimensions_per_az} + + # Set the value for this AZ (as identified by key) for the timestamp + az_counts[epoch_timestamp][az_id] = item["Values"][index] + + if "NextToken" in data: + next_token = data["NextToken"] + + if next_token is None: + break + + # now we should have fault counts in each az by timestamp. Next we need to compare + # each AZ at each timestamp to calculate the chi squared result + + # { + # "1716494472" : { + # "use1-az1": 0, + # "use1-az2": 10, + # "use1-az6": 5 + # } + # } + + metrics.set_property("InterimCalculation", json.loads(json.dumps(az_counts, default = str))) + + results = [] + + match algorithm: + case "Z_SCORE": + results = z_score(az_counts = az_counts, az_id = az_id, threshold = threshold, metrics = metrics) + case "IQR": + results = iqr(az_counts = az_counts, az_id = az_id, threshold = threshold, metrics = metrics) + case "MAD": + results = iqr(az_counts = az_counts, az_id = az_id, threshold = threshold, metrics = metrics) + case "CHI_SQUARED" | _: + results = chi_squared(az_counts = az_counts, az_id = az_id, threshold = threshold, metrics = metrics) + + data_results = { + "MetricDataResults": [ + { + "StatusCode": "Complete", + "Label": az_id, + "Timestamps": sorted(az_counts.keys(), reverse = True), + "Values": results + } + ] + } + + return data_results + +# Chi-squared +def chi_squared(az_counts: dict, az_id: str, threshold, metrics): + results = [] + for timestamp_key in sorted(az_counts.keys(), reverse = True): + vals = list(az_counts[timestamp_key].values()) + + if not all(v == 0 for v in vals): + chi_sq_result = chisquare(vals) + + if len(vals) > 0: + expected = sum(vals) / len(vals) + p_value: float64 = chi_sq_result.pvalue + metrics.set_property("PValue_" + str(timestamp_key), str(p_value)) + + for az in az_counts[timestamp_key]: + # set the farthest from the average to initially be the first AZ + farthest_from_expected = az + break + + # compare the other AZs for this timestamp and find the one + # farthest from the average + for az in az_counts[timestamp_key]: + if abs(az_counts[timestamp_key][az] - expected) > abs(az_counts[timestamp_key][farthest_from_expected] - expected): + farthest_from_expected = az + + # if the p-value result is less than the threshold + # and the one that is farthest from is the AZ we are + # concerned with, then there is a statistically significant + # difference and emit a 1 value + if not numpy.isnan(p_value) and p_value <= threshold and az_id == farthest_from_expected: + results.append(1) + else: + results.append(0) + else: + metrics.set_property("PValue_" + str(timestamp_key), "No values for timestamp.") + results.append(0) + + else: + metrics.set_property("PValue_" + str(timestamp_key), "All values are zero.") + results.append(0) + + return results + +# Z-Score +def z_score(az_counts: dict, az_id: str, threshold, metrics): + results = [] + for timestamp_key in sorted(az_counts.keys(), reverse = True): + vals = list(az_counts[timestamp_key].values()) + mean = numpy.mean(vals) + metrics.set_property("Mean_" + str(timestamp_key), mean) + std = numpy.std(vals) + metrics.set_property("StdDev_" + str(timestamp_key), std) + + val = az_counts[timestamp_key][az_id] + z_score = (val - mean) / std + metrics.set_property("ZScore_" + str(timestamp_key), z_score) + + if z_score >= threshold: + results.append(1) + else: + results.append(0) + + return results + +# Interquartile Range Method +def iqr(az_counts: dict, az_id: str, threshold, metrics): + results = [] + for timestamp_key in sorted(az_counts.keys(), reverse = True): + vals = sorted(list(az_counts[timestamp_key].values())) + q1 = numpy.percentile(vals, 25) + metrics.set_property("Q1_" + str(timestamp_key), iqr) + q3 = numpy.percentile(vals, 75) + metrics.set_property("Q3_" + str(timestamp_key), iqr) + iqr = q3 - q1 + metrics.set_property("IQR_" + str(timestamp_key), iqr) + + upper_bound = q3 + (1.5 * iqr) + + metrics.set_property("UPPER_BOUND_" + str(timestamp_key), upper_bound) + + if az_counts[timestamp_key][az_id] > upper_bound: + results.append(1) + else: + results.append(0) + + return results + +# Median Absolute Deviation (MAD) +def mad(az_counts: dict, az_id: str, threshold, metrics): + results = [] + for timestamp_key in sorted(az_counts.keys(), reverse = True): + vals = sorted(list(az_counts[timestamp_key].values())) + median = numpy.median(vals) + + absolute_deviations = [] + for val in vals: + absolute_deviations.append(abs(val - median)) + + mad = numpy.median(absolute_deviations) + + metrics.set_property("MAD_" + str(timestamp_key), mad) + + median + (threshold * mad) + + if az_counts[timestamp_key][az_id] >= median + (threshold * mad): + results.append(1) + else: + results.append(0) + + return results + + +def z_score2(data: list, tested_number, threshold, timestamp_key, metrics): + mean = numpy.mean(data) + #metrics.set_property("Mean_" + str(timestamp_key), mean) + std = numpy.std(data) + #metrics.set_property("StdDev_" + str(timestamp_key), std) + + z_score = (tested_number - mean) / std + #metrics.set_property("ZScore_" + str(timestamp_key), z_score) + + if z_score >= threshold: + return 1 + else: + return 0 + \ No newline at end of file diff --git a/src/services/BasicServiceMultiAZObservability.ts b/src/services/BasicServiceMultiAZObservability.ts new file mode 100644 index 0000000..ff32345 --- /dev/null +++ b/src/services/BasicServiceMultiAZObservability.ts @@ -0,0 +1,810 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Fn } from 'aws-cdk-lib'; +import { + Alarm, + AlarmRule, + ComparisonOperator, + CompositeAlarm, + Dashboard, + IAlarm, + IMetric, + MathExpression, + Metric, + TreatMissingData, + Unit, +} from 'aws-cdk-lib/aws-cloudwatch'; +import { CfnNatGateway } from 'aws-cdk-lib/aws-ec2'; +import { + BaseLoadBalancer, + HttpCodeElb, + HttpCodeTarget, + IApplicationLoadBalancer, + ILoadBalancerV2, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { Construct } from 'constructs'; +import { IBasicServiceMultiAZObservability } from './IBasicServiceMultiAZObservability'; +import { BasicServiceMultiAZObservabilityProps } from './props/BasicServiceMultiAZObservabilityProps'; +import { AvailabilityAndLatencyAlarmsAndRules } from '../alarmsandrules/AvailabilityAndLatencyAlarmsAndRules'; +import { AvailabilityZoneMapper } from '../azmapper/AvailabilityZoneMapper'; +import { IAvailabilityZoneMapper } from '../azmapper/IAvailabilityZoneMapper'; +import { BasicServiceDashboard } from '../dashboards/BasicServiceDashboard'; +import { AvailabilityAndLatencyMetrics } from '../metrics/AvailabilityAndLatencyMetrics'; +import { OutlierDetectionFunction } from '../outlier-detection/OutlierDetectionFunction'; +import { OutlierDetectionAlgorithm } from '../utilities/OutlierDetectionAlgorithm'; +import { StackWithDynamicSource } from '../utilities/StackWithDynamicSource'; + +/** + * Basic observability for a service using metrics from + * ALBs and NAT Gateways + */ +export class BasicServiceMultiAZObservability + extends Construct + implements IBasicServiceMultiAZObservability { + /** + * The NAT Gateways being used in the service, each set of NAT Gateways + * are keyed by their Availability Zone Id + */ + natGateways?: { [key: string]: CfnNatGateway[] }; + + /** + * The application load balancers being used by the service + */ + applicationLoadBalancers?: IApplicationLoadBalancer[]; + + /** + * The name of the service + */ + serviceName: string; + + /** + * The alarms indicating if an AZ is an outlier for NAT GW + * packet loss and has isolated impact + */ + natGWZonalIsolatedImpactAlarms?: { [key: string]: IAlarm }; + + /** + * The alarms indicating if an AZ is an outlier for ALB + * faults and has isolated impact + */ + albZonalIsolatedImpactAlarms?: { [key: string]: IAlarm }; + + /** + * The alarms indicating if an AZ has isolated impact + * from either ALB or NAT GW metrics + */ + aggregateZonalIsolatedImpactAlarms: { [key: string]: IAlarm }; + + /** + * The dashboard that is optionally created + */ + dashboard?: Dashboard; + + /** + * The chi-squared function + */ + private outlierDetectionFunction?: IFunction; + + private azMapper: IAvailabilityZoneMapper; + + private _natGWZonalIsolatedImpactAlarms: { [key: string]: IAlarm }; + + private _albZonalIsolatedImpactAlarms: { [key: string]: IAlarm }; + + private _packetDropsPerZone: { [key: string]: IMetric }; + + private _faultsPerZone: { [key: string]: IMetric }; + + constructor( + scope: Construct, + id: string, + props: BasicServiceMultiAZObservabilityProps, + ) { + super(scope, id); + + // Initialize class properties + this.serviceName = props.serviceName; + this.applicationLoadBalancers = props.applicationLoadBalancers; + this.natGateways = props.natGateways; + this._natGWZonalIsolatedImpactAlarms = {}; + this._albZonalIsolatedImpactAlarms = {}; + this.aggregateZonalIsolatedImpactAlarms = {}; + this._packetDropsPerZone = {}; + this._faultsPerZone = {}; + + let outlierThreshold: number; + + if (!props.outlierThreshold) { + switch (props.outlierDetectionAlgorithm) { + case OutlierDetectionAlgorithm.CHI_SQUARED: + outlierThreshold = 0.05; + break; + case OutlierDetectionAlgorithm.IQR: + outlierThreshold = 1.5; + break; + case OutlierDetectionAlgorithm.MAD: + outlierThreshold = 3; + break; + case OutlierDetectionAlgorithm.STATIC: + outlierThreshold = 0.7; + break; + case OutlierDetectionAlgorithm.Z_SCORE: + outlierThreshold = 2; + break; + } + } else { + outlierThreshold = props.outlierThreshold; + } + + // Create the AZ mapper resource to translate AZ names to ids + this.azMapper = new AvailabilityZoneMapper(this, 'AvailabilityZoneMapper'); + + if (props.outlierDetectionAlgorithm != OutlierDetectionAlgorithm.STATIC) { + let outlierDetectionStack: StackWithDynamicSource = + new StackWithDynamicSource(this, 'OutlierDetectionStack', { + assetsBucketsParameterName: props.assetsBucketParameterName, + assetsBucketPrefixParameterName: + props.assetsBucketPrefixParameterName, + }); + + this.outlierDetectionFunction = new OutlierDetectionFunction( + outlierDetectionStack, + 'OutlierDetectionFunction', + {}, + ).function; + } + + // Create metrics and alarms for just load balancers if they were provided + if ( + this.applicationLoadBalancers !== undefined && + this.applicationLoadBalancers != null + ) { + this.doAlbMetrics(props, outlierThreshold); + } + + // Create NAT Gateway metrics and alarms + if (this.natGateways !== undefined && this.natGateways != null) { + this.doNatGatewayMetrics(props, outlierThreshold); + } + + let counter: number = 1; + // Go through the ALB zonal isolated impact alarms and see if there is a NAT GW + // isolated impact alarm for the same AZ ID, if so, create a composite alarm with both + // otherwise create a composite alarm with just the ALB + Object.keys(this._albZonalIsolatedImpactAlarms).forEach((az: string) => { + let tmp: IAlarm[] = []; + tmp.push(this._albZonalIsolatedImpactAlarms[az]); + if ( + this._natGWZonalIsolatedImpactAlarms[az] !== undefined && + this._natGWZonalIsolatedImpactAlarms[az] != null + ) { + tmp.push(this._natGWZonalIsolatedImpactAlarms[az]); + } + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + az.substring(az.length - 1), + ); + + this.aggregateZonalIsolatedImpactAlarms[az] = new CompositeAlarm( + this, + 'AZ' + counter++ + 'AggregateIsolatedImpactAlarm', + { + compositeAlarmName: availabilityZoneId + '-aggregate-isolated-impact', + alarmRule: AlarmRule.anyOf(...tmp), + actionsEnabled: false, + }, + ); + }); + + // In case there were AZs with only a NAT GW and no ALB, create a composite alarm + // for the NAT GW metrics + Object.keys(this._natGWZonalIsolatedImpactAlarms).forEach((az: string) => { + // If we don't yet have an isolated impact alarm for this AZ, proceed + if ( + this.aggregateZonalIsolatedImpactAlarms[az] === undefined || + this.aggregateZonalIsolatedImpactAlarms[az] == null + ) { + let tmp: IAlarm[] = []; + tmp.push(this._natGWZonalIsolatedImpactAlarms[az]); + + if ( + this._albZonalIsolatedImpactAlarms[az] !== undefined && + this.albZonalIsolatedImpactAlarms != null + ) { + tmp.push(this.albZonalIsolatedImpactAlarms[az]); + } + + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter( + az.substring(az.length - 1), + ); + + this.aggregateZonalIsolatedImpactAlarms[az] = new CompositeAlarm( + this, + 'AZ' + counter++ + 'AggregateIsolatedImpactAlarm', + { + compositeAlarmName: + availabilityZoneId + '-aggregate-isolated-impact', + alarmRule: AlarmRule.anyOf(...tmp), + actionsEnabled: false, + }, + ); + } + }); + + this.albZonalIsolatedImpactAlarms = this._albZonalIsolatedImpactAlarms; + this.natGWZonalIsolatedImpactAlarms = this._natGWZonalIsolatedImpactAlarms; + + // Should we create the dashboard + if (props.createDashboard == true) { + this.dashboard = new BasicServiceDashboard( + this, + 'BasicServiceDashboard', + { + serviceName: props.serviceName.toLowerCase(), + zonalAggregateIsolatedImpactAlarms: + this.aggregateZonalIsolatedImpactAlarms, + zonalLoadBalancerIsolatedImpactAlarms: + this.albZonalIsolatedImpactAlarms, + zonalNatGatewayIsolatedImpactAlarms: + this.natGWZonalIsolatedImpactAlarms, + interval: props.interval, + zonalLoadBalancerFaultRateMetrics: this._faultsPerZone, + zonalNatGatewayPacketDropMetrics: this._packetDropsPerZone, + azMapper: this.azMapper, + }, + ).dashboard; + } + } + + private doAlbMetrics( + props: BasicServiceMultiAZObservabilityProps, + outlierThreshold: number, + ) { + // Collect total fault count metrics per AZ + let albZoneFaultCountMetrics: { [key: string]: IMetric[] } = {}; + + // Create fault rate alarms per AZ indicating at least 1 ALB + // in the AZ saw a fault rate that exceeded the threshold + let faultRatePercentageAlarms: { [key: string]: IAlarm[] } = {}; + + let keyPrefix: string = AvailabilityAndLatencyMetrics.nextChar(''); + + // Iterate each ALB + this.applicationLoadBalancers!.forEach((alb) => { + // Iterate each AZ in the VPC + alb.vpc?.availabilityZones.forEach((az, index) => { + let azLetter = az.substring(az.length - 1); + + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + faultRatePercentageAlarms[azLetter] = []; + + // 5xx responses from targets + let target5xx: IMetric = alb.metrics.httpCodeTarget( + HttpCodeTarget.TARGET_5XX_COUNT, + { + dimensionsMap: { + AvailabilityZone: az, + LoadBalancer: (alb as ILoadBalancerV2 as BaseLoadBalancer) + .loadBalancerFullName, + }, + label: availabilityZoneId, + period: props.period, + }, + ); + + // 5xx responses from ELB + let elb5xx: IMetric = alb.metrics.httpCodeElb( + HttpCodeElb.ELB_5XX_COUNT, + { + dimensionsMap: { + AvailabilityZone: az, + LoadBalancer: (alb as ILoadBalancerV2 as BaseLoadBalancer) + .loadBalancerFullName, + }, + label: availabilityZoneId, + period: props.period, + }, + ); + + // 2xx responses from targets + let target2xx: IMetric = alb.metrics.httpCodeTarget( + HttpCodeTarget.TARGET_2XX_COUNT, + { + dimensionsMap: { + AvailabilityZone: az, + LoadBalancer: (alb as ILoadBalancerV2 as BaseLoadBalancer) + .loadBalancerFullName, + }, + label: availabilityZoneId, + period: props.period, + }, + ); + + // 3xx responses from targets + let target3xx: IMetric = alb.metrics.httpCodeTarget( + HttpCodeTarget.TARGET_3XX_COUNT, + { + dimensionsMap: { + AvailabilityZone: az, + LoadBalancer: (alb as ILoadBalancerV2 as BaseLoadBalancer) + .loadBalancerFullName, + }, + label: availabilityZoneId, + period: props.period, + }, + ); + + // 3xx responess from ELB + let elb3xx: IMetric = alb.metrics.httpCodeElb( + HttpCodeElb.ELB_3XX_COUNT, + { + dimensionsMap: { + AvailabilityZone: az, + LoadBalancer: (alb as ILoadBalancerV2 as BaseLoadBalancer) + .loadBalancerFullName, + }, + label: availabilityZoneId, + period: props.period, + }, + ); + + // Create metrics for total fault count from this ALB + let usingMetrics: { [key: string]: IMetric } = {}; + usingMetrics[`${keyPrefix}1`] = target5xx; + usingMetrics[`${keyPrefix}2`] = elb5xx; + + if ( + albZoneFaultCountMetrics[azLetter] === undefined || + albZoneFaultCountMetrics[azLetter] == null + ) { + albZoneFaultCountMetrics[azLetter] = []; + } + + albZoneFaultCountMetrics[azLetter].push( + new MathExpression({ + expression: `(${keyPrefix}1 + ${keyPrefix}2)`, + usingMetrics: usingMetrics, + label: + availabilityZoneId + ' ' + alb.loadBalancerArn + ' fault count', + period: props.period, + }), + ); + + // Create metrics to calculate fault rate for this ALB + usingMetrics = {}; + usingMetrics[`${keyPrefix}1`] = target2xx; + usingMetrics[`${keyPrefix}2`] = target3xx; + usingMetrics[`${keyPrefix}3`] = elb3xx; + usingMetrics[`${keyPrefix}4`] = target5xx; + usingMetrics[`${keyPrefix}5`] = elb5xx; + + // The ALB fault rate + let faultRate: IMetric = new MathExpression({ + expression: `((${keyPrefix}4+${keyPrefix}5)/(${keyPrefix}1+${keyPrefix}2+${keyPrefix}3+${keyPrefix}4+${keyPrefix}5)) * 100`, + usingMetrics: usingMetrics, + label: availabilityZoneId + ' ' + alb.loadBalancerArn + ' fault rate', + period: props.period, + }); + + let threshold: number = props.faultCountPercentageThreshold ?? 5; + + // Create a fault rate alarm for the ALB + let faultRateAlarm: IAlarm = new Alarm( + this, + 'AZ' + index + keyPrefix + 'FaultRatePercentageAlarm', + { + alarmName: + availabilityZoneId + '-' + alb.loadBalancerArn + '-fault-rate', + actionsEnabled: false, + metric: faultRate, + evaluationPeriods: props.evaluationPeriods, + datapointsToAlarm: props.datapointsToAlarm, + threshold: threshold, + comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, + }, + ); + + // Add this ALB's fault rate alarm + faultRatePercentageAlarms[azLetter].push(faultRateAlarm); + + // Get next unique key + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + }); + }); + + // Iterate AZs for the ALB fault count metrics + Object.keys(albZoneFaultCountMetrics).forEach((azLetter) => { + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + + let counter: number = 1; + let usingMetrics: { [key: string]: IMetric } = {}; + + // Add each ALB's fault count metrics to the dictionary + albZoneFaultCountMetrics[azLetter].forEach((metric) => { + usingMetrics[`${keyPrefix}${counter++}`] = metric; + }); + + // Sum the total faults for the availability zone across all ALBs + let totalFaultsPerZone: IMetric = new MathExpression({ + expression: Object.keys(usingMetrics).join('+'), + usingMetrics: usingMetrics, + label: availabilityZoneId + ' fault count', + period: props.period, + }); + + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + counter = 1; + + // Assign the total faults per zone to the dictionary + this._faultsPerZone[azLetter] = totalFaultsPerZone; + }); + + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + + let tmp: { [key: string]: IMetric } = {}; + Object.keys(this._faultsPerZone).forEach((azLetter, index) => { + tmp[`${keyPrefix}${index}`] = this._faultsPerZone[azLetter]; + }); + + // Calculate the total faults in the region by adding all AZs together + let totalFaults: IMetric = new MathExpression({ + expression: Object.keys(tmp).join('+'), + usingMetrics: tmp, + label: Fn.sub('${AWS::Region} fault count'), + period: props.period, + }); + + if (props.outlierDetectionAlgorithm == OutlierDetectionAlgorithm.STATIC) { + // Finally, iterate back through each AZ + Object.keys(this._faultsPerZone).forEach((azLetter, index) => { + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + // Determine if AZ is an outlier for faults by exceeding + // a static threshold + let outlierMetrics: IMetric; + + // These metrics will give the percent of faults for the AZ + let usingMetrics: { [key: string]: IMetric } = {}; + usingMetrics[`${keyPrefix}1`] = this._faultsPerZone[azLetter]; + usingMetrics[`${keyPrefix}2`] = totalFaults; + outlierMetrics = new MathExpression({ + expression: `${keyPrefix}1 / ${keyPrefix}2`, + usingMetrics: usingMetrics, + }); + + let azIsOutlierForFaults: IAlarm = new Alarm( + this, + 'AZ' + index + 'FaultCountOutlierAlarm', + { + alarmName: availabilityZoneId + '-fault-count-outlier', + metric: outlierMetrics, + threshold: outlierThreshold, + evaluationPeriods: props.evaluationPeriods, + datapointsToAlarm: props.datapointsToAlarm, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + comparisonOperator: + ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + }, + ); + + // Create isolated AZ impact alarms by determining + // if the AZ is an outlier for fault count and at least + // one ALB exceeds the fault rate threshold provided + this._albZonalIsolatedImpactAlarms[azLetter] = new CompositeAlarm( + this, + 'AZ' + index + 'IsolatedFaultCountImpact', + { + compositeAlarmName: + availabilityZoneId + '-isolated-fault-count-impact', + alarmRule: AlarmRule.allOf( + azIsOutlierForFaults, + AlarmRule.anyOf(...faultRatePercentageAlarms[azLetter]), + ), + }, + ); + }); + } else { + let allAZs: string[] = Array.from( + new Set( + this.applicationLoadBalancers!.flatMap((x) => { + return x.vpc!.availabilityZones; + }), + ), + ); + + allAZs.forEach((az: string, index: number) => { + let azLetter: string = az.substring(az.length - 1); + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + let azIsOutlierForFaults: IAlarm = + AvailabilityAndLatencyAlarmsAndRules.createZonalFaultRateOutlierAlarmForAlb( + this, + props.applicationLoadBalancers!, + availabilityZoneId, + outlierThreshold, + this.outlierDetectionFunction!, + props.outlierDetectionAlgorithm, + this.azMapper, + index, + props.evaluationPeriods, + props.datapointsToAlarm, + '', + ); + + // In addition to being an outlier for fault count, make sure + // the fault count is substantial enough to trigger the alarm + // by making sure at least 1 ALB sees packet loss that exceeds the threshold + let azIsOutlierAndSeesImpact: IAlarm = new CompositeAlarm( + this, + 'AZ' + index + 'ALBIsolatedImpact', + { + compositeAlarmName: + availabilityZoneId + '-isolated-fault-count-impact', + alarmRule: AlarmRule.allOf( + azIsOutlierForFaults, + AlarmRule.anyOf(...faultRatePercentageAlarms[azLetter]), + ), + }, + ); + + // Record these so they can be used in dashboard or for combination + // with AZ + this._albZonalIsolatedImpactAlarms[azLetter] = azIsOutlierAndSeesImpact; + }); + } + } + + private doNatGatewayMetrics( + props: BasicServiceMultiAZObservabilityProps, + outlierThreshold: number, + ) { + let keyPrefix: string = AvailabilityAndLatencyMetrics.nextChar(''); + + // Collect alarms for packet drops exceeding a threshold per NAT GW + let packetDropPercentageAlarms: { [key: string]: IAlarm[] } = {}; + + // For each AZ, create metrics for each NAT GW + Object.entries(this.natGateways!).forEach((entry, index) => { + // The number of packet drops for each NAT GW in the AZ + let packetDropMetricsForAZ: { [key: string]: IMetric } = {}; + + let az: string = entry[0]; + let azLetter: string = az.substring(az.length - 1); + let availabilityZoneId = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + packetDropPercentageAlarms[azLetter] = []; + + // Iterate through each NAT GW in the current AZ + entry[1].forEach((natgw) => { + // Calculate packet drops + let packetDropCount: IMetric = new Metric({ + metricName: 'PacketsDropCount', + namespace: 'AWS/NATGateway', + statistic: 'Sum', + unit: Unit.COUNT, + label: availabilityZoneId + ' packet drops', + dimensionsMap: { + NatGatewayId: natgw.attrNatGatewayId, + }, + period: props.period, + }); + + // Calculate packets in from source + let packetsInFromSourceCount: IMetric = new Metric({ + metricName: 'PacketsInFromSource', + namespace: 'AWS/NATGateway', + statistic: 'Sum', + unit: Unit.COUNT, + label: availabilityZoneId + ' packets in from source', + dimensionsMap: { + NatGatewayId: natgw.attrNatGatewayId, + }, + period: props.period, + }); + + // Calculate packets in from destination + let packetsInFromDestinationCount: IMetric = new Metric({ + metricName: 'PacketsInFromDestination', + namespace: 'AWS/NATGateway', + statistic: 'Sum', + unit: Unit.COUNT, + label: availabilityZoneId + ' packets in from destination', + dimensionsMap: { + NatGatewayId: natgw.attrNatGatewayId, + }, + period: props.period, + }); + + let usingMetrics: { [key: string]: IMetric } = {}; + usingMetrics[`${keyPrefix}1`] = packetDropCount; + usingMetrics[`${keyPrefix}2`] = packetsInFromSourceCount; + usingMetrics[`${keyPrefix}3`] = packetsInFromDestinationCount; + + // Calculate a percentage of dropped packets for the NAT GW + let packetDropPercentage: IMetric = new MathExpression({ + expression: `(${keyPrefix}1 / (${keyPrefix}2 + ${keyPrefix}3)) * 100`, + usingMetrics: usingMetrics, + label: availabilityZoneId + ' packet drop percentage', + period: props.period, + }); + + let threshold: number = + props.packetLossImpactPercentageThreshold ?? 0.01; + + // Create an alarm for this NAT GW if packet drops exceed the specified threshold + let packetDropImpactAlarm: IAlarm = new Alarm( + this, + 'AZ' + (index + 1) + 'PacketDropImpactAlarm', + { + alarmName: + availabilityZoneId + + '-' + + natgw.attrNatGatewayId + + '-packet-drop-impact', + actionsEnabled: false, + metric: packetDropPercentage, + threshold: threshold, + comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, + evaluationPeriods: props.evaluationPeriods, + datapointsToAlarm: props.datapointsToAlarm, + }, + ); + + // Collect all of the packet drop impact alarms for each + // NAT GW in this AZ, need to know at least 1 sees substantial + // enough impact to consider the AZ as impaired + packetDropPercentageAlarms[azLetter].push(packetDropImpactAlarm); + + // Collect the packet drop metrics for this AZ so we can + // add them all together and count total packet drops + // for all NAT GWs in the AZ + packetDropMetricsForAZ[`m${index}`] = packetDropCount; + }); + + // Create a metric that adds up all packets drops from each + // NAT GW in the AZ + let packetDropsInThisAZ: IMetric = new MathExpression({ + expression: Object.keys(packetDropMetricsForAZ).join('+'), + usingMetrics: packetDropMetricsForAZ, + label: availabilityZoneId + ' dropped packets', + period: props.period, + }); + + // Record these so we can add them up + // and get a total amount of packet drops + // in the region across all AZs + this._packetDropsPerZone[azLetter] = packetDropsInThisAZ; + }); + + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + + let tmp: { [key: string]: IMetric } = {}; + Object.keys(this._packetDropsPerZone).forEach((azLetter, index) => { + tmp[`${keyPrefix}${index}`] = this._packetDropsPerZone[azLetter]; + }); + + // Calculate total packet drops for the region + let totalPacketDrops: IMetric = new MathExpression({ + expression: Object.keys(tmp).join('+'), + usingMetrics: tmp, + label: Fn.ref('AWS::Region') + ' dropped packets', + period: props.period, + }); + + if (props.outlierDetectionAlgorithm == OutlierDetectionAlgorithm.STATIC) { + // Create outlier detection alarms by comparing packet + // drops in one AZ versus total packet drops in the region + Object.keys(this._packetDropsPerZone).forEach((azLetter, index) => { + let azIsOutlierForPacketDrops: IAlarm; + keyPrefix = AvailabilityAndLatencyMetrics.nextChar(keyPrefix); + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + // Determine if AZ is an outlier for faults by exceeding + // a static threshold + let outlierMetrics: IMetric; + + // These metrics will give the percent of faults for the AZ + let usingMetrics: { [key: string]: IMetric } = {}; + usingMetrics[`${keyPrefix}1`] = this._packetDropsPerZone[azLetter]; + usingMetrics[`${keyPrefix}2`] = totalPacketDrops; + + outlierMetrics = new MathExpression({ + expression: `(${keyPrefix}1 / ${keyPrefix}2) * 100`, + usingMetrics: usingMetrics, + label: availabilityZoneId + ' percentage of dropped packets', + }); + + azIsOutlierForPacketDrops = new Alarm( + this, + 'AZ' + (index + 1) + 'NATGWDroppedPacketsOutlierAlarm', + { + metric: outlierMetrics, + alarmName: availabilityZoneId + '-dropped-packets-outlier', + evaluationPeriods: props.evaluationPeriods, + datapointsToAlarm: props.datapointsToAlarm, + threshold: outlierThreshold, + actionsEnabled: false, + treatMissingData: TreatMissingData.IGNORE, + comparisonOperator: + ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + }, + ); + + // In addition to being an outlier for packet drops, make sure + // the packet loss is substantial enough to trigger the alarm + // by making sure at least 1 NAT GW sees packet loss more than 0.01% + let azIsOutlierAndSeesImpact: IAlarm = new CompositeAlarm( + this, + 'AZ' + index + 'NATGWIsolatedImpact', + { + compositeAlarmName: availabilityZoneId + '-isolated-natgw-impact', + alarmRule: AlarmRule.allOf( + azIsOutlierForPacketDrops, + AlarmRule.anyOf(...packetDropPercentageAlarms[azLetter]), + ), + }, + ); + + // Record these so they can be used in dashboard or for combination + // with AZ + this._natGWZonalIsolatedImpactAlarms[azLetter] = + azIsOutlierAndSeesImpact; + }); + } else { + Object.keys(props.natGateways!).forEach((az: string, index: number) => { + let azLetter: string = az.substring(az.length - 1); + let availabilityZoneId: string = + this.azMapper.availabilityZoneIdFromAvailabilityZoneLetter(azLetter); + + // Iterate all NAT GWs in this AZ + + let azIsOutlierForPacketDrops: IAlarm = + AvailabilityAndLatencyAlarmsAndRules.createZonalFaultRateOutlierAlarmForNatGW( + this, + this.natGateways!, + availabilityZoneId, + outlierThreshold, + this.outlierDetectionFunction!, + props.outlierDetectionAlgorithm, + this.azMapper, + index, + props.evaluationPeriods, + props.datapointsToAlarm, + '', + ); + + // In addition to being an outlier for packet drops, make sure + // the packet loss is substantial enough to trigger the alarm + // by making sure at least 1 NAT GW sees packet loss more than 0.01% + let azIsOutlierAndSeesImpact: IAlarm = new CompositeAlarm( + this, + 'AZ' + index + 'NATGWIsolatedImpact', + { + compositeAlarmName: availabilityZoneId + '-isolated-natgw-impact', + alarmRule: AlarmRule.allOf( + azIsOutlierForPacketDrops, + AlarmRule.anyOf(...packetDropPercentageAlarms[azLetter]), + ), + }, + ); + + // Record these so they can be used in dashboard or for combination + // with AZ + this._natGWZonalIsolatedImpactAlarms[azLetter] = + azIsOutlierAndSeesImpact; + }); + } + } +} diff --git a/src/services/CanaryMetrics.ts b/src/services/CanaryMetrics.ts new file mode 100644 index 0000000..70eb3a0 --- /dev/null +++ b/src/services/CanaryMetrics.ts @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ICanaryMetrics } from './ICanaryMetrics'; +import { IOperationMetricDetails } from './IOperationMetricDetails'; +import { CanaryMetricProps } from './props/CanaryMetricProps'; + +/** + * Represents metrics for a canary testing a service + */ +export class CanaryMetrics implements ICanaryMetrics { + /** + * The canary availability metric details + */ + readonly canaryAvailabilityMetricDetails: IOperationMetricDetails; + + /** + * The canary latency metric details + */ + readonly canaryLatencyMetricDetails: IOperationMetricDetails; + + constructor(props: CanaryMetricProps) { + this.canaryAvailabilityMetricDetails = + props.canaryAvailabilityMetricDetails; + this.canaryLatencyMetricDetails = props.canaryLatencyMetricDetails; + } +} diff --git a/src/services/CanaryTestMetricsOverride.ts b/src/services/CanaryTestMetricsOverride.ts new file mode 100644 index 0000000..64a9308 --- /dev/null +++ b/src/services/CanaryTestMetricsOverride.ts @@ -0,0 +1,55 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { ICanaryTestMetricsOverride } from './ICanaryTestMetricsOverride'; +import { CanaryTestMetricsOverrideProps } from './props/CanaryTestMetricsOverrideProps'; + +/** + * Provides overrides for the default metric settings + * used for the automatically created canary tests + */ +export class CanaryTestMetricsOverride implements ICanaryTestMetricsOverride { + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic?: string; + + /** + * The period for the metrics + */ + readonly period?: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods?: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm?: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold?: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold?: number; + + constructor(props: CanaryTestMetricsOverrideProps) { + this.alarmStatistic = props.alarmStatistic; + this.period = props.period; + this.evaluationPeriods = props.evaluationPeriods; + this.datapointsToAlarm = props.datapointsToAlarm; + this.successAlarmThreshold = props.successAlarmThreshold; + this.faultAlarmThreshold = props.faultAlarmThreshold; + } +} diff --git a/src/services/ContributorInsightRuleDetails.ts b/src/services/ContributorInsightRuleDetails.ts new file mode 100644 index 0000000..d733851 --- /dev/null +++ b/src/services/ContributorInsightRuleDetails.ts @@ -0,0 +1,59 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; +import { IContributorInsightRuleDetails } from './IContributorInsightRuleDetails'; +import { ContributorInsightRuleDetailsProps } from './props/ContributorInsightRuleDetailsProps'; + +/** + * The contributor insight rule details for creating an + * insight rule + */ +export class ContributorInsightRuleDetails +implements IContributorInsightRuleDetails { + /** + * The log groups where CloudWatch logs for the operation are located. If + * this is not provided, Contributor Insight rules cannot be created. + */ + readonly logGroups: ILogGroup[]; + + /** + * The path in the log files to the field that indicates the latency + * for the response. This could either be success latency or fault + * latency depending on the alarms and rules you are creating. + */ + readonly successLatencyMetricJsonPath: string; + + /** + * The path in the log files to the field that identifies the operation + * the log file is for. + */ + readonly operationNameJsonPath: string; + + /** + * The path in the log files to the field that identifies if the response + * resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault + */ + readonly faultMetricJsonPath: string; + + /** + * The path in the log files to the field that identifies the Availability Zone + * Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would + * have a path of $.AZ-ID + */ + readonly availabilityZoneIdJsonPath: string; + + /** + * The JSON path to the instance id field in the log files, only required for server-side + * rules + */ + readonly instanceIdJsonPath: string; + + constructor(props: ContributorInsightRuleDetailsProps) { + this.availabilityZoneIdJsonPath = props.availabilityZoneIdJsonPath; + this.faultMetricJsonPath = props.faultMetricJsonPath; + this.instanceIdJsonPath = props.instanceIdJsonPath; + this.logGroups = props.logGroups; + this.operationNameJsonPath = props.operationNameJsonPath; + this.successLatencyMetricJsonPath = props.successLatencyMetricJsonPath; + } +} diff --git a/src/services/IBasicServiceMultiAZObservability.ts b/src/services/IBasicServiceMultiAZObservability.ts new file mode 100644 index 0000000..5d1c784 --- /dev/null +++ b/src/services/IBasicServiceMultiAZObservability.ts @@ -0,0 +1,45 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { CfnNatGateway } from 'aws-cdk-lib/aws-ec2'; +import { IApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IConstruct } from 'constructs'; + +/** + * Properties of a basic service + */ +export interface IBasicServiceMultiAZObservability extends IConstruct { + /** + * The NAT Gateways being used in the service, each set of NAT Gateways + * are keyed by their Availability Zone Id + */ + natGateways?: { [key: string]: CfnNatGateway[] }; + + /** + * The application load balancers being used by the service + */ + applicationLoadBalancers?: IApplicationLoadBalancer[]; + + /** + * The name of the service + */ + serviceName: string; + + /** + * The alarms indicating if an AZ is an outlier for NAT GW + * packet loss and has isolated impact + */ + natGWZonalIsolatedImpactAlarms?: { [key: string]: IAlarm }; + + /** + * The alarms indicating if an AZ is an outlier for ALB + * faults and has isolated impact + */ + albZonalIsolatedImpactAlarms?: { [key: string]: IAlarm }; + + /** + * The alarms indicating if an AZ has isolated impact + * from either ALB or NAT GW metrics + */ + aggregateZonalIsolatedImpactAlarms: { [key: string]: IAlarm }; +} diff --git a/src/services/ICanaryMetrics.ts b/src/services/ICanaryMetrics.ts new file mode 100644 index 0000000..0dd1d8e --- /dev/null +++ b/src/services/ICanaryMetrics.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IOperationMetricDetails } from './IOperationMetricDetails'; + +/** + * The metric definitions for metric produced by the canary + */ +export interface ICanaryMetrics { + /** + * The canary availability metric details + */ + readonly canaryAvailabilityMetricDetails: IOperationMetricDetails; + + /** + * The canary latency metric details + */ + readonly canaryLatencyMetricDetails: IOperationMetricDetails; +} diff --git a/src/services/ICanaryTestMetricsOverride.ts b/src/services/ICanaryTestMetricsOverride.ts new file mode 100644 index 0000000..0ee5cc2 --- /dev/null +++ b/src/services/ICanaryTestMetricsOverride.ts @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; + +/** + * Provides overrides for the default metric settings + * used for the automatically created canary tests + */ +export interface ICanaryTestMetricsOverride { + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic?: string; + + /** + * The period for the metrics + */ + readonly period?: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods?: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm?: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold?: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold?: number; +} diff --git a/src/services/IContributorInsightRuleDetails.ts b/src/services/IContributorInsightRuleDetails.ts new file mode 100644 index 0000000..8bdc6f3 --- /dev/null +++ b/src/services/IContributorInsightRuleDetails.ts @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; + +/** + * Details for setting up Contributor Insight rules + */ +export interface IContributorInsightRuleDetails { + /** + * The log groups where CloudWatch logs for the operation are located. If + * this is not provided, Contributor Insight rules cannot be created. + */ + readonly logGroups: ILogGroup[]; + + /** + * The path in the log files to the field that indicates the latency + * for the response. This could either be success latency or fault + * latency depending on the alarms and rules you are creating. + */ + readonly successLatencyMetricJsonPath: string; + + /** + * The path in the log files to the field that identifies the operation + * the log file is for. + */ + readonly operationNameJsonPath: string; + + /** + * The path in the log files to the field that identifies if the response + * resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault + */ + readonly faultMetricJsonPath: string; + + /** + * The path in the log files to the field that identifies the Availability Zone + * Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would + * have a path of $.AZ-ID + */ + readonly availabilityZoneIdJsonPath: string; + + /** + * The JSON path to the instance id field in the log files, only required for server-side + * rules + */ + readonly instanceIdJsonPath: string; +} diff --git a/src/services/IInstrumentedServiceMultiAZObservability.ts b/src/services/IInstrumentedServiceMultiAZObservability.ts new file mode 100644 index 0000000..e595dda --- /dev/null +++ b/src/services/IInstrumentedServiceMultiAZObservability.ts @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Dashboard, IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; +import { IConstruct } from 'constructs'; +import { IServiceAlarmsAndRules } from '../alarmsandrules/IServiceAlarmsAndRules'; + +/** + * Observability for an instrumented service + */ +export interface IInstrumentedServiceMultiAZObservability extends IConstruct { + /** + * The alarms and rules for the overall service + */ + readonly serviceAlarms: IServiceAlarmsAndRules; + + /** + * Index into the dictionary by operation name, then by Availability Zone Id + * to get the alarms that indicate an AZ shows isolated impact from availability + * or latency as seen by either the server-side or canary. These are the alarms + * you would want to use to trigger automation to evacuate an AZ. + */ + readonly perOperationZonalImpactAlarms: { + [key: string]: { [key: string]: IAlarm }; + }; + + /** + * The dashboards for each operation + */ + readonly operationDashboards?: Dashboard[]; + + /** + * The service level dashboard + */ + readonly serviceDashboard?: Dashboard; + + /** + * If the service is configured to have canary tests created, this will + * be the log group where the canary's logs are stored. + * + * @default - No log group is created if the canary is not requested. + */ + readonly canaryLogGroup?: ILogGroup; +} diff --git a/src/services/IOperation.ts b/src/services/IOperation.ts new file mode 100644 index 0000000..689601c --- /dev/null +++ b/src/services/IOperation.ts @@ -0,0 +1,90 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ICanaryMetrics } from './ICanaryMetrics'; +import { ICanaryTestMetricsOverride } from './ICanaryTestMetricsOverride'; +import { IContributorInsightRuleDetails } from './IContributorInsightRuleDetails'; +import { IOperationMetricDetails } from './IOperationMetricDetails'; +import { IService } from './IService'; +import { AddCanaryTestProps } from '../canaries/props/AddCanaryTestProps'; + +/** + * Represents an operation in a service + */ +export interface IOperation { + /** + * The service the operation is associated with + */ + readonly service: IService; + + /** + * The name of the operation + */ + readonly operationName: string; + + /** + * The HTTP path for the operation for canaries + * to run against, something like "/products/list" + */ + readonly path: string; + + /** + * The server side availability metric details + */ + readonly serverSideAvailabilityMetricDetails: IOperationMetricDetails; + + /** + * The server side latency metric details + */ + readonly serverSideLatencyMetricDetails: IOperationMetricDetails; + + /** + * Optional metric details if the service has an existing canary. + */ + readonly canaryMetricDetails?: ICanaryMetrics; + + /** + * The override values for automatically created canary tests so you can + * use values other than the service defaults to define the thresholds for + * availability. + */ + readonly canaryTestAvailabilityMetricsOverride?: ICanaryTestMetricsOverride; + + /** + * The override values for automatically created canary tests so you can + * use values other than the service defaults to define the thresholds for + * latency. + */ + readonly canaryTestLatencyMetricsOverride?: ICanaryTestMetricsOverride; + + /** + * The server side details for contributor insights rules + */ + readonly serverSideContributorInsightRuleDetails?: IContributorInsightRuleDetails; + + /** + * Indicates this is a critical operation for the service + * and will be included in service level metrics and + * dashboards + */ + readonly critical: boolean; + + /** + * The http methods supported by the operation + */ + readonly httpMethods: string[]; + + /** + * If they have been added, the properties for + * creating new canary tests on this operation + */ + readonly canaryTestProps?: AddCanaryTestProps; + + /** + * Set to true if you have defined CanaryTestProps for your + * service, which applies to all operations, but you want to + * opt out of creating the canary test for this operation. + * + * @default - The operation is not opted out + */ + readonly optOutOfServiceCreatedCanary?: boolean; +} diff --git a/src/services/IOperationMetricDetails.ts b/src/services/IOperationMetricDetails.ts new file mode 100644 index 0000000..23f8eb9 --- /dev/null +++ b/src/services/IOperationMetricDetails.ts @@ -0,0 +1,94 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { MetricDimensions } from './props/MetricDimensions'; + +/** + * Details for operation metrics in one perspective, such as server side latency + */ +export interface IOperationMetricDetails { + /** + * The operation these metric details are for + */ + readonly operationName: string; + + /** + * The CloudWatch metric namespace for these metrics + */ + readonly metricNamespace: string; + + /** + * The names of success indicating metrics + */ + readonly successMetricNames: string[]; + + /** + * The names of fault indicating metrics + */ + readonly faultMetricNames: string[]; + + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic: string; + + /** + * The statistics for successes you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedSuccessStatistics?: string[]; + + /** + * The statistics for faults you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedFaultStatistics?: string[]; + + /** + * The unit used for these metrics + */ + readonly unit: Unit; + + /** + * The period for the metrics + */ + readonly period: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold: number; + + /** + * The metric dimensions for this operation, must be implemented + * as a concrete class by the user + */ + readonly metricDimensions: MetricDimensions; +} diff --git a/src/services/IService.ts b/src/services/IService.ts new file mode 100644 index 0000000..cd74502 --- /dev/null +++ b/src/services/IService.ts @@ -0,0 +1,90 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IContributorInsightRuleDetails } from './IContributorInsightRuleDetails'; +import { IOperation } from './IOperation'; +import { IServiceMetricDetails } from './IServiceMetricDetails'; +import { AddCanaryTestProps } from '../canaries/props/AddCanaryTestProps'; + +/** + * Represents a complete service composed of one or more operations + */ +export interface IService { + /** + * The name of your service + */ + readonly serviceName: string; + + /** + * The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. + */ + readonly baseUrl: string; + + /** + * The fault count threshold that indicates the service is unhealthy. This is an absolute value of faults + * being produced by all critical operations in aggregate. + */ + readonly faultCountThreshold: number; + + /** + * A list of the Availability Zone names used by this application + */ + readonly availabilityZoneNames: string[]; + + /** + * The period for which metrics for the service should be aggregated + */ + readonly period: Duration; + + /** + * The operations that are part of this service + */ + readonly operations: IOperation[]; + + /** + * The load balancer this service sits behind + * + * @default - No load balancer metrics are included in + * dashboards and its ARN is not added to top level AZ + * alarm descriptions. + */ + readonly loadBalancer?: ILoadBalancerV2; + + /** + * Define these settings if you want to automatically add canary + * tests to your operations. Operations can individually opt out + * of canary test creation if you define this setting. + * + * @default - Automatic canary tests will not be created for + * operations in this service. + */ + readonly canaryTestProps?: AddCanaryTestProps; + + /** + * The default settings that are used for availability metrics + * for all operations unless specifically overridden in an + * operation definition. + */ + readonly defaultAvailabilityMetricDetails: IServiceMetricDetails; + + /** + * The default settings that are used for availability metrics + * for all operations unless specifically overridden in an + * operation definition. + */ + readonly defaultLatencyMetricDetails: IServiceMetricDetails; + + /** + * The default settings that are used for contributor insight + * rules. + * + * @default - No defaults are provided and must be specified per operation + */ + readonly defaultContributorInsightRuleDetails?: IContributorInsightRuleDetails; + + /** + * Adds an operation to this service + */ + addOperation(operation: IOperation): void; +} diff --git a/src/services/IServiceMetricDetails.ts b/src/services/IServiceMetricDetails.ts new file mode 100644 index 0000000..867bf7f --- /dev/null +++ b/src/services/IServiceMetricDetails.ts @@ -0,0 +1,82 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * Details for the defaults used in a service for metrics in one perspective, such as server side latency + */ +export interface IServiceMetricDetails { + /** + * The CloudWatch metric namespace for these metrics + */ + readonly metricNamespace: string; + + /** + * The names of success indicating metrics + */ + readonly successMetricNames: string[]; + + /** + * The names of fault indicating metrics + */ + readonly faultMetricNames: string[]; + + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic: string; + + /** + * The statistics for successes you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedSuccessStatistics?: string[]; + + /** + * The statistics for faults you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedFaultStatistics?: string[]; + + /** + * The unit used for these metrics + */ + readonly unit: Unit; + + /** + * The period for the metrics + */ + readonly period: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold: number; +} diff --git a/src/services/InstrumentedServiceMultiAZObservability.ts b/src/services/InstrumentedServiceMultiAZObservability.ts new file mode 100644 index 0000000..0ddfd97 --- /dev/null +++ b/src/services/InstrumentedServiceMultiAZObservability.ts @@ -0,0 +1,552 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration, NestedStack } from 'aws-cdk-lib'; +import { Dashboard, IAlarm } from 'aws-cdk-lib/aws-cloudwatch'; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; +import { Construct } from 'constructs'; +import { CanaryMetrics } from './CanaryMetrics'; +import { IInstrumentedServiceMultiAZObservability } from './IInstrumentedServiceMultiAZObservability'; +import { IOperation } from './IOperation'; +import { IOperationMetricDetails } from './IOperationMetricDetails'; +import { Operation } from './Operation'; +import { OperationMetricDetails } from './OperationMetricDetails'; +import { InstrumentedServiceMultiAZObservabilityProps } from './props/InstrumentedServiceMultiAZObservabilityProps'; +import { MetricDimensions } from './props/MetricDimensions'; +import { IOperationAlarmsAndRules } from '../alarmsandrules/IOperationAlarmsAndRules'; +import { IServiceAlarmsAndRules } from '../alarmsandrules/IServiceAlarmsAndRules'; +import { OperationAlarmsAndRules } from '../alarmsandrules/OperationAlarmsAndRules'; +import { ServiceAlarmsAndRules } from '../alarmsandrules/ServiceAlarmsAndRules'; +import { AvailabilityZoneMapper } from '../azmapper/AvailabilityZoneMapper'; +import { CanaryFunction } from '../canaries/CanaryFunction'; +import { CanaryTest } from '../canaries/CanaryTest'; +import { AddCanaryTestProps } from '../canaries/props/AddCanaryTestProps'; +import { OperationAvailabilityAndLatencyDashboard } from '../dashboards/OperationAvailabilityAndLatencyDashboard'; +import { ServiceAvailabilityAndLatencyDashboard } from '../dashboards/ServiceAvailabilityAndLatencyDashboard'; +import { OutlierDetectionFunction } from '../outlier-detection/OutlierDetectionFunction'; +import { OutlierDetectionAlgorithm } from '../utilities/OutlierDetectionAlgorithm'; +import { StackWithDynamicSource } from '../utilities/StackWithDynamicSource'; + +/** + * An service that implements its own instrumentation to record + * availability and latency metrics that can be used to create + * alarms, rules, and dashboards from + */ +export class InstrumentedServiceMultiAZObservability + extends Construct + implements IInstrumentedServiceMultiAZObservability { + /** + * Key represents the operation name and the value is the set + * of zonal alarms and rules for that operation. The values themselves + * are dictionaries that have a key for each AZ ID. + */ + private readonly perOperationAlarmsAndRules: { + [key: string]: IOperationAlarmsAndRules; + }; + + /** + * Index into the dictionary by operation name, then by Availability Zone Id + * to get the alarms that indicate an AZ shows isolated impact from availability + * or latency as seen by either the server-side or canary. These are the alarms + * you would want to use to trigger automation to evacuate an AZ. + */ + readonly perOperationZonalImpactAlarms: { + [key: string]: { [key: string]: IAlarm }; + }; + + /** + * The alarms and rules for the overall service + */ + readonly serviceAlarms: IServiceAlarmsAndRules; + + /** + * The dashboards for each operation + */ + readonly operationDashboards?: Dashboard[]; + + /** + * The service level dashboard + */ + readonly serviceDashboard?: Dashboard; + + /** + * The AZ mapper custom resource used to map AZ Ids to Names and vice a versa + */ + private readonly azMapper: AvailabilityZoneMapper; + + /** + * If the service is configured to have canary tests created, this will + * be the log group where the canary's logs are stored. + * + * @default - No log group is created if the canary is not requested. + */ + readonly canaryLogGroup?: ILogGroup; + + private readonly outlierDetectionFunction?: IFunction; + + constructor( + scope: Construct, + id: string, + props: InstrumentedServiceMultiAZObservabilityProps, + ) { + super(scope, id); + + let outlierThreshold: number; + + if (!props.outlierThreshold) { + switch (props.outlierDetectionAlgorithm) { + case OutlierDetectionAlgorithm.CHI_SQUARED: + outlierThreshold = 0.05; + break; + case OutlierDetectionAlgorithm.IQR: + outlierThreshold = 1.5; + break; + case OutlierDetectionAlgorithm.MAD: + outlierThreshold = 3; + break; + case OutlierDetectionAlgorithm.STATIC: + outlierThreshold = 0.7; + break; + case OutlierDetectionAlgorithm.Z_SCORE: + outlierThreshold = 2; + break; + } + } else { + outlierThreshold = props.outlierThreshold; + } + + this.azMapper = new AvailabilityZoneMapper(this, 'AZMapper', { + availabilityZoneNames: props.service.availabilityZoneNames, + }); + + if (props.service.canaryTestProps !== undefined) { + let canaryStack: StackWithDynamicSource = new StackWithDynamicSource( + this, + 'Canary', + { + assetsBucketsParameterName: props.assetsBucketParameterName, + assetsBucketPrefixParameterName: + props.assetsBucketPrefixParameterName, + }, + ); + + let canary = new CanaryFunction(canaryStack, 'CanaryFunction', { + vpc: props.service.canaryTestProps.networkConfiguration?.vpc, + subnetSelection: + props.service.canaryTestProps.networkConfiguration?.subnetSelection, + httpTimeout: props.service.canaryTestProps.timeout + ? props.service.canaryTestProps.timeout + : Duration.seconds(2), + ignoreTlsErrors: props.service.canaryTestProps.ignoreTlsErrors + ? props.service.canaryTestProps.ignoreTlsErrors + : false, + }); + + this.canaryLogGroup = canary.logGroup; + + for (let i = 0; i < props.service.operations.length; i++) { + let operation: IOperation = props.service.operations[i]; + + if (operation.optOutOfServiceCreatedCanary != true) { + let testProps: AddCanaryTestProps = operation.canaryTestProps + ? operation.canaryTestProps + : (props.service.canaryTestProps as AddCanaryTestProps); + + let nestedStack: NestedStack = new NestedStack( + this, + operation.operationName + 'CanaryTest', + {}, + ); + + let test = new CanaryTest(nestedStack, operation.operationName, { + function: canary.function, + requestCount: testProps.requestCount, + regionalRequestCount: testProps.regionalRequestCount + ? testProps.regionalRequestCount + : testProps.requestCount, + schedule: testProps.schedule, + operation: operation, + loadBalancer: testProps.loadBalancer, + headers: testProps.headers, + postData: testProps.postData, + azMapper: this.azMapper, + }); + + let defaultAvailabilityMetricDetails: IOperationMetricDetails; + let defaultLatencyMetricDetails: IOperationMetricDetails; + + if (operation.canaryMetricDetails?.canaryAvailabilityMetricDetails) { + defaultAvailabilityMetricDetails = new OperationMetricDetails( + { + operationName: operation.operationName, + metricDimensions: new MetricDimensions( + { Operation: operation.operationName }, + 'AZ-ID', + 'Region', + ), + alarmStatistic: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .alarmStatistic, + datapointsToAlarm: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .datapointsToAlarm, + evaluationPeriods: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .evaluationPeriods, + faultAlarmThreshold: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .faultAlarmThreshold, + faultMetricNames: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .faultMetricNames, + graphedFaultStatistics: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .graphedFaultStatistics, + graphedSuccessStatistics: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .graphedSuccessStatistics, + metricNamespace: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .metricNamespace, + period: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .period, + successAlarmThreshold: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .successAlarmThreshold, + successMetricNames: + operation.canaryMetricDetails.canaryAvailabilityMetricDetails + .successMetricNames, + unit: operation.canaryMetricDetails + .canaryAvailabilityMetricDetails.unit, + }, + operation.service.defaultAvailabilityMetricDetails, + ); + } else if (operation.canaryTestAvailabilityMetricsOverride) { + defaultAvailabilityMetricDetails = new OperationMetricDetails( + { + operationName: operation.operationName, + metricNamespace: test.metricNamespace, + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Failure'], + metricDimensions: new MetricDimensions( + { Operation: operation.operationName }, + 'AZ-ID', + 'Region', + ), + + alarmStatistic: + operation.canaryTestAvailabilityMetricsOverride + .alarmStatistic, + datapointsToAlarm: + operation.canaryTestAvailabilityMetricsOverride + .datapointsToAlarm, + evaluationPeriods: + operation.canaryTestAvailabilityMetricsOverride + .evaluationPeriods, + faultAlarmThreshold: + operation.canaryTestAvailabilityMetricsOverride + .faultAlarmThreshold, + period: operation.canaryTestAvailabilityMetricsOverride.period, + successAlarmThreshold: + operation.canaryTestAvailabilityMetricsOverride + .successAlarmThreshold, + }, + props.service.defaultAvailabilityMetricDetails, + ); + } else { + defaultAvailabilityMetricDetails = new OperationMetricDetails( + { + operationName: operation.operationName, + metricNamespace: test.metricNamespace, + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Failure'], + metricDimensions: new MetricDimensions( + { Operation: operation.operationName }, + 'AZ-ID', + 'Region', + ), + }, + props.service.defaultAvailabilityMetricDetails, + ); + } + + if (operation.canaryMetricDetails?.canaryLatencyMetricDetails) { + defaultLatencyMetricDetails = new OperationMetricDetails( + { + operationName: operation.operationName, + metricDimensions: new MetricDimensions( + { Operation: operation.operationName }, + 'AZ-ID', + 'Region', + ), + alarmStatistic: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .alarmStatistic, + datapointsToAlarm: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .datapointsToAlarm, + evaluationPeriods: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .evaluationPeriods, + faultAlarmThreshold: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .faultAlarmThreshold, + faultMetricNames: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .faultMetricNames, + graphedFaultStatistics: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .graphedFaultStatistics, + graphedSuccessStatistics: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .graphedSuccessStatistics, + metricNamespace: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .metricNamespace, + period: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .period, + successAlarmThreshold: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .successAlarmThreshold, + successMetricNames: + operation.canaryMetricDetails.canaryLatencyMetricDetails + .successMetricNames, + unit: operation.canaryMetricDetails.canaryLatencyMetricDetails + .unit, + }, + operation.service.defaultLatencyMetricDetails, + ); + } else if (operation.canaryTestLatencyMetricsOverride) { + defaultLatencyMetricDetails = new OperationMetricDetails( + { + operationName: operation.operationName, + metricNamespace: test.metricNamespace, + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + metricDimensions: new MetricDimensions( + { Operation: operation.operationName }, + 'AZ-ID', + 'Region', + ), + + alarmStatistic: + operation.canaryTestLatencyMetricsOverride.alarmStatistic, + datapointsToAlarm: + operation.canaryTestLatencyMetricsOverride.datapointsToAlarm, + evaluationPeriods: + operation.canaryTestLatencyMetricsOverride.evaluationPeriods, + faultAlarmThreshold: + operation.canaryTestLatencyMetricsOverride + .faultAlarmThreshold, + period: operation.canaryTestLatencyMetricsOverride.period, + successAlarmThreshold: + operation.canaryTestLatencyMetricsOverride + .successAlarmThreshold, + }, + props.service.defaultLatencyMetricDetails, + ); + } else { + defaultLatencyMetricDetails = new OperationMetricDetails( + { + operationName: operation.operationName, + metricNamespace: test.metricNamespace, + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + metricDimensions: new MetricDimensions( + { Operation: operation.operationName }, + 'AZ-ID', + 'Region', + ), + }, + props.service.defaultLatencyMetricDetails, + ); + } + + let newOperation = new Operation({ + serverSideAvailabilityMetricDetails: + operation.serverSideAvailabilityMetricDetails, + serverSideLatencyMetricDetails: + operation.serverSideLatencyMetricDetails, + serverSideContributorInsightRuleDetails: + operation.serverSideContributorInsightRuleDetails, + service: operation.service, + operationName: operation.operationName, + path: operation.path, + critical: operation.critical, + httpMethods: operation.httpMethods, + canaryMetricDetails: new CanaryMetrics({ + canaryAvailabilityMetricDetails: defaultAvailabilityMetricDetails, + canaryLatencyMetricDetails: defaultLatencyMetricDetails, + }), + }); + + props.service.operations[i] = newOperation; + } + } + } + + if (props.outlierDetectionAlgorithm != OutlierDetectionAlgorithm.STATIC) { + let outlierDetectionStack: StackWithDynamicSource = + new StackWithDynamicSource(this, 'OutlierDetectionStack', { + assetsBucketsParameterName: props.assetsBucketParameterName, + assetsBucketPrefixParameterName: + props.assetsBucketPrefixParameterName, + }); + + this.outlierDetectionFunction = new OutlierDetectionFunction( + outlierDetectionStack, + 'OutlierDetectionFunction', + {}, + ).function; + } + + this.perOperationAlarmsAndRules = Object.fromEntries( + props.service.operations.map((operation: IOperation) => { + let nestedStack: NestedStack = new NestedStack( + this, + operation.operationName + 'OperationAlarmsAndRulesNestedStack', + ); + + return [ + operation.operationName, + new OperationAlarmsAndRules(nestedStack, operation.operationName, { + operation: operation, + outlierDetectionAlgorithm: props.outlierDetectionAlgorithm, + outlierThreshold: outlierThreshold, + loadBalancer: props.service.loadBalancer, + azMapper: this.azMapper, + outlierDetectionFunction: this.outlierDetectionFunction, + }), + ]; + }), + ); + + this.perOperationZonalImpactAlarms = Object.fromEntries( + Object.entries(this.perOperationAlarmsAndRules).map(([key, value]) => { + return [key, value.aggregateZonalAlarmsMap]; + }), + ); + + let serviceAlarmsStack: NestedStack = new NestedStack( + this, + 'ServiceAlarmsNestedStack', + ); + + this.serviceAlarms = new ServiceAlarmsAndRules( + serviceAlarmsStack, + props.service.serviceName, + { + perOperationAlarmsAndRules: this.perOperationAlarmsAndRules, + service: props.service, + azMapper: this.azMapper, + }, + ); + + if (props.createDashboards) { + this.operationDashboards = []; + + props.service.operations.forEach((x) => { + let dashboardStack: NestedStack = new NestedStack( + this, + x.operationName + 'Dashboard', + {}, + ); + + if (this.operationDashboards) { + this.operationDashboards.push( + new OperationAvailabilityAndLatencyDashboard( + dashboardStack, + x.operationName, + { + operation: x, + azMapper: this.azMapper, + interval: props.interval + ? props.interval + : Duration.minutes(60), + loadBalancer: props.service.loadBalancer, + + regionalEndpointCanaryAvailabilityAlarm: + this.perOperationAlarmsAndRules[x.operationName] + .canaryRegionalAlarmsAndRules?.availabilityAlarm, + + regionalEndpointCanaryLatencyAlarm: + this.perOperationAlarmsAndRules[x.operationName] + .canaryRegionalAlarmsAndRules?.latencyAlarm, + + regionalEndpointServerAvailabilityAlarm: + this.perOperationAlarmsAndRules[x.operationName] + .serverSideRegionalAlarmsAndRules.availabilityAlarm, + + regionalEndpointServerLatencyAlarm: + this.perOperationAlarmsAndRules[x.operationName] + .serverSideRegionalAlarmsAndRules.latencyAlarm, + + zonalEndpointCanaryAvailabilityAlarms: + this.perOperationAlarmsAndRules[ + x.operationName + ].canaryZonalAlarmsAndRules?.map((a) => a.availabilityAlarm), + + zonalEndpointCanaryLatencyAlarms: + this.perOperationAlarmsAndRules[ + x.operationName + ].canaryZonalAlarmsAndRules?.map((a) => a.latencyAlarm), + + zonalEndpointServerAvailabilityAlarms: + this.perOperationAlarmsAndRules[ + x.operationName + ].serverSideZonalAlarmsAndRules.map( + (a) => a.availabilityAlarm, + ), + + zonalEndpointServerLatencyAlarms: + this.perOperationAlarmsAndRules[ + x.operationName + ].serverSideZonalAlarmsAndRules.map((a) => a.latencyAlarm), + + isolatedAZImpactAlarms: + this.perOperationAlarmsAndRules[x.operationName] + .aggregateZonalAlarms, + + regionalImpactAlarm: + this.perOperationAlarmsAndRules[x.operationName] + .aggregateRegionalAlarm, + + instanceContributorsToFaults: + this.perOperationAlarmsAndRules[x.operationName] + .serverSideRegionalAlarmsAndRules + .instanceContributorsToRegionalFaults, + + instanceContributorsToHighLatency: + this.perOperationAlarmsAndRules[x.operationName] + .serverSideRegionalAlarmsAndRules + .instanceContributorsToRegionalHighLatency, + }, + ).dashboard, + ); + } + }); + + let dashboardStack: NestedStack = new NestedStack( + this, + 'ServiceDashboard', + {}, + ); + + this.serviceDashboard = new ServiceAvailabilityAndLatencyDashboard( + dashboardStack, + props.service.serviceName.toLowerCase(), + { + interval: props.interval ? props.interval : Duration.minutes(60), + service: props.service, + aggregateRegionalAlarm: + this.serviceAlarms.regionalFaultCountServerSideAlarm, + zonalAggregateAlarms: + this.serviceAlarms.zonalAggregateIsolatedImpactAlarms, + azMapper: this.azMapper, + }, + ).dashboard; + } + } +} diff --git a/src/services/Operation.ts b/src/services/Operation.ts new file mode 100644 index 0000000..77d9f6b --- /dev/null +++ b/src/services/Operation.ts @@ -0,0 +1,113 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ICanaryMetrics } from './ICanaryMetrics'; +import { ICanaryTestMetricsOverride } from './ICanaryTestMetricsOverride'; +import { IContributorInsightRuleDetails } from './IContributorInsightRuleDetails'; +import { IOperation } from './IOperation'; +import { IOperationMetricDetails } from './IOperationMetricDetails'; +import { IService } from './IService'; +import { OperationProps } from './props/OperationProps'; +import { AddCanaryTestProps } from '../canaries/props/AddCanaryTestProps'; + +/** + * A single operation that is part of a service + */ +export class Operation implements IOperation { + /** + * The service the operation is associated with + */ + readonly service: IService; + + /** + * The name of the operation + */ + readonly operationName: string; + + /** + * The HTTP path for the operation for canaries + * to run against, something like "/products/list" + */ + readonly path: string; + + /** + * The server side availability metric details + */ + readonly serverSideAvailabilityMetricDetails: IOperationMetricDetails; + + /** + * The server side latency metric details + */ + readonly serverSideLatencyMetricDetails: IOperationMetricDetails; + + /** + * Optional metric details if the service has a canary + */ + readonly canaryMetricDetails?: ICanaryMetrics; + + /** + * The override values for automatically created canary tests so you can + * use values other than the service defaults to define the thresholds for + * availability. + */ + readonly canaryTestAvailabilityMetricsOverride?: ICanaryTestMetricsOverride; + + /** + * The override values for automatically created canary tests so you can + * use values other than the service defaults to define the thresholds for + * latency. + */ + readonly canaryTestLatencyMetricsOverride?: ICanaryTestMetricsOverride; + + /** + * The server side details for contributor insights rules + */ + readonly serverSideContributorInsightRuleDetails?: IContributorInsightRuleDetails; + + /** + * Indicates this is a critical operation for the service + * and will be included in service level metrics and + * dashboards + */ + readonly critical: boolean; + + /** + * The http methods supported by the operation + */ + readonly httpMethods: string[]; + + /** + * If they have been added, the properties for + * creating new canary tests on this operation + */ + readonly canaryTestProps?: AddCanaryTestProps; + + /** + * Set to true if you have defined CanaryTestProps for your + * service, which applies to all operations, but you want to + * opt out of creating the canary test for this operation. + * + * @default - The operation is not opted out + */ + readonly optOutOfServiceCreatedCanary?: boolean; + + constructor(props: OperationProps) { + this.canaryMetricDetails = props.canaryMetricDetails; + this.httpMethods = props.httpMethods; + this.critical = props.critical; + this.operationName = props.operationName; + this.path = props.path; + this.serverSideAvailabilityMetricDetails = + props.serverSideAvailabilityMetricDetails; + this.serverSideLatencyMetricDetails = props.serverSideLatencyMetricDetails; + this.serverSideContributorInsightRuleDetails = + props.serverSideContributorInsightRuleDetails; + this.service = props.service; + this.canaryTestProps = props.canaryTestProps; + this.optOutOfServiceCreatedCanary = props.optOutOfServiceCreatedCanary; + this.canaryTestAvailabilityMetricsOverride = + props.canaryTestAvailabilityMetricsOverride; + this.canaryTestLatencyMetricsOverride = + props.canaryTestLatencyMetricsOverride; + this.optOutOfServiceCreatedCanary = props.optOutOfServiceCreatedCanary; + } +} diff --git a/src/services/OperationMetricDetails.ts b/src/services/OperationMetricDetails.ts new file mode 100644 index 0000000..19e2db3 --- /dev/null +++ b/src/services/OperationMetricDetails.ts @@ -0,0 +1,137 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { IOperationMetricDetails } from './IOperationMetricDetails'; +import { IServiceMetricDetails } from './IServiceMetricDetails'; +import { MetricDimensions } from './props/MetricDimensions'; +import { OperationMetricDetailsProps } from './props/OperationMetricDetailsProps'; + +/** + * Generic metric details for an operation + */ +export class OperationMetricDetails implements IOperationMetricDetails { + /** + * The operation these metric details are for + */ + readonly operationName: string; + + /** + * The CloudWatch metric namespace for these metrics + */ + readonly metricNamespace: string; + + /** + * The names of success indicating metrics + */ + readonly successMetricNames: string[]; + + /** + * The names of fault indicating metrics + */ + readonly faultMetricNames: string[]; + + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic: string; + + /** + * The statistics for successes you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedSuccessStatistics?: string[]; + + /** + * The statistics for faults you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedFaultStatistics?: string[]; + + /** + * The unit used for these metrics + */ + readonly unit: Unit; + + /** + * The period for the metrics + */ + readonly period: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold: number; + + /** + * The metric dimensions for this operation, must be implemented + * as a concrete class by the user + */ + readonly metricDimensions: MetricDimensions; + + constructor( + props: OperationMetricDetailsProps, + defaultProps: IServiceMetricDetails, + ) { + this.alarmStatistic = props.alarmStatistic + ? props.alarmStatistic + : defaultProps.alarmStatistic; + this.datapointsToAlarm = props.datapointsToAlarm + ? props.datapointsToAlarm + : defaultProps.datapointsToAlarm; + this.evaluationPeriods = props.evaluationPeriods + ? props.evaluationPeriods + : defaultProps.evaluationPeriods; + this.faultAlarmThreshold = props.faultAlarmThreshold + ? props.faultAlarmThreshold + : defaultProps.faultAlarmThreshold; + this.faultMetricNames = props.faultMetricNames + ? props.faultMetricNames + : defaultProps.faultMetricNames; + this.graphedFaultStatistics = props.graphedFaultStatistics + ? props.graphedFaultStatistics + : defaultProps.graphedFaultStatistics; + this.graphedSuccessStatistics = props.graphedSuccessStatistics + ? props.graphedSuccessStatistics + : defaultProps.graphedSuccessStatistics; + this.metricNamespace = props.metricNamespace + ? props.metricNamespace + : defaultProps.metricNamespace; + this.operationName = props.operationName; + this.period = props.period ? props.period : defaultProps.period; + this.successAlarmThreshold = props.successAlarmThreshold + ? props.successAlarmThreshold + : defaultProps.successAlarmThreshold; + this.successMetricNames = props.successMetricNames + ? props.successMetricNames + : defaultProps.successMetricNames; + this.unit = props.unit ? props.unit : defaultProps.unit; + this.metricDimensions = props.metricDimensions; + } +} diff --git a/src/services/Service.ts b/src/services/Service.ts new file mode 100644 index 0000000..2a86e39 --- /dev/null +++ b/src/services/Service.ts @@ -0,0 +1,111 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { IContributorInsightRuleDetails } from './IContributorInsightRuleDetails'; +import { IOperation } from './IOperation'; +import { IService } from './IService'; +import { IServiceMetricDetails } from './IServiceMetricDetails'; +import { ServiceProps } from './props/ServiceProps'; +import { AddCanaryTestProps } from '../canaries/props/AddCanaryTestProps'; + +/** + * The representation of a service composed of multiple operations + */ +export class Service implements IService { + /** + * The name of your service + */ + readonly serviceName: string; + + /** + * The base endpoint for this service, like "https://www.example.com". Operation paths will be appended to this endpoint for canary testing the service. + */ + readonly baseUrl: string; + + /** + * The fault count threshold that indicates the service is unhealthy. This is an absolute value of faults + * being produced by all critical operations in aggregate. + */ + readonly faultCountThreshold: number; + + /** + * A list of the Availability Zone names used by this application + */ + readonly availabilityZoneNames: string[]; + + /** + * The period for which metrics for the service should be aggregated + */ + readonly period: Duration; + + /** + * The operations that are part of this service + */ + readonly operations: IOperation[]; + + /** + * The load balancer this service sits behind + * + * @default - No load balancer metrics will be included in + * dashboards and its ARN will not be added to top level AZ + * alarm descriptions. + */ + readonly loadBalancer?: ILoadBalancerV2; + + /** + * Define these settings if you want to automatically add canary + * tests to your operations. Operations can individually opt out + * of canary test creation if you define this setting. + * + * @default - Automatic canary tests will not be created for + * operations in this service. + */ + readonly canaryTestProps?: AddCanaryTestProps; + + /** + * The default settings that are used for availability metrics + * for all operations unless specifically overridden in an + * operation definition. + */ + readonly defaultAvailabilityMetricDetails: IServiceMetricDetails; + + /** + * The default settings that are used for availability metrics + * for all operations unless specifically overridden in an + * operation definition. + */ + readonly defaultLatencyMetricDetails: IServiceMetricDetails; + + /** + * The default settings that are used for contributor insight + * rules. + * + * @default - No defaults are provided and must be specified per operation + */ + readonly defaultContributorInsightRuleDetails?: IContributorInsightRuleDetails; + + constructor(props: ServiceProps) { + this.serviceName = props.serviceName; + this.availabilityZoneNames = props.availabilityZoneNames; + this.baseUrl = props.baseUrl; + this.faultCountThreshold = props.faultCountThreshold; + this.operations = []; + this.period = props.period; + this.loadBalancer = props.loadBalancer; + this.canaryTestProps = props.canaryTestProps; + this.defaultAvailabilityMetricDetails = + props.defaultAvailabilityMetricDetails; + this.defaultLatencyMetricDetails = props.defaultLatencyMetricDetails; + this.defaultContributorInsightRuleDetails = + props.defaultContributorInsightRuleDetails; + } + + /** + * Adds an operation to this service and sets the operation's + * service property + */ + addOperation(operation: IOperation): void { + this.operations.push(operation); + } +} diff --git a/src/services/ServiceMetricDetails.ts b/src/services/ServiceMetricDetails.ts new file mode 100644 index 0000000..4fbda7c --- /dev/null +++ b/src/services/ServiceMetricDetails.ts @@ -0,0 +1,99 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { IServiceMetricDetails } from './IServiceMetricDetails'; +import { ServiceMetricDetailsProps } from './props/ServiceMetricDetailsProps'; + +/** + * Default metric details for a service + */ +export class ServiceMetricDetails implements IServiceMetricDetails { + /** + * The CloudWatch metric namespace for these metrics + */ + readonly metricNamespace: string; + + /** + * The names of success indicating metrics + */ + readonly successMetricNames: string[]; + + /** + * The names of fault indicating metrics + */ + readonly faultMetricNames: string[]; + + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic: string; + + /** + * The statistics for successes you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedSuccessStatistics?: string[]; + + /** + * The statistics for faults you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedFaultStatistics?: string[]; + + /** + * The unit used for these metrics + */ + readonly unit: Unit; + + /** + * The period for the metrics + */ + readonly period: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold: number; + + constructor(props: ServiceMetricDetailsProps) { + this.alarmStatistic = props.alarmStatistic; + this.datapointsToAlarm = props.datapointsToAlarm; + this.evaluationPeriods = props.evaluationPeriods; + this.faultAlarmThreshold = props.faultAlarmThreshold; + this.faultMetricNames = props.faultMetricNames; + this.graphedFaultStatistics = props.graphedFaultStatistics; + this.graphedSuccessStatistics = props.graphedSuccessStatistics; + this.metricNamespace = props.metricNamespace; + this.period = props.period; + this.successAlarmThreshold = props.successAlarmThreshold; + this.successMetricNames = props.successMetricNames; + this.unit = props.unit; + } +} diff --git a/src/services/props/BasicServiceMultiAZObservabilityProps.ts b/src/services/props/BasicServiceMultiAZObservabilityProps.ts new file mode 100644 index 0000000..9f5a9a5 --- /dev/null +++ b/src/services/props/BasicServiceMultiAZObservabilityProps.ts @@ -0,0 +1,140 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { CfnNatGateway } from 'aws-cdk-lib/aws-ec2'; +import { IApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { OutlierDetectionAlgorithm } from '../../utilities/OutlierDetectionAlgorithm'; + +/** + * Properties for creating a basic service + */ +export interface BasicServiceMultiAZObservabilityProps { + /** + * (Optional) A map of Availability Zone name to the NAT Gateways + * in that AZ + * + * @default - No alarms for NAT Gateways will be created + */ + readonly natGateways?: { [key: string]: CfnNatGateway[] }; + + /** + * The application load balancers being used by the service + * + * @default - No alarms for ALBs will be created + */ + readonly applicationLoadBalancers?: IApplicationLoadBalancer[]; + + /** + * The service's name + */ + readonly serviceName: string; + + /** + * The outlier threshold for determining if an AZ is an + * outlier for latency or faults. This number is interpreted + * differently for different outlier algorithms. When used with + * STATIC, the number should be between 0 and 1 to represent the + * percentage of errors (like .7) that an AZ must be responsible + * for to be considered an outlier. When used with CHI_SQUARED, it + * represents the p value that indicates statistical significance, like + * 0.05 which means the skew has less than or equal to a 5% chance of + * occuring. When used with Z_SCORE it indicates how many standard + * deviations to evaluate for an AZ being an outlier, typically 3 is + * standard for Z_SCORE. + * + * Standard defaults based on the outlier detection algorithm: + * STATIC: 0.7 + * CHI_SQUARED: 0.05 + * Z_SCORE: 2 + * IQR: 1.5 + * MAD: 3 + * + * @default - Depends on the outlier detection algorithm selected + */ + readonly outlierThreshold?: number; + + /** + * The amount of packet loss in a NAT GW to determine if an AZ + * is actually impacted, recommendation is 0.01% + * + * @default - 0.01 (as in 0.01%) + */ + readonly packetLossImpactPercentageThreshold?: number; + + /** + * The percentage of faults for a single ALB to consider an AZ + * to be unhealthy, this should align with your availability goal. For example + * 1% or 5%. + * + * @default - 5 (as in 5%) + */ + readonly faultCountPercentageThreshold?: number; + + /** + * The algorithm to use for performing outlier detection + */ + readonly outlierDetectionAlgorithm: OutlierDetectionAlgorithm; + + /** + * The period to evaluate metrics + */ + readonly period: Duration; + + /** + * Whether to create a dashboard displaying the metrics and alarms + */ + readonly createDashboard: boolean; + + /** + * Dashboard interval + * + * @default - 1 hour + */ + readonly interval?: Duration; + + /** + * If you are not using a static bucket to deploy assets, for example + * you are synthing this and it gets uploaded to a bucket whose name + * is unknown to you (maybe used as part of a central CI/CD system) + * and is provided as a parameter to your stack, specify that parameter + * name here. It will override the bucket location CDK provides by + * default for bundled assets. The stack containing this contruct needs + * to have a parameter defined that uses this name. The underlying + * stacks in this construct that deploy assets will copy the parent stack's + * value for this property. + * + * @default - The assets will be uploaded to the default defined + * asset location. + */ + readonly assetsBucketParameterName?: string; + + /** + * If you are not using a static bucket to deploy assets, for example + * you are synthing this and it gets uploaded to a bucket that uses a prefix + * that is unknown to you (maybe used as part of a central CI/CD system) + * and is provided as a parameter to your stack, specify that parameter + * name here. It will override the bucket prefix CDK provides by + * default for bundled assets. This property only takes effect if you + * defined the assetsBucketParameterName. The stack containing this contruct needs + * to have a parameter defined that uses this name. The underlying + * stacks in this construct that deploy assets will copy the parent stack's + * value for this property. + * + * @default - No object prefix will be added to your custom assets location. + * However, if you have overridden something like the 'BucketPrefix' property + * in your stack synthesizer with a variable like "${AssetsBucketPrefix", + * you will need to define this property so it doesn't cause a reference error + * even if the prefix value is blank. + */ + readonly assetsBucketPrefixParameterName?: string; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm: number; +} diff --git a/src/services/props/CanaryMetricProps.ts b/src/services/props/CanaryMetricProps.ts new file mode 100644 index 0000000..981040b --- /dev/null +++ b/src/services/props/CanaryMetricProps.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { IOperationMetricDetails } from '../IOperationMetricDetails'; + +/** + * Properties for canary metrics in an operation + */ +export interface CanaryMetricProps { + /** + * The canary availability metric details + */ + readonly canaryAvailabilityMetricDetails: IOperationMetricDetails; + + /** + * The canary latency metric details + */ + readonly canaryLatencyMetricDetails: IOperationMetricDetails; +} diff --git a/src/services/props/CanaryTestMetricsOverrideProps.ts b/src/services/props/CanaryTestMetricsOverrideProps.ts new file mode 100644 index 0000000..36967fd --- /dev/null +++ b/src/services/props/CanaryTestMetricsOverrideProps.ts @@ -0,0 +1,55 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; + +/** + * The properties for creating an override + */ +export interface CanaryTestMetricsOverrideProps { + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + * + * @default - This property will use the default defined for the service + */ + readonly alarmStatistic?: string; + + /** + * The period for the metrics + * + * @default - This property will use the default defined for the service + */ + readonly period?: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + * + * @default - This property will use the default defined for the service + */ + readonly evaluationPeriods?: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + * + * @default - This property will use the default defined for the service + */ + readonly datapointsToAlarm?: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + * + * @default - This property will use the default defined for the service + */ + readonly successAlarmThreshold?: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + * + * @default - This property will use the default defined for the service + */ + readonly faultAlarmThreshold?: number; +} diff --git a/src/services/props/ContributorInsightRuleDetailsProps.ts b/src/services/props/ContributorInsightRuleDetailsProps.ts new file mode 100644 index 0000000..f3e2b93 --- /dev/null +++ b/src/services/props/ContributorInsightRuleDetailsProps.ts @@ -0,0 +1,45 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; + +/** + * The contributor insight rule details properties + */ +export interface ContributorInsightRuleDetailsProps { + /** + * The log groups where CloudWatch logs for the operation are located. If + * this is not provided, Contributor Insight rules cannot be created. + */ + readonly logGroups: ILogGroup[]; + /** + * The path in the log files to the field that indicates the latency + * for the response. This could either be success latency or fault + * latency depending on the alarms and rules you are creating. + */ + readonly successLatencyMetricJsonPath: string; + + /** + * The path in the log files to the field that identifies the operation + * the log file is for. + */ + readonly operationNameJsonPath: string; + + /** + * The path in the log files to the field that identifies if the response + * resulted in a fault, for example { "Fault" : 1 } would have a path of $.Fault + */ + readonly faultMetricJsonPath: string; + + /** + * The path in the log files to the field that identifies the Availability Zone + * Id that the request was handled in, for example { "AZ-ID": "use1-az1" } would + * have a path of $.AZ-ID + */ + readonly availabilityZoneIdJsonPath: string; + + /** + * The JSON path to the instance id field in the log files, only required for server-side + * rules + */ + readonly instanceIdJsonPath: string; +} diff --git a/src/services/props/InstrumentedServiceMultiAZObservabilityProps.ts b/src/services/props/InstrumentedServiceMultiAZObservabilityProps.ts new file mode 100644 index 0000000..7e77355 --- /dev/null +++ b/src/services/props/InstrumentedServiceMultiAZObservabilityProps.ts @@ -0,0 +1,97 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { OutlierDetectionAlgorithm } from '../../utilities/OutlierDetectionAlgorithm'; +import { IService } from '../IService'; + +/** + * The properties for adding alarms and dashboards + * for an instrumented service. + */ +export interface InstrumentedServiceMultiAZObservabilityProps { + /** + * The service that the alarms and dashboards are being crated for. + */ + readonly service: IService; + + /** + * Indicates whether to create per operation and overall service + * dashboards. + * + * @default - No dashboards are created + */ + readonly createDashboards?: boolean; + + /** + * The outlier threshold for determining if an AZ is an + * outlier for latency or faults. This number is interpreted + * differently for different outlier algorithms. When used with + * STATIC, the number should be between 0 and 1 to represent the + * percentage of errors (like .7) that an AZ must be responsible + * for to be considered an outlier. When used with CHI_SQUARED, it + * represents the p value that indicates statistical significance, like + * 0.05 which means the skew has less than or equal to a 5% chance of + * occuring. When used with Z_SCORE it indicates how many standard + * deviations to evaluate for an AZ being an outlier, typically 3 is + * standard for Z_SCORE. + * + * Standard defaults based on the outlier detection algorithm: + * STATIC: 0.7 + * CHI_SQUARED: 0.05 + * Z_SCORE: 2 + * IQR: 1.5 + * MAD: 3 + * + * @default - Depends on the outlier detection algorithm selected + */ + readonly outlierThreshold?: number; + + /** + * The algorithm to use for performing outlier detection + */ + readonly outlierDetectionAlgorithm: OutlierDetectionAlgorithm; + + /** + * The interval used in the dashboard, defaults to + * 60 minutes. + * + * @default - 60 minutes + */ + readonly interval?: Duration; + + /** + * If you are not using a static bucket to deploy assets, for example + * you are synthing this and it gets uploaded to a bucket whose name + * is unknown to you (maybe used as part of a central CI/CD system) + * and is provided as a parameter to your stack, specify that parameter + * name here. It will override the bucket location CDK provides by + * default for bundled assets. The stack containing this contruct needs + * to have a parameter defined that uses this name. The underlying + * stacks in this construct that deploy assets will copy the parent stack's + * value for this property. + * + * @default - The assets will be uploaded to the default defined + * asset location. + */ + readonly assetsBucketParameterName?: string; + + /** + * If you are not using a static bucket to deploy assets, for example + * you are synthing this and it gets uploaded to a bucket that uses a prefix + * that is unknown to you (maybe used as part of a central CI/CD system) + * and is provided as a parameter to your stack, specify that parameter + * name here. It will override the bucket prefix CDK provides by + * default for bundled assets. This property only takes effect if you + * defined the assetsBucketParameterName. The stack containing this contruct needs + * to have a parameter defined that uses this name. The underlying + * stacks in this construct that deploy assets will copy the parent stack's + * value for this property. + * + * @default - No object prefix will be added to your custom assets location. + * However, if you have overridden something like the 'BucketPrefix' property + * in your stack synthesizer with a variable like "${AssetsBucketPrefix", + * you will need to define this property so it doesn't cause a reference error + * even if the prefix value is blank. + */ + readonly assetsBucketPrefixParameterName?: string; +} diff --git a/src/services/props/MetricDimensions.ts b/src/services/props/MetricDimensions.ts new file mode 100644 index 0000000..fb3d01e --- /dev/null +++ b/src/services/props/MetricDimensions.ts @@ -0,0 +1,91 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Provides the ability to get operation specific metric dimensions + * for metrics at the regional level as well as Availability Zone level + */ +export class MetricDimensions { + /** + * The dimensions that are the same for all Availability Zones for example: + * { + * "Operation": "ride", + * "Service": "WildRydes" + * } + */ + staticDimensions: { [key: string]: string }; + + /** + * The key used to specify an Availability Zone specific metric dimension, for + * example: "AZ-ID" + */ + availabilityZoneIdKey: string; + + /** + * The key used for the Region in your dimensions, if you provide one. + * + * @default - A region specific key and value is not added to your + * zonal and regional metric dimensions + */ + regionKey?: string; + + constructor( + staticDimensions: { [key: string]: string }, + availabilityZoneIdKey: string, + regionKey?: string, + ) { + this.staticDimensions = staticDimensions; + this.availabilityZoneIdKey = availabilityZoneIdKey; + this.regionKey = regionKey; + } + + /** + * Gets the zonal dimensions for these metrics by combining the static + * metric dimensions with the keys provided for Availability Zone and + * optional Region, expected to return something like + * { + * "Region": "us-east-1", + * "AZ-ID": "use1-az1", + * "Operation": "ride", + * "Service": "WildRydes" + * } + * @param availabilityZoneId + * @param region + */ + zonalDimensions( + availabilityZoneId: string, + region: string, + ): { [key: string]: string } { + let tmp: { [key: string]: string } = {}; + Object.assign(tmp, this.staticDimensions); + tmp[this.availabilityZoneIdKey] = availabilityZoneId; + + if (this.regionKey !== undefined) { + tmp[this.regionKey] = region; + } + + return tmp; + } + + /** + * Gets the regional dimensions for these metrics by combining the static + * metric dimensions with the keys provided the optional Region key, + * expected to return something like + * { + * "Region": "us-east-1", + * "Operation": "ride", + * "Service": "WildRydes" + * } + * @param region + */ + regionalDimensions(region: string): { [key: string]: string } { + let tmp: { [key: string]: string } = {}; + Object.assign(tmp, this.staticDimensions); + + if (this.regionKey !== undefined) { + tmp[this.regionKey] = region; + } + + return tmp; + } +} diff --git a/src/services/props/OperationMetricDetailsProps.ts b/src/services/props/OperationMetricDetailsProps.ts new file mode 100644 index 0000000..6c38806 --- /dev/null +++ b/src/services/props/OperationMetricDetailsProps.ts @@ -0,0 +1,113 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { MetricDimensions } from './MetricDimensions'; + +/** + * The properties for operation metric details + */ +export interface OperationMetricDetailsProps { + /** + * The operation these metric details are for + */ + readonly operationName: string; + + /** + * The CloudWatch metric namespace for these metrics + * + * @default - The service default is used + */ + readonly metricNamespace?: string; + + /** + * The names of success indicating metrics + * + * @default - The service default is used + */ + readonly successMetricNames?: string[]; + + /** + * The names of fault indicating metrics + * + * @default - The service default is used + */ + readonly faultMetricNames?: string[]; + + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + * + * @default - The service default is used + */ + readonly alarmStatistic?: string; + + /** + * The statistics for successes you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - The service default is used + */ + readonly graphedSuccessStatistics?: string[]; + + /** + * The statistics for faults you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - The service default is used + */ + readonly graphedFaultStatistics?: string[]; + + /** + * The unit used for these metrics + * + * @default - The service default is used + */ + readonly unit?: Unit; + + /** + * The period for the metrics + * + * @default - The service default is used + */ + readonly period?: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + * + * @default - The service default is used + */ + readonly evaluationPeriods?: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + * + * @default - The service default is used + */ + readonly datapointsToAlarm?: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + * + * @default - The service default is used + */ + readonly successAlarmThreshold?: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + * + * @default - The service default is used + */ + readonly faultAlarmThreshold?: number; + + /** + * The user implemented functions for providing the metric's dimensions + */ + readonly metricDimensions: MetricDimensions; +} diff --git a/src/services/props/OperationProps.ts b/src/services/props/OperationProps.ts new file mode 100644 index 0000000..e3a1333 --- /dev/null +++ b/src/services/props/OperationProps.ts @@ -0,0 +1,108 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AddCanaryTestProps } from '../../canaries/props/AddCanaryTestProps'; +import { ICanaryMetrics } from '../ICanaryMetrics'; +import { ICanaryTestMetricsOverride } from '../ICanaryTestMetricsOverride'; +import { IContributorInsightRuleDetails } from '../IContributorInsightRuleDetails'; +import { IOperationMetricDetails } from '../IOperationMetricDetails'; +import { IService } from '../IService'; + +/** + * Properties for an operation + */ +export interface OperationProps { + /** + * The service the operation is associated with + */ + readonly service: IService; + + /** + * The name of the operation + */ + readonly operationName: string; + + /** + * The HTTP path for the operation for canaries + * to run against, something like "/products/list" + */ + readonly path: string; + + /** + * The server side availability metric details + */ + readonly serverSideAvailabilityMetricDetails: IOperationMetricDetails; + + /** + * The server side latency metric details + */ + readonly serverSideLatencyMetricDetails: IOperationMetricDetails; + + /** + * Optional metric details if the service has a canary + * + * @default - No alarms, rules, or dashboards will be created + * from canary metrics + */ + readonly canaryMetricDetails?: ICanaryMetrics; + + /** + * The override values for automatically created canary tests so you can + * use values other than the service defaults to define the thresholds for + * availability. + * + * @default - No availability metric details will be overridden and the + * service defaults will be used for the automatically created canaries + */ + readonly canaryTestAvailabilityMetricsOverride?: ICanaryTestMetricsOverride; + + /** + * The override values for automatically created canary tests so you can + * use values other than the service defaults to define the thresholds for + * latency. + * + * @default - No latency metric details will be overridden and the + * service defaults will be used for the automatically created canaries + */ + readonly canaryTestLatencyMetricsOverride?: ICanaryTestMetricsOverride; + + /** + * The server side details for contributor insights rules + * + * @default - The default service contributor insight rule + * details will be used. If those are not defined no Contributor Insight + * rules will be created and the number of instances contributing to AZ + * faults or high latency will not be considered, so a single bad instance + * could make the AZ appear to look impaired. + */ + readonly serverSideContributorInsightRuleDetails?: IContributorInsightRuleDetails; + + /** + * Indicates this is a critical operation for the service + * and will be included in service level metrics and + * dashboards + */ + readonly critical: boolean; + + /** + * If you define this property, a synthetic + * canary will be provisioned to test the operation. + * + * @default - The default for the service will be used, if that + * is undefined, then no canary will be provisioned for this operation. + */ + readonly canaryTestProps?: AddCanaryTestProps; + + /** + * Set to true if you have defined CanaryTestProps for your + * service, which applies to all operations, but you want to + * opt out of creating the canary test for this operation. + * + * @default - The operation is not opted out + */ + readonly optOutOfServiceCreatedCanary?: boolean; + + /** + * The http methods supported by the operation + */ + readonly httpMethods: string[]; +} diff --git a/src/services/props/ServiceMetricDetailsProps.ts b/src/services/props/ServiceMetricDetailsProps.ts new file mode 100644 index 0000000..4f5b5ce --- /dev/null +++ b/src/services/props/ServiceMetricDetailsProps.ts @@ -0,0 +1,82 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; + +/** + * The properties for default service metric details + */ +export interface ServiceMetricDetailsProps { + /** + * The CloudWatch metric namespace for these metrics + */ + readonly metricNamespace: string; + + /** + * The names of success indicating metrics + */ + readonly successMetricNames: string[]; + + /** + * The names of fault indicating metrics + */ + readonly faultMetricNames: string[]; + + /** + * The statistic used for alarms, for availability metrics this should + * be "Sum", for latency metrics it could something like "p99" or "p99.9" + */ + readonly alarmStatistic: string; + + /** + * The statistics for successes you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedSuccessStatistics?: string[]; + + /** + * The statistics for faults you want to appear on dashboards, for example, with + * latency metrics, you might want p50, p99, and tm99. For availability + * metrics this will typically just be "Sum". + * + * @default - For availability metrics, this will be "Sum", for latency metrics it will be just "p99" + */ + readonly graphedFaultStatistics?: string[]; + + /** + * The unit used for these metrics + */ + readonly unit: Unit; + + /** + * The period for the metrics + */ + readonly period: Duration; + + /** + * The number of evaluation periods for latency and availabiltiy alarms + */ + readonly evaluationPeriods: number; + + /** + * The number of datapoints to alarm on for latency and availability alarms + */ + readonly datapointsToAlarm: number; + + /** + * The threshold for alarms associated with success metrics, for example if measuring + * success rate, the threshold may be 99, meaning you would want an alarm that triggers + * if success drops below 99%. + */ + readonly successAlarmThreshold: number; + + /** + * The threshold for alarms associated with fault metrics, for example if measuring + * fault rate, the threshold may be 1, meaning you would want an alarm that triggers + * if the fault rate goes above 1%. + */ + readonly faultAlarmThreshold: number; +} diff --git a/src/services/props/ServiceProps.ts b/src/services/props/ServiceProps.ts new file mode 100644 index 0000000..7b4a9a4 --- /dev/null +++ b/src/services/props/ServiceProps.ts @@ -0,0 +1,81 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Duration } from 'aws-cdk-lib'; +import { ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { AddCanaryTestProps } from '../../canaries/props/AddCanaryTestProps'; +import { IContributorInsightRuleDetails } from '../IContributorInsightRuleDetails'; +import { IServiceMetricDetails } from '../IServiceMetricDetails'; + +/** + * Properties to initialize a service + */ +export interface ServiceProps { + /** + * The name of your service + */ + readonly serviceName: string; + + /** + * The base endpoint for this service, like "https://www.example.com". Operation + * paths will be appended to this endpoint for canary testing the service. + */ + readonly baseUrl: string; + + /** + * The fault count threshold that indicates the service is unhealthy. This is an absolute value of faults + * being produced by all critical operations in aggregate. + */ + readonly faultCountThreshold: number; + + /** + * A list of the Availability Zone names used by this application + */ + readonly availabilityZoneNames: string[]; + + /** + * The period for which metrics for the service should be aggregated + */ + readonly period: Duration; + + /** + * The load balancer this service sits behind + * + * @default - Load balancer metrics won't be shown on dashboards + * and its ARN won't be included in top level alarm descriptions + * that automation can use to implement a zonal shift. + */ + readonly loadBalancer?: ILoadBalancerV2; + + /** + * Define these settings if you want to automatically add canary + * tests to your operations. Operations can individually opt out + * of canary test creation if you define this setting. + * + * @default - Automatic canary tests will not be created for + * operations in this service. + */ + readonly canaryTestProps?: AddCanaryTestProps; + + /** + * The default settings that are used for availability metrics + * for all operations unless specifically overridden in an + * operation definition. + */ + readonly defaultAvailabilityMetricDetails: IServiceMetricDetails; + + /** + * The default settings that are used for availability metrics + * for all operations unless specifically overridden in an + * operation definition. + */ + readonly defaultLatencyMetricDetails: IServiceMetricDetails; + + /** + * The default settings that are used for contributor insight + * rules. + * + * @default - No defaults are provided and must be specified per operation + * if the operation has logs that can be queried by contributor insights + */ + readonly defaultContributorInsightRuleDetails?: IContributorInsightRuleDetails; +} diff --git a/src/utilities/AvailabilityMetricType.ts b/src/utilities/AvailabilityMetricType.ts new file mode 100644 index 0000000..248ae83 --- /dev/null +++ b/src/utilities/AvailabilityMetricType.ts @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Different availability metric types + */ +export enum AvailabilityMetricType { + /** + * The success rate, i.e. (successful responses) / (successful + fault responses) * 100 + */ + SUCCESS_RATE = 'Success_Rate', + + /** + * The number of success responses as an absolute value + */ + SUCCESS_COUNT = 'Success_Count', + + /** + * The fault rate, i.e. (fault responses) / (successful + fault responses) * 100 + */ + FAULT_RATE = 'Fault_Rate', + + /** + * The number of fault responses as an absolute value + */ + FAULT_COUNT = 'Fault_Count', + + /** + * The number of requests received that resulted in either a fault or success. This + * does not include "error" responses that would be equivalent to 4xx responses. + */ + REQUEST_COUNT = 'Request_Count', +} diff --git a/src/utilities/LatencyMetricType.ts b/src/utilities/LatencyMetricType.ts new file mode 100644 index 0000000..bb44239 --- /dev/null +++ b/src/utilities/LatencyMetricType.ts @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * The type of latency metric + */ +export enum LatencyMetricType { + /** + * Successful response latency + */ + SUCCESS_LATENCY = 'Success_Latency', + + /** + * Fault response latency + */ + FAULT_LATENCY = 'Fault_Latency', +} diff --git a/src/utilities/OutlierDetectionAlgorithm.ts b/src/utilities/OutlierDetectionAlgorithm.ts new file mode 100644 index 0000000..0f96ceb --- /dev/null +++ b/src/utilities/OutlierDetectionAlgorithm.ts @@ -0,0 +1,49 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Available algorithms for performing outlier detection + */ +export enum OutlierDetectionAlgorithm { + /** + * Defines using a static value to compare skew in faults or + * high latency responses. + * + * A good default threshold for this is .7 meaning one AZ + * is responsible for 70% of the total errors or high latency responses + */ + STATIC = 'STATIC', + + /** + * Uses the chi squared statistic to determine if there is a statistically + * significant skew in fault rate or high latency distribution + * + * A normal default threshold for this is 0.05, which means there is a 5% or + * less chance of the skew in errors or high latency responses occuring + */ + CHI_SQUARED = 'CHI_SQUARED', + + /** + * Uses z-score to determine if the skew in faults or high latency respones + * exceeds a defined number of standard devations + * + * A good default threshold value for this is 2, meaning the outlier value is outside + * 95% of the normal distribution. Using 3 means the outlier is outside 99.7% of + * the normal distribution. + */ + Z_SCORE = 'Z_SCORE', + + /** + * Uses Interquartile Range Method to determine an outlier for faults or latency + * + * No threshold is required for this method and will be ignored + */ + IQR = 'IQR', + + /** + * Median Absolute Deviation (MAD) to determine an outlier for faults or latency + * + * A common default value threshold 3 + */ + MAD = 'MAD', +} diff --git a/src/utilities/StackWithDynamicSource.ts b/src/utilities/StackWithDynamicSource.ts new file mode 100644 index 0000000..1c1a8a9 --- /dev/null +++ b/src/utilities/StackWithDynamicSource.ts @@ -0,0 +1,121 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { + CfnParameter, + NestedStack, + NestedStackProps, + Stack, +} from 'aws-cdk-lib'; +import { Construct, IConstruct } from 'constructs'; + +export interface NestedStackWithDynamicSourceProps extends NestedStackProps { + assetsBucketsParameterName?: string; + assetsBucketPrefixParameterName?: string; +} + +/** + * If you are using a dynamic location for assets and pass those as a variable + * in your top stack, this allows you to continue passing those down + */ +export class StackWithDynamicSource extends NestedStack { + private static updateBaseParams( + scope: IConstruct, + props: NestedStackWithDynamicSourceProps, + ): NestedStackProps { + if (props.assetsBucketsParameterName !== undefined) { + let currentNode: IConstruct | undefined = scope; + + while (currentNode !== undefined && !(currentNode instanceof Stack)) { + currentNode = currentNode.node.scope; + } + + if (currentNode !== undefined) { + let assetsBucket: IConstruct | undefined = ( + currentNode as Stack + ).node.tryFindChild(props.assetsBucketsParameterName); + + if ( + assetsBucket !== undefined && + assetsBucket instanceof CfnParameter + ) { + if (props.parameters === undefined) { + let tmp: NestedStackProps = { + parameters: {}, + timeout: props.timeout, + notificationArns: props.notificationArns, + removalPolicy: props.removalPolicy, + description: props.description, + }; + + if (tmp.parameters !== undefined) { + tmp.parameters[props.assetsBucketsParameterName] = + assetsBucket.valueAsString; + + if (props.assetsBucketPrefixParameterName !== undefined) { + let assetsBucketPrefix: IConstruct | undefined = ( + currentNode as Stack + ).node.tryFindChild(props.assetsBucketPrefixParameterName); + if ( + assetsBucketPrefix !== undefined && + assetsBucketPrefix instanceof CfnParameter + ) { + tmp.parameters[props.assetsBucketPrefixParameterName] = + assetsBucketPrefix.valueAsString; + } + } + } + + return tmp; + } else { + props.parameters[props.assetsBucketsParameterName] = + assetsBucket.valueAsString; + + if (props.assetsBucketPrefixParameterName !== undefined) { + let assetsBucketPrefix: IConstruct | undefined = ( + currentNode as Stack + ).node.tryFindChild(props.assetsBucketPrefixParameterName); + + if ( + assetsBucketPrefix !== undefined && + assetsBucketPrefix instanceof CfnParameter + ) { + props.parameters[props.assetsBucketPrefixParameterName] = + assetsBucketPrefix.valueAsString; + } + } + } + } + } + } + + return props as NestedStackProps; + } + + constructor( + scope: Construct, + id: string, + props: NestedStackWithDynamicSourceProps, + ) { + super(scope, id, StackWithDynamicSource.updateBaseParams(scope, props)); + + let currentNode: IConstruct | undefined = this; + + while (currentNode !== undefined && !(currentNode instanceof Stack)) { + currentNode = currentNode.node.scope; + } + + if (currentNode !== undefined) { + if (props.assetsBucketsParameterName !== undefined) { + new CfnParameter(currentNode, props.assetsBucketsParameterName, { + type: 'String', + }); + } + + if (props.assetsBucketPrefixParameterName !== undefined) { + new CfnParameter(currentNode, props.assetsBucketPrefixParameterName, { + type: 'String', + }); + } + } + } +} diff --git a/test/basic-service-synth-test.ts b/test/basic-service-synth-test.ts new file mode 100644 index 0000000..a402446 --- /dev/null +++ b/test/basic-service-synth-test.ts @@ -0,0 +1,73 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as cdk from 'aws-cdk-lib'; +import { + CfnNatGateway, + SelectedSubnets, + SubnetType, + Vpc, +} from 'aws-cdk-lib/aws-ec2'; +import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { BasicServiceMultiAZObservability } from '../src/services/BasicServiceMultiAZObservability'; +import { OutlierDetectionAlgorithm } from '../src/utilities/OutlierDetectionAlgorithm'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'TestStack', { + stackName: 'test-stack', +}); + +let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', +]; + +let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + restrictDefaultSecurityGroup: false, +}); + +let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, +}); + +let natGateways: { [key: string]: CfnNatGateway[] } = {}; + +subnets.subnets.forEach((subnet, index) => { + let az = subnet.availabilityZone; + let subnetId = subnet.subnetId; + + natGateways[az] = [ + new CfnNatGateway(stack, 'AZ' + index + 'NatGateway', { + subnetId: subnetId, + }), + ]; +}); + +new BasicServiceMultiAZObservability(stack, 'MAZObservability', { + applicationLoadBalancers: [ + new ApplicationLoadBalancer(stack, 'alb', { + vpc: vpc, + crossZoneEnabled: true, + }), + ], + natGateways: natGateways, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.CHI_SQUARED, + outlierThreshold: 0.05, + faultCountPercentageThreshold: 1.0, + packetLossImpactPercentageThreshold: 0.01, + serviceName: 'test', + period: cdk.Duration.seconds(60), + createDashboard: true, + datapointsToAlarm: 3, + evaluationPeriods: 5, +}); diff --git a/test/basic-service.test.ts b/test/basic-service.test.ts new file mode 100644 index 0000000..946941b --- /dev/null +++ b/test/basic-service.test.ts @@ -0,0 +1,138 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as cdk from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import { + CfnNatGateway, + SelectedSubnets, + SubnetType, + Vpc, +} from 'aws-cdk-lib/aws-ec2'; +import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { BasicServiceMultiAZObservability } from '../src/services/BasicServiceMultiAZObservability'; +import { OutlierDetectionAlgorithm } from '../src/utilities/OutlierDetectionAlgorithm'; + +test('Basic service observability test', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + restrictDefaultSecurityGroup: false, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let natGateways: { [key: string]: CfnNatGateway[] } = {}; + + subnets.subnets.forEach((subnet, index) => { + let az = subnet.availabilityZone; + let subnetId = subnet.subnetId; + + natGateways[az] = [ + new CfnNatGateway(stack, 'AZ' + index + 'NatGateway', { + subnetId: subnetId, + }), + ]; + }); + + new BasicServiceMultiAZObservability(stack, 'MAZObservability', { + applicationLoadBalancers: [ + new ApplicationLoadBalancer(stack, 'alb', { + vpc: vpc, + crossZoneEnabled: true, + }), + ], + natGateways: natGateways, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + outlierThreshold: 0.7, + faultCountPercentageThreshold: 1.0, + packetLossImpactPercentageThreshold: 0.01, + serviceName: 'test', + period: cdk.Duration.seconds(60), + createDashboard: true, + evaluationPeriods: 5, + datapointsToAlarm: 3, + }); + + Template.fromStack(stack); +}); + +test('Basic service observability with chi-squared', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let natGateways: { [key: string]: CfnNatGateway[] } = {}; + + subnets.subnets.forEach((subnet, index) => { + let az = subnet.availabilityZone; + let subnetId = subnet.subnetId; + + natGateways[az] = [ + new CfnNatGateway(stack, 'AZ' + index + 'NatGateway', { + subnetId: subnetId, + }), + ]; + }); + + new BasicServiceMultiAZObservability(stack, 'MAZObservability', { + applicationLoadBalancers: [ + new ApplicationLoadBalancer(stack, 'alb', { + vpc: vpc, + crossZoneEnabled: true, + }), + ], + natGateways: natGateways, + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.CHI_SQUARED, + outlierThreshold: 0.05, + faultCountPercentageThreshold: 1.0, + packetLossImpactPercentageThreshold: 0.01, + serviceName: 'test', + period: cdk.Duration.seconds(60), + createDashboard: true, + evaluationPeriods: 5, + datapointsToAlarm: 3, + }); + + Template.fromStack(stack); +}); diff --git a/test/cdk-nag.test.ts b/test/cdk-nag.test.ts new file mode 100644 index 0000000..2a399a2 --- /dev/null +++ b/test/cdk-nag.test.ts @@ -0,0 +1,155 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as cdk from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { SelectedSubnets, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2'; +import { ApplicationLoadBalancer, ILoadBalancerV2 } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { ILogGroup, LogGroup } from 'aws-cdk-lib/aws-logs'; +import { AwsSolutionsChecks } from 'cdk-nag'; +import { IService } from '../src/services/IService'; +import { Service } from '../src/services/Service'; +import { ServiceMetricDetails } from '../src/services/ServiceMetricDetails'; +import { InstrumentedServiceMultiAZObservability } from '../src/services/InstrumentedServiceMultiAZObservability'; +import { OutlierDetectionAlgorithm } from '../src/utilities/OutlierDetectionAlgorithm'; +import { Template } from 'aws-cdk-lib/assertions'; +import { Aspects, Duration } from 'aws-cdk-lib'; +import { MetricDimensions } from '../src/services/props/MetricDimensions'; +import { OperationMetricDetails } from '../src/services/OperationMetricDetails'; +import { Operation } from '../src/services/Operation'; +import { IOperation } from '../src/services/IOperation'; + +test('Fully instrumented service', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: 'private_isolated_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_ISOLATED, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let logGroup: ILogGroup = new LogGroup(stack, 'Logs', {}); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: cdk.Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: cdk.Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + defaultContributorInsightRuleDetails: { + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + logGroups: [logGroup], + }, + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + outlierThreshold: 0.7, + interval: cdk.Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + Template.fromStack(stack); + Aspects.of(stack).add(new AwsSolutionsChecks({ verbose: true })); + }); \ No newline at end of file diff --git a/test/fully-instrumented-service.test.ts b/test/fully-instrumented-service.test.ts new file mode 100644 index 0000000..7fbc49e --- /dev/null +++ b/test/fully-instrumented-service.test.ts @@ -0,0 +1,779 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as cdk from 'aws-cdk-lib'; +import { Duration } from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { SelectedSubnets, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2'; +import { + ApplicationLoadBalancer, + ILoadBalancerV2, + NetworkLoadBalancer, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { ILogGroup, LogGroup } from 'aws-cdk-lib/aws-logs'; +import { InstrumentedServiceMultiAZObservability } from '../src/services/InstrumentedServiceMultiAZObservability'; +import { IOperation } from '../src/services/IOperation'; +import { IService } from '../src/services/IService'; +import { Operation } from '../src/services/Operation'; +import { OperationMetricDetails } from '../src/services/OperationMetricDetails'; +import { MetricDimensions } from '../src/services/props/MetricDimensions'; +import { Service } from '../src/services/Service'; +import { ServiceMetricDetails } from '../src/services/ServiceMetricDetails'; +import { OutlierDetectionAlgorithm } from '../src/utilities/OutlierDetectionAlgorithm'; + +test('Fully instrumented service', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: 'private_isolated_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_ISOLATED, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let logGroup: ILogGroup = new LogGroup(stack, 'Logs', {}); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + defaultContributorInsightRuleDetails: { + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + logGroups: [logGroup], + }, + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + Template.fromStack(stack); +}); + +test('Fully instrumented service with NLB', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: 'private_isolated_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_ISOLATED, + }); + + let loadBalancer: ILoadBalancerV2 = new NetworkLoadBalancer(stack, 'nlb', { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }); + + let logGroup: ILogGroup = new LogGroup(stack, 'Logs', {}); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + defaultContributorInsightRuleDetails: { + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + logGroups: [logGroup], + }, + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + Template.fromStack(stack); +}); + +test('Fully instrumented service with chi-squared', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: 'private_isolated_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_ISOLATED, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let logGroup: ILogGroup = new LogGroup(stack, 'Logs', {}); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + defaultContributorInsightRuleDetails: { + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + logGroups: [logGroup], + }, + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.CHI_SQUARED, + }); + + Template.fromStack(stack); +}); + +/* +test('Fully instrumented service adding canaries', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer(stack, 'alb', { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + }); + + let logGroup: ILogGroup = new LogGroup(stack, 'Logs', { + }); + + let rideOperation: Operation = { + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails({ + operationName: 'ride', + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + metricDimensions: new MetricDimensions({ Operation: 'ride' }, 'AZ-ID', 'Region'), + }), + serverSideLatencyMetricDetails: new OperationMetricDetails({ + operationName: 'ride', + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + metricDimensions: new MetricDimensions({ Operation: 'ride' }, 'AZ-ID', 'Region'), + }), + canaryTestProps: { + requestCount: 10, + schedule: 'rate(1 minute)', + loadBalancer: loadBalancer, + }, + }; + + let payOperation: Operation = { + operationName: 'pay', + service: service, + path: '/pay', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails({ + operationName: 'pay', + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + metricDimensions: new MetricDimensions({ Operation: 'ride' }, 'AZ-ID', 'Region'), + }), + serverSideLatencyMetricDetails: new OperationMetricDetails({ + operationName: 'pay', + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + metricDimensions: new MetricDimensions({ Operation: 'ride' }, 'AZ-ID', 'Region'), + }), + canaryTestProps: { + requestCount: 10, + schedule: 'rate(1 minute)', + loadBalancer: loadBalancer, + }, + }; + + service.addOperation(rideOperation); + service.addOperation(payOperation); + + new MultiAvailabilityZoneObservability(stack, 'MAZObservability', { + instrumentedServiceObservabilityProps: { + createDashboards: true, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + }, + }); + + Template.fromStack(stack); +});*/ + +test('Fully instrumented service adding canaries with dynamic source', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack', { + stackName: 'test-stack', + }); + + new cdk.CfnParameter(stack, 'AssetsBucket', { + type: 'string', + default: '{{.AssetsBucket}}', + }); + + new cdk.CfnParameter(stack, 'AssetsBucketPrefix', { + type: 'string', + default: '{{.AssetsBucketPrefix}}', + }); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: 'private_isolated_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_ISOLATED, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + canaryTestProps: { + requestCount: 10, + schedule: 'rate(1 minute)', + loadBalancer: loadBalancer, + networkConfiguration: { + vpc: vpc, + subnetSelection: { subnetType: SubnetType.PRIVATE_ISOLATED }, + }, + }, + }); + + let logGroup: ILogGroup = new LogGroup(stack, 'Logs', {}); + + let rideOperation: Operation = { + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }; + + let payOperation: Operation = { + operationName: 'pay', + service: service, + path: '/pay', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'pay', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'pay', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }; + + service.addOperation(rideOperation); + service.addOperation(payOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + assetsBucketParameterName: 'AssetsBucket', + assetsBucketPrefixParameterName: 'AssetsBucketPrefix', + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + //Template.fromStack(stack); + app.synth(); +}); diff --git a/test/hello.test.ts b/test/hello.test.ts deleted file mode 100644 index acbacd4..0000000 --- a/test/hello.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Hello } from '../src'; - -test('hello', () => { - expect(new Hello().sayHello()).toBe('hello, world!'); -}); \ No newline at end of file diff --git a/test/partially-instrumented-service.test.ts b/test/partially-instrumented-service.test.ts new file mode 100644 index 0000000..701f450 --- /dev/null +++ b/test/partially-instrumented-service.test.ts @@ -0,0 +1,614 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as cdk from 'aws-cdk-lib'; +import { Duration } from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { SelectedSubnets, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2'; +import { + ApplicationLoadBalancer, + ILoadBalancerV2, + NetworkLoadBalancer, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { InstrumentedServiceMultiAZObservability } from '../src/services/InstrumentedServiceMultiAZObservability'; +import { IOperation } from '../src/services/IOperation'; +import { IService } from '../src/services/IService'; +import { Operation } from '../src/services/Operation'; +import { OperationMetricDetails } from '../src/services/OperationMetricDetails'; +import { MetricDimensions } from '../src/services/props/MetricDimensions'; +import { Service } from '../src/services/Service'; +import { ServiceMetricDetails } from '../src/services/ServiceMetricDetails'; +import { OutlierDetectionAlgorithm } from '../src/utilities/OutlierDetectionAlgorithm'; + +test('Partially instrumented service', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: false, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + Template.fromStack(stack); +}); + +test('Partially instrumented service with NLB and dashboard', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let loadBalancer: ILoadBalancerV2 = new NetworkLoadBalancer(stack, 'nlb', { + vpc: vpc, + crossZoneEnabled: false, + vpcSubnets: subnets, + }); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + Template.fromStack(stack); +}); + +test('Partially instrumented service with chi-squared', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }); + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: false, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.CHI_SQUARED, + }); + + Template.fromStack(stack); +}); + +/* +test('Partially instrumented service adds canaries', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer(stack, 'alb', { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + }); + + let rideOperation: IOperation = new Operation({ + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideAvailabilityMetricDetails: new OperationMetricDetails({ + operationName: 'ride', + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + metricDimensions: new MetricDimensions({ Operation: 'ride' }, 'AZ-ID', 'Region'), + }), + serverSideLatencyMetricDetails: new OperationMetricDetails({ + operationName: 'ride', + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + metricDimensions: new MetricDimensions({ Operation: 'ride' }, 'AZ-ID', 'Region'), + }), + canaryTestProps: { + requestCount: 10, + schedule: 'rate(1 minute)', + loadBalancer: loadBalancer, + }, + }); + + service.addOperation(rideOperation); + + new MultiAvailabilityZoneObservability(stack, 'MAZObservability', { + instrumentedServiceObservabilityProps: { + createDashboards: false, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + }, + }); + + Template.fromStack(stack); +}); +*/ + +test('Partially instrumented service with canaries', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'TestStack'); + + let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', + ]; + + let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'private_with_egress_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + }); + + let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }); + + let loadBalancer: ILoadBalancerV2 = new ApplicationLoadBalancer( + stack, + 'alb', + { + vpc: vpc, + crossZoneEnabled: true, + vpcSubnets: subnets, + }, + ); + + let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + }); + + let rideOperation: Operation = { + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + canaryMetricDetails: { + canaryAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricNamespace: 'canary/metrics', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + canaryLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricNamespace: 'canary/metrics', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + }, + }; + + service.addOperation(rideOperation); + + new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + outlierThreshold: 0.7, + interval: Duration.minutes(30), + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.STATIC, + }); + + Template.fromStack(stack); +}); diff --git a/test/synth-test.ts b/test/synth-test.ts new file mode 100644 index 0000000..080d984 --- /dev/null +++ b/test/synth-test.ts @@ -0,0 +1,210 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as cdk from 'aws-cdk-lib'; +import { Aspects, Duration } from 'aws-cdk-lib'; +import { Unit } from 'aws-cdk-lib/aws-cloudwatch'; +import { SelectedSubnets, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2'; +import { + ILoadBalancerV2, + NetworkLoadBalancer, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { ILogGroup, LogGroup } from 'aws-cdk-lib/aws-logs'; +import { InstrumentedServiceMultiAZObservability } from '../src/services/InstrumentedServiceMultiAZObservability'; +import { IService } from '../src/services/IService'; +import { Operation } from '../src/services/Operation'; +import { OperationMetricDetails } from '../src/services/OperationMetricDetails'; +import { MetricDimensions } from '../src/services/props/MetricDimensions'; +import { Service } from '../src/services/Service'; +import { ServiceMetricDetails } from '../src/services/ServiceMetricDetails'; +import { OutlierDetectionAlgorithm } from '../src/utilities/OutlierDetectionAlgorithm'; +import { AwsSolutionsChecks } from 'cdk-nag'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'TestStack', { + stackName: 'test-stack', + //synthesizer: new cdk.DefaultStackSynthesizer({ + // fileAssetsBucketName: "${AssetsBucket}", + // bucketPrefix: "${AssetsBucketPrefix}" + //}) +}); +/*new cdk.CfnParameter(stack, 'AssetsBucket', { + type: 'string', + default: '{{.AssetsBucket}}', +}); +new cdk.CfnParameter(stack, 'AssetsBucketPrefix', { + type: 'string', + default: '{{.AssetsBucketPrefix}}', +});*/ +let azs: string[] = [ + cdk.Fn.ref('AWS::Region') + 'a', + cdk.Fn.ref('AWS::Region') + 'b', + cdk.Fn.ref('AWS::Region') + 'c', +]; + +let vpc = new Vpc(stack, 'vpc', { + availabilityZones: azs, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: 'private_isolated_subnets', + cidrMask: 24, + }, + ], + createInternetGateway: false, + natGateways: 0, + restrictDefaultSecurityGroup: false, +}); + +let subnets: SelectedSubnets = vpc.selectSubnets({ + subnetType: SubnetType.PRIVATE_ISOLATED, +}); + +let loadBalancer: ILoadBalancerV2 = new NetworkLoadBalancer(stack, 'alb', { + vpc: vpc, + crossZoneEnabled: false, + vpcSubnets: subnets, +}); + +let logGroup: ILogGroup = new LogGroup(stack, 'Logs', {}); + +let service: IService = new Service({ + serviceName: 'test', + availabilityZoneNames: vpc.availabilityZones, + baseUrl: 'http://www.example.com', + faultCountThreshold: 25, + period: Duration.seconds(60), + loadBalancer: loadBalancer, + defaultAvailabilityMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['Success'], + faultMetricNames: ['Fault', 'Error'], + alarmStatistic: 'Sum', + unit: Unit.COUNT, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 99.9, + faultAlarmThreshold: 0.1, + graphedFaultStatistics: ['Sum'], + graphedSuccessStatistics: ['Sum'], + }), + defaultLatencyMetricDetails: new ServiceMetricDetails({ + metricNamespace: 'front-end/metrics', + successMetricNames: ['SuccessLatency'], + faultMetricNames: ['FaultLatency'], + alarmStatistic: 'p99', + unit: Unit.MILLISECONDS, + period: Duration.seconds(60), + evaluationPeriods: 5, + datapointsToAlarm: 3, + successAlarmThreshold: 100, + faultAlarmThreshold: 1, + graphedFaultStatistics: ['p99'], + graphedSuccessStatistics: ['p50', 'p99', 'tm99'], + }), + canaryTestProps: { + requestCount: 10, + schedule: 'rate(1 minute)', + loadBalancer: loadBalancer, + networkConfiguration: { + vpc: vpc, + subnetSelection: { subnetType: SubnetType.PRIVATE_ISOLATED }, + }, + }, + defaultContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, +}); + +let rideOperation: Operation = { + operationName: 'ride', + service: service, + path: '/ride', + critical: true, + httpMethods: ['GET'], + serverSideContributorInsightRuleDetails: { + logGroups: [logGroup], + successLatencyMetricJsonPath: '$.SuccessLatency', + faultMetricJsonPath: '$.Faults', + operationNameJsonPath: '$.Operation', + instanceIdJsonPath: '$.InstanceId', + availabilityZoneIdJsonPath: '$.AZ-ID', + }, + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'ride', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + canaryTestLatencyMetricsOverride: { + successAlarmThreshold: 251, + }, +}; +let payOperation: Operation = { + operationName: 'pay', + service: service, + path: '/pay', + critical: true, + httpMethods: ['GET'], + serverSideAvailabilityMetricDetails: new OperationMetricDetails( + { + operationName: 'pay', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultAvailabilityMetricDetails, + ), + serverSideLatencyMetricDetails: new OperationMetricDetails( + { + operationName: 'pay', + metricDimensions: new MetricDimensions( + { Operation: 'ride' }, + 'AZ-ID', + 'Region', + ), + }, + service.defaultLatencyMetricDetails, + ), + canaryTestLatencyMetricsOverride: { + successAlarmThreshold: 301, + }, +}; + +service.addOperation(rideOperation); +service.addOperation(payOperation); + +new InstrumentedServiceMultiAZObservability(stack, 'MAZObservability', { + createDashboards: true, + service: service, + interval: Duration.minutes(30), + assetsBucketParameterName: 'AssetsBucket', + assetsBucketPrefixParameterName: 'AssetsBucketPrefix', + outlierDetectionAlgorithm: OutlierDetectionAlgorithm.CHI_SQUARED, +}); + +app.synth(); +Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); diff --git a/yarn.lock b/yarn.lock index 24db7bd..269d22c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,23 +4,46 @@ "@ampproject/remapping@^2.2.0": version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz" integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== dependencies: "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@aws-cdk/asset-awscli-v1@^2.2.208": + version "2.2.212" + resolved "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.212.tgz" + integrity sha512-7WqbnWUkBBcAzEdfRrpz6sCOheUPf4JEUdGvzJ4EEufXeT7v7nRbRmTvUBbQ+OQlCv9UrVj9XuFxKPjkvneGMQ== + +"@aws-cdk/asset-kubectl-v20@^2.1.3": + version "2.1.3" + resolved "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.3.tgz" + integrity sha512-cDG1w3ieM6eOT9mTefRuTypk95+oyD7P5X/wRltwmYxU7nZc3+076YEVS6vrjDKr3ADYbfn0lDKpfB1FBtO9CQ== + +"@aws-cdk/asset-node-proxy-agent-v6@^2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz" + integrity sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A== + "@aws-cdk/aws-service-spec@0.1.37": version "0.1.37" - resolved "https://registry.yarnpkg.com/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.37.tgz#39e78a07079fc276f2f2bfdb31c3c7226939a04a" + resolved "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.37.tgz" integrity sha512-WFGAvjslG8Jdj9XmzDtV4JbsWEmLj8K9pA882mc6iNK59l4ocGt2GqS4n3JuzRdzoEpzcVYqfgrqGUuV1ez7vg== dependencies: "@aws-cdk/service-spec-types" "^0.0.104" "@cdklabs/tskb" "^0.0.3" +"@aws-cdk/cloud-assembly-schema@^38.0.1": + version "38.0.1" + resolved "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-38.0.1.tgz" + integrity sha512-KvPe+NMWAulfNVwY7jenFhzhuLhLqJ/OPy5jx7wUstbjnYnjRVLpUHPU3yCjXFE0J8cuJVdx95BJ4rOs66Pi9w== + dependencies: + jsonschema "^1.4.1" + semver "^7.6.3" + "@aws-cdk/integ-runner@latest": version "2.173.1-alpha.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/integ-runner/-/integ-runner-2.173.1-alpha.0.tgz#2ca3bdd3a50ab497ecafe0e6ed5f6c0c0d609212" + resolved "https://registry.npmjs.org/@aws-cdk/integ-runner/-/integ-runner-2.173.1-alpha.0.tgz" integrity sha512-sn7OdH+qEiL5GSvekeUIm6CgfocvPD095bjpkQQNstRbL528HuJeNyPyz4tp5WHDBBiLS89N+mz/SrkzDrfAnQ== dependencies: "@aws-cdk/aws-service-spec" "0.1.37" @@ -30,19 +53,19 @@ "@aws-cdk/integ-tests-alpha@latest": version "2.173.1-alpha.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/integ-tests-alpha/-/integ-tests-alpha-2.173.1-alpha.0.tgz#d5f8e69414e5b9ba60e57fd6791e6f77f305c87a" + resolved "https://registry.npmjs.org/@aws-cdk/integ-tests-alpha/-/integ-tests-alpha-2.173.1-alpha.0.tgz" integrity sha512-Al6FYviuwTeF+E38PYndCZXisNDnHoZtMqmDrIyAFJ/cYlH2MUnzctJwoTwXMIp1XR3hA9/kgcbHlbXiUWwZGg== "@aws-cdk/service-spec-types@^0.0.104": version "0.0.104" - resolved "https://registry.yarnpkg.com/@aws-cdk/service-spec-types/-/service-spec-types-0.0.104.tgz#9f7e632ee00c5f6d2c68b3950fa118c663beef64" + resolved "https://registry.npmjs.org/@aws-cdk/service-spec-types/-/service-spec-types-0.0.104.tgz" integrity sha512-VMDgWLLmCXV81VzI9tOGZ6AQWOSQvXEGlIemaJZRmO3mK9foZtO068PFZbY92h/5BO0WCUh5JJII5g3pFqKQRQ== dependencies: "@cdklabs/tskb" "^0.0.3" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: "@babel/helper-validator-identifier" "^7.25.9" @@ -50,13 +73,13 @@ picocolors "^1.0.0" "@babel/compat-data@^7.25.9": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" - integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== + version "7.26.2" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz" + integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== dependencies: "@ampproject/remapping" "^2.2.0" @@ -75,20 +98,20 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.26.0", "@babel/generator@^7.26.3", "@babel/generator@^7.7.2": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" - integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== +"@babel/generator@^7.25.9", "@babel/generator@^7.26.0", "@babel/generator@^7.7.2": + version "7.26.2" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== dependencies: - "@babel/parser" "^7.26.3" - "@babel/types" "^7.26.3" + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" "@babel/helper-compilation-targets@^7.25.9": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz" integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== dependencies: "@babel/compat-data" "^7.25.9" @@ -99,7 +122,7 @@ "@babel/helper-module-imports@^7.25.9": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz" integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== dependencies: "@babel/traverse" "^7.25.9" @@ -107,7 +130,7 @@ "@babel/helper-module-transforms@^7.26.0": version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz" integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== dependencies: "@babel/helper-module-imports" "^7.25.9" @@ -116,161 +139,161 @@ "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== "@babel/helper-string-parser@^7.25.9": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== "@babel/helper-validator-option@^7.25.9": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz" integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== "@babel/helpers@^7.26.0": version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz" integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== dependencies: "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" - integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": + version "7.26.2" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== dependencies: - "@babel/types" "^7.26.3" + "@babel/types" "^7.26.0" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.12.13": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-import-attributes@^7.24.7": version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz" integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== dependencies: "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz" integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== dependencies: "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz" integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== dependencies: "@babel/helper-plugin-utils" "^7.25.9" "@babel/template@^7.25.9", "@babel/template@^7.3.3": version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz" integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== dependencies: "@babel/code-frame" "^7.25.9" @@ -278,144 +301,61 @@ "@babel/types" "^7.25.9" "@babel/traverse@^7.25.9": - version "7.26.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" - integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== + version "7.25.9" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.3" - "@babel/parser" "^7.26.3" + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" "@babel/template" "^7.25.9" - "@babel/types" "^7.26.3" + "@babel/types" "^7.25.9" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.3": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" - integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.3.3": + version "7.26.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== dependencies: "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" "@balena/dockerignore@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" + resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz" integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@cdklabs/tskb@^0.0.3": version "0.0.3" - resolved "https://registry.yarnpkg.com/@cdklabs/tskb/-/tskb-0.0.3.tgz#4b79846d9381eb1252ba85d5d20b7cd7d99b6ecb" + resolved "https://registry.npmjs.org/@cdklabs/tskb/-/tskb-0.0.3.tgz" integrity sha512-JR+MuD4awAXvutu7HArephXfZm09GPTaSAQUqNcJB5+ZENRm4kV+L6vJL6Tn1xHjCcHksO+HAqj3gYtm5K94vA== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" - integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/config-array@^0.19.0": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.1.tgz#734aaea2c40be22bbb1f2a9dac687c57a6a4c984" - integrity sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA== - dependencies: - "@eslint/object-schema" "^2.1.5" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/core@^0.9.0": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.9.1.tgz#31763847308ef6b7084a4505573ac9402c51f9d1" - integrity sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.17.0": - version "9.17.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.17.0.tgz#1523e586791f80376a6f8398a3964455ecc651ec" - integrity sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w== - -"@eslint/object-schema@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.5.tgz#8670a8f6258a2be5b2c620ff314a1d984c23eb2e" - integrity sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ== - -"@eslint/plugin-kit@^0.2.3": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz#2b78e7bb3755784bb13faa8932a1d994d6537792" - integrity sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg== - dependencies: - levn "^0.4.1" - -"@humanfs/core@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" - integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== - -"@humanfs/node@^0.16.6": - version "0.16.6" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" - integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== - dependencies: - "@humanfs/core" "^0.19.1" - "@humanwhocodes/retry" "^0.3.0" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" - integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== - -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== +"@hutson/parse-repository-url@^3.0.0": + version "3.0.2" + resolved "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz" + integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== "@iarna/toml@^2.2.5": version "2.2.5" - resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" + resolved "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz" integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -426,12 +366,12 @@ "@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@jest/console@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz" integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: "@jest/types" "^29.6.3" @@ -443,7 +383,7 @@ "@jest/core@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz" integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== dependencies: "@jest/console" "^29.7.0" @@ -477,7 +417,7 @@ "@jest/environment@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz" integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: "@jest/fake-timers" "^29.7.0" @@ -487,14 +427,14 @@ "@jest/expect-utils@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz" integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: jest-get-type "^29.6.3" "@jest/expect@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz" integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== dependencies: expect "^29.7.0" @@ -502,7 +442,7 @@ "@jest/fake-timers@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz" integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== dependencies: "@jest/types" "^29.6.3" @@ -514,7 +454,7 @@ "@jest/globals@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz" integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== dependencies: "@jest/environment" "^29.7.0" @@ -524,7 +464,7 @@ "@jest/reporters@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz" integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -554,14 +494,14 @@ "@jest/schemas@^29.6.3": version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" "@jest/source-map@^29.6.3": version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz" integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: "@jridgewell/trace-mapping" "^0.3.18" @@ -570,7 +510,7 @@ "@jest/test-result@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz" integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== dependencies: "@jest/console" "^29.7.0" @@ -580,7 +520,7 @@ "@jest/test-sequencer@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz" integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== dependencies: "@jest/test-result" "^29.7.0" @@ -590,7 +530,7 @@ "@jest/transform@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz" integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== dependencies: "@babel/core" "^7.11.6" @@ -611,7 +551,7 @@ "@jest/types@^29.6.3": version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: "@jest/schemas" "^29.6.3" @@ -622,9 +562,9 @@ chalk "^4.0.0" "@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + version "0.3.5" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== dependencies: "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -632,22 +572,22 @@ "@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/set-array@^1.2.1": version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -655,38 +595,30 @@ "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jsii/check-node@1.101.0": - version "1.101.0" - resolved "https://registry.yarnpkg.com/@jsii/check-node/-/check-node-1.101.0.tgz#175e5a2b9b31f232fd5df2942dacc4b20820aa93" - integrity sha512-io8u1GAF9XGp2crx0C/WGiJeUnHGw5X0du4fisbrNJHmVVFwcJbBMjbfXKWq+JSzl8fo/JV3F1LqtjsnawKA2A== - dependencies: - chalk "^4.1.2" - semver "^7.6.0" - "@jsii/check-node@1.105.0": version "1.105.0" - resolved "https://registry.yarnpkg.com/@jsii/check-node/-/check-node-1.105.0.tgz#92ffea17d1497ddf9b104088d65a363bbf9d2b64" + resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.105.0.tgz" integrity sha512-7QIzioc9//TwRjLhGMllcTBfIvJ0h6OeGVUEYdXB1DpCNtMbr8Xcj5KaeKHRAF9iRjB1d0IGzKm4A8fRUzIf+Q== dependencies: chalk "^4.1.2" semver "^7.6.3" -"@jsii/spec@^1.101.0", "@jsii/spec@^1.105.0": +"@jsii/spec@^1.105.0": version "1.105.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.105.0.tgz#500dcffc0e2258932c5b71b4a342ce4a8dfab641" + resolved "https://registry.npmjs.org/@jsii/spec/-/spec-1.105.0.tgz" integrity sha512-pLy8JyPT9Vv5UTxtM4ZHm/iisWzU62m3/dn9TPW2oANI5TLiBpjLlJw/iF+KRjYAeQnFASUfXtWTXgbN4xp9Rw== dependencies: ajv "^8.17.1" "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -694,25 +626,20 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nolyfill/is-core-module@1.0.39": - version "1.0.39" - resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" - integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== - "@oozcitak/dom@1.15.10": version "1.15.10" - resolved "https://registry.yarnpkg.com/@oozcitak/dom/-/dom-1.15.10.tgz#dca7289f2b292cff2a901ea4fbbcc0a1ab0b05c2" + resolved "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz" integrity sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ== dependencies: "@oozcitak/infra" "1.0.8" @@ -721,14 +648,14 @@ "@oozcitak/infra@1.0.8": version "1.0.8" - resolved "https://registry.yarnpkg.com/@oozcitak/infra/-/infra-1.0.8.tgz#b0b089421f7d0f6878687608301fbaba837a7d17" + resolved "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz" integrity sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg== dependencies: "@oozcitak/util" "8.3.8" "@oozcitak/url@1.0.4": version "1.0.4" - resolved "https://registry.yarnpkg.com/@oozcitak/url/-/url-1.0.4.tgz#ca8b1c876319cf5a648dfa1123600a6aa5cda6ba" + resolved "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz" integrity sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw== dependencies: "@oozcitak/infra" "1.0.8" @@ -736,67 +663,51 @@ "@oozcitak/util@8.3.8": version "8.3.8" - resolved "https://registry.yarnpkg.com/@oozcitak/util/-/util-8.3.8.tgz#10f65fe1891fd8cde4957360835e78fd1936bfdd" + resolved "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz" integrity sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ== -"@rtsao/scc@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" - integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== - "@sinclair/typebox@^0.27.8": version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@sinonjs/commons@^3.0.0": version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz" integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz" integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: "@sinonjs/commons" "^3.0.0" -"@stylistic/eslint-plugin@^2": - version "2.12.1" - resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-2.12.1.tgz#e341beb4e4315084d8be20bceeeda7d8a46f079f" - integrity sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ== - dependencies: - "@typescript-eslint/utils" "^8.13.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" - estraverse "^5.3.0" - picomatch "^4.0.2" - "@tsconfig/node10@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/babel__core@^7.1.14": version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== dependencies: "@babel/parser" "^7.20.7" @@ -807,14 +718,14 @@ "@types/babel__generator@*": version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz" integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== dependencies: "@babel/parser" "^7.1.0" @@ -822,207 +733,119 @@ "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": version "7.20.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz" integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== dependencies: "@babel/types" "^7.20.7" -"@types/estree@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - "@types/graceful-fs@^4.1.3": version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz" integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== "@types/istanbul-lib-report@*": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz" integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz" integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.5.14": version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz" integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.15": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/node@*": - version "22.10.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" - integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== - dependencies: - undici-types "~6.20.0" +"@types/minimist@^1.2.0": + version "1.2.5" + resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== -"@types/node@^18": - version "18.19.68" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.68.tgz#f4f10d9927a7eaf3568c46a6d739cc0967ccb701" - integrity sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw== +"@types/node@*", "@types/node@^18": + version "18.19.64" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz" + integrity sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ== dependencies: undici-types "~5.26.4" +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + "@types/stack-utils@^2.0.0": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== "@types/yargs-parser@*": version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== "@types/yargs@^17.0.8": version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz" integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^8": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz#0901933326aea4443b81df3f740ca7dfc45c7bea" - integrity sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/type-utils" "8.18.0" - "@typescript-eslint/utils" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - graphemer "^1.4.0" - ignore "^5.3.1" - natural-compare "^1.4.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/parser@^8": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.0.tgz#a1c9456cbb6a089730bf1d3fc47946c5fb5fe67b" - integrity sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q== - dependencies: - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/typescript-estree" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz#30b040cb4557804a7e2bcc65cf8fdb630c96546f" - integrity sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw== - dependencies: - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - -"@typescript-eslint/type-utils@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz#6f0d12cf923b6fd95ae4d877708c0adaad93c471" - integrity sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow== - dependencies: - "@typescript-eslint/typescript-estree" "8.18.0" - "@typescript-eslint/utils" "8.18.0" - debug "^4.3.4" - ts-api-utils "^1.3.0" - -"@typescript-eslint/types@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.0.tgz#3afcd30def8756bc78541268ea819a043221d5f3" - integrity sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA== - -"@typescript-eslint/typescript-estree@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz#d8ca785799fbb9c700cdff1a79c046c3e633c7f9" - integrity sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg== - dependencies: - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/utils@8.18.0", "@typescript-eslint/utils@^8.13.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.0.tgz#48f67205d42b65d895797bb7349d1be5c39a62f7" - integrity sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/typescript-estree" "8.18.0" - -"@typescript-eslint/visitor-keys@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz#7b6d33534fa808e33a19951907231ad2ea5c36dd" - integrity sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw== - dependencies: - "@typescript-eslint/types" "8.18.0" - eslint-visitor-keys "^4.2.0" - "@xmldom/xmldom@^0.9.5": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.9.6.tgz#3eaefb585c1f920e06fb35e6dcb8d505445621c0" - integrity sha512-Su4xcxR0CPGwlDHNmVP09fqET9YxbyDXHaSob6JlBH7L6reTYaeim6zbk9o08UarO0L5GTRo3uzl0D+9lSxmvw== + version "0.9.5" + resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.5.tgz" + integrity sha512-6g1EwSs8cr8JhP1iBxzyVAWM6BIDvx9Y3FZRIQiMDzgG43Pxi8YkWOZ0nQj2NHgNzgXDZbJewFx/n+YAvMZrfg== -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" acorn-walk@^8.1.1: version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: +acorn@^8.11.0, acorn@^8.4.1: version "8.14.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +add-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz" + integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== + +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + debug "^4.3.4" -ajv@^8.17.1: +ajv@^8.0.1, ajv@^8.17.1: version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: fast-deep-equal "^3.1.3" @@ -1032,31 +855,38 @@ ajv@^8.17.1: ansi-escapes@^4.2.1: version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== anymatch@^3.0.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -1064,133 +894,77 @@ anymatch@^3.0.3: arg@^4.1.0: version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-includes@^3.1.8: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz" + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== array-timsort@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + resolved "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz" integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== -array.prototype.findlastindex@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" - integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-shim-unscopables "^1.0.2" - -array.prototype.flat@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" - integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-shim-unscopables "^1.0.2" - -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== -arraybuffer.prototype.slice@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" - integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.8" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - is-array-buffer "^3.0.4" +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async@^3.2.3: version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -aws-cdk-lib@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.1.0.tgz#2497484cfd4e2eeaba99b070bbfa54486d52ae34" - integrity sha512-W607G3aSrWpawpcqzIuUYKlU+grfvkbszyqikyVYqJgMHFCCQXq0S1ynPMzfQ49CwjlwZsu4LIsPM+dNR+Yj6g== +aws-cdk-lib@2.173.1: + version "2.173.1" + resolved "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.173.1.tgz" + integrity sha512-xlbom4s3sbJDoHzIQmvunTufDQoJHQK8PTh653TE3338PysMX3liZ7efET9/FSQn50S2U3nINDGhrMvjkMBoKw== dependencies: + "@aws-cdk/asset-awscli-v1" "^2.2.208" + "@aws-cdk/asset-kubectl-v20" "^2.1.3" + "@aws-cdk/asset-node-proxy-agent-v6" "^2.1.0" + "@aws-cdk/cloud-assembly-schema" "^38.0.1" "@balena/dockerignore" "^1.0.2" case "1.6.3" - fs-extra "^9.1.0" - ignore "^5.1.9" - jsonschema "^1.4.0" - minimatch "^3.0.4" - punycode "^2.1.1" - semver "^7.3.5" + fs-extra "^11.2.0" + ignore "^5.3.2" + jsonschema "^1.4.1" + mime-types "^2.1.35" + minimatch "^3.1.2" + punycode "^2.3.1" + semver "^7.6.3" + table "^6.8.2" yaml "1.10.2" aws-cdk@2.173.1: version "2.173.1" - resolved "https://registry.yarnpkg.com/aws-cdk/-/aws-cdk-2.173.1.tgz#539c38b2d4c7aaa9b339d04ba1fcff6feaa6cfeb" + resolved "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.173.1.tgz" integrity sha512-1KWz6ZPPpBk3LyxE+iR4Gi1bbdY5N6Zj7kx/26jqvavBfZle93vT3M0jlTKI6v/bBtpYsVHTOmPFcq0fg1DfCw== optionalDependencies: fsevents "2.3.2" babel-jest@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== dependencies: "@jest/transform" "^29.7.0" @@ -1203,7 +977,7 @@ babel-jest@^29.7.0: babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1214,7 +988,7 @@ babel-plugin-istanbul@^6.1.1: babel-plugin-jest-hoist@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz" integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: "@babel/template" "^7.3.3" @@ -1224,7 +998,7 @@ babel-plugin-jest-hoist@^29.6.3: babel-preset-current-node-syntax@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz" integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -1245,7 +1019,7 @@ babel-preset-current-node-syntax@^1.0.0: babel-preset-jest@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz" integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: babel-plugin-jest-hoist "^29.6.3" @@ -1253,12 +1027,12 @@ babel-preset-jest@^29.6.3: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -1266,108 +1040,105 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" braces@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" browserslist@^4.24.0: - version "4.24.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" - integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== + version "4.24.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" update-browserslist-db "^1.1.1" bs-logger@^0.2.6: version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" bser@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" - integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.2.tgz#9dbd4daf9f5f753bec3e4c8fbb8a2ecc4de6c39b" - integrity sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg== - dependencies: - call-bind "^1.0.8" - get-intrinsic "^1.2.5" - callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001688: - version "1.0.30001688" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz#f9d3ede749f083ce0db4c13db9d828adaf2e8d0a" - integrity sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA== +caniuse-lite@^1.0.30001669: + version "1.0.30001683" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz" + integrity sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q== case@1.6.3, case@^1.6.3: version "1.6.3" - resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" + resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== +cdk-nag@^2.34.23: + version "2.34.23" + resolved "https://registry.yarnpkg.com/cdk-nag/-/cdk-nag-2.34.23.tgz#78ae2b9e20e7a92777020906e8b5da2cc0f5b622" + integrity sha512-TCvQy5uCk1QHek7UhbmFh4Uk2HveXyuQ6jfUC0ZfW5HgAMhv8+6lTY56Jq09/rm0csKDzWnMpGGAXjgLW6rg0A== + cdklabs-projen-project-types@^0.1.211: version "0.1.211" - resolved "https://registry.yarnpkg.com/cdklabs-projen-project-types/-/cdklabs-projen-project-types-0.1.211.tgz#eabda813d476b19cd3a46a19d0438701c9062e00" + resolved "https://registry.npmjs.org/cdklabs-projen-project-types/-/cdklabs-projen-project-types-0.1.211.tgz" integrity sha512-54LEo76oQZw6gtrEV5YTzxhsFbVXm0KGLhePNJGyWHghpSAMpJiTqIPyTE3luj3nur5Fp/+IK8S0YCgT+CO/fg== dependencies: yaml "^2.6.1" +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -1375,22 +1146,22 @@ chalk@^4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== ci-info@^3.2.0: version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== cjs-module-lexer@^1.0.0: version "1.4.1" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz" integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== cliui@^7.0.2: version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" @@ -1399,7 +1170,7 @@ cliui@^7.0.2: cliui@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -1408,17 +1179,17 @@ cliui@^8.0.1: clone@^2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== codemaker@^1.105.0: version "1.105.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.105.0.tgz#0ecc787a1277ad9c8e53052860f7fca663747fae" + resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.105.0.tgz" integrity sha512-eTePuHlpXNDD4P1vdEv61j+O4f7cnzTSXfbl5bsz+dp8TC95B4YKUS7A/MfR39/CMfqjPn3+KjnCubRbqv5RMA== dependencies: camelcase "^6.3.0" @@ -1427,24 +1198,43 @@ codemaker@^1.105.0: collect-v8-coverage@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + comment-json@4.2.2: version "4.2.2" - resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.2.tgz#5fae70a94e0c8f84a077bd31df5aa5269252f293" + resolved "https://registry.npmjs.org/comment-json/-/comment-json-4.2.2.tgz" integrity sha512-H8T+kl3nZesZu41zO2oNXIJWojNeK3mHxCLrsBNu6feksBXsgb+PtYz5daP5P86A0F3sz3840KVYehr04enISQ== dependencies: array-timsort "^1.0.3" @@ -1453,48 +1243,217 @@ comment-json@4.2.2: has-own-prop "^2.0.0" repeat-string "^1.6.1" +commit-and-tag-version@^12: + version "12.5.0" + resolved "https://registry.npmjs.org/commit-and-tag-version/-/commit-and-tag-version-12.5.0.tgz" + integrity sha512-Ll7rkKntH20iEFOPUT4e503Jf3J0J8jSN+aSeHuvNdtv4xmv9kSLSBg2CWsMVihwF3J2WvMHBEUSCKuDNesiTA== + dependencies: + chalk "^2.4.2" + conventional-changelog "4.0.0" + conventional-changelog-config-spec "2.1.0" + conventional-changelog-conventionalcommits "6.1.0" + conventional-recommended-bump "7.0.1" + detect-indent "^6.0.0" + detect-newline "^3.1.0" + dotgitignore "^2.1.0" + figures "^3.1.0" + find-up "^5.0.0" + git-semver-tags "^5.0.0" + jsdom "^25.0.0" + semver "^7.6.3" + w3c-xmlserializer "^5.0.0" + yaml "^2.4.1" + yargs "^17.7.2" + commonmark@^0.31.2: version "0.31.2" - resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.31.2.tgz#9d8d5439c82c9a235154d858a53e1a7965d573a5" + resolved "https://registry.npmjs.org/commonmark/-/commonmark-0.31.2.tgz" integrity sha512-2fRLTyb9r/2835k5cwcAwOj0DEc44FARnMp5veGsJ+mEAZdi52sNopLu07ZyElQUz058H43whzlERDIaaSw4rg== dependencies: entities "~3.0.1" mdurl "~1.0.1" minimist "~1.2.8" +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -constructs@10.0.5: +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +constructs@10.0.5, constructs@^10.0.0: version "10.0.5" - resolved "https://registry.yarnpkg.com/constructs/-/constructs-10.0.5.tgz#48c0402f1b98bbf5664efff74a8015e6e8a9f41e" + resolved "https://registry.npmjs.org/constructs/-/constructs-10.0.5.tgz" integrity sha512-IwOwekzrASFC3qt4ozCtV09rteAIAesuCGsW0p+uBfqHd2XcvA5CXqJjgf4eUqm6g8e/noXlVCMDWwC8GaLtrg== -constructs@^10.0.0: - version "10.4.2" - resolved "https://registry.yarnpkg.com/constructs/-/constructs-10.4.2.tgz#e875a78bef932cca12ea63965969873a25c1c132" - integrity sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA== +conventional-changelog-angular@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz" + integrity sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg== + dependencies: + compare-func "^2.0.0" + +conventional-changelog-atom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-3.0.0.tgz" + integrity sha512-pnN5bWpH+iTUWU3FaYdw5lJmfWeqSyrUkG+wyHBI9tC1dLNnHkbAOg1SzTQ7zBqiFrfo55h40VsGXWMdopwc5g== + +conventional-changelog-codemirror@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-3.0.0.tgz" + integrity sha512-wzchZt9HEaAZrenZAUUHMCFcuYzGoZ1wG/kTRMICxsnW5AXohYMRxnyecP9ob42Gvn5TilhC0q66AtTPRSNMfw== -conventional-changelog-config-spec@^2.1.0: +conventional-changelog-config-spec@2.1.0, conventional-changelog-config-spec@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz#874a635287ef8b581fd8558532bf655d4fb59f2d" + resolved "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz" integrity sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ== +conventional-changelog-conventionalcommits@6.1.0, conventional-changelog-conventionalcommits@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz" + integrity sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw== + dependencies: + compare-func "^2.0.0" + +conventional-changelog-core@^5.0.0: + version "5.0.2" + resolved "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-5.0.2.tgz" + integrity sha512-RhQOcDweXNWvlRwUDCpaqXzbZemKPKncCWZG50Alth72WITVd6nhVk9MJ6w1k9PFNBcZ3YwkdkChE+8+ZwtUug== + dependencies: + add-stream "^1.0.0" + conventional-changelog-writer "^6.0.0" + conventional-commits-parser "^4.0.0" + dateformat "^3.0.3" + get-pkg-repo "^4.2.1" + git-raw-commits "^3.0.0" + git-remote-origin-url "^2.0.0" + git-semver-tags "^5.0.0" + normalize-package-data "^3.0.3" + read-pkg "^3.0.0" + read-pkg-up "^3.0.0" + +conventional-changelog-ember@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-3.0.0.tgz" + integrity sha512-7PYthCoSxIS98vWhVcSphMYM322OxptpKAuHYdVspryI0ooLDehRXWeRWgN+zWSBXKl/pwdgAg8IpLNSM1/61A== + +conventional-changelog-eslint@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-4.0.0.tgz" + integrity sha512-nEZ9byP89hIU0dMx37JXQkE1IpMmqKtsaR24X7aM3L6Yy/uAtbb+ogqthuNYJkeO1HyvK7JsX84z8649hvp43Q== + +conventional-changelog-express@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-3.0.0.tgz" + integrity sha512-HqxihpUMfIuxvlPvC6HltA4ZktQEUan/v3XQ77+/zbu8No/fqK3rxSZaYeHYant7zRxQNIIli7S+qLS9tX9zQA== + +conventional-changelog-jquery@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-4.0.0.tgz" + integrity sha512-TTIN5CyzRMf8PUwyy4IOLmLV2DFmPtasKN+x7EQKzwSX8086XYwo+NeaeA3VUT8bvKaIy5z/JoWUvi7huUOgaw== + +conventional-changelog-jshint@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-3.0.0.tgz" + integrity sha512-bQof4byF4q+n+dwFRkJ/jGf9dCNUv4/kCDcjeCizBvfF81TeimPZBB6fT4HYbXgxxfxWXNl/i+J6T0nI4by6DA== + dependencies: + compare-func "^2.0.0" + +conventional-changelog-preset-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-3.0.0.tgz" + integrity sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA== + +conventional-changelog-writer@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz" + integrity sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ== + dependencies: + conventional-commits-filter "^3.0.0" + dateformat "^3.0.3" + handlebars "^4.7.7" + json-stringify-safe "^5.0.1" + meow "^8.1.2" + semver "^7.0.0" + split "^1.0.1" + +conventional-changelog@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-4.0.0.tgz" + integrity sha512-JbZjwE1PzxQCvm+HUTIr+pbSekS8qdOZzMakdFyPtdkEWwFvwEJYONzjgMm0txCb2yBcIcfKDmg8xtCKTdecNQ== + dependencies: + conventional-changelog-angular "^6.0.0" + conventional-changelog-atom "^3.0.0" + conventional-changelog-codemirror "^3.0.0" + conventional-changelog-conventionalcommits "^6.0.0" + conventional-changelog-core "^5.0.0" + conventional-changelog-ember "^3.0.0" + conventional-changelog-eslint "^4.0.0" + conventional-changelog-express "^3.0.0" + conventional-changelog-jquery "^4.0.0" + conventional-changelog-jshint "^3.0.0" + conventional-changelog-preset-loader "^3.0.0" + +conventional-commits-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz" + integrity sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q== + dependencies: + lodash.ismatch "^4.4.0" + modify-values "^1.0.1" + +conventional-commits-parser@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz" + integrity sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg== + dependencies: + JSONStream "^1.3.5" + is-text-path "^1.0.1" + meow "^8.1.2" + split2 "^3.2.2" + +conventional-recommended-bump@7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-7.0.1.tgz" + integrity sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA== + dependencies: + concat-stream "^2.0.0" + conventional-changelog-preset-loader "^3.0.0" + conventional-commits-filter "^3.0.0" + conventional-commits-parser "^4.0.0" + git-raw-commits "^3.0.0" + git-semver-tags "^5.0.0" + meow "^8.1.2" + convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-util-is@^1.0.3: +core-util-is@^1.0.3, core-util-is@~1.0.0: version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== create-jest@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz" integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== dependencies: "@jest/types" "^29.6.3" @@ -1507,457 +1466,214 @@ create-jest@^29.7.0: create-require@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.3, cross-spawn@^7.0.6: +cross-spawn@^7.0.3: version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== +cssstyle@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz" + integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" + rrweb-cssom "^0.7.1" -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" date-format@^4.0.14: version "4.0.14" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" + resolved "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz" integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" +dateformat@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" +decamelize-keys@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decamelize@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz" integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + dedent@^1.0.0: version "1.5.3" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz" integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - deepmerge@^4.2.2: version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== detect-indent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz" integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== +detect-indent@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz" + integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== + detect-newline@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz" integrity sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg== -detect-newline@^3.0.0: +detect-newline@^3.0.0, detect-newline@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== diff-sequences@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -doctrine@^2.1.0: +dot-prop@^5.1.0: + version "5.3.0" + resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotgitignore@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + resolved "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz" + integrity sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA== dependencies: - esutils "^2.0.2" + find-up "^3.0.0" + minimatch "^3.0.4" downlevel-dts@^0.11.0: version "0.11.0" - resolved "https://registry.yarnpkg.com/downlevel-dts/-/downlevel-dts-0.11.0.tgz#514a2d723009c5845730c1db6c994484c596ed9c" + resolved "https://registry.npmjs.org/downlevel-dts/-/downlevel-dts-0.11.0.tgz" integrity sha512-vo835pntK7kzYStk7xUHDifiYJvXxVhUapt85uk2AI94gUUAQX9HNRtrcMHNSc3YHJUEHGbYIGsM99uIbgAtxw== dependencies: semver "^7.3.2" shelljs "^0.8.3" typescript next -dunder-proto@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80" - integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-errors "^1.3.0" - gopd "^1.2.0" - ejs@^3.1.10: version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" -electron-to-chromium@^1.5.73: - version "1.5.73" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz#f32956ce40947fa3c8606726a96cd8fb5bb5f720" - integrity sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg== +electron-to-chromium@^1.5.41: + version "1.5.64" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz" + integrity sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ== emittery@^0.13.1: version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -enhanced-resolve@^5.15.0: - version "5.17.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" - integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== entities@~3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + resolved "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz" integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.22.1, es-abstract@^1.23.2, es-abstract@^1.23.5: - version "1.23.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.5.tgz#f4599a4946d57ed467515ed10e4f157289cd52fb" - integrity sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.3" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.3" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== - dependencies: - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" - integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== - dependencies: - is-callable "^1.2.7" - is-date-object "^1.0.5" - is-symbol "^1.0.4" - escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-import-resolver-node@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-import-resolver-typescript@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz#e69925936a771a9cb2de418ccebc4cdf6c0818aa" - integrity sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow== - dependencies: - "@nolyfill/is-core-module" "1.0.39" - debug "^4.3.7" - enhanced-resolve "^5.15.0" - fast-glob "^3.3.2" - get-tsconfig "^4.7.5" - is-bun-module "^1.0.2" - is-glob "^4.0.3" - stable-hash "^0.0.4" - -eslint-module-utils@^2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" - integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== - dependencies: - debug "^3.2.7" - -eslint-plugin-import@^2.31.0: - version "2.31.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7" - integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== - dependencies: - "@rtsao/scc" "^1.1.0" - array-includes "^3.1.8" - array.prototype.findlastindex "^1.2.5" - array.prototype.flat "^1.3.2" - array.prototype.flatmap "^1.3.2" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.9" - eslint-module-utils "^2.12.0" - hasown "^2.0.2" - is-core-module "^2.15.1" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.fromentries "^2.0.8" - object.groupby "^1.0.3" - object.values "^1.2.0" - semver "^6.3.1" - string.prototype.trimend "^1.0.8" - tsconfig-paths "^3.15.0" - -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== - -eslint@^9: - version "9.17.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.17.0.tgz#faa1facb5dd042172fdc520106984b5c2421bb0c" - integrity sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.9.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.17.0" - "@eslint/plugin-kit" "^0.2.3" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" - "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - -espree@^10.0.1, espree@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== - dependencies: - acorn "^8.14.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - execa@^5.0.0: version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -1972,12 +1688,12 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expect@^29.0.0, expect@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== dependencies: "@jest/expect-utils" "^29.7.0" @@ -1986,14 +1702,14 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.3.2: version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -2004,62 +1720,71 @@ fast-glob@^3.3.2: fast-json-patch@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz#85064ea1b1ebf97a3f7ad01e23f9337e72c66947" + resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz" integrity sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ== -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - fast-uri@^3.0.1: version "3.0.3" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz" integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== fastq@^1.6.0: version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== dependencies: reusify "^1.0.4" fb-watchman@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== +figures@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: - flat-cache "^4.0.0" + escape-string-regexp "^1.0.5" filelist@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== dependencies: minimatch "^5.0.1" fill-range@^7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -2067,170 +1792,150 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.7, flatted@^3.2.9: +flatted@^3.2.7: version "3.3.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz" integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== dependencies: - is-callable "^1.1.3" + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" fs-extra@^10.1.0: version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: graceful-fs "^4.2.0" jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== fsevents@^2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.6: - version "1.1.7" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.7.tgz#9df48ea5f746bf577d7e15b5da89df8952a98e7b" - integrity sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - functions-have-names "^1.2.3" - hasown "^2.0.2" - is-callable "^1.2.7" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.1, get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.6.tgz#43dd3dd0e7b49b82b2dfcad10dc824bf7fc265d5" - integrity sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA== - dependencies: - call-bind-apply-helpers "^1.0.1" - dunder-proto "^1.0.0" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - function-bind "^1.1.2" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.0.0" - get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-pkg-repo@^4.2.1: + version "4.2.1" + resolved "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz" + integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== + dependencies: + "@hutson/parse-repository-url" "^3.0.0" + hosted-git-info "^4.0.0" + through2 "^2.0.0" + yargs "^16.2.0" + get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== +git-raw-commits@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz" + integrity sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw== dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" + dargs "^7.0.0" + meow "^8.1.2" + split2 "^3.2.2" -get-tsconfig@^4.7.5: - version "4.8.1" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" - integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== +git-remote-origin-url@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz" + integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== + dependencies: + gitconfiglocal "^1.0.0" + pify "^2.3.0" + +git-semver-tags@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-5.0.1.tgz" + integrity sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA== dependencies: - resolve-pkg-maps "^1.0.0" + meow "^8.1.2" + semver "^7.0.0" + +gitconfiglocal@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz" + integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== + dependencies: + ini "^1.3.2" glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - glob-promise@^6.0.7: version "6.0.7" - resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-6.0.7.tgz#6d894212c63a42e1b86d1cbb04f4582b658308e4" + resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.7.tgz" integrity sha512-DEAe6br1w8ZF+y6KM2pzgdfhpreladtNvyNNVgSkxxkFWzXTJFXxQrJQQbAnc7kL0EUd7w5cR8u4K0P4+/q+Gw== glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -2242,7 +1947,7 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: glob@^8, glob@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" @@ -2253,111 +1958,113 @@ glob@^8, glob@^8.1.0: globals@^11.1.0: version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== - -globalthis@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +handlebars@^4.7.7: + version "4.7.8" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" -has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-own-prop@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af" + resolved "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz" integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ== -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" - integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: - dunder-proto "^1.0.0" + function-bind "^1.1.2" -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== +hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== dependencies: - has-symbols "^1.0.3" + lru-cache "^6.0.0" -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== dependencies: - function-bind "^1.1.2" + whatwg-encoding "^3.1.1" html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +https-proxy-agent@^7.0.5: + version "7.0.5" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + dependencies: + agent-base "^7.0.2" + debug "4" + human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -ignore@^5.1.9, ignore@^5.2.0, ignore@^5.3.1: +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - import-local@^3.0.2: version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz" integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" @@ -2365,260 +2072,126 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@^1.3.2: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + ini@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -internal-slot@^1.0.7: - version "1.1.0" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" - integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.2" - side-channel "^1.1.0" - interpret@^1.0.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== - dependencies: - has-tostringtag "^1.0.0" - -is-bigint@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" - integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== - dependencies: - has-bigints "^1.0.2" - -is-boolean-object@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.1.tgz#c20d0c654be05da4fbc23c562635c019e93daf89" - integrity sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng== - dependencies: - call-bound "^1.0.2" - has-tostringtag "^1.0.2" - -is-bun-module@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-1.3.0.tgz#ea4d24fdebfcecc98e81bcbcb506827fee288760" - integrity sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA== - dependencies: - semver "^7.6.3" - -is-callable@^1.1.3, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.0.tgz#6c01ffdd5e33c49c1d2abfa93334a85cb56bd81c" - integrity sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g== +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.15.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== dependencies: hasown "^2.0.2" -is-data-view@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" - integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== - dependencies: - call-bound "^1.0.2" - get-intrinsic "^1.2.6" - is-typed-array "^1.1.13" - -is-date-object@^1.0.5, is-date-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" - integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== - dependencies: - call-bound "^1.0.2" - has-tostringtag "^1.0.2" - is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-finalizationregistry@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz#d74a7d0c5f3578e34a20729e69202e578d495dc2" - integrity sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA== - dependencies: - call-bind "^1.0.7" - is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-map@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== - -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.0.tgz#5a867e9ecc3d294dda740d9f127835857af7eb05" - integrity sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw== - dependencies: - call-bind "^1.0.7" - has-tostringtag "^1.0.2" - is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-regex@^1.1.4, is-regex@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" - integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== - dependencies: - call-bound "^1.0.2" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -is-set@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== - -is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-string@^1.0.7, is-string@^1.1.0: +is-plain-obj@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.0.tgz#8cb83c5d57311bf8058bc6c8db294711641da45d" - integrity sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g== - dependencies: - call-bind "^1.0.7" - has-tostringtag "^1.0.2" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== -is-symbol@^1.0.4, is-symbol@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" - integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== - dependencies: - call-bound "^1.0.2" - has-symbols "^1.1.0" - safe-regex-test "^1.1.0" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-weakmap@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" - integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-weakref@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.0.tgz#47e3472ae95a63fa9cf25660bcf0c181c39770ef" - integrity sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q== - dependencies: - call-bound "^1.0.2" +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== +is-text-path@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz" + integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" + text-extensions "^1.0.0" -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== istanbul-lib-instrument@^5.0.4: version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" @@ -2629,7 +2202,7 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-instrument@^6.0.0: version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz" integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== dependencies: "@babel/core" "^7.23.9" @@ -2640,7 +2213,7 @@ istanbul-lib-instrument@^6.0.0: istanbul-lib-report@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz" integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -2649,7 +2222,7 @@ istanbul-lib-report@^3.0.0: istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -2658,7 +2231,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.1.3: version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz" integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== dependencies: html-escaper "^2.0.0" @@ -2666,7 +2239,7 @@ istanbul-reports@^3.1.3: jake@^10.8.5: version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + resolved "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz" integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== dependencies: async "^3.2.3" @@ -2676,7 +2249,7 @@ jake@^10.8.5: jest-changed-files@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz" integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== dependencies: execa "^5.0.0" @@ -2685,7 +2258,7 @@ jest-changed-files@^29.7.0: jest-circus@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz" integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== dependencies: "@jest/environment" "^29.7.0" @@ -2711,7 +2284,7 @@ jest-circus@^29.7.0: jest-cli@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz" integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== dependencies: "@jest/core" "^29.7.0" @@ -2728,7 +2301,7 @@ jest-cli@^29.7.0: jest-config@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz" integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== dependencies: "@babel/core" "^7.11.6" @@ -2756,7 +2329,7 @@ jest-config@^29.7.0: jest-diff@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz" integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: chalk "^4.0.0" @@ -2766,14 +2339,14 @@ jest-diff@^29.7.0: jest-docblock@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz" integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== dependencies: detect-newline "^3.0.0" jest-each@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz" integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: "@jest/types" "^29.6.3" @@ -2784,7 +2357,7 @@ jest-each@^29.7.0: jest-environment-node@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz" integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== dependencies: "@jest/environment" "^29.7.0" @@ -2796,12 +2369,12 @@ jest-environment-node@^29.7.0: jest-get-type@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== jest-haste-map@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz" integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== dependencies: "@jest/types" "^29.6.3" @@ -2820,7 +2393,7 @@ jest-haste-map@^29.7.0: jest-junit@^15: version "15.0.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-15.0.0.tgz#a47544ab42e9f8fe7ada56306c218e09e52bd690" + resolved "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz" integrity sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg== dependencies: mkdirp "^1.0.4" @@ -2830,7 +2403,7 @@ jest-junit@^15: jest-leak-detector@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz" integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== dependencies: jest-get-type "^29.6.3" @@ -2838,7 +2411,7 @@ jest-leak-detector@^29.7.0: jest-matcher-utils@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz" integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== dependencies: chalk "^4.0.0" @@ -2848,7 +2421,7 @@ jest-matcher-utils@^29.7.0: jest-message-util@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz" integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: "@babel/code-frame" "^7.12.13" @@ -2863,7 +2436,7 @@ jest-message-util@^29.7.0: jest-mock@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz" integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== dependencies: "@jest/types" "^29.6.3" @@ -2872,17 +2445,17 @@ jest-mock@^29.7.0: jest-pnp-resolver@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== jest-regex-util@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz" integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== jest-resolve-dependencies@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz" integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== dependencies: jest-regex-util "^29.6.3" @@ -2890,7 +2463,7 @@ jest-resolve-dependencies@^29.7.0: jest-resolve@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== dependencies: chalk "^4.0.0" @@ -2905,7 +2478,7 @@ jest-resolve@^29.7.0: jest-runner@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz" integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== dependencies: "@jest/console" "^29.7.0" @@ -2932,7 +2505,7 @@ jest-runner@^29.7.0: jest-runtime@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz" integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== dependencies: "@jest/environment" "^29.7.0" @@ -2960,7 +2533,7 @@ jest-runtime@^29.7.0: jest-snapshot@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz" integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== dependencies: "@babel/core" "^7.11.6" @@ -2986,7 +2559,7 @@ jest-snapshot@^29.7.0: jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: "@jest/types" "^29.6.3" @@ -2998,7 +2571,7 @@ jest-util@^29.0.0, jest-util@^29.7.0: jest-validate@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz" integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== dependencies: "@jest/types" "^29.6.3" @@ -3010,7 +2583,7 @@ jest-validate@^29.7.0: jest-watcher@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz" integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== dependencies: "@jest/test-result" "^29.7.0" @@ -3024,7 +2597,7 @@ jest-watcher@^29.7.0: jest-worker@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz" integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: "@types/node" "*" @@ -3034,7 +2607,7 @@ jest-worker@^29.7.0: jest@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== dependencies: "@jest/core" "^29.7.0" @@ -3044,32 +2617,52 @@ jest@^29.7.0: js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@3.14.1, js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" +jsdom@^25.0.0: + version "25.0.1" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== + dependencies: + cssstyle "^4.1.0" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.12" + parse5 "^7.1.2" + rrweb-cssom "^0.7.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + version "3.0.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== jsii-diff@^1.105.0: version "1.105.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.105.0.tgz#9564444e743b0b7de430b342081251dfaabc5e5d" + resolved "https://registry.npmjs.org/jsii-diff/-/jsii-diff-1.105.0.tgz" integrity sha512-peLrfFYMWCsyO2REKGIcIjMWkkPTue/D3a6CaONgYGLrZHv8hADf1btD6wsgXq77bRO0Vo9Vb1eh9ThtfhyWAQ== dependencies: "@jsii/check-node" "1.105.0" @@ -3080,9 +2673,9 @@ jsii-diff@^1.105.0: yargs "^16.2.0" jsii-docgen@^10.5.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/jsii-docgen/-/jsii-docgen-10.6.0.tgz#c0a7ffe4da8bd8ace1e8501a9e4832c93ce34487" - integrity sha512-WDLCIz7iuWpyOzBuhJQJngpWvioTLysyFQDpe0nFgKQJC2Qy7mOsLFInSL3BSDYzKcsjuwd7jyyRU9js0yONjw== + version "10.5.7" + resolved "https://registry.npmjs.org/jsii-docgen/-/jsii-docgen-10.5.7.tgz" + integrity sha512-tpnFXbDsp36xvIxrSGpOH0QkNvJBdsLVAdr3pbZA+9ANHKEuC7PemopaVzkKHX8GCxvhyyAKTCOu3kR2Yw1ClQ== dependencies: "@jsii/spec" "^1.105.0" case "^1.6.3" @@ -3095,7 +2688,7 @@ jsii-docgen@^10.5.0: jsii-pacmak@^1.105.0: version "1.105.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.105.0.tgz#b04ad5a18deda0f0af9d85bc722bb049d6097477" + resolved "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-1.105.0.tgz" integrity sha512-oTQSoCfBite5cb+gwu2N6SWoJY/vteQSiCI24flo9UqR+PdGBlFDIJGPciPS6ZEC2v20et4YSJYyHFXFGfMQsQ== dependencies: "@jsii/check-node" "1.105.0" @@ -3113,7 +2706,7 @@ jsii-pacmak@^1.105.0: jsii-reflect@^1.105.0: version "1.105.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.105.0.tgz#0bead638caccf21444eb6b9c60f53b6fa7f2d513" + resolved "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-1.105.0.tgz" integrity sha512-YzlU3VJ0sBfsEqdVFYlzCB3MhW1J/3KuGec2MwBXZ8XMrZ69OODBtnDnoSTyRsnigqCnkuYTD27JhNcEic8v9g== dependencies: "@jsii/check-node" "1.105.0" @@ -3123,10 +2716,10 @@ jsii-reflect@^1.105.0: oo-ascii-tree "^1.105.0" yargs "^16.2.0" -jsii-rosetta@^5.6.2: - version "5.7.1" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-5.7.1.tgz#8b5721ff5d6ea6572f12aa97e0657222aa75c961" - integrity sha512-joxTYrpaYYUFpDYwNhf0lHyp1i6ZfXjpl2AkSCEd6vd0FvmK00tKbWu8UnY/dA7ryI1hvDkm1MTi6f4s36ZzZg== +jsii-rosetta@~5.5.0: + version "5.5.14" + resolved "https://registry.npmjs.org/jsii-rosetta/-/jsii-rosetta-5.5.14.tgz" + integrity sha512-vEdfrZFImN5hM4BPSljc1gCtHEOu/w0X1/NFjd9q3XHUYtPB/TqYmrcdMlST8A+qTaPIGI7z1GRdz7tyA29y4g== dependencies: "@jsii/check-node" "1.105.0" "@jsii/spec" "^1.105.0" @@ -3134,166 +2727,162 @@ jsii-rosetta@^5.6.2: chalk "^4" commonmark "^0.31.2" fast-glob "^3.3.2" - jsii "~5.7.0" + jsii "~5.5.0" semver "^7.6.3" semver-intersect "^1.5.0" stream-json "^1.9.1" - typescript "~5.7" + typescript "~5.5" workerpool "^6.5.1" yargs "^17.7.2" -jsii@~5.2: - version "5.2.51" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-5.2.51.tgz#280853b48e4470eb7aad694f4e4f4bd5d4984992" - integrity sha512-F0tt1K+TaRCtp8tUl5IjlNQkRzqILAOP/amSvh1mVCvdO0RHHPpoLn3mg4l/NL7FU9kl7GzIZuhdpitAUK+73w== - dependencies: - "@jsii/check-node" "1.101.0" - "@jsii/spec" "^1.101.0" - case "^1.6.3" - chalk "^4" - downlevel-dts "^0.11.0" - fast-deep-equal "^3.1.3" - log4js "^6.9.1" - semver "^7.6.2" - semver-intersect "^1.5.0" - sort-json "^2.0.1" - spdx-license-list "^6.9.0" - typescript "~5.2" - yargs "^17.7.2" - -jsii@~5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-5.7.1.tgz#f13624c0098ddd0aac6b7f35d9430731659b1cf7" - integrity sha512-EQ9HWmzD+Xpe+yciMQUx3i3Rm1ZVVeIXADbA53KSCDDbd9tqHvZ9kTOrFXakCXcaWIOqXFro+2TLiIIx5HtJhQ== +jsii@~5.5.0: + version "5.5.10" + resolved "https://registry.npmjs.org/jsii/-/jsii-5.5.10.tgz" + integrity sha512-LsZlV3Nf2IkLtwHKs9rLW+0Lo3i7H735f4eVpQ3C5PVuWhklUIWkm6Wa4qf7/ZRK7CniNeIPhPTlL9bc2Np6ug== dependencies: "@jsii/check-node" "1.105.0" "@jsii/spec" "^1.105.0" case "^1.6.3" chalk "^4" + downlevel-dts "^0.11.0" fast-deep-equal "^3.1.3" log4js "^6.9.1" semver "^7.6.3" semver-intersect "^1.5.0" sort-json "^2.0.1" spdx-license-list "^6.9.0" - typescript "~5.7" + typescript "~5.5" yargs "^17.7.2" -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema-traverse@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^2.2.3: version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" jsonfile@^6.0.1: version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" -jsonschema@^1.4.0: +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +jsonschema@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" + resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" +kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== kleur@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== leven@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz" + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" +lodash.ismatch@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz" + integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== + lodash.memoize@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== log4js@^6.9.1: version "6.9.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" + resolved "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz" integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== dependencies: date-format "^4.0.14" @@ -3304,248 +2893,298 @@ log4js@^6.9.1: lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz" integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: semver "^7.5.3" make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.12: version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" -math-intrinsics@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.0.0.tgz#4e04bf87c85aa51e90d078dac2252b4eb5260817" - integrity sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA== +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== mdurl@~1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== +meow@^8.1.2: + version "8.1.2" + resolved "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.35: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^5.0.1: version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== dependencies: - brace-expansion "^2.0.1" + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@~1.2.8: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@~1.2.8: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mkdirp@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -ms@^2.1.1, ms@^2.1.3: +modify-values@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz" + integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + +ms@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0, normalize-package-data@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" normalize-path@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" -object-inspect@^1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.fromentries@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.groupby@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" - integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - -object.values@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" +nwsapi@^2.2.12: + version "2.2.13" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz" + integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" oo-ascii-tree@^1.105.0: version "1.105.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.105.0.tgz#203b5439eb2aa72c6ef1e844b041d455a5510779" + resolved "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.105.0.tgz" integrity sha512-fz4QixX/ImVEMbABqCJxxSwvJGfw9vfq2121RMq/qtCv7BiarY4ZPpheHheOTBvEnhqy81dyMpxiXAY8U3rPjA== -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" + p-try "^1.0.0" -p-limit@^2.2.0: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz" + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz" + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== + p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: - callsites "^3.0.0" + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" -parse-json@^5.2.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -3553,75 +3192,99 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5@^7.1.2: + version "7.2.1" + resolved "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + picocolors@^1.0.0, picocolors@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pirates@^4.0.4: version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + projen@^0.90.5: version "0.90.5" - resolved "https://registry.yarnpkg.com/projen/-/projen-0.90.5.tgz#89eb914721bbab2b2f73400d4b30d468b33e7f05" + resolved "https://registry.npmjs.org/projen/-/projen-0.90.5.tgz" integrity sha512-MfywqL+8e1gTHblreaxJOxW/Cyvm6dcY/Sg0tBwS6xCAsnlqO0qx+MQSPpPEIouVqcDNKLx8Jsd7BwTqGuQBBQ== dependencies: "@iarna/toml" "^2.2.5" @@ -3641,205 +3304,232 @@ projen@^0.90.5: prompts@^2.0.1: version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" sisteransi "^1.0.5" -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== pure-rand@^6.0.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + react-is@^18.0.0: version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz" + integrity sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw== + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz" + integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +readable-stream@^3.0.0, readable-stream@^3.0.2: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + rechoir@^0.6.2: version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== dependencies: resolve "^1.1.6" -reflect.getprototypeof@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz#c58afb17a4007b4d1118c07b92c23fca422c5d82" - integrity sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - dunder-proto "^1.0.0" - es-abstract "^1.23.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - gopd "^1.2.0" - which-builtin-type "^1.2.0" - -regexp.prototype.flags@^1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" - integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.2" + indent-string "^4.0.0" + strip-indent "^3.0.0" repeat-string@^1.6.1: version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + version "2.0.2" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.1.6, resolve@^1.20.0, resolve@^1.22.4: - version "1.22.9" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.9.tgz#6da76e4cdc57181fa4471231400e8851d0a924f3" - integrity sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A== +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rfdc@^1.3.0: version "1.4.1" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== +rrweb-cssom@^0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz" + integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== + run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" -safe-array-concat@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" - integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - get-intrinsic "^1.2.6" - has-symbols "^1.1.0" - isarray "^2.0.5" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" - integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-regex "^1.2.1" + xmlchars "^2.2.0" semver-intersect@^1.5.0: version "1.5.0" - resolved "https://registry.yarnpkg.com/semver-intersect/-/semver-intersect-1.5.0.tgz#bb3aa0ea504935410d34cf15f49818d56906bd48" + resolved "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.5.0.tgz" integrity sha512-BDjWX7yCC0haX4W/zrnV2JaMpVirwaEkGOBmgRQtH++F1N3xl9v7k9H44xfTqwl+yLNNSbMKosoVSTIiJVQ2Pw== dependencies: semver "^6.3.0" +"semver@2 || 3 || 4 || 5": + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + semver@^6.3.0, semver@^6.3.1: version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.6.3: +semver@^7.0.0, semver@^7.3.2, semver@^7.3.4, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shelljs@^0.8.3, shelljs@^0.8.5: version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" @@ -3848,70 +3538,39 @@ shelljs@^0.8.3, shelljs@^0.8.5: shx@^0.3.4: version "0.3.4" - resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02" + resolved "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz" integrity sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g== dependencies: minimist "^1.2.3" shelljs "^0.8.5" -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + sort-json@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/sort-json/-/sort-json-2.0.1.tgz#7338783bef807185dc37d5b02e3afd905d537cfb" + resolved "https://registry.npmjs.org/sort-json/-/sort-json-2.0.1.tgz" integrity sha512-s8cs2bcsQCzo/P2T/uoU6Js4dS/jnX8+4xunziNoq9qmSpZNCrRIAIvp4avsz0ST18HycV4z/7myJ7jsHWB2XQ== dependencies: detect-indent "^5.0.0" @@ -3920,7 +3579,7 @@ sort-json@^2.0.1: source-map-support@0.5.13: version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" @@ -3928,46 +3587,81 @@ source-map-support@0.5.13: source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.20" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz" + integrity sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw== + spdx-license-list@^6.9.0: version "6.9.0" - resolved "https://registry.yarnpkg.com/spdx-license-list/-/spdx-license-list-6.9.0.tgz#5543abb3a15f985a12808f642a622d2721c372ad" + resolved "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.9.0.tgz" integrity sha512-L2jl5vc2j6jxWcNCvcVj/BW9A8yGIG02Dw+IUw0ZxDM70f7Ylf5Hq39appV1BI9yxyWQRpq2TQ1qaXvf+yjkqA== +split2@^3.2.2: + version "3.2.2" + resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +split@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -stable-hash@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.4.tgz#55ae7dadc13e4b3faed13601587cec41859b42f7" - integrity sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g== - stack-utils@^2.0.3: version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" stream-chain@^2.2.5: version "2.2.5" - resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" + resolved "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz" integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== stream-json@^1.9.1: version "1.9.1" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.9.1.tgz#e3fec03e984a503718946c170db7d74556c2a187" + resolved "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz" integrity sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw== dependencies: stream-chain "^2.2.5" streamroller@^3.1.5: version "3.1.5" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" + resolved "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz" integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== dependencies: date-format "^4.0.14" @@ -3976,7 +3670,7 @@ streamroller@^3.1.5: string-length@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" @@ -3984,125 +3678,176 @@ string-length@^4.0.1: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trim@^1.2.9: - version "1.2.10" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" - integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - define-data-property "^1.1.4" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-object-atoms "^1.0.0" - has-property-descriptors "^1.0.2" - -string.prototype.trimend@^1.0.8: - version "1.0.9" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" - integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" + safe-buffer "~5.1.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^8.0.0: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^6.8.2: + version "6.8.2" + resolved "https://registry.npmjs.org/table/-/table-6.8.2.tgz" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" glob "^7.1.4" minimatch "^3.0.4" +text-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz" + integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through@2, "through@>=2.2.7 <3": + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tldts-core@^6.1.63: + version "6.1.63" + resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.63.tgz" + integrity sha512-H1XCt54xY+QPbwhTgmxLkepX0MVHu3USfMmejiCOdkMbRcP22Pn2FVF127r/GWXVDmXTRezyF3Ckvhn4Fs6j7Q== + +tldts@^6.1.32: + version "6.1.63" + resolved "https://registry.npmjs.org/tldts/-/tldts-6.1.63.tgz" + integrity sha512-YWwhsjyn9sB/1rOkSRYxvkN/wl5LFM1QDv6F2pVR+pb/jFne4EOBxHfkKVWvDIBEAw9iGOwwubHtQTm0WRT5sQ== + dependencies: + tldts-core "^6.1.63" + tmpl@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" -ts-api-utils@^1.3.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" - integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== +tough-cookie@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz" + integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== + dependencies: + tldts "^6.1.32" + +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== ts-jest@^29.2.5: version "29.2.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz" integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== dependencies: bs-logger "^0.2.6" @@ -4117,7 +3862,7 @@ ts-jest@^29.2.5: ts-node@^10.9.2: version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -4134,235 +3879,170 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsconfig-paths@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" - integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - type-detect@4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typed-array-byte-offset@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz#3fa9f22567700cc86aaf86a1e7176f74b59600f2" - integrity sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - reflect.getprototypeof "^1.0.6" - -typed-array-length@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" - integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - reflect.getprototypeof "^1.0.6" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^5.7.2, typescript@~5.7: - version "5.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" - integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== +typescript@^5.6.3: + version "5.6.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== typescript@next: - version "5.8.0-dev.20241215" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.0-dev.20241215.tgz#7bb26cc7a3e4d316c728a9e0f447b468bb11964d" - integrity sha512-NBCUoCbNZYPlRb7TiVCx6xEdntU5ElHwJZ4XfEqsU/8YAlPp/w98OJSegWjea8D/T5ZcoZDxy73JE6rzQRYBgw== + version "5.8.0-dev.20241216" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.0-dev.20241216.tgz#7b34b1a7814cd2c86069b6272f1bf106f9a67468" + integrity sha512-qNAT1xMB511YG1+ayBaUjx/+poPP/vsQlMNIu9h+lK8aIu9HKcuxVr2R2BZHt67dO+JV6vPXyLEeUYFcUG8Oqw== -typescript@~5.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@~5.5: + version "5.5.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== undici-types@~5.26.4: version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== - universalify@^0.1.0: version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== update-browserslist-db@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz" integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== dependencies: escalade "^3.2.0" picocolors "^1.1.0" -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== uuid@^8.3.2: version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-to-istanbul@^9.0.1: version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz" integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + walker@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" -which-boxed-primitive@^1.0.2, which-boxed-primitive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz#2d850d6c4ac37b95441a67890e19f3fda8b6c6d9" - integrity sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng== - dependencies: - is-bigint "^1.1.0" - is-boolean-object "^1.2.0" - is-number-object "^1.1.0" - is-string "^1.1.0" - is-symbol "^1.1.0" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -which-builtin-type@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" - integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== - dependencies: - call-bound "^1.0.2" - function.prototype.name "^1.1.6" - has-tostringtag "^1.0.2" - is-async-function "^2.0.0" - is-date-object "^1.1.0" - is-finalizationregistry "^1.1.0" - is-generator-function "^1.0.10" - is-regex "^1.2.1" - is-weakref "^1.0.2" - isarray "^2.0.5" - which-boxed-primitive "^1.1.0" - which-collection "^1.0.2" - which-typed-array "^1.1.16" - -which-collection@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" - integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== dependencies: - is-map "^2.0.3" - is-set "^2.0.3" - is-weakmap "^2.0.2" - is-weakset "^2.0.3" + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.16: - version "1.1.16" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" - integrity sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ== +whatwg-url@^14.0.0: + version "14.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz" + integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" + tr46 "^5.0.0" + webidl-conversions "^7.0.0" which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== workerpool@^6.5.1: version "6.5.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -4371,25 +4051,35 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^8.18.0: + version "8.18.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + xml@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + resolved "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== xmlbuilder2@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz#b977ef8a6fb27a1ea7ffa7d850d2c007ff343bc0" + resolved "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz" integrity sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw== dependencies: "@oozcitak/dom" "1.15.10" @@ -4399,42 +4089,57 @@ xmlbuilder2@^3.1.1: xmlbuilder@^15.1.1: version "15.1.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@1.10.2: version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^2.2.2, yaml@^2.6.1: +yaml@^2.2.2, yaml@^2.4.1, yaml@^2.6.1: version "2.6.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz" integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^16.2.0: version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" @@ -4447,7 +4152,7 @@ yargs@^16.2.0: yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -4460,10 +4165,10 @@ yargs@^17.3.1, yargs@^17.7.2: yn@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==