From 00eb8fd0fe6f46653cb078470d60c7c658a02208 Mon Sep 17 00:00:00 2001 From: letsgomeow Date: Sun, 22 Mar 2026 19:27:27 +0900 Subject: [PATCH 1/5] WIP --- .claude/.claude/CLAUDE.md | 71 ++ .claude/.claude/pr-37048-fix-proposal.md | 232 +++++++ .claude/.claude/pr-37247-fix-proposal.md | 177 +++++ .claude/CLAUDE.md | 71 ++ .claude/pr-37247-fix-proposal.md | 177 +++++ .claude/pr-37299-fix-proposal.md | 652 ++++++++++++++++++ .serena/.gitignore | 2 + .serena/.serena/.gitignore | 2 + .../memories/code_style_and_conventions.md | 54 ++ .serena/.serena/memories/project_overview.md | 48 ++ .../.serena/memories/suggested_commands.md | 45 ++ .../memories/task_completion_checklist.md | 41 ++ .serena/.serena/project.yml | 138 ++++ .../memories/code_style_and_conventions.md | 54 ++ .serena/memories/project_overview.md | 48 ++ .serena/memories/suggested_commands.md | 45 ++ .serena/memories/task_completion_checklist.md | 41 ++ .serena/project.yml | 138 ++++ 18 files changed, 2036 insertions(+) create mode 100644 .claude/.claude/CLAUDE.md create mode 100644 .claude/.claude/pr-37048-fix-proposal.md create mode 100644 .claude/.claude/pr-37247-fix-proposal.md create mode 100644 .claude/CLAUDE.md create mode 100644 .claude/pr-37247-fix-proposal.md create mode 100644 .claude/pr-37299-fix-proposal.md create mode 100644 .serena/.gitignore create mode 100644 .serena/.serena/.gitignore create mode 100644 .serena/.serena/memories/code_style_and_conventions.md create mode 100644 .serena/.serena/memories/project_overview.md create mode 100644 .serena/.serena/memories/suggested_commands.md create mode 100644 .serena/.serena/memories/task_completion_checklist.md create mode 100644 .serena/.serena/project.yml create mode 100644 .serena/memories/code_style_and_conventions.md create mode 100644 .serena/memories/project_overview.md create mode 100644 .serena/memories/suggested_commands.md create mode 100644 .serena/memories/task_completion_checklist.md create mode 100644 .serena/project.yml diff --git a/.claude/.claude/CLAUDE.md b/.claude/.claude/CLAUDE.md new file mode 100644 index 0000000000000..cfdf65a5c62f9 --- /dev/null +++ b/.claude/.claude/CLAUDE.md @@ -0,0 +1,71 @@ +# AWS CDK プロジェクト - Claude Code ガイド + +## プロジェクト概要 + +AWS Cloud Development Kit (AWS CDK) のモノレポ。TypeScript 製のオープンソース IaC フレームワーク。 + +- メインブランチ: `main` +- 言語: TypeScript (jsii) +- パッケージ管理: Yarn + Lerna + Nx + +## リポジトリ構成 + +``` +packages/ + aws-cdk-lib/ # メインライブラリ (旧 @aws-cdk/* の統合) + @aws-cdk/ # 個別パッケージ群 (alpha/experimental 含む) + @aws-cdk-testing/ # テストユーティリティ +tools/ # ビルド・開発ツール +scripts/ # CI/CD スクリプト +design/ # RFC・設計ドキュメント +``` + +## ビルド・テストコマンド + +```bash +# 全体ビルド +./build.sh + +# 特定パッケージのビルド +cd packages/aws-cdk-lib && yarn build + +# ユニットテスト +yarn test + +# 特定パッケージのテスト +cd packages/aws-cdk-lib && yarn test + +# Linter +yarn eslint + +# pkglint (パッケージ規約チェック) +yarn pkglint +``` + +## コーディング規約 + +- **jsii 互換**: 公開 API は jsii の制約に従う (Java/Python/C# 向けバインディング生成のため) +- **breaking changes 禁止**: 既存の公開 API シグネチャは変更しない。変更が必要な場合は `allowed-breaking-changes.txt` を参照 +- **Feature Flags**: 動作変更は `@aws-cdk/cx-api` の Feature Flag 経由で導入する +- **テスト必須**: ユニットテスト (`*.test.ts`) と可能であればインテグレーションテスト (`integ.*.ts`) を追加する + +## PR・コントリビューション + +- PRタイトル: `feat(module): ...` / `fix(module): ...` / `chore: ...` の conventional commits 形式 +- CHANGELOG は自動生成されるため手動編集不要 +- 新機能追加時は `README.md` のドキュメント更新も必要 + +## コード調査・編集ツール + +**Serena MCP を常に優先して使用すること。** + +- コードの調査・検索には `mcp__serena__find_symbol` / `mcp__serena__get_symbols_overview` / `mcp__serena__search_for_pattern` を使う +- シンボル単位の編集には `mcp__serena__replace_symbol_body` / `mcp__serena__insert_after_symbol` を使う +- ファイル全体を読む前に、まずシンボルレベルで必要な箇所だけ取得する +- セッション開始時に `mcp__serena__check_onboarding_performed` で初期化状態を確認する + +## 注意事項 + +- `node_modules` や `.jsii` ファイルは読まない +- jsii の型制約により、`any` や非直列化可能な型は公開 API に使えない +- インテグレーションテストはAWSアカウントが必要なため、CI以外では通常スキップ diff --git a/.claude/.claude/pr-37048-fix-proposal.md b/.claude/.claude/pr-37048-fix-proposal.md new file mode 100644 index 0000000000000..b0a978369a5c4 --- /dev/null +++ b/.claude/.claude/pr-37048-fix-proposal.md @@ -0,0 +1,232 @@ +# PR #37048 マージ修正案 + +## 背景 + +`ValidationError` のコンストラクタシグネチャが main ブランチで変更された。 + +| 旧 (HEAD/feature branch) | 新 (main) | +|---|---| +| `new ValidationError(msg: string, scope: IConstruct)` | `new ValidationError(name: string, msg: string, scope: IConstruct)` | + +`packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts` を手動マージした際に以下2種類の問題が残っている。 + +--- + +## 問題1: マージコンフリクトマーカーが残存 (2箇所) + +### 箇所A: `validateServiceConnectConfiguration` 冒頭 (約1203行目) + +``` +<<<<<<< HEAD + const hasDefaultContainer = TaskDefinition.isTaskDefinition(this.taskDefinition) && + this.taskDefinition.defaultContainer !== undefined; + + if (!hasDefaultContainer) { + throw new ValidationError('Task definition must have at least one container to enable service connect.', this); +======= + if (!this.taskDefinition.defaultContainer) { + throw new ValidationError('TaskDefinitionLeastContainer', 'Task definition must have at least one container to enable service connect.', this); +>>>>>>> main +``` + +**修正方針**: HEAD 側を採用する。`taskDefinition` はコンストラクタで `ITaskDefinition` を受け付けるように変更されており、`defaultContainer` は `TaskDefinition` クラス固有のメンバーのため `isTaskDefinition()` で型ガードする。エラー名は main 側のものを採用。 + +**修正後:** +```typescript + const hasDefaultContainer = TaskDefinition.isTaskDefinition(this.taskDefinition) && + this.taskDefinition.defaultContainer !== undefined; + + if (!hasDefaultContainer) { + throw new ValidationError('TaskDefinitionLeastContainer', 'Task definition must have at least one container to enable service connect.', this); + } +``` + +--- + +### 箇所B: `config.services.forEach` 内のポートマッピング確認 (約1240行目) + +``` +<<<<<<< HEAD + if (!(this.taskDefinition as TaskDefinition).findPortMappingByName(serviceConnectService.portMappingName)) { + throw new ValidationError(`Port Mapping '${serviceConnectService.portMappingName}' does not exist on the task definition.`, this); +======= + if (!this.taskDefinition.findPortMappingByName(serviceConnectService.portMappingName)) { + throw new ValidationError('PortMappingDoesExist', `Port Mapping '${serviceConnectService.portMappingName}' does not exist on the task definition.`, this); +>>>>>>> main +``` + +**修正方針**: HEAD 側を採用する。`findPortMappingByName` は `ITaskDefinition` にないため `as TaskDefinition` キャストは維持。エラー名は main 側のものを採用。 + +**修正後:** +```typescript + if (!(this.taskDefinition as TaskDefinition).findPortMappingByName(serviceConnectService.portMappingName)) { + throw new ValidationError('PortMappingDoesExist', `Port Mapping '${serviceConnectService.portMappingName}' does not exist on the task definition.`, this); + } +``` + +--- + +## 問題2: 旧シグネチャ (2引数) の `ValidationError` 呼び出しが残存 (6箇所) + +これらは HEAD 側のコードとして取り込まれたが、エラー名 (`name`) が欠落しているためコンパイルエラーになる。 + +### 箇所1: CODE_DEPLOY + imported TaskDefinition チェック (約887行目) + +**現状:** +```typescript + throw new ValidationError( + 'CODE_DEPLOY deployment controller requires an owned TaskDefinition. Cannot use imported task definitions.', + this, + ); +``` + +**修正後:** +```typescript + throw new ValidationError( + 'CodeDeployRequiresOwnedTaskDefinition', + 'CODE_DEPLOY deployment controller requires an owned TaskDefinition. Cannot use imported task definitions.', + this, + ); +``` + +--- + +### 箇所2: taskDefinitionRevision + imported TaskDefinition チェック (約897行目) + +**現状:** +```typescript + throw new ValidationError( + 'taskDefinitionRevision requires an owned TaskDefinition. Cannot use imported task definitions.', + this, + ); +``` + +**修正後:** +```typescript + throw new ValidationError( + 'TaskDefinitionRevisionRequiresOwnedTaskDefinition', + 'taskDefinitionRevision requires an owned TaskDefinition. Cannot use imported task definitions.', + this, + ); +``` + +--- + +### 箇所3: Service Connect + imported TaskDefinition チェック (約1231行目) + +**現状:** +```typescript + throw new ValidationError( + 'Service Connect requires an owned TaskDefinition. Cannot use imported task definitions with Service Connect.', + this, + ); +``` + +**修正後:** +```typescript + throw new ValidationError( + 'ServiceConnectRequiresOwnedTaskDefinition', + 'Service Connect requires an owned TaskDefinition. Cannot use imported task definitions with Service Connect.', + this, + ); +``` + +--- + +### 箇所4: `loadBalancerTarget` + imported TaskDefinition チェック (約1519行目) + +**現状:** +```typescript + throw new ValidationError( + 'Cannot create load balancer target from imported TaskDefinition. ' + + 'Use a concrete TaskDefinition created in this stack.', + this, + ); +``` + +**修正後:** +```typescript + throw new ValidationError( + 'LoadBalancerTargetRequiresOwnedTaskDefinition', + 'Cannot create load balancer target from imported TaskDefinition. ' + + 'Use a concrete TaskDefinition created in this stack.', + this, + ); +``` + +--- + +### 箇所5: `defaultLoadBalancerTarget` getter (約1826行目) + +**現状:** +```typescript + throw new ValidationError( + 'Cannot create default load balancer target. TaskDefinition must have a default container.', + this, + ); +``` + +**修正後:** +```typescript + throw new ValidationError( + 'DefaultLoadBalancerTargetRequiresDefaultContainer', + 'Cannot create default load balancer target. TaskDefinition must have a default container.', + this, + ); +``` + +--- + +### 箇所6: Cloud Map + imported TaskDefinition チェック (約2132行目) + +**現状:** +```typescript + throw new ValidationError('Cloud Map service discovery requires an owned TaskDefinition with container configuration', scope); +``` + +**修正後:** +```typescript + throw new ValidationError('CloudMapRequiresOwnedTaskDefinition', 'Cloud Map service discovery requires an owned TaskDefinition with container configuration', scope); +``` + +--- + +## 修正の全体フロー + +1. **コンフリクト解消**: マーカー (`<<<<<<<`, `=======`, `>>>>>>>`) を削除し、main 側のコードを採用 +2. **引数追加**: 旧 2引数呼び出し 6箇所に、先頭の `name` 文字列を追加 +3. **コンパイル確認**: `cd packages/aws-cdk-lib && yarn build` でエラーがないことを確認 +4. **テスト確認**: `cd packages/aws-cdk-lib && yarn test aws-ecs` でユニットテストが通ることを確認 + +--- + +## awslint 対応状況 + +### 完了: `prefer-ref-interface` を awslint.json に移動 + +以下3エントリを `packages/aws-cdk-lib/awslint.json` に追加し、各ファイルのインラインコメントを削除済み。 + +```json +"prefer-ref-interface:aws-cdk-lib.aws_ecs.Ec2ServiceProps.taskDefinition", +"prefer-ref-interface:aws-cdk-lib.aws_ecs.FargateServiceProps.taskDefinition", +"prefer-ref-interface:aws-cdk-lib.aws_ecs.ExternalServiceProps.taskDefinition", +``` + +### 要確認: `ref-via-interface` インラインコメントの扱い + +以下3ファイルに `[disable-awslint:ref-via-interface]` のインラインコメントが残っている。 + +- `aws-ecs/lib/ec2/ec2-service.ts` (Ec2ServiceProps.taskDefinition) +- `aws-ecs/lib/fargate/fargate-service.ts` (FargateServiceProps.taskDefinition) +- `aws-ecs/lib/external/external-service.ts` (ExternalServiceProps.taskDefinition) + +`awslint.json` には ECS 向けの `ref-via-interface` エントリが存在しないため、**インラインのままでよいか、awslint.json に移動すべきか**をメンテナに確認すること。 + +また、これら3つのプロパティはすでに `ITaskDefinition`(インターフェース)型を使っているため、`ref-via-interface` ルール(「コンクリートクラスではなくインターフェースを使うべき」)がそもそも適用されるべきか自体も確認する。 + +--- + +## 注意事項 + +- エラー名 (`name`) は他の `ValidationError` の命名規則に合わせて PascalCase のキャメルケース (単語を省略しない) を採用した +- 同一エラーIDが既存コードと重複しないよう確認済み diff --git a/.claude/.claude/pr-37247-fix-proposal.md b/.claude/.claude/pr-37247-fix-proposal.md new file mode 100644 index 0000000000000..028f5da12cb4e --- /dev/null +++ b/.claude/.claude/pr-37247-fix-proposal.md @@ -0,0 +1,177 @@ +# PR #37247 修正提案 + +**PR タイトル**: fix(eks-v2): respect securityGroup(s) in KubectlProviderOptions +**PR URL**: https://github.com/aws/aws-cdk/pull/37247 +**レビュアー**: @leonmk-aws +**参照 PR**: https://github.com/aws/aws-cdk/pull/7857 (feat(events-targets): support multiple security groups for an ECS task) + +--- + +## 参照 PR (#7857) との比較・検証結果 + +PR #7857 は同じパターン(`securityGroup` → `securityGroups` への移行)の先行実装。以下を確認した。 + +| 項目 | PR #7857 の実装 | 今回の方針 | 判定 | +|------|----------------|-----------|------| +| `@deprecated` タグ | `@deprecated use securityGroups instead` | 同様 | ✅ 妥当 | +| 両方指定時の挙動 | `throw new Error(...)` | `throw new ValidationError(...)` | ✅ 妥当(現代の CDK 規約に合わせ upgrade) | +| `awslint.json` への移動 | 対応なし(旧 PR のため) | awslint.json に追加 | ✅ メンテナ指示通り | + +**特記事項**: PR #7857 は 2020 年の旧コードのため `new Error()` を使用しているが、現在の aws-eks-v2 コードベースでは `cluster.ts` の実装に倣い `ValidationError(message, this)` を使うのが正しい。 + +--- + +## メンテナから指摘された修正点 + +### 1. `securityGroup` を `@deprecated` にする + +**コメント**: 「`securityGroups` オプションを導入するなら、`securityGroup` を deprecated にすべき」 +**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 50付近) + +#### 現在のコード +```typescript +/** + * A security group to use for `kubectl` execution. + * + * If you specify both `securityGroup` and `securityGroups`, a warning will be issued + * and `securityGroups` will be used. + * + * @default - If not specified, the k8s endpoint is expected to be accessible + * publicly. + */ +readonly securityGroup?: ec2.ISecurityGroup; +``` + +#### 修正後 +```typescript +/** + * A security group to use for `kubectl` execution. + * + * @default - If not specified, the k8s endpoint is expected to be accessible + * publicly. + * @deprecated Use `securityGroups` instead. + */ +readonly securityGroup?: ec2.ISecurityGroup; +``` + +> **補足**: `@deprecated` を付けると JSDoc の説明に `securityGroups` オプションへの移行を促すメッセージを含める。両プロパティを同時指定したときの説明は `securityGroup` 側のコメントから削除する(#3 の変更と合わせて不要になる)。 + +--- + +### 2. `[disable-awslint:prefer-ref-interface]` を `awslint.json` に移動する + +**コメント**: 「lint 抑制コメントをコード内に書かず、`packages/aws-cdk-lib/awslint.json` の既存エントリ群(line 1926付近)へ移動すること」 +**対象ファイル**: +- `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 62付近) — 削除 +- `packages/aws-cdk-lib/awslint.json` (line 1926付近) — 追加 + +#### kubectl-provider.ts の修正(コメント削除) +```typescript +/** + * Security groups to use for `kubectl` execution. + * + * @default - If not specified, the k8s endpoint is expected to be accessible + * publicly. + */ +readonly securityGroups?: ec2.ISecurityGroup[]; +``` + +#### awslint.json への追加 +既存の `prefer-ref-interface` エントリ群(`KubectlProviderProps.securityGroup` など)の直後に追加する: + +```json +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", +``` + +追加位置のイメージ(awslint.json line 1926付近): +```json +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.securityGroup", +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", ← 追加 +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.cluster", +``` + +--- + +### 3. 両プロパティが同時指定された場合は warning ではなく error を throw する + +**コメント**: 「両プロパティを同時に指定した場合はサポート外であり、ユーザーの設定ミスを示す可能性があるため、warning ではなく throw すべき」 +**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 209付近) + +#### 現在のコード +```typescript +if (props.securityGroups && props.securityGroups.length > 0) { + securityGroups = props.securityGroups; + + // Issue warning if both properties are specified + if (props.securityGroup) { + Annotations.of(this).addWarningV2('@aws-cdk/aws-eks-v2:securityGroupConflict', + 'Both securityGroup and securityGroups are specified. Using securityGroups.'); + } +} else if (props.securityGroup) { +``` + +#### 修正後 +`ValidationError` を throw するよう変更する(CDK では `ValidationError` を使う): + +```typescript +if (props.securityGroups && props.securityGroups.length > 0 && props.securityGroup) { + throw new ValidationError( + 'SecurityGroupConflict', + 'Cannot specify both "securityGroup" and "securityGroups". Use "securityGroups" only.', + this, + ); +} + +if (props.securityGroups && props.securityGroups.length > 0) { + securityGroups = props.securityGroups; +} else if (props.securityGroup) { +``` + +> **補足**: +> - `ValidationError` は `../../core/lib/errors` からインポートする(`cluster.ts` で同様に使われている) +> - `kubectl-provider.ts` の現在のインポート文に `ValidationError` が含まれていないため、追加が必要 +> - PR #7857 ではエラーチェックをコンストラクタ冒頭(`privateSubnets` 分岐の外側)で行っているが、今回は `privateSubnets` がない場合はセキュリティグループ自体が無視されるため、**チェックは `privateSubnets` 分岐の内側 or 外側どちらでも機能的には同じ**。ただし PR #7857 に倣い、コンストラクタの冒頭(`super()` の直後)で早期チェックする方が明快 +> - `securityGroup` JSDoc の両プロパティ同時指定に関する記述も削除する(動作が変わるため) + +--- + +## ユニットテストの修正 + +`packages/aws-cdk-lib/aws-eks-v2/test/cluster.test.ts` + +### 3 の変更に伴い、「両方指定したときのテスト」を変更する + +#### 現在のテスト(warning を期待) +```typescript +it('warns when both securityGroup and securityGroups are specified', () => { + // ... + expect(Annotations.fromStack(stack).hasWarning(...)); +}); +``` + +#### 修正後(throw を期待) +```typescript +it('throws when both securityGroup and securityGroups are specified', () => { + expect(() => { + // securityGroup と securityGroups を両方指定するコード + }).toThrow('Cannot specify both "securityGroup" and "securityGroups"'); +}); +``` + +--- + +## 修正作業の順序 + +1. `kubectl-provider.ts`: + - `securityGroup` に `@deprecated` タグを追加 + - `[disable-awslint:prefer-ref-interface]` コメントを削除 + - warning → throw に変更 +2. `awslint.json`: + - `prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups` を追加 +3. `cluster.test.ts`: + - warning テスト → throw テストに変更 +4. ビルド・テスト確認: + ```bash + cd packages/aws-cdk-lib && yarn build && yarn test aws-eks-v2 + cd packages/aws-cdk-lib && yarn lint + ``` diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000000000..cfdf65a5c62f9 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,71 @@ +# AWS CDK プロジェクト - Claude Code ガイド + +## プロジェクト概要 + +AWS Cloud Development Kit (AWS CDK) のモノレポ。TypeScript 製のオープンソース IaC フレームワーク。 + +- メインブランチ: `main` +- 言語: TypeScript (jsii) +- パッケージ管理: Yarn + Lerna + Nx + +## リポジトリ構成 + +``` +packages/ + aws-cdk-lib/ # メインライブラリ (旧 @aws-cdk/* の統合) + @aws-cdk/ # 個別パッケージ群 (alpha/experimental 含む) + @aws-cdk-testing/ # テストユーティリティ +tools/ # ビルド・開発ツール +scripts/ # CI/CD スクリプト +design/ # RFC・設計ドキュメント +``` + +## ビルド・テストコマンド + +```bash +# 全体ビルド +./build.sh + +# 特定パッケージのビルド +cd packages/aws-cdk-lib && yarn build + +# ユニットテスト +yarn test + +# 特定パッケージのテスト +cd packages/aws-cdk-lib && yarn test + +# Linter +yarn eslint + +# pkglint (パッケージ規約チェック) +yarn pkglint +``` + +## コーディング規約 + +- **jsii 互換**: 公開 API は jsii の制約に従う (Java/Python/C# 向けバインディング生成のため) +- **breaking changes 禁止**: 既存の公開 API シグネチャは変更しない。変更が必要な場合は `allowed-breaking-changes.txt` を参照 +- **Feature Flags**: 動作変更は `@aws-cdk/cx-api` の Feature Flag 経由で導入する +- **テスト必須**: ユニットテスト (`*.test.ts`) と可能であればインテグレーションテスト (`integ.*.ts`) を追加する + +## PR・コントリビューション + +- PRタイトル: `feat(module): ...` / `fix(module): ...` / `chore: ...` の conventional commits 形式 +- CHANGELOG は自動生成されるため手動編集不要 +- 新機能追加時は `README.md` のドキュメント更新も必要 + +## コード調査・編集ツール + +**Serena MCP を常に優先して使用すること。** + +- コードの調査・検索には `mcp__serena__find_symbol` / `mcp__serena__get_symbols_overview` / `mcp__serena__search_for_pattern` を使う +- シンボル単位の編集には `mcp__serena__replace_symbol_body` / `mcp__serena__insert_after_symbol` を使う +- ファイル全体を読む前に、まずシンボルレベルで必要な箇所だけ取得する +- セッション開始時に `mcp__serena__check_onboarding_performed` で初期化状態を確認する + +## 注意事項 + +- `node_modules` や `.jsii` ファイルは読まない +- jsii の型制約により、`any` や非直列化可能な型は公開 API に使えない +- インテグレーションテストはAWSアカウントが必要なため、CI以外では通常スキップ diff --git a/.claude/pr-37247-fix-proposal.md b/.claude/pr-37247-fix-proposal.md new file mode 100644 index 0000000000000..028f5da12cb4e --- /dev/null +++ b/.claude/pr-37247-fix-proposal.md @@ -0,0 +1,177 @@ +# PR #37247 修正提案 + +**PR タイトル**: fix(eks-v2): respect securityGroup(s) in KubectlProviderOptions +**PR URL**: https://github.com/aws/aws-cdk/pull/37247 +**レビュアー**: @leonmk-aws +**参照 PR**: https://github.com/aws/aws-cdk/pull/7857 (feat(events-targets): support multiple security groups for an ECS task) + +--- + +## 参照 PR (#7857) との比較・検証結果 + +PR #7857 は同じパターン(`securityGroup` → `securityGroups` への移行)の先行実装。以下を確認した。 + +| 項目 | PR #7857 の実装 | 今回の方針 | 判定 | +|------|----------------|-----------|------| +| `@deprecated` タグ | `@deprecated use securityGroups instead` | 同様 | ✅ 妥当 | +| 両方指定時の挙動 | `throw new Error(...)` | `throw new ValidationError(...)` | ✅ 妥当(現代の CDK 規約に合わせ upgrade) | +| `awslint.json` への移動 | 対応なし(旧 PR のため) | awslint.json に追加 | ✅ メンテナ指示通り | + +**特記事項**: PR #7857 は 2020 年の旧コードのため `new Error()` を使用しているが、現在の aws-eks-v2 コードベースでは `cluster.ts` の実装に倣い `ValidationError(message, this)` を使うのが正しい。 + +--- + +## メンテナから指摘された修正点 + +### 1. `securityGroup` を `@deprecated` にする + +**コメント**: 「`securityGroups` オプションを導入するなら、`securityGroup` を deprecated にすべき」 +**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 50付近) + +#### 現在のコード +```typescript +/** + * A security group to use for `kubectl` execution. + * + * If you specify both `securityGroup` and `securityGroups`, a warning will be issued + * and `securityGroups` will be used. + * + * @default - If not specified, the k8s endpoint is expected to be accessible + * publicly. + */ +readonly securityGroup?: ec2.ISecurityGroup; +``` + +#### 修正後 +```typescript +/** + * A security group to use for `kubectl` execution. + * + * @default - If not specified, the k8s endpoint is expected to be accessible + * publicly. + * @deprecated Use `securityGroups` instead. + */ +readonly securityGroup?: ec2.ISecurityGroup; +``` + +> **補足**: `@deprecated` を付けると JSDoc の説明に `securityGroups` オプションへの移行を促すメッセージを含める。両プロパティを同時指定したときの説明は `securityGroup` 側のコメントから削除する(#3 の変更と合わせて不要になる)。 + +--- + +### 2. `[disable-awslint:prefer-ref-interface]` を `awslint.json` に移動する + +**コメント**: 「lint 抑制コメントをコード内に書かず、`packages/aws-cdk-lib/awslint.json` の既存エントリ群(line 1926付近)へ移動すること」 +**対象ファイル**: +- `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 62付近) — 削除 +- `packages/aws-cdk-lib/awslint.json` (line 1926付近) — 追加 + +#### kubectl-provider.ts の修正(コメント削除) +```typescript +/** + * Security groups to use for `kubectl` execution. + * + * @default - If not specified, the k8s endpoint is expected to be accessible + * publicly. + */ +readonly securityGroups?: ec2.ISecurityGroup[]; +``` + +#### awslint.json への追加 +既存の `prefer-ref-interface` エントリ群(`KubectlProviderProps.securityGroup` など)の直後に追加する: + +```json +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", +``` + +追加位置のイメージ(awslint.json line 1926付近): +```json +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.securityGroup", +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", ← 追加 +"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.cluster", +``` + +--- + +### 3. 両プロパティが同時指定された場合は warning ではなく error を throw する + +**コメント**: 「両プロパティを同時に指定した場合はサポート外であり、ユーザーの設定ミスを示す可能性があるため、warning ではなく throw すべき」 +**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 209付近) + +#### 現在のコード +```typescript +if (props.securityGroups && props.securityGroups.length > 0) { + securityGroups = props.securityGroups; + + // Issue warning if both properties are specified + if (props.securityGroup) { + Annotations.of(this).addWarningV2('@aws-cdk/aws-eks-v2:securityGroupConflict', + 'Both securityGroup and securityGroups are specified. Using securityGroups.'); + } +} else if (props.securityGroup) { +``` + +#### 修正後 +`ValidationError` を throw するよう変更する(CDK では `ValidationError` を使う): + +```typescript +if (props.securityGroups && props.securityGroups.length > 0 && props.securityGroup) { + throw new ValidationError( + 'SecurityGroupConflict', + 'Cannot specify both "securityGroup" and "securityGroups". Use "securityGroups" only.', + this, + ); +} + +if (props.securityGroups && props.securityGroups.length > 0) { + securityGroups = props.securityGroups; +} else if (props.securityGroup) { +``` + +> **補足**: +> - `ValidationError` は `../../core/lib/errors` からインポートする(`cluster.ts` で同様に使われている) +> - `kubectl-provider.ts` の現在のインポート文に `ValidationError` が含まれていないため、追加が必要 +> - PR #7857 ではエラーチェックをコンストラクタ冒頭(`privateSubnets` 分岐の外側)で行っているが、今回は `privateSubnets` がない場合はセキュリティグループ自体が無視されるため、**チェックは `privateSubnets` 分岐の内側 or 外側どちらでも機能的には同じ**。ただし PR #7857 に倣い、コンストラクタの冒頭(`super()` の直後)で早期チェックする方が明快 +> - `securityGroup` JSDoc の両プロパティ同時指定に関する記述も削除する(動作が変わるため) + +--- + +## ユニットテストの修正 + +`packages/aws-cdk-lib/aws-eks-v2/test/cluster.test.ts` + +### 3 の変更に伴い、「両方指定したときのテスト」を変更する + +#### 現在のテスト(warning を期待) +```typescript +it('warns when both securityGroup and securityGroups are specified', () => { + // ... + expect(Annotations.fromStack(stack).hasWarning(...)); +}); +``` + +#### 修正後(throw を期待) +```typescript +it('throws when both securityGroup and securityGroups are specified', () => { + expect(() => { + // securityGroup と securityGroups を両方指定するコード + }).toThrow('Cannot specify both "securityGroup" and "securityGroups"'); +}); +``` + +--- + +## 修正作業の順序 + +1. `kubectl-provider.ts`: + - `securityGroup` に `@deprecated` タグを追加 + - `[disable-awslint:prefer-ref-interface]` コメントを削除 + - warning → throw に変更 +2. `awslint.json`: + - `prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups` を追加 +3. `cluster.test.ts`: + - warning テスト → throw テストに変更 +4. ビルド・テスト確認: + ```bash + cd packages/aws-cdk-lib && yarn build && yarn test aws-eks-v2 + cd packages/aws-cdk-lib && yarn lint + ``` diff --git a/.claude/pr-37299-fix-proposal.md b/.claude/pr-37299-fix-proposal.md new file mode 100644 index 0000000000000..50c6c8cb1448b --- /dev/null +++ b/.claude/pr-37299-fix-proposal.md @@ -0,0 +1,652 @@ +# Issue #37299 修正提案 + +## 概要 + +`eks.ServiceAccount` を `IdentityType.POD_IDENTITY` で使用する際に、既存の IAM ロールを `role` プロパティで渡せるようにする。 + +- 対象パッケージ: `aws-eks-v2`(まず対応)、のちに `aws-eks` も同様に対応 +- 対象ファイル: + - `packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts` + - `packages/aws-cdk-lib/aws-eks-v2/test/service-account.test.ts` + +--- + +## 型の方針: `IRole` ではなく `IRoleRef` を使う + +### 根拠 + +CDK の昨今のスタイルでは、ロールを受け取るプロパティには `IRole` ではなく `IRoleRef` を使うことが推奨されている。 + +- `IRoleRef` は `{ roleRef: { roleArn: string; roleName: string } }` を持つ軽量インターフェース +- `IRole extends IRoleRef` のため、`iam.Role` / `iam.Role.fromRoleArn()` など既存の `IRole` はすべて `IRoleRef` として渡せる +- 後方互換性を損なわず、より広い型を受け入れられる + +### 参考実装 + +`aws-stepfunctions-tasks` の `BedrockCreateModelCustomizationJob` が全く同じパターンを採用している: + +``` +packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/create-model-customization-job.ts +``` + +- `props.role?: iam.IRoleRef` で受け取る +- 内部フィールド `private _role: iam.IRoleRef` で保持 +- `public get role(): iam.IRole` で公開(duck typing でダウンキャスト) +- ARN 参照は `this._role.roleRef.roleArn` を使う +- ロール決定ロジックを private メソッドに集約(自動生成フォールバックパターン) + +### 自動生成フォールバックパターンの役割 + +`props.role` が渡されたか否かの分岐を private メソッド 1 か所に集約し、コンストラクタ冒頭でその結果を `_role` に格納する。これにより、その後の ARN 参照箇所(`CfnPodIdentityAssociation`・`KubernetesManifest` アノテーション)で条件分岐が不要になる。 + +```typescript +// コンストラクタ内: 1回だけ決定する +this._role = this.resolvePodIdentityRole(); + +// 以降: 条件分岐なしで参照できる +roleArn: this._role.roleRef.roleArn +``` + +--- + +## 変更 1: `import` 文の更新 + +**ファイル**: `service-account.ts` L5 + +`IRoleRef` を追加で import する: + +```typescript +import type { AddToPrincipalPolicyResult, IPrincipal, IRole, IRoleRef, PrincipalPolicyFragment } from '../../aws-iam'; +``` + +--- + +## 変更 2: `ServiceAccountOptions` に `role` プロパティ追加 + +**ファイル**: `service-account.ts`(`ServiceAccountOptions` インターフェース末尾) + +型は `IRoleRef`(`IRole` ではない): + +```typescript +/** + * An existing IAM role to associate with this service account via Pod Identity. + * Only valid when `identityType` is `IdentityType.POD_IDENTITY`. + * + * When specified, the provided role is used instead of auto-generating one. + * The caller is responsible for configuring the trust policy of the role correctly. + * + * @default - a new IAM role is created automatically + */ +readonly role?: IRoleRef; +``` + +--- + +## 変更 3: クラスフィールドの変更 + +**ファイル**: `service-account.ts`(`ServiceAccount` クラス内) + +`public readonly role: IRole` を **getter** に変更し、内部保持用フィールドを追加する: + +```typescript +// 変更: readonly プロパティから getter へ +// (TypeScript の API 互換性は維持される。消費者側から見た挙動は同じ) +public get role(): IRole { ... } // 変更 3-b で定義 + +// 追加: IRSA 時のロールを保持 +private _irsaRole?: IRole; + +// 追加: POD_IDENTITY 時のロールを IRoleRef として保持 +private _podIdentityRole?: IRoleRef; +``` + +> **補足**: `BedrockCreateModelCustomizationJob` では `_role` を常に `IRoleRef` で保持しているが、`ServiceAccount` は IRSA / POD_IDENTITY の両モードがあるため、モード別にフィールドを分けて持つ設計とする。 + +### 変更 3-b: `role` getter の追加 + +```typescript +/** + * The role which is linked to the service account. + * + * Note: If an L1 construct (e.g. CfnRole) was provided as the `role` option, + * accessing this property will throw an error. Use `roleRef.roleArn` via the + * CfnPodIdentityAssociation directly in that case. + */ +public get role(): IRole { + if (this._podIdentityRole !== undefined) { + // POD_IDENTITY の場合: IRole かどうか確認してキャスト + if ('grant' in this._podIdentityRole) { + return this._podIdentityRole as IRole; + } + throw new ValidationError( + 'ServiceAccountRole', + 'The provided role is not an instance of IRole. ' + + 'Cannot access role grants when using an L1 construct (e.g. CfnRole) as the role.', + this, + ); + } + // IRSA の場合: 常に IRole として保持されている + return this._irsaRole!; +} +``` + +**この設計の意図**: + +| 操作 | L2 `Role` を渡した場合 | L1 `CfnRole` を渡した場合 | +|---|---|---| +| ServiceAccount の作成 | ✅ 成功 | ✅ 成功 | +| PodIdentityAssociation の作成 | ✅ 成功 | ✅ 成功 | +| `sa.role.addManagedPolicy(...)` | ✅ 成功 | ❌ getter でエラー | +| `sa.role.grant(...)` | ✅ 成功 | ❌ getter でエラー | + +コンストラクタでエラーにすると L1 を渡した場合に ServiceAccount 自体が作れなくなるが、`CfnPodIdentityAssociation` の作成には `roleRef.roleArn` があれば十分なため、getter でのみエラーにするほうが意図した設計となる。 + +--- + +## 変更 4: コンストラクタのバリデーション追加 + +**ファイル**: `service-account.ts`(DNS バリデーションの直後) + +```typescript +if (props.role !== undefined && props.identityType !== IdentityType.POD_IDENTITY) { + throw new Error( + 'The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.', + ); +} +``` + +--- + +## 変更 5: コンストラクタの `principal` / `role` 決定ロジック置き換え + +**ファイル**: `service-account.ts` L161〜211(`let principal: IPrincipal;` から `this.role = role;` まで) + +```typescript +let role: IRole; +if (props.identityType !== IdentityType.POD_IDENTITY) { + /* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role. + * See documentation: https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html + */ + const conditions = new CfnJson(this, 'ConditionJson', { + value: { + [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:aud`]: 'sts.amazonaws.com', + [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:sub`]: `system:serviceaccount:${this.serviceAccountNamespace}:${this.serviceAccountName}`, + }, + }); + const principal = new OpenIdConnectPrincipal(cluster.openIdConnectProvider).withConditions({ + StringEquals: conditions, + }); + role = new Role(this, 'Role', { assumedBy: principal }); +} else { + // EKS Pod Identity does not support Fargate + // TODO: raise an error when using Fargate + + // ロール決定ロジックを private メソッドに集約(自動生成フォールバックパターン) + this._podIdentityRole = this.resolvePodIdentityRole(); + + // ensure the pod identity agent + cluster.eksPodIdentityAgent; + + // associate this service account with the pod role for the cluster + new CfnPodIdentityAssociation(this, 'Association', { + clusterName: cluster.clusterName, + namespace: props.namespace ?? 'default', + roleArn: this._podIdentityRole.roleRef.roleArn, + serviceAccount: this.serviceAccountName, + }); + +} + +// IRSA の場合のみ _irsaRole に格納(POD_IDENTITY は getter 経由で _podIdentityRole から返す) +if (props.identityType !== IdentityType.POD_IDENTITY) { + this._irsaRole = role; +} +``` + +--- + +## 変更 6: private メソッド `resolvePodIdentityRole` の追加 + +**ファイル**: `service-account.ts`(クラス末尾) + +```typescript +/** + * Resolves the IAM role to use for Pod Identity. + * Returns the provided role if specified, otherwise auto-generates one. + */ +private resolvePodIdentityRole(): IRoleRef { + if (this.props.role) { + return this.props.role; + } + const role = new Role(this, 'Role', { + assumedBy: new ServicePrincipal('pods.eks.amazonaws.com'), + }); + // EKS Pod Identities requires both assumed role actions otherwise it would fail. + role.assumeRolePolicy!.addStatements(new PolicyStatement({ + actions: ['sts:AssumeRole', 'sts:TagSession'], + principals: [new ServicePrincipal('pods.eks.amazonaws.com')], + })); + return role; +} +``` + +> **注意**: `props` をメソッド内から参照するため、`props` をクラスフィールドとして保持する必要がある(`BedrockCreateModelCustomizationJob` と同様に `private readonly props` として保持)。あるいはコンストラクタ引数を private メソッドに渡す形でも可。 + +--- + +## 変更 7: インテグレーションテスト追加 + +**ファイル**: `packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts`(新規作成) + +`aws-eks`(v1)の `integ.eks-pod-identities.ts` を参考に、v2 向けに作成する。 +既存の auto-generate ケース(Case 1)に加え、外部ロールを `role` prop で渡すケース(Case 2)を同一 Stack 内に含める。 + +```typescript +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import type { StackProps } from 'aws-cdk-lib'; +import { App, Stack } from 'aws-cdk-lib'; +import { getClusterVersionConfig } from './integ-tests-kubernetes-version'; +import * as eks from 'aws-cdk-lib/aws-eks-v2'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +/** + * After deployment, verify with: + * + * $ kubectl get po + * + * You should see two pods named `demo` and `demo-external-role` in Completed STATUS. + * + * $ kubectl logs -f demo + * $ kubectl logs -f demo-external-role + * + * Both pods should show a log message with UserId, Account and Arn, + * indicating they are running with the correct eks pod identity defined with ServiceAccount. + * The `demo-external-role` pod uses an externally-created IAM role passed via the `role` prop. + */ + +class EksPodIdentitiesStack extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'VPC', { natGateways: 1 }); + + const cluster = new eks.Cluster(this, 'Cluster', { + vpc, + defaultCapacity: 1, + ...getClusterVersionConfig(this), + }); + + // Case 1: auto-generated IAM role (existing behavior) + const sa = new eks.ServiceAccount(this, 'ServiceAccount', { + cluster, + name: 'test-sa', + namespace: 'default', + identityType: eks.IdentityType.POD_IDENTITY, + }); + + sa.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + + const pod = cluster.addManifest('demopod', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'demo' }, + spec: { + serviceAccountName: sa.serviceAccountName, + containers: [ + { + name: 'demo', + image: 'public.ecr.aws/amazonlinux/amazonlinux:2023', + command: ['/bin/bash', '-c', 'yum update -y && yum install -y awscli && aws sts get-caller-identity'], + }, + ], + }, + }); + pod.node.addDependency(sa); + + // Case 2: externally-created IAM role passed via the `role` prop + // The trust policy must allow pods.eks.amazonaws.com with sts:AssumeRole and sts:TagSession. + const externalRole = new iam.Role(this, 'ExternalRole', { + assumedBy: new iam.SessionTagsPrincipal( + new iam.ServicePrincipal('pods.eks.amazonaws.com'), + ), + }); + externalRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + + const saWithExternalRole = new eks.ServiceAccount(this, 'ServiceAccountWithExternalRole', { + cluster, + name: 'test-sa-external-role', + namespace: 'default', + identityType: eks.IdentityType.POD_IDENTITY, + role: externalRole, + }); + + const podWithExternalRole = cluster.addManifest('demopod-external-role', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'demo-external-role' }, + spec: { + serviceAccountName: saWithExternalRole.serviceAccountName, + containers: [ + { + name: 'demo', + image: 'public.ecr.aws/amazonlinux/amazonlinux:2023', + command: ['/bin/bash', '-c', 'yum update -y && yum install -y awscli && aws sts get-caller-identity'], + }, + ], + }, + }); + podWithExternalRole.node.addDependency(saWithExternalRole); + } +} + +const app = new App({ + postCliContext: { + '@aws-cdk/aws-lambda:useCdkManagedLogGroup': false, + '@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy': false, + }, +}); + +const stack = new EksPodIdentitiesStack(app, 'eks-pod-identities-v2'); + +new IntegTest(app, 'integ-eks-pod-identities-v2', { + testCases: [stack], +}); +``` + +### ポイント + +- **Case 1**: 既存動作の確認。`role` prop なしで auto-generate されたロールが正しく機能すること +- **Case 2**: 新機能の確認。`SessionTagsPrincipal(ServicePrincipal('pods.eks.amazonaws.com'))` で事前作成したロールを `role` prop で渡し、Pod Identity Association が正しく作成されること +- 両ケースとも Pod が `Completed` になり `aws sts get-caller-identity` の出力が確認できることが検証の基準 + +--- + +## 変更 8: ユニットテスト追加 + +**ファイル**: `service-account.test.ts` の `describe('Service Account with eks.IdentityType.POD_IDENTITY')` ブロック内に追記 + +```typescript +test('uses provided role when role prop is specified', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN + new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + role: existingRole, + }); + const t = Template.fromStack(stack); + + // THEN + // provided role ARN が PodIdentityAssociation に使われること + t.hasResourceProperties('AWS::EKS::PodIdentityAssociation', { + ClusterName: { Ref: 'ClusterEB0386A7' }, + Namespace: 'default', + RoleArn: { 'Fn::GetAtt': ['ExistingRole...', 'Arn'] }, + ServiceAccount: 'stackmyserviceaccount58b9529e', + }); + // ServiceAccount 用の IAM Role が auto-generate されないこと(ExistingRole のみ存在) + t.resourceCountIs('AWS::IAM::Role', 1); + // Pod Identity Agent addon が作られること + t.hasResourceProperties('AWS::EKS::Addon', { + AddonName: 'eks-pod-identity-agent', + }); +}); + +test('throws if role is specified without POD_IDENTITY identity type', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN / THEN + expect(() => new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.IRSA, + role: existingRole, + })).toThrow('The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.'); +}); + +test('throws if role is specified with default identity type (IRSA)', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN / THEN + expect(() => new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + // identityType 未指定 → デフォルト IRSA + role: existingRole, + })).toThrow('The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.'); +}); + +test('sa.role getter returns the provided L2 role', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN + const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + role: existingRole, + }); + + // THEN: getter が提供した L2 ロールをそのまま返すこと + expect(sa.role).toBe(existingRole); +}); + +test('ServiceAccount creation succeeds when L1 CfnRole is provided, but sa.role getter throws', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const cfnRole = new iam.CfnRole(stack, 'CfnRole', { + assumeRolePolicyDocument: { + Statement: [{ + Action: ['sts:AssumeRole', 'sts:TagSession'], + Effect: 'Allow', + Principal: { Service: 'pods.eks.amazonaws.com' }, + }], + }, + }); + + // WHEN: ServiceAccount の作成自体は成功すること(コンストラクタでエラーにならない) + const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + role: cfnRole, + }); + + // THEN: PodIdentityAssociation は正常に作成されること + Template.fromStack(stack).hasResourceProperties('AWS::EKS::PodIdentityAssociation', { + ClusterName: { Ref: 'ClusterEB0386A7' }, + Namespace: 'default', + ServiceAccount: 'stackmyserviceaccount58b9529e', + }); + + // THEN: sa.role にアクセスすると getter でエラーになること + expect(() => sa.role).toThrow( + 'The provided role is not an instance of IRole.', + ); +}); + +test('sa.role getter returns auto-generated role when no role prop is provided with POD_IDENTITY', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + }); + + // THEN: getter が IRole を返すこと + expect(sa.role).toBeDefined(); + // addToPrincipalPolicy が呼べること(IRole として機能すること) + expect(() => sa.addToPrincipalPolicy( + new iam.PolicyStatement({ actions: ['s3:GetObject'], resources: ['*'] }), + )).not.toThrow(); +}); +``` + +--- + +## 変更のポイント + +| 項目 | 詳細 | +|---|---| +| **`props.role` の型** | `IRoleRef`(`IRole` より広い型。`IRole` は `IRoleRef` を継承しているため渡せる) | +| **`this.role` の実装** | `readonly` プロパティから getter に変更。TypeScript API 互換性は維持される | +| **後方互換性** | `role` 未指定時は従来通りロールを自動生成する | +| **バリデーション** | IRSA(またはデフォルト)で `role` を指定するとコンストラクタ内でエラー | +| **既存ロール使用時の trust policy** | 変更しない(ユーザー責任) | +| **`CfnPodIdentityAssociation`** | 既存ロール・自動生成どちらでも必ず作成される | +| **`cluster.eksPodIdentityAgent`** | 既存ロール・自動生成どちらでも必ず呼ばれる | +| **ARN 参照** | `role.roleArn` ではなく `_podIdentityRole.roleRef.roleArn` を使う | +| **L1 CfnRole を渡した場合** | ServiceAccount・PodIdentityAssociation の作成は成功。`sa.role` アクセス時のみ getter でエラー | + +--- + +## 変更 9: README 追記 + +**ファイル**: `packages/aws-cdk-lib/aws-eks-v2/README.md` + +### 追記箇所 + +Service Accounts セクション(L830〜)の末尾、`#### Migrating from the deprecated eks.OpenIdConnectProvider` セクションの直前に追記する。 + +v2 README には Pod Identities セクションが完全に存在しないため、新設する。 + +### 追記内容 + +```markdown +### Pod Identities + +[Amazon EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) is a feature that simplifies how +Kubernetes applications running on Amazon EKS can obtain AWS IAM credentials. It provides a way to associate an IAM role with a +Kubernetes service account, allowing pods to retrieve temporary AWS credentials without the need +to manage IAM roles and policies directly. + +By default, `ServiceAccount` creates an `OpenIdConnectProvider` for +[IRSA (IAM roles for service accounts)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) if +`identityType` is `undefined` or `IdentityType.IRSA`. + +You may opt in to Amazon EKS Pod Identities as below: + +```ts +declare const cluster: eks.Cluster; + +new eks.ServiceAccount(this, 'ServiceAccount', { + cluster, + name: 'test-sa', + namespace: 'default', + identityType: eks.IdentityType.POD_IDENTITY, +}); +``` + +When you create the `ServiceAccount` with the `identityType` set to `POD_IDENTITY`, +the `ServiceAccount` construct will perform the following actions behind the scenes: + +1. It will create an IAM role with the necessary trust policy to allow the `pods.eks.amazonaws.com` principal to assume the role. + This trust policy grants the EKS service the permission to retrieve temporary AWS credentials on behalf of the pods using this service account. + +2. It will enable the "Amazon EKS Pod Identity Agent" add-on on the EKS cluster. This add-on is responsible for managing the temporary + AWS credentials and making them available to the pods. + +3. It will create an association between the IAM role and the Kubernetes service account. This association allows the pods using this + service account to obtain the temporary AWS credentials from the associated IAM role. + +#### Using an existing IAM role with Pod Identity + +If you want to manage IAM roles centrally (e.g., in a dedicated `IamConstruct`) or reuse an existing role created via +`iam.Role.fromRoleArn()`, you can pass it to `ServiceAccount` via the `role` property. + +The `role` property accepts any `IRoleRef`, including `iam.Role`, `iam.Role.fromRoleArn()`, and L1 `iam.CfnRole`. +**This option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.** + +The caller is responsible for configuring the trust policy of the role correctly. For Pod Identity, the role must allow +`pods.eks.amazonaws.com` to perform `sts:AssumeRole` and `sts:TagSession`. + +```ts +import * as iam from 'aws-cdk-lib/aws-iam'; +declare const cluster: eks.Cluster; + +// Create and manage the IAM role separately +const appRole = new iam.Role(this, 'AppRole', { + assumedBy: new iam.SessionTagsPrincipal( + new iam.ServicePrincipal('pods.eks.amazonaws.com'), + ), +}); +appRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + +// Pass the existing role to ServiceAccount +new eks.ServiceAccount(this, 'AppServiceAccount', { + cluster, + name: 'app-sa', + namespace: 'production', + identityType: eks.IdentityType.POD_IDENTITY, + role: appRole, +}); +``` + +When `role` is specified, the auto-generation of an IAM role is skipped. +The provided role's ARN is used directly in the `PodIdentityAssociation`. + +> **Note:** If you pass an L1 construct (`iam.CfnRole`) as the `role`, the `ServiceAccount` and `PodIdentityAssociation` +> are created successfully. However, accessing `serviceAccount.role` to call methods such as `grant()` or +> `addManagedPolicy()` will throw an error, as those methods are only available on L2 `IRole` instances. +``` + +--- + +## 注意事項 + +- `IRoleRef` の import を追加する必要がある(現在は `IRole` のみ import 済み) +- `ServiceAccountProps extends ServiceAccountOptions` のため、`ServiceAccountProps` にも自動的に `role` が追加される +- `props` を private メソッドから参照するため、コンストラクタ引数の扱いを調整する(メソッドに引数として渡すか、クラスフィールドとして保持するか、実装時に判断) +- `aws-eks`(v1)パッケージへの同様の対応は別途実施 + +--- + +## 検討経緯: IRSA への `role` プロパティ対応について + +### 検討内容 + +POD_IDENTITY だけでなく IRSA でも既存 IAM ロールを受け入れるべきか検討した。 + +### IRSA における実装上の特性 + +`service-account.ts` の IRSA ブロックを確認すると、Trust Policy の条件式は以下のようにコンストラクタ内で動的に生成されている: + +```typescript +const conditions = new CfnJson(this, 'ConditionJson', { + value: { + [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:aud`]: 'sts.amazonaws.com', + [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:sub`]: + `system:serviceaccount:${this.serviceAccountNamespace}:${this.serviceAccountName}`, + }, +}); +``` + +つまり、OIDC Issuer URL・namespace・SA 名はいずれも `ServiceAccount` コンストラクト内で解決される値であり、**クラスターが存在して初めて確定する**。 + +### 結論: 今回は POD_IDENTITY のみ対応 + +| 観点 | POD_IDENTITY | IRSA | +|---|---|---| +| Trust Policy に必要な情報 | 静的(`pods.eks.amazonaws.com`) | 動的(OIDC Issuer URL・namespace・SA 名) | +| クラスター外での事前ロール作成 | ✅ 容易 | ❌ クラスター作成後でないと OIDC Issuer URL が確定しない | +| 原 issue の要求スコープ | ✅ 明示的に要求 | ❌ 要求外 | + +IRSA の場合、ユーザーが外で IAM ロールを作成するには OIDC Issuer URL を別途取得する必要があり、CDK を使うメリットが薄れる。実装難易度は低いが、**ユーザーにとっての実用性が限られる**ため、今回のスコープから外した。 + +IRSA 対応が必要な場合は別途 issue/PR として扱う。 diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000000000..2e510aff5855b --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1,2 @@ +/cache +/project.local.yml diff --git a/.serena/.serena/.gitignore b/.serena/.serena/.gitignore new file mode 100644 index 0000000000000..2e510aff5855b --- /dev/null +++ b/.serena/.serena/.gitignore @@ -0,0 +1,2 @@ +/cache +/project.local.yml diff --git a/.serena/.serena/memories/code_style_and_conventions.md b/.serena/.serena/memories/code_style_and_conventions.md new file mode 100644 index 0000000000000..b6c8d35e92c93 --- /dev/null +++ b/.serena/.serena/memories/code_style_and_conventions.md @@ -0,0 +1,54 @@ +# コードスタイルと規約 + +## TypeScript / jsii 規約 +- **jsii 互換**: 公開 API は jsii の型制約に従う (Java/Python/C# バインディング生成のため) + - `any` 型を公開 API に使わない + - 非直列化可能な型 (関数型など) を公開 API に使わない + - `interface` は `I` プレフィックス (例: `IBucket`, `ILambdaFunction`) +- **命名規則**: + - クラス: PascalCase (例: `Bucket`, `BucketPolicy`) + - インターフェース: `I` + PascalCase (例: `IBucket`) + - メソッド/プロパティ: camelCase + - 定数/Enum: PascalCase +- **型ヒント**: TypeScript なので型を明示する。戻り値型も省略しない +- **JSDoc**: 公開 API には JSDoc コメントを必ず記述する (`/** ... */`) + - `@attribute` タグ: CloudFormation 属性にマッピングされるプロパティ + - `@deprecated` タグ: 廃止予定 API + +## インポートスタイル +```typescript +// 同一パッケージ内: 相対インポート +import { BucketPolicy } from './bucket-policy'; +import type { IBucketNotificationDestination } from './destination'; + +// aws-cdk-lib 内の他モジュール: 相対インポート (../../) +import * as iam from '../../aws-iam'; +import * as kms from '../../aws-kms'; +import { Stack, Resource } from '../../core'; + +// 型のみのインポートには import type を使用 +import type { IGrantable } from '../../aws-iam'; +``` + +## 自動生成ファイル +- `*.generated.ts` (例: `s3.generated.ts`) は CloudFormation spec から自動生成。手動編集禁止。 +- `*.d.ts`, `*.js` はビルド成果物。手動編集禁止。 + +## Breaking Changes の扱い +- 公開 API シグネチャの変更は原則禁止 +- 変更が必要な場合は `allowed-breaking-changes.txt` を確認 +- 動作変更は `cx-api` の Feature Flag 経由で導入 (既存動作はデフォルトで維持) + +## テストスタイル +- フレームワーク: Jest +- テストファイル: `test/*.test.ts` +- `Template.fromStack(stack).hasResourceProperties(...)` パターンでアサーション +- インテグレーションテスト: `integ.*.ts` ファイル (AWS アカウント必要) + +## PR コミットメッセージ形式 +conventional commits 形式: +``` +feat(aws-s3): add new bucket property +fix(aws-lambda): fix memory size validation +chore: update dependencies +``` diff --git a/.serena/.serena/memories/project_overview.md b/.serena/.serena/memories/project_overview.md new file mode 100644 index 0000000000000..10fca2cf8a08b --- /dev/null +++ b/.serena/.serena/memories/project_overview.md @@ -0,0 +1,48 @@ +# AWS CDK プロジェクト概要 + +## 目的 +AWS Cloud Development Kit (AWS CDK) - TypeScript 製のオープンソース IaC フレームワーク。 +CloudFormation テンプレートをプログラマブルに生成できる。jsii により Java/Python/C#/.NET 向けバインディングも生成。 + +## 技術スタック +- 言語: TypeScript (jsii) +- パッケージ管理: Yarn (v1) + Lerna + Nx +- テスト: Jest +- Linter: ESLint + pkglint + awslint +- ビルド: cdk-build-tools (tools/@aws-cdk/cdk-build-tools) +- Node.js: >= 20.x + +## リポジトリ構成 +``` +packages/ + aws-cdk-lib/ # メインライブラリ (全サービスモジュール統合) + aws-s3/ # 例: S3 モジュール (lib/, test/, index.ts) + aws-lambda/ + core/ # CDK コア (Construct, Stack, App など) + cx-api/ # Cloud Assembly API / Feature Flags + ... # 200以上の AWS サービスモジュール + @aws-cdk/ # 個別パッケージ (alpha/experimental) + @aws-cdk-testing/ # テストユーティリティ + aws-cdk/ # CDK CLI + cdk/ # CDK CLI エイリアス +tools/ + @aws-cdk/cdk-build-tools/ # ビルドツール + @aws-cdk/pkglint/ # パッケージ規約チェック + @aws-cdk/eslint-config/ # ESLint 設定 + @aws-cdk/spec2cdk/ # CloudFormation spec → CDK コード生成 +scripts/ # CI/CD スクリプト +design/ # RFC・設計ドキュメント +``` + +## モジュール構造 (例: aws-cdk-lib/aws-s3) +``` +aws-s3/ + lib/ + bucket.ts # メイン実装 + bucket-policy.ts + s3.generated.ts # CloudFormation spec から自動生成 (編集不可) + test/ + bucket.test.ts # Jest ユニットテスト + index.ts # 公開 API のエクスポート + README.md +``` diff --git a/.serena/.serena/memories/suggested_commands.md b/.serena/.serena/memories/suggested_commands.md new file mode 100644 index 0000000000000..2ca20d6f45600 --- /dev/null +++ b/.serena/.serena/memories/suggested_commands.md @@ -0,0 +1,45 @@ +# 主要コマンド一覧 + +## セットアップ +```bash +yarn install # 依存パッケージのインストール +``` + +## ビルド +```bash +./build.sh # 全体ビルド (時間がかかる) +cd packages/aws-cdk-lib && yarn build # aws-cdk-lib だけビルド +yarn watch # ウォッチモード (変更時に再ビルド) +``` + +## テスト +```bash +# 特定モジュールのユニットテスト +cd packages/aws-cdk-lib && yarn test aws-lambda +cd packages/aws-cdk-lib && yarn test aws-s3 + +# 特定テストファイル +cd packages/aws-cdk-lib && yarn test aws-s3/test/bucket.test.ts + +# インテグレーションテスト (AWS アカウント必要) +cd packages/@aws-cdk-testing/framework-integ +yarn integ test/aws-lambda/test/integ.lambda.js --update-on-failed +``` + +## Lint / フォーマット +```bash +cd packages/aws-cdk-lib && yarn lint # ESLint +yarn pkglint # パッケージ規約チェック +``` + +## その他 +```bash +yarn build+test # ビルド + テスト +./scripts/run-rosetta.sh # README サンプルコードのコンパイル確認 +``` + +## Nx (モノレポ向け) +```bash +npx nx build aws-cdk-lib # Nx 経由でビルド +npx nx test aws-cdk-lib # Nx 経由でテスト +``` diff --git a/.serena/.serena/memories/task_completion_checklist.md b/.serena/.serena/memories/task_completion_checklist.md new file mode 100644 index 0000000000000..f0cd329f1e43e --- /dev/null +++ b/.serena/.serena/memories/task_completion_checklist.md @@ -0,0 +1,41 @@ +# タスク完了時のチェックリスト + +## コード変更後に必ず実施すること + +1. **ビルド確認** + ```bash + cd packages/aws-cdk-lib && yarn build + # または変更したパッケージのディレクトリで + yarn build + ``` + +2. **ユニットテスト実行** + ```bash + cd packages/aws-cdk-lib && yarn test <モジュール名> + # 例: yarn test aws-s3 + ``` + +3. **Lint チェック** + ```bash + cd packages/aws-cdk-lib && yarn lint + ``` + +4. **pkglint チェック** (package.json 変更時) + ```bash + yarn pkglint + ``` + +5. **README サンプルコード確認** (README 変更時) + ```bash + ./scripts/run-rosetta.sh + ``` + +6. **自動生成ファイルの確認** + - `*.generated.ts` を手動編集していないか確認 + - `.jsii` ファイルがビルドで正しく生成されているか確認 + +## PR 作成前チェック +- [ ] 新機能には JSDoc コメントがある +- [ ] 公開 API の breaking change がない (または `allowed-breaking-changes.txt` に記載) +- [ ] ユニットテストが追加・更新されている +- [ ] コミットメッセージが conventional commits 形式 diff --git a/.serena/.serena/project.yml b/.serena/.serena/project.yml new file mode 100644 index 0000000000000..deb8941047e4f --- /dev/null +++ b/.serena/.serena/project.yml @@ -0,0 +1,138 @@ +# the name by which the project can be referenced within Serena +project_name: "aws-cdk" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- typescript + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# line ending convention to use when writing source files. +# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) +# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. +line_ending: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: + +# list of regex patterns which, when matched, mark a memory entry as read‑only. +# Extends the list from the global configuration, merging the two lists. +read_only_memory_patterns: [] diff --git a/.serena/memories/code_style_and_conventions.md b/.serena/memories/code_style_and_conventions.md new file mode 100644 index 0000000000000..b6c8d35e92c93 --- /dev/null +++ b/.serena/memories/code_style_and_conventions.md @@ -0,0 +1,54 @@ +# コードスタイルと規約 + +## TypeScript / jsii 規約 +- **jsii 互換**: 公開 API は jsii の型制約に従う (Java/Python/C# バインディング生成のため) + - `any` 型を公開 API に使わない + - 非直列化可能な型 (関数型など) を公開 API に使わない + - `interface` は `I` プレフィックス (例: `IBucket`, `ILambdaFunction`) +- **命名規則**: + - クラス: PascalCase (例: `Bucket`, `BucketPolicy`) + - インターフェース: `I` + PascalCase (例: `IBucket`) + - メソッド/プロパティ: camelCase + - 定数/Enum: PascalCase +- **型ヒント**: TypeScript なので型を明示する。戻り値型も省略しない +- **JSDoc**: 公開 API には JSDoc コメントを必ず記述する (`/** ... */`) + - `@attribute` タグ: CloudFormation 属性にマッピングされるプロパティ + - `@deprecated` タグ: 廃止予定 API + +## インポートスタイル +```typescript +// 同一パッケージ内: 相対インポート +import { BucketPolicy } from './bucket-policy'; +import type { IBucketNotificationDestination } from './destination'; + +// aws-cdk-lib 内の他モジュール: 相対インポート (../../) +import * as iam from '../../aws-iam'; +import * as kms from '../../aws-kms'; +import { Stack, Resource } from '../../core'; + +// 型のみのインポートには import type を使用 +import type { IGrantable } from '../../aws-iam'; +``` + +## 自動生成ファイル +- `*.generated.ts` (例: `s3.generated.ts`) は CloudFormation spec から自動生成。手動編集禁止。 +- `*.d.ts`, `*.js` はビルド成果物。手動編集禁止。 + +## Breaking Changes の扱い +- 公開 API シグネチャの変更は原則禁止 +- 変更が必要な場合は `allowed-breaking-changes.txt` を確認 +- 動作変更は `cx-api` の Feature Flag 経由で導入 (既存動作はデフォルトで維持) + +## テストスタイル +- フレームワーク: Jest +- テストファイル: `test/*.test.ts` +- `Template.fromStack(stack).hasResourceProperties(...)` パターンでアサーション +- インテグレーションテスト: `integ.*.ts` ファイル (AWS アカウント必要) + +## PR コミットメッセージ形式 +conventional commits 形式: +``` +feat(aws-s3): add new bucket property +fix(aws-lambda): fix memory size validation +chore: update dependencies +``` diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md new file mode 100644 index 0000000000000..10fca2cf8a08b --- /dev/null +++ b/.serena/memories/project_overview.md @@ -0,0 +1,48 @@ +# AWS CDK プロジェクト概要 + +## 目的 +AWS Cloud Development Kit (AWS CDK) - TypeScript 製のオープンソース IaC フレームワーク。 +CloudFormation テンプレートをプログラマブルに生成できる。jsii により Java/Python/C#/.NET 向けバインディングも生成。 + +## 技術スタック +- 言語: TypeScript (jsii) +- パッケージ管理: Yarn (v1) + Lerna + Nx +- テスト: Jest +- Linter: ESLint + pkglint + awslint +- ビルド: cdk-build-tools (tools/@aws-cdk/cdk-build-tools) +- Node.js: >= 20.x + +## リポジトリ構成 +``` +packages/ + aws-cdk-lib/ # メインライブラリ (全サービスモジュール統合) + aws-s3/ # 例: S3 モジュール (lib/, test/, index.ts) + aws-lambda/ + core/ # CDK コア (Construct, Stack, App など) + cx-api/ # Cloud Assembly API / Feature Flags + ... # 200以上の AWS サービスモジュール + @aws-cdk/ # 個別パッケージ (alpha/experimental) + @aws-cdk-testing/ # テストユーティリティ + aws-cdk/ # CDK CLI + cdk/ # CDK CLI エイリアス +tools/ + @aws-cdk/cdk-build-tools/ # ビルドツール + @aws-cdk/pkglint/ # パッケージ規約チェック + @aws-cdk/eslint-config/ # ESLint 設定 + @aws-cdk/spec2cdk/ # CloudFormation spec → CDK コード生成 +scripts/ # CI/CD スクリプト +design/ # RFC・設計ドキュメント +``` + +## モジュール構造 (例: aws-cdk-lib/aws-s3) +``` +aws-s3/ + lib/ + bucket.ts # メイン実装 + bucket-policy.ts + s3.generated.ts # CloudFormation spec から自動生成 (編集不可) + test/ + bucket.test.ts # Jest ユニットテスト + index.ts # 公開 API のエクスポート + README.md +``` diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md new file mode 100644 index 0000000000000..2ca20d6f45600 --- /dev/null +++ b/.serena/memories/suggested_commands.md @@ -0,0 +1,45 @@ +# 主要コマンド一覧 + +## セットアップ +```bash +yarn install # 依存パッケージのインストール +``` + +## ビルド +```bash +./build.sh # 全体ビルド (時間がかかる) +cd packages/aws-cdk-lib && yarn build # aws-cdk-lib だけビルド +yarn watch # ウォッチモード (変更時に再ビルド) +``` + +## テスト +```bash +# 特定モジュールのユニットテスト +cd packages/aws-cdk-lib && yarn test aws-lambda +cd packages/aws-cdk-lib && yarn test aws-s3 + +# 特定テストファイル +cd packages/aws-cdk-lib && yarn test aws-s3/test/bucket.test.ts + +# インテグレーションテスト (AWS アカウント必要) +cd packages/@aws-cdk-testing/framework-integ +yarn integ test/aws-lambda/test/integ.lambda.js --update-on-failed +``` + +## Lint / フォーマット +```bash +cd packages/aws-cdk-lib && yarn lint # ESLint +yarn pkglint # パッケージ規約チェック +``` + +## その他 +```bash +yarn build+test # ビルド + テスト +./scripts/run-rosetta.sh # README サンプルコードのコンパイル確認 +``` + +## Nx (モノレポ向け) +```bash +npx nx build aws-cdk-lib # Nx 経由でビルド +npx nx test aws-cdk-lib # Nx 経由でテスト +``` diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md new file mode 100644 index 0000000000000..f0cd329f1e43e --- /dev/null +++ b/.serena/memories/task_completion_checklist.md @@ -0,0 +1,41 @@ +# タスク完了時のチェックリスト + +## コード変更後に必ず実施すること + +1. **ビルド確認** + ```bash + cd packages/aws-cdk-lib && yarn build + # または変更したパッケージのディレクトリで + yarn build + ``` + +2. **ユニットテスト実行** + ```bash + cd packages/aws-cdk-lib && yarn test <モジュール名> + # 例: yarn test aws-s3 + ``` + +3. **Lint チェック** + ```bash + cd packages/aws-cdk-lib && yarn lint + ``` + +4. **pkglint チェック** (package.json 変更時) + ```bash + yarn pkglint + ``` + +5. **README サンプルコード確認** (README 変更時) + ```bash + ./scripts/run-rosetta.sh + ``` + +6. **自動生成ファイルの確認** + - `*.generated.ts` を手動編集していないか確認 + - `.jsii` ファイルがビルドで正しく生成されているか確認 + +## PR 作成前チェック +- [ ] 新機能には JSDoc コメントがある +- [ ] 公開 API の breaking change がない (または `allowed-breaking-changes.txt` に記載) +- [ ] ユニットテストが追加・更新されている +- [ ] コミットメッセージが conventional commits 形式 diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000000000..deb8941047e4f --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,138 @@ +# the name by which the project can be referenced within Serena +project_name: "aws-cdk" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- typescript + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# line ending convention to use when writing source files. +# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) +# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. +line_ending: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: + +# list of regex patterns which, when matched, mark a memory entry as read‑only. +# Extends the list from the global configuration, merging the two lists. +read_only_memory_patterns: [] From 74c33227ace827335435ac369f104537d3801d13 Mon Sep 17 00:00:00 2001 From: letsgomeow Date: Sat, 28 Mar 2026 21:02:02 +0900 Subject: [PATCH 2/5] WIP --- .claude/.claude/CLAUDE.md | 71 ------ .claude/.claude/pr-37048-fix-proposal.md | 232 ------------------ .claude/.claude/pr-37247-fix-proposal.md | 177 ------------- .serena/project.yml | 14 ++ .../test/integ.eks-pod-identities.ts | 110 +++++++++ .../aws-eks-v2/lib/service-account.ts | 117 ++++++--- .../aws-eks-v2/test/service-account.test.ts | 140 ++++++++++- 7 files changed, 349 insertions(+), 512 deletions(-) delete mode 100644 .claude/.claude/CLAUDE.md delete mode 100644 .claude/.claude/pr-37048-fix-proposal.md delete mode 100644 .claude/.claude/pr-37247-fix-proposal.md create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts diff --git a/.claude/.claude/CLAUDE.md b/.claude/.claude/CLAUDE.md deleted file mode 100644 index cfdf65a5c62f9..0000000000000 --- a/.claude/.claude/CLAUDE.md +++ /dev/null @@ -1,71 +0,0 @@ -# AWS CDK プロジェクト - Claude Code ガイド - -## プロジェクト概要 - -AWS Cloud Development Kit (AWS CDK) のモノレポ。TypeScript 製のオープンソース IaC フレームワーク。 - -- メインブランチ: `main` -- 言語: TypeScript (jsii) -- パッケージ管理: Yarn + Lerna + Nx - -## リポジトリ構成 - -``` -packages/ - aws-cdk-lib/ # メインライブラリ (旧 @aws-cdk/* の統合) - @aws-cdk/ # 個別パッケージ群 (alpha/experimental 含む) - @aws-cdk-testing/ # テストユーティリティ -tools/ # ビルド・開発ツール -scripts/ # CI/CD スクリプト -design/ # RFC・設計ドキュメント -``` - -## ビルド・テストコマンド - -```bash -# 全体ビルド -./build.sh - -# 特定パッケージのビルド -cd packages/aws-cdk-lib && yarn build - -# ユニットテスト -yarn test - -# 特定パッケージのテスト -cd packages/aws-cdk-lib && yarn test - -# Linter -yarn eslint - -# pkglint (パッケージ規約チェック) -yarn pkglint -``` - -## コーディング規約 - -- **jsii 互換**: 公開 API は jsii の制約に従う (Java/Python/C# 向けバインディング生成のため) -- **breaking changes 禁止**: 既存の公開 API シグネチャは変更しない。変更が必要な場合は `allowed-breaking-changes.txt` を参照 -- **Feature Flags**: 動作変更は `@aws-cdk/cx-api` の Feature Flag 経由で導入する -- **テスト必須**: ユニットテスト (`*.test.ts`) と可能であればインテグレーションテスト (`integ.*.ts`) を追加する - -## PR・コントリビューション - -- PRタイトル: `feat(module): ...` / `fix(module): ...` / `chore: ...` の conventional commits 形式 -- CHANGELOG は自動生成されるため手動編集不要 -- 新機能追加時は `README.md` のドキュメント更新も必要 - -## コード調査・編集ツール - -**Serena MCP を常に優先して使用すること。** - -- コードの調査・検索には `mcp__serena__find_symbol` / `mcp__serena__get_symbols_overview` / `mcp__serena__search_for_pattern` を使う -- シンボル単位の編集には `mcp__serena__replace_symbol_body` / `mcp__serena__insert_after_symbol` を使う -- ファイル全体を読む前に、まずシンボルレベルで必要な箇所だけ取得する -- セッション開始時に `mcp__serena__check_onboarding_performed` で初期化状態を確認する - -## 注意事項 - -- `node_modules` や `.jsii` ファイルは読まない -- jsii の型制約により、`any` や非直列化可能な型は公開 API に使えない -- インテグレーションテストはAWSアカウントが必要なため、CI以外では通常スキップ diff --git a/.claude/.claude/pr-37048-fix-proposal.md b/.claude/.claude/pr-37048-fix-proposal.md deleted file mode 100644 index b0a978369a5c4..0000000000000 --- a/.claude/.claude/pr-37048-fix-proposal.md +++ /dev/null @@ -1,232 +0,0 @@ -# PR #37048 マージ修正案 - -## 背景 - -`ValidationError` のコンストラクタシグネチャが main ブランチで変更された。 - -| 旧 (HEAD/feature branch) | 新 (main) | -|---|---| -| `new ValidationError(msg: string, scope: IConstruct)` | `new ValidationError(name: string, msg: string, scope: IConstruct)` | - -`packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts` を手動マージした際に以下2種類の問題が残っている。 - ---- - -## 問題1: マージコンフリクトマーカーが残存 (2箇所) - -### 箇所A: `validateServiceConnectConfiguration` 冒頭 (約1203行目) - -``` -<<<<<<< HEAD - const hasDefaultContainer = TaskDefinition.isTaskDefinition(this.taskDefinition) && - this.taskDefinition.defaultContainer !== undefined; - - if (!hasDefaultContainer) { - throw new ValidationError('Task definition must have at least one container to enable service connect.', this); -======= - if (!this.taskDefinition.defaultContainer) { - throw new ValidationError('TaskDefinitionLeastContainer', 'Task definition must have at least one container to enable service connect.', this); ->>>>>>> main -``` - -**修正方針**: HEAD 側を採用する。`taskDefinition` はコンストラクタで `ITaskDefinition` を受け付けるように変更されており、`defaultContainer` は `TaskDefinition` クラス固有のメンバーのため `isTaskDefinition()` で型ガードする。エラー名は main 側のものを採用。 - -**修正後:** -```typescript - const hasDefaultContainer = TaskDefinition.isTaskDefinition(this.taskDefinition) && - this.taskDefinition.defaultContainer !== undefined; - - if (!hasDefaultContainer) { - throw new ValidationError('TaskDefinitionLeastContainer', 'Task definition must have at least one container to enable service connect.', this); - } -``` - ---- - -### 箇所B: `config.services.forEach` 内のポートマッピング確認 (約1240行目) - -``` -<<<<<<< HEAD - if (!(this.taskDefinition as TaskDefinition).findPortMappingByName(serviceConnectService.portMappingName)) { - throw new ValidationError(`Port Mapping '${serviceConnectService.portMappingName}' does not exist on the task definition.`, this); -======= - if (!this.taskDefinition.findPortMappingByName(serviceConnectService.portMappingName)) { - throw new ValidationError('PortMappingDoesExist', `Port Mapping '${serviceConnectService.portMappingName}' does not exist on the task definition.`, this); ->>>>>>> main -``` - -**修正方針**: HEAD 側を採用する。`findPortMappingByName` は `ITaskDefinition` にないため `as TaskDefinition` キャストは維持。エラー名は main 側のものを採用。 - -**修正後:** -```typescript - if (!(this.taskDefinition as TaskDefinition).findPortMappingByName(serviceConnectService.portMappingName)) { - throw new ValidationError('PortMappingDoesExist', `Port Mapping '${serviceConnectService.portMappingName}' does not exist on the task definition.`, this); - } -``` - ---- - -## 問題2: 旧シグネチャ (2引数) の `ValidationError` 呼び出しが残存 (6箇所) - -これらは HEAD 側のコードとして取り込まれたが、エラー名 (`name`) が欠落しているためコンパイルエラーになる。 - -### 箇所1: CODE_DEPLOY + imported TaskDefinition チェック (約887行目) - -**現状:** -```typescript - throw new ValidationError( - 'CODE_DEPLOY deployment controller requires an owned TaskDefinition. Cannot use imported task definitions.', - this, - ); -``` - -**修正後:** -```typescript - throw new ValidationError( - 'CodeDeployRequiresOwnedTaskDefinition', - 'CODE_DEPLOY deployment controller requires an owned TaskDefinition. Cannot use imported task definitions.', - this, - ); -``` - ---- - -### 箇所2: taskDefinitionRevision + imported TaskDefinition チェック (約897行目) - -**現状:** -```typescript - throw new ValidationError( - 'taskDefinitionRevision requires an owned TaskDefinition. Cannot use imported task definitions.', - this, - ); -``` - -**修正後:** -```typescript - throw new ValidationError( - 'TaskDefinitionRevisionRequiresOwnedTaskDefinition', - 'taskDefinitionRevision requires an owned TaskDefinition. Cannot use imported task definitions.', - this, - ); -``` - ---- - -### 箇所3: Service Connect + imported TaskDefinition チェック (約1231行目) - -**現状:** -```typescript - throw new ValidationError( - 'Service Connect requires an owned TaskDefinition. Cannot use imported task definitions with Service Connect.', - this, - ); -``` - -**修正後:** -```typescript - throw new ValidationError( - 'ServiceConnectRequiresOwnedTaskDefinition', - 'Service Connect requires an owned TaskDefinition. Cannot use imported task definitions with Service Connect.', - this, - ); -``` - ---- - -### 箇所4: `loadBalancerTarget` + imported TaskDefinition チェック (約1519行目) - -**現状:** -```typescript - throw new ValidationError( - 'Cannot create load balancer target from imported TaskDefinition. ' + - 'Use a concrete TaskDefinition created in this stack.', - this, - ); -``` - -**修正後:** -```typescript - throw new ValidationError( - 'LoadBalancerTargetRequiresOwnedTaskDefinition', - 'Cannot create load balancer target from imported TaskDefinition. ' + - 'Use a concrete TaskDefinition created in this stack.', - this, - ); -``` - ---- - -### 箇所5: `defaultLoadBalancerTarget` getter (約1826行目) - -**現状:** -```typescript - throw new ValidationError( - 'Cannot create default load balancer target. TaskDefinition must have a default container.', - this, - ); -``` - -**修正後:** -```typescript - throw new ValidationError( - 'DefaultLoadBalancerTargetRequiresDefaultContainer', - 'Cannot create default load balancer target. TaskDefinition must have a default container.', - this, - ); -``` - ---- - -### 箇所6: Cloud Map + imported TaskDefinition チェック (約2132行目) - -**現状:** -```typescript - throw new ValidationError('Cloud Map service discovery requires an owned TaskDefinition with container configuration', scope); -``` - -**修正後:** -```typescript - throw new ValidationError('CloudMapRequiresOwnedTaskDefinition', 'Cloud Map service discovery requires an owned TaskDefinition with container configuration', scope); -``` - ---- - -## 修正の全体フロー - -1. **コンフリクト解消**: マーカー (`<<<<<<<`, `=======`, `>>>>>>>`) を削除し、main 側のコードを採用 -2. **引数追加**: 旧 2引数呼び出し 6箇所に、先頭の `name` 文字列を追加 -3. **コンパイル確認**: `cd packages/aws-cdk-lib && yarn build` でエラーがないことを確認 -4. **テスト確認**: `cd packages/aws-cdk-lib && yarn test aws-ecs` でユニットテストが通ることを確認 - ---- - -## awslint 対応状況 - -### 完了: `prefer-ref-interface` を awslint.json に移動 - -以下3エントリを `packages/aws-cdk-lib/awslint.json` に追加し、各ファイルのインラインコメントを削除済み。 - -```json -"prefer-ref-interface:aws-cdk-lib.aws_ecs.Ec2ServiceProps.taskDefinition", -"prefer-ref-interface:aws-cdk-lib.aws_ecs.FargateServiceProps.taskDefinition", -"prefer-ref-interface:aws-cdk-lib.aws_ecs.ExternalServiceProps.taskDefinition", -``` - -### 要確認: `ref-via-interface` インラインコメントの扱い - -以下3ファイルに `[disable-awslint:ref-via-interface]` のインラインコメントが残っている。 - -- `aws-ecs/lib/ec2/ec2-service.ts` (Ec2ServiceProps.taskDefinition) -- `aws-ecs/lib/fargate/fargate-service.ts` (FargateServiceProps.taskDefinition) -- `aws-ecs/lib/external/external-service.ts` (ExternalServiceProps.taskDefinition) - -`awslint.json` には ECS 向けの `ref-via-interface` エントリが存在しないため、**インラインのままでよいか、awslint.json に移動すべきか**をメンテナに確認すること。 - -また、これら3つのプロパティはすでに `ITaskDefinition`(インターフェース)型を使っているため、`ref-via-interface` ルール(「コンクリートクラスではなくインターフェースを使うべき」)がそもそも適用されるべきか自体も確認する。 - ---- - -## 注意事項 - -- エラー名 (`name`) は他の `ValidationError` の命名規則に合わせて PascalCase のキャメルケース (単語を省略しない) を採用した -- 同一エラーIDが既存コードと重複しないよう確認済み diff --git a/.claude/.claude/pr-37247-fix-proposal.md b/.claude/.claude/pr-37247-fix-proposal.md deleted file mode 100644 index 028f5da12cb4e..0000000000000 --- a/.claude/.claude/pr-37247-fix-proposal.md +++ /dev/null @@ -1,177 +0,0 @@ -# PR #37247 修正提案 - -**PR タイトル**: fix(eks-v2): respect securityGroup(s) in KubectlProviderOptions -**PR URL**: https://github.com/aws/aws-cdk/pull/37247 -**レビュアー**: @leonmk-aws -**参照 PR**: https://github.com/aws/aws-cdk/pull/7857 (feat(events-targets): support multiple security groups for an ECS task) - ---- - -## 参照 PR (#7857) との比較・検証結果 - -PR #7857 は同じパターン(`securityGroup` → `securityGroups` への移行)の先行実装。以下を確認した。 - -| 項目 | PR #7857 の実装 | 今回の方針 | 判定 | -|------|----------------|-----------|------| -| `@deprecated` タグ | `@deprecated use securityGroups instead` | 同様 | ✅ 妥当 | -| 両方指定時の挙動 | `throw new Error(...)` | `throw new ValidationError(...)` | ✅ 妥当(現代の CDK 規約に合わせ upgrade) | -| `awslint.json` への移動 | 対応なし(旧 PR のため) | awslint.json に追加 | ✅ メンテナ指示通り | - -**特記事項**: PR #7857 は 2020 年の旧コードのため `new Error()` を使用しているが、現在の aws-eks-v2 コードベースでは `cluster.ts` の実装に倣い `ValidationError(message, this)` を使うのが正しい。 - ---- - -## メンテナから指摘された修正点 - -### 1. `securityGroup` を `@deprecated` にする - -**コメント**: 「`securityGroups` オプションを導入するなら、`securityGroup` を deprecated にすべき」 -**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 50付近) - -#### 現在のコード -```typescript -/** - * A security group to use for `kubectl` execution. - * - * If you specify both `securityGroup` and `securityGroups`, a warning will be issued - * and `securityGroups` will be used. - * - * @default - If not specified, the k8s endpoint is expected to be accessible - * publicly. - */ -readonly securityGroup?: ec2.ISecurityGroup; -``` - -#### 修正後 -```typescript -/** - * A security group to use for `kubectl` execution. - * - * @default - If not specified, the k8s endpoint is expected to be accessible - * publicly. - * @deprecated Use `securityGroups` instead. - */ -readonly securityGroup?: ec2.ISecurityGroup; -``` - -> **補足**: `@deprecated` を付けると JSDoc の説明に `securityGroups` オプションへの移行を促すメッセージを含める。両プロパティを同時指定したときの説明は `securityGroup` 側のコメントから削除する(#3 の変更と合わせて不要になる)。 - ---- - -### 2. `[disable-awslint:prefer-ref-interface]` を `awslint.json` に移動する - -**コメント**: 「lint 抑制コメントをコード内に書かず、`packages/aws-cdk-lib/awslint.json` の既存エントリ群(line 1926付近)へ移動すること」 -**対象ファイル**: -- `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 62付近) — 削除 -- `packages/aws-cdk-lib/awslint.json` (line 1926付近) — 追加 - -#### kubectl-provider.ts の修正(コメント削除) -```typescript -/** - * Security groups to use for `kubectl` execution. - * - * @default - If not specified, the k8s endpoint is expected to be accessible - * publicly. - */ -readonly securityGroups?: ec2.ISecurityGroup[]; -``` - -#### awslint.json への追加 -既存の `prefer-ref-interface` エントリ群(`KubectlProviderProps.securityGroup` など)の直後に追加する: - -```json -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", -``` - -追加位置のイメージ(awslint.json line 1926付近): -```json -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.securityGroup", -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", ← 追加 -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.cluster", -``` - ---- - -### 3. 両プロパティが同時指定された場合は warning ではなく error を throw する - -**コメント**: 「両プロパティを同時に指定した場合はサポート外であり、ユーザーの設定ミスを示す可能性があるため、warning ではなく throw すべき」 -**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 209付近) - -#### 現在のコード -```typescript -if (props.securityGroups && props.securityGroups.length > 0) { - securityGroups = props.securityGroups; - - // Issue warning if both properties are specified - if (props.securityGroup) { - Annotations.of(this).addWarningV2('@aws-cdk/aws-eks-v2:securityGroupConflict', - 'Both securityGroup and securityGroups are specified. Using securityGroups.'); - } -} else if (props.securityGroup) { -``` - -#### 修正後 -`ValidationError` を throw するよう変更する(CDK では `ValidationError` を使う): - -```typescript -if (props.securityGroups && props.securityGroups.length > 0 && props.securityGroup) { - throw new ValidationError( - 'SecurityGroupConflict', - 'Cannot specify both "securityGroup" and "securityGroups". Use "securityGroups" only.', - this, - ); -} - -if (props.securityGroups && props.securityGroups.length > 0) { - securityGroups = props.securityGroups; -} else if (props.securityGroup) { -``` - -> **補足**: -> - `ValidationError` は `../../core/lib/errors` からインポートする(`cluster.ts` で同様に使われている) -> - `kubectl-provider.ts` の現在のインポート文に `ValidationError` が含まれていないため、追加が必要 -> - PR #7857 ではエラーチェックをコンストラクタ冒頭(`privateSubnets` 分岐の外側)で行っているが、今回は `privateSubnets` がない場合はセキュリティグループ自体が無視されるため、**チェックは `privateSubnets` 分岐の内側 or 外側どちらでも機能的には同じ**。ただし PR #7857 に倣い、コンストラクタの冒頭(`super()` の直後)で早期チェックする方が明快 -> - `securityGroup` JSDoc の両プロパティ同時指定に関する記述も削除する(動作が変わるため) - ---- - -## ユニットテストの修正 - -`packages/aws-cdk-lib/aws-eks-v2/test/cluster.test.ts` - -### 3 の変更に伴い、「両方指定したときのテスト」を変更する - -#### 現在のテスト(warning を期待) -```typescript -it('warns when both securityGroup and securityGroups are specified', () => { - // ... - expect(Annotations.fromStack(stack).hasWarning(...)); -}); -``` - -#### 修正後(throw を期待) -```typescript -it('throws when both securityGroup and securityGroups are specified', () => { - expect(() => { - // securityGroup と securityGroups を両方指定するコード - }).toThrow('Cannot specify both "securityGroup" and "securityGroups"'); -}); -``` - ---- - -## 修正作業の順序 - -1. `kubectl-provider.ts`: - - `securityGroup` に `@deprecated` タグを追加 - - `[disable-awslint:prefer-ref-interface]` コメントを削除 - - warning → throw に変更 -2. `awslint.json`: - - `prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups` を追加 -3. `cluster.test.ts`: - - warning テスト → throw テストに変更 -4. ビルド・テスト確認: - ```bash - cd packages/aws-cdk-lib && yarn build && yarn test aws-eks-v2 - cd packages/aws-cdk-lib && yarn lint - ``` diff --git a/.serena/project.yml b/.serena/project.yml index deb8941047e4f..0baaf14f225a8 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -136,3 +136,17 @@ symbol_info_budget: # list of regex patterns which, when matched, mark a memory entry as read‑only. # Extends the list from the global configuration, merging the two lists. read_only_memory_patterns: [] + +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# Extends the list from the global configuration, merging the two lists. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: [] + +# advanced configuration option allowing to configure language server-specific options. +# Maps the language key to the options. +# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. +# No documentation on options means no options are available. +ls_specific_settings: {} diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts new file mode 100644 index 0000000000000..e0170c8d9d4cf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts @@ -0,0 +1,110 @@ +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import type { StackProps } from 'aws-cdk-lib'; +import { App, Stack } from 'aws-cdk-lib'; +import { getClusterVersionConfig } from './integ-tests-kubernetes-version'; +import * as eks from 'aws-cdk-lib/aws-eks-v2'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +/** + * After deployment, verify with: + * + * $ kubectl get po + * + * You should see two pods named `demo` and `demo-external-role` in Completed STATUS. + * + * $ kubectl logs -f demo + * $ kubectl logs -f demo-external-role + * + * Both pods should show a log message with UserId, Account and Arn, + * indicating they are running with the correct eks pod identity defined with ServiceAccount. + * The `demo-external-role` pod uses an externally-created IAM role passed via the `role` prop. + */ + +class EksPodIdentitiesStack extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'VPC', { natGateways: 1 }); + + const cluster = new eks.Cluster(this, 'Cluster', { + vpc, + defaultCapacity: 1, + ...getClusterVersionConfig(this), + }); + + // Case 1: auto-generated IAM role (existing behavior) + const sa = new eks.ServiceAccount(this, 'ServiceAccount', { + cluster, + name: 'test-sa', + namespace: 'default', + identityType: eks.IdentityType.POD_IDENTITY, + }); + + sa.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + + const pod = cluster.addManifest('demopod', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'demo' }, + spec: { + serviceAccountName: sa.serviceAccountName, + containers: [ + { + name: 'demo', + image: 'public.ecr.aws/amazonlinux/amazonlinux:2023', + command: ['/bin/bash', '-c', 'yum update -y && yum install -y awscli && aws sts get-caller-identity'], + }, + ], + }, + }); + pod.node.addDependency(sa); + + // Case 2: externally-created IAM role passed via the `role` prop + // The trust policy must allow pods.eks.amazonaws.com with sts:AssumeRole and sts:TagSession. + const externalRole = new iam.Role(this, 'ExternalRole', { + assumedBy: new iam.SessionTagsPrincipal( + new iam.ServicePrincipal('pods.eks.amazonaws.com'), + ), + }); + externalRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + + const saWithExternalRole = new eks.ServiceAccount(this, 'ServiceAccountWithExternalRole', { + cluster, + name: 'test-sa-external-role', + namespace: 'default', + identityType: eks.IdentityType.POD_IDENTITY, + role: externalRole, + }); + + const podWithExternalRole = cluster.addManifest('demopod-external-role', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'demo-external-role' }, + spec: { + serviceAccountName: saWithExternalRole.serviceAccountName, + containers: [ + { + name: 'demo', + image: 'public.ecr.aws/amazonlinux/amazonlinux:2023', + command: ['/bin/bash', '-c', 'yum update -y && yum install -y awscli && aws sts get-caller-identity'], + }, + ], + }, + }); + podWithExternalRole.node.addDependency(saWithExternalRole); + } +} + +const app = new App({ + postCliContext: { + '@aws-cdk/aws-lambda:useCdkManagedLogGroup': false, + '@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy': false, + }, +}); + +const stack = new EksPodIdentitiesStack(app, 'eks-pod-identities-v2'); + +new IntegTest(app, 'integ-eks-pod-identities-v2', { + testCases: [stack], +}); diff --git a/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts b/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts index 30361e70e9d81..afb6e098d092d 100644 --- a/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts +++ b/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts @@ -2,13 +2,13 @@ import { Construct } from 'constructs'; import type { ICluster } from './cluster'; import { KubernetesManifest } from './k8s-manifest'; import { CfnPodIdentityAssociation } from '../../aws-eks'; -import type { AddToPrincipalPolicyResult, IPrincipal, IRole, PrincipalPolicyFragment } from '../../aws-iam'; +import type { AddToPrincipalPolicyResult, IPrincipal, IRole, IRoleRef, PrincipalPolicyFragment } from '../../aws-iam'; import { OpenIdConnectPrincipal, PolicyStatement, Role, ServicePrincipal, } from '../../aws-iam'; import type { RemovalPolicy } from '../../core'; -import { CfnJson, Names, RemovalPolicies } from '../../core'; +import { CfnJson, Names, RemovalPolicies, ValidationError } from '../../core'; // import { FargateCluster } from './index'; /** @@ -82,6 +82,23 @@ export interface ServiceAccountOptions { */ readonly identityType?: IdentityType; + /** + * An existing IAM role to associate with this service account via Pod Identity. + * Only valid when `identityType` is `IdentityType.POD_IDENTITY`. + * + * When specified, the provided role is used instead of auto-generating one. + * The caller is responsible for configuring the trust policy of the role correctly. + * For Pod Identity, the role must allow `pods.eks.amazonaws.com` to perform + * `sts:AssumeRole` and `sts:TagSession`. + * + * Accepts any `IRoleRef`, including L2 `iam.Role`, `iam.Role.fromRoleArn()`, and L1 `iam.CfnRole`. + * Note: If an L1 construct is provided, accessing `serviceAccount.role` will throw an error, + * as L1 constructs do not implement the `IRole` interface. + * + * @default - a new IAM role is created automatically + */ + readonly role?: IRoleRef; + /** * Overwrite existing service account. * @@ -124,12 +141,35 @@ export interface ServiceAccountProps extends ServiceAccountOptions { export class ServiceAccount extends Construct implements IPrincipal { /** * The role which is linked to the service account. + * + * @throws if the provided `role` option is an L1 construct (e.g. `CfnRole`) rather than an L2 `IRole`. */ - public readonly role: IRole; + public get role(): IRole { + if (this._podIdentityRole !== undefined) { + if ('grant' in this._podIdentityRole) { + return this._podIdentityRole as IRole; + } + throw new ValidationError( + 'ServiceAccountRole', + 'The provided role is not an instance of IRole. ' + + 'Cannot access role grants when using an L1 construct (e.g. CfnRole) as the role.', + this, + ); + } + return this._irsaRole!; + } + + public get assumeRoleAction(): string { + return this.role.assumeRoleAction; + } - public readonly assumeRoleAction: string; - public readonly grantPrincipal: IPrincipal; - public readonly policyFragment: PrincipalPolicyFragment; + public get grantPrincipal(): IPrincipal { + return this.role.grantPrincipal; + } + + public get policyFragment(): PrincipalPolicyFragment { + return this.role.policyFragment; + } /** * The name of the service account. @@ -141,6 +181,9 @@ export class ServiceAccount extends Construct implements IPrincipal { */ public readonly serviceAccountNamespace: string; + private _irsaRole?: IRole; + private _podIdentityRole?: IRoleRef; + constructor(scope: Construct, id: string, props: ServiceAccountProps) { super(scope, id); @@ -158,7 +201,16 @@ export class ServiceAccount extends Construct implements IPrincipal { throw RangeError('All namespace names must be valid RFC 1123 DNS labels.'); } - let principal: IPrincipal; + if (props.role !== undefined && props.identityType !== IdentityType.POD_IDENTITY) { + throw new ValidationError( + 'ServiceAccountRoleOption', + 'The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.', + this, + ); + } + + let roleArn: string; + if (props.identityType !== IdentityType.POD_IDENTITY) { /* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role. * See documentation: https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html @@ -169,51 +221,35 @@ export class ServiceAccount extends Construct implements IPrincipal { [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:sub`]: `system:serviceaccount:${this.serviceAccountNamespace}:${this.serviceAccountName}`, }, }); - principal = new OpenIdConnectPrincipal(cluster.openIdConnectProvider).withConditions({ + const principal = new OpenIdConnectPrincipal(cluster.openIdConnectProvider).withConditions({ StringEquals: conditions, }); + this._irsaRole = new Role(this, 'Role', { assumedBy: principal }); + roleArn = this._irsaRole.roleArn; } else { /** * Identity type is POD_IDENTITY. - * Create a service principal with "Service": "pods.eks.amazonaws.com" * See https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html */ // EKS Pod Identity does not support Fargate // TODO: raise an error when using Fargate - principal = new ServicePrincipal('pods.eks.amazonaws.com'); - } - const role = new Role(this, 'Role', { assumedBy: principal }); - - // pod identities requires 'sts:TagSession' in its principal actions - if (props.identityType === IdentityType.POD_IDENTITY) { - /** - * EKS Pod Identities requires both assumed role actions otherwise it would fail. - */ - role.assumeRolePolicy!.addStatements(new PolicyStatement({ - actions: ['sts:AssumeRole', 'sts:TagSession'], - principals: [new ServicePrincipal('pods.eks.amazonaws.com')], - })); + this._podIdentityRole = this.resolvePodIdentityRole(props); + roleArn = this._podIdentityRole.roleRef.roleArn; // ensure the pod identity agent cluster.eksPodIdentityAgent; - // associate this service account with the pod role we just created for the cluster + // associate this service account with the pod role for the cluster new CfnPodIdentityAssociation(this, 'Association', { clusterName: cluster.clusterName, namespace: props.namespace ?? 'default', - roleArn: role.roleArn, + roleArn: roleArn, serviceAccount: this.serviceAccountName, }); } - this.role = role; - - this.assumeRoleAction = this.role.assumeRoleAction; - this.grantPrincipal = this.role.grantPrincipal; - this.policyFragment = this.role.policyFragment; - // Note that we cannot use `cluster.addManifest` here because that would create the manifest // constrct in the scope of the cluster stack, which might be a different stack than this one. // This means that the cluster stack would depend on this stack because of the role, @@ -232,7 +268,7 @@ export class ServiceAccount extends Construct implements IPrincipal { ...props.labels, }, annotations: { - 'eks.amazonaws.com/role-arn': this.role.roleArn, + 'eks.amazonaws.com/role-arn': roleArn, ...props.annotations, }, }, @@ -255,6 +291,25 @@ export class ServiceAccount extends Construct implements IPrincipal { return this.role.addToPrincipalPolicy(statement); } + /** + * Resolves the IAM role to use for Pod Identity. + * Returns the provided role if specified, otherwise auto-generates one. + */ + private resolvePodIdentityRole(props: ServiceAccountProps): IRoleRef { + if (props.role) { + return props.role; + } + const role = new Role(this, 'Role', { + assumedBy: new ServicePrincipal('pods.eks.amazonaws.com'), + }); + // EKS Pod Identities requires both assumed role actions otherwise it would fail. + role.assumeRolePolicy!.addStatements(new PolicyStatement({ + actions: ['sts:AssumeRole', 'sts:TagSession'], + principals: [new ServicePrincipal('pods.eks.amazonaws.com')], + })); + return role; + } + /** * If the value is a DNS subdomain name as defined in RFC 1123, from K8s docs. * diff --git a/packages/aws-cdk-lib/aws-eks-v2/test/service-account.test.ts b/packages/aws-cdk-lib/aws-eks-v2/test/service-account.test.ts index bc432f8d1518a..775818ca3179b 100644 --- a/packages/aws-cdk-lib/aws-eks-v2/test/service-account.test.ts +++ b/packages/aws-cdk-lib/aws-eks-v2/test/service-account.test.ts @@ -1,5 +1,5 @@ import { testFixture, testFixtureCluster } from './util'; -import { Template } from '../../assertions'; +import { Match, Template } from '../../assertions'; import * as iam from '../../aws-iam'; import * as cdk from '../../core'; import * as eks from '../lib'; @@ -380,7 +380,145 @@ describe('service account', () => { // should not create OpenIdConnectProvider t.resourceCountIs('Custom::AWSCDKOpenIdConnectProvider', 0); }); + + test('uses provided role when role prop is specified', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN + new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + role: existingRole, + }); + const t = Template.fromStack(stack); + + // THEN + // the provided role ARN should be used in PodIdentityAssociation + t.hasResourceProperties('AWS::EKS::PodIdentityAssociation', { + ClusterName: { Ref: 'ClusterEB0386A7' }, + Namespace: 'default', + RoleArn: { 'Fn::GetAtt': ['ExistingRole5EDF2D93', 'Arn'] }, + ServiceAccount: 'stackmyserviceaccount58b9529e', + }); + // no auto-generated IAM role for ServiceAccount should exist + // the auto-generated role has a statement with sts:TagSession; ExistingRole does not + t.resourcePropertiesCountIs('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ Action: Match.arrayWith(['sts:TagSession']), Principal: { Service: 'pods.eks.amazonaws.com' } }), + ]), + }, + }, 0); + // the Pod Identity Agent addon should be created + t.hasResourceProperties('AWS::EKS::Addon', { + AddonName: 'eks-pod-identity-agent', + }); + }); + + test('throws if role is specified with IRSA identity type', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN / THEN + expect(() => new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.IRSA, + role: existingRole, + })).toThrow('The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.'); + }); + + test('throws if role is specified with default identity type (IRSA)', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN / THEN + expect(() => new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + // identityType is not specified, defaults to IRSA + role: existingRole, + })).toThrow('The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.'); + }); + + test('sa.role getter returns the provided L2 role', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const existingRole = new iam.Role(stack, 'ExistingRole', { + assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), + }); + + // WHEN + const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + role: existingRole, + }); + + // THEN: the getter should return the provided L2 role as-is + expect(sa.role).toBe(existingRole); + }); + + test('ServiceAccount creation succeeds when L1 CfnRole is provided, but sa.role getter throws', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + const cfnRole = new iam.CfnRole(stack, 'CfnRole', { + assumeRolePolicyDocument: { + Statement: [{ + Action: ['sts:AssumeRole', 'sts:TagSession'], + Effect: 'Allow', + Principal: { Service: 'pods.eks.amazonaws.com' }, + }], + }, + }); + + // WHEN: ServiceAccount creation itself should succeed (no error in constructor) + const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + role: cfnRole, + }); + + // THEN: PodIdentityAssociation should be created successfully + Template.fromStack(stack).hasResourceProperties('AWS::EKS::PodIdentityAssociation', { + ClusterName: { Ref: 'ClusterEB0386A7' }, + Namespace: 'default', + ServiceAccount: 'stackmyserviceaccount58b9529e', + }); + + // THEN: accessing sa.role should throw in the getter + expect(() => sa.role).toThrow( + 'The provided role is not an instance of IRole.', + ); + }); + + test('sa.role getter returns auto-generated role when no role prop is provided with POD_IDENTITY', () => { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { + cluster, + identityType: eks.IdentityType.POD_IDENTITY, + }); + + // THEN: the getter should return an IRole + expect(sa.role).toBeDefined(); + // addToPrincipalPolicy should be callable (functioning as IRole) + expect(() => sa.addToPrincipalPolicy( + new iam.PolicyStatement({ actions: ['s3:GetObject'], resources: ['*'] }), + )).not.toThrow(); + }); }); + describe('Service Account with eks.IdentityType.IRSA', () => { test('default', () => { // GIVEN From b9c901cf260233d8ecc6bc6d8c4af1676125cdfb Mon Sep 17 00:00:00 2001 From: letsgomeow Date: Sat, 28 Mar 2026 23:49:09 +0900 Subject: [PATCH 3/5] feat(aws-eks-v2): allow providing an existing IAM role to ServiceAccount with POD_IDENTITY Add an optional `role` property to `ServiceAccountOptions` so that `ServiceAccount` with `IdentityType.POD_IDENTITY` can accept an externally-created IAM role instead of always auto-generating one. --- .claude/CLAUDE.md | 71 - .claude/pr-37247-fix-proposal.md | 177 --- .claude/pr-37299-fix-proposal.md | 652 -------- .serena/.gitignore | 2 - .serena/.serena/.gitignore | 2 - .../memories/code_style_and_conventions.md | 54 - .serena/.serena/memories/project_overview.md | 48 - .../.serena/memories/suggested_commands.md | 45 - .../memories/task_completion_checklist.md | 41 - .serena/.serena/project.yml | 138 -- .../memories/code_style_and_conventions.md | 54 - .serena/memories/project_overview.md | 48 - .serena/memories/suggested_commands.md | 45 - .serena/memories/task_completion_checklist.md | 41 - .serena/project.yml | 152 -- ...77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip | 3 + .../__entrypoint__.js | 154 ++ .../index.js | 1 + ...9417df0fdeb6966645f90c88ec1d7e28130112.zip | 3 + .../cfn-response.js | 104 ++ .../consts.js | 10 + .../framework.js | 183 +++ .../outbound.js | 82 + .../util.js | 54 + .../apply/__init__.py | 93 ++ .../get/__init__.py | 86 + .../helm/__init__.py | 255 +++ .../index.py | 26 + .../patch/__init__.py | 68 + .../cdk.out | 1 + .../eks-pod-identities-v2.assets.json | 90 ++ .../eks-pod-identities-v2.metadata.json | 964 +++++++++++ .../eks-pod-identities-v2.template.json | 1409 +++++++++++++++++ .../integ.json | 13 + ...efaultTestDeployAssert83452A00.assets.json | 20 + ...aultTestDeployAssert83452A00.metadata.json | 14 + ...aultTestDeployAssert83452A00.template.json | 36 + .../manifest.json | 594 +++++++ .../tree.json | 1 + .../test/integ.eks-pod-identities.ts | 8 +- 40 files changed, 4270 insertions(+), 1572 deletions(-) delete mode 100644 .claude/CLAUDE.md delete mode 100644 .claude/pr-37247-fix-proposal.md delete mode 100644 .claude/pr-37299-fix-proposal.md delete mode 100644 .serena/.gitignore delete mode 100644 .serena/.serena/.gitignore delete mode 100644 .serena/.serena/memories/code_style_and_conventions.md delete mode 100644 .serena/.serena/memories/project_overview.md delete mode 100644 .serena/.serena/memories/suggested_commands.md delete mode 100644 .serena/.serena/memories/task_completion_checklist.md delete mode 100644 .serena/.serena/project.yml delete mode 100644 .serena/memories/code_style_and_conventions.md delete mode 100644 .serena/memories/project_overview.md delete mode 100644 .serena/memories/suggested_commands.md delete mode 100644 .serena/memories/task_completion_checklist.md delete mode 100644 .serena/project.yml create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/__entrypoint__.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/index.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/cfn-response.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/consts.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/framework.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/outbound.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/util.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/apply/__init__.py create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/get/__init__.py create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/helm/__init__.py create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/index.py create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/patch/__init__.py create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.metadata.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.metadata.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/tree.json diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index cfdf65a5c62f9..0000000000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,71 +0,0 @@ -# AWS CDK プロジェクト - Claude Code ガイド - -## プロジェクト概要 - -AWS Cloud Development Kit (AWS CDK) のモノレポ。TypeScript 製のオープンソース IaC フレームワーク。 - -- メインブランチ: `main` -- 言語: TypeScript (jsii) -- パッケージ管理: Yarn + Lerna + Nx - -## リポジトリ構成 - -``` -packages/ - aws-cdk-lib/ # メインライブラリ (旧 @aws-cdk/* の統合) - @aws-cdk/ # 個別パッケージ群 (alpha/experimental 含む) - @aws-cdk-testing/ # テストユーティリティ -tools/ # ビルド・開発ツール -scripts/ # CI/CD スクリプト -design/ # RFC・設計ドキュメント -``` - -## ビルド・テストコマンド - -```bash -# 全体ビルド -./build.sh - -# 特定パッケージのビルド -cd packages/aws-cdk-lib && yarn build - -# ユニットテスト -yarn test - -# 特定パッケージのテスト -cd packages/aws-cdk-lib && yarn test - -# Linter -yarn eslint - -# pkglint (パッケージ規約チェック) -yarn pkglint -``` - -## コーディング規約 - -- **jsii 互換**: 公開 API は jsii の制約に従う (Java/Python/C# 向けバインディング生成のため) -- **breaking changes 禁止**: 既存の公開 API シグネチャは変更しない。変更が必要な場合は `allowed-breaking-changes.txt` を参照 -- **Feature Flags**: 動作変更は `@aws-cdk/cx-api` の Feature Flag 経由で導入する -- **テスト必須**: ユニットテスト (`*.test.ts`) と可能であればインテグレーションテスト (`integ.*.ts`) を追加する - -## PR・コントリビューション - -- PRタイトル: `feat(module): ...` / `fix(module): ...` / `chore: ...` の conventional commits 形式 -- CHANGELOG は自動生成されるため手動編集不要 -- 新機能追加時は `README.md` のドキュメント更新も必要 - -## コード調査・編集ツール - -**Serena MCP を常に優先して使用すること。** - -- コードの調査・検索には `mcp__serena__find_symbol` / `mcp__serena__get_symbols_overview` / `mcp__serena__search_for_pattern` を使う -- シンボル単位の編集には `mcp__serena__replace_symbol_body` / `mcp__serena__insert_after_symbol` を使う -- ファイル全体を読む前に、まずシンボルレベルで必要な箇所だけ取得する -- セッション開始時に `mcp__serena__check_onboarding_performed` で初期化状態を確認する - -## 注意事項 - -- `node_modules` や `.jsii` ファイルは読まない -- jsii の型制約により、`any` や非直列化可能な型は公開 API に使えない -- インテグレーションテストはAWSアカウントが必要なため、CI以外では通常スキップ diff --git a/.claude/pr-37247-fix-proposal.md b/.claude/pr-37247-fix-proposal.md deleted file mode 100644 index 028f5da12cb4e..0000000000000 --- a/.claude/pr-37247-fix-proposal.md +++ /dev/null @@ -1,177 +0,0 @@ -# PR #37247 修正提案 - -**PR タイトル**: fix(eks-v2): respect securityGroup(s) in KubectlProviderOptions -**PR URL**: https://github.com/aws/aws-cdk/pull/37247 -**レビュアー**: @leonmk-aws -**参照 PR**: https://github.com/aws/aws-cdk/pull/7857 (feat(events-targets): support multiple security groups for an ECS task) - ---- - -## 参照 PR (#7857) との比較・検証結果 - -PR #7857 は同じパターン(`securityGroup` → `securityGroups` への移行)の先行実装。以下を確認した。 - -| 項目 | PR #7857 の実装 | 今回の方針 | 判定 | -|------|----------------|-----------|------| -| `@deprecated` タグ | `@deprecated use securityGroups instead` | 同様 | ✅ 妥当 | -| 両方指定時の挙動 | `throw new Error(...)` | `throw new ValidationError(...)` | ✅ 妥当(現代の CDK 規約に合わせ upgrade) | -| `awslint.json` への移動 | 対応なし(旧 PR のため) | awslint.json に追加 | ✅ メンテナ指示通り | - -**特記事項**: PR #7857 は 2020 年の旧コードのため `new Error()` を使用しているが、現在の aws-eks-v2 コードベースでは `cluster.ts` の実装に倣い `ValidationError(message, this)` を使うのが正しい。 - ---- - -## メンテナから指摘された修正点 - -### 1. `securityGroup` を `@deprecated` にする - -**コメント**: 「`securityGroups` オプションを導入するなら、`securityGroup` を deprecated にすべき」 -**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 50付近) - -#### 現在のコード -```typescript -/** - * A security group to use for `kubectl` execution. - * - * If you specify both `securityGroup` and `securityGroups`, a warning will be issued - * and `securityGroups` will be used. - * - * @default - If not specified, the k8s endpoint is expected to be accessible - * publicly. - */ -readonly securityGroup?: ec2.ISecurityGroup; -``` - -#### 修正後 -```typescript -/** - * A security group to use for `kubectl` execution. - * - * @default - If not specified, the k8s endpoint is expected to be accessible - * publicly. - * @deprecated Use `securityGroups` instead. - */ -readonly securityGroup?: ec2.ISecurityGroup; -``` - -> **補足**: `@deprecated` を付けると JSDoc の説明に `securityGroups` オプションへの移行を促すメッセージを含める。両プロパティを同時指定したときの説明は `securityGroup` 側のコメントから削除する(#3 の変更と合わせて不要になる)。 - ---- - -### 2. `[disable-awslint:prefer-ref-interface]` を `awslint.json` に移動する - -**コメント**: 「lint 抑制コメントをコード内に書かず、`packages/aws-cdk-lib/awslint.json` の既存エントリ群(line 1926付近)へ移動すること」 -**対象ファイル**: -- `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 62付近) — 削除 -- `packages/aws-cdk-lib/awslint.json` (line 1926付近) — 追加 - -#### kubectl-provider.ts の修正(コメント削除) -```typescript -/** - * Security groups to use for `kubectl` execution. - * - * @default - If not specified, the k8s endpoint is expected to be accessible - * publicly. - */ -readonly securityGroups?: ec2.ISecurityGroup[]; -``` - -#### awslint.json への追加 -既存の `prefer-ref-interface` エントリ群(`KubectlProviderProps.securityGroup` など)の直後に追加する: - -```json -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", -``` - -追加位置のイメージ(awslint.json line 1926付近): -```json -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.securityGroup", -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups", ← 追加 -"prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderProps.cluster", -``` - ---- - -### 3. 両プロパティが同時指定された場合は warning ではなく error を throw する - -**コメント**: 「両プロパティを同時に指定した場合はサポート外であり、ユーザーの設定ミスを示す可能性があるため、warning ではなく throw すべき」 -**対象ファイル**: `packages/aws-cdk-lib/aws-eks-v2/lib/kubectl-provider.ts` (line 209付近) - -#### 現在のコード -```typescript -if (props.securityGroups && props.securityGroups.length > 0) { - securityGroups = props.securityGroups; - - // Issue warning if both properties are specified - if (props.securityGroup) { - Annotations.of(this).addWarningV2('@aws-cdk/aws-eks-v2:securityGroupConflict', - 'Both securityGroup and securityGroups are specified. Using securityGroups.'); - } -} else if (props.securityGroup) { -``` - -#### 修正後 -`ValidationError` を throw するよう変更する(CDK では `ValidationError` を使う): - -```typescript -if (props.securityGroups && props.securityGroups.length > 0 && props.securityGroup) { - throw new ValidationError( - 'SecurityGroupConflict', - 'Cannot specify both "securityGroup" and "securityGroups". Use "securityGroups" only.', - this, - ); -} - -if (props.securityGroups && props.securityGroups.length > 0) { - securityGroups = props.securityGroups; -} else if (props.securityGroup) { -``` - -> **補足**: -> - `ValidationError` は `../../core/lib/errors` からインポートする(`cluster.ts` で同様に使われている) -> - `kubectl-provider.ts` の現在のインポート文に `ValidationError` が含まれていないため、追加が必要 -> - PR #7857 ではエラーチェックをコンストラクタ冒頭(`privateSubnets` 分岐の外側)で行っているが、今回は `privateSubnets` がない場合はセキュリティグループ自体が無視されるため、**チェックは `privateSubnets` 分岐の内側 or 外側どちらでも機能的には同じ**。ただし PR #7857 に倣い、コンストラクタの冒頭(`super()` の直後)で早期チェックする方が明快 -> - `securityGroup` JSDoc の両プロパティ同時指定に関する記述も削除する(動作が変わるため) - ---- - -## ユニットテストの修正 - -`packages/aws-cdk-lib/aws-eks-v2/test/cluster.test.ts` - -### 3 の変更に伴い、「両方指定したときのテスト」を変更する - -#### 現在のテスト(warning を期待) -```typescript -it('warns when both securityGroup and securityGroups are specified', () => { - // ... - expect(Annotations.fromStack(stack).hasWarning(...)); -}); -``` - -#### 修正後(throw を期待) -```typescript -it('throws when both securityGroup and securityGroups are specified', () => { - expect(() => { - // securityGroup と securityGroups を両方指定するコード - }).toThrow('Cannot specify both "securityGroup" and "securityGroups"'); -}); -``` - ---- - -## 修正作業の順序 - -1. `kubectl-provider.ts`: - - `securityGroup` に `@deprecated` タグを追加 - - `[disable-awslint:prefer-ref-interface]` コメントを削除 - - warning → throw に変更 -2. `awslint.json`: - - `prefer-ref-interface:aws-cdk-lib.aws_eks_v2.KubectlProviderOptions.securityGroups` を追加 -3. `cluster.test.ts`: - - warning テスト → throw テストに変更 -4. ビルド・テスト確認: - ```bash - cd packages/aws-cdk-lib && yarn build && yarn test aws-eks-v2 - cd packages/aws-cdk-lib && yarn lint - ``` diff --git a/.claude/pr-37299-fix-proposal.md b/.claude/pr-37299-fix-proposal.md deleted file mode 100644 index 50c6c8cb1448b..0000000000000 --- a/.claude/pr-37299-fix-proposal.md +++ /dev/null @@ -1,652 +0,0 @@ -# Issue #37299 修正提案 - -## 概要 - -`eks.ServiceAccount` を `IdentityType.POD_IDENTITY` で使用する際に、既存の IAM ロールを `role` プロパティで渡せるようにする。 - -- 対象パッケージ: `aws-eks-v2`(まず対応)、のちに `aws-eks` も同様に対応 -- 対象ファイル: - - `packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts` - - `packages/aws-cdk-lib/aws-eks-v2/test/service-account.test.ts` - ---- - -## 型の方針: `IRole` ではなく `IRoleRef` を使う - -### 根拠 - -CDK の昨今のスタイルでは、ロールを受け取るプロパティには `IRole` ではなく `IRoleRef` を使うことが推奨されている。 - -- `IRoleRef` は `{ roleRef: { roleArn: string; roleName: string } }` を持つ軽量インターフェース -- `IRole extends IRoleRef` のため、`iam.Role` / `iam.Role.fromRoleArn()` など既存の `IRole` はすべて `IRoleRef` として渡せる -- 後方互換性を損なわず、より広い型を受け入れられる - -### 参考実装 - -`aws-stepfunctions-tasks` の `BedrockCreateModelCustomizationJob` が全く同じパターンを採用している: - -``` -packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/create-model-customization-job.ts -``` - -- `props.role?: iam.IRoleRef` で受け取る -- 内部フィールド `private _role: iam.IRoleRef` で保持 -- `public get role(): iam.IRole` で公開(duck typing でダウンキャスト) -- ARN 参照は `this._role.roleRef.roleArn` を使う -- ロール決定ロジックを private メソッドに集約(自動生成フォールバックパターン) - -### 自動生成フォールバックパターンの役割 - -`props.role` が渡されたか否かの分岐を private メソッド 1 か所に集約し、コンストラクタ冒頭でその結果を `_role` に格納する。これにより、その後の ARN 参照箇所(`CfnPodIdentityAssociation`・`KubernetesManifest` アノテーション)で条件分岐が不要になる。 - -```typescript -// コンストラクタ内: 1回だけ決定する -this._role = this.resolvePodIdentityRole(); - -// 以降: 条件分岐なしで参照できる -roleArn: this._role.roleRef.roleArn -``` - ---- - -## 変更 1: `import` 文の更新 - -**ファイル**: `service-account.ts` L5 - -`IRoleRef` を追加で import する: - -```typescript -import type { AddToPrincipalPolicyResult, IPrincipal, IRole, IRoleRef, PrincipalPolicyFragment } from '../../aws-iam'; -``` - ---- - -## 変更 2: `ServiceAccountOptions` に `role` プロパティ追加 - -**ファイル**: `service-account.ts`(`ServiceAccountOptions` インターフェース末尾) - -型は `IRoleRef`(`IRole` ではない): - -```typescript -/** - * An existing IAM role to associate with this service account via Pod Identity. - * Only valid when `identityType` is `IdentityType.POD_IDENTITY`. - * - * When specified, the provided role is used instead of auto-generating one. - * The caller is responsible for configuring the trust policy of the role correctly. - * - * @default - a new IAM role is created automatically - */ -readonly role?: IRoleRef; -``` - ---- - -## 変更 3: クラスフィールドの変更 - -**ファイル**: `service-account.ts`(`ServiceAccount` クラス内) - -`public readonly role: IRole` を **getter** に変更し、内部保持用フィールドを追加する: - -```typescript -// 変更: readonly プロパティから getter へ -// (TypeScript の API 互換性は維持される。消費者側から見た挙動は同じ) -public get role(): IRole { ... } // 変更 3-b で定義 - -// 追加: IRSA 時のロールを保持 -private _irsaRole?: IRole; - -// 追加: POD_IDENTITY 時のロールを IRoleRef として保持 -private _podIdentityRole?: IRoleRef; -``` - -> **補足**: `BedrockCreateModelCustomizationJob` では `_role` を常に `IRoleRef` で保持しているが、`ServiceAccount` は IRSA / POD_IDENTITY の両モードがあるため、モード別にフィールドを分けて持つ設計とする。 - -### 変更 3-b: `role` getter の追加 - -```typescript -/** - * The role which is linked to the service account. - * - * Note: If an L1 construct (e.g. CfnRole) was provided as the `role` option, - * accessing this property will throw an error. Use `roleRef.roleArn` via the - * CfnPodIdentityAssociation directly in that case. - */ -public get role(): IRole { - if (this._podIdentityRole !== undefined) { - // POD_IDENTITY の場合: IRole かどうか確認してキャスト - if ('grant' in this._podIdentityRole) { - return this._podIdentityRole as IRole; - } - throw new ValidationError( - 'ServiceAccountRole', - 'The provided role is not an instance of IRole. ' + - 'Cannot access role grants when using an L1 construct (e.g. CfnRole) as the role.', - this, - ); - } - // IRSA の場合: 常に IRole として保持されている - return this._irsaRole!; -} -``` - -**この設計の意図**: - -| 操作 | L2 `Role` を渡した場合 | L1 `CfnRole` を渡した場合 | -|---|---|---| -| ServiceAccount の作成 | ✅ 成功 | ✅ 成功 | -| PodIdentityAssociation の作成 | ✅ 成功 | ✅ 成功 | -| `sa.role.addManagedPolicy(...)` | ✅ 成功 | ❌ getter でエラー | -| `sa.role.grant(...)` | ✅ 成功 | ❌ getter でエラー | - -コンストラクタでエラーにすると L1 を渡した場合に ServiceAccount 自体が作れなくなるが、`CfnPodIdentityAssociation` の作成には `roleRef.roleArn` があれば十分なため、getter でのみエラーにするほうが意図した設計となる。 - ---- - -## 変更 4: コンストラクタのバリデーション追加 - -**ファイル**: `service-account.ts`(DNS バリデーションの直後) - -```typescript -if (props.role !== undefined && props.identityType !== IdentityType.POD_IDENTITY) { - throw new Error( - 'The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.', - ); -} -``` - ---- - -## 変更 5: コンストラクタの `principal` / `role` 決定ロジック置き換え - -**ファイル**: `service-account.ts` L161〜211(`let principal: IPrincipal;` から `this.role = role;` まで) - -```typescript -let role: IRole; -if (props.identityType !== IdentityType.POD_IDENTITY) { - /* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role. - * See documentation: https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html - */ - const conditions = new CfnJson(this, 'ConditionJson', { - value: { - [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:aud`]: 'sts.amazonaws.com', - [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:sub`]: `system:serviceaccount:${this.serviceAccountNamespace}:${this.serviceAccountName}`, - }, - }); - const principal = new OpenIdConnectPrincipal(cluster.openIdConnectProvider).withConditions({ - StringEquals: conditions, - }); - role = new Role(this, 'Role', { assumedBy: principal }); -} else { - // EKS Pod Identity does not support Fargate - // TODO: raise an error when using Fargate - - // ロール決定ロジックを private メソッドに集約(自動生成フォールバックパターン) - this._podIdentityRole = this.resolvePodIdentityRole(); - - // ensure the pod identity agent - cluster.eksPodIdentityAgent; - - // associate this service account with the pod role for the cluster - new CfnPodIdentityAssociation(this, 'Association', { - clusterName: cluster.clusterName, - namespace: props.namespace ?? 'default', - roleArn: this._podIdentityRole.roleRef.roleArn, - serviceAccount: this.serviceAccountName, - }); - -} - -// IRSA の場合のみ _irsaRole に格納(POD_IDENTITY は getter 経由で _podIdentityRole から返す) -if (props.identityType !== IdentityType.POD_IDENTITY) { - this._irsaRole = role; -} -``` - ---- - -## 変更 6: private メソッド `resolvePodIdentityRole` の追加 - -**ファイル**: `service-account.ts`(クラス末尾) - -```typescript -/** - * Resolves the IAM role to use for Pod Identity. - * Returns the provided role if specified, otherwise auto-generates one. - */ -private resolvePodIdentityRole(): IRoleRef { - if (this.props.role) { - return this.props.role; - } - const role = new Role(this, 'Role', { - assumedBy: new ServicePrincipal('pods.eks.amazonaws.com'), - }); - // EKS Pod Identities requires both assumed role actions otherwise it would fail. - role.assumeRolePolicy!.addStatements(new PolicyStatement({ - actions: ['sts:AssumeRole', 'sts:TagSession'], - principals: [new ServicePrincipal('pods.eks.amazonaws.com')], - })); - return role; -} -``` - -> **注意**: `props` をメソッド内から参照するため、`props` をクラスフィールドとして保持する必要がある(`BedrockCreateModelCustomizationJob` と同様に `private readonly props` として保持)。あるいはコンストラクタ引数を private メソッドに渡す形でも可。 - ---- - -## 変更 7: インテグレーションテスト追加 - -**ファイル**: `packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts`(新規作成) - -`aws-eks`(v1)の `integ.eks-pod-identities.ts` を参考に、v2 向けに作成する。 -既存の auto-generate ケース(Case 1)に加え、外部ロールを `role` prop で渡すケース(Case 2)を同一 Stack 内に含める。 - -```typescript -import * as ec2 from 'aws-cdk-lib/aws-ec2'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import type { StackProps } from 'aws-cdk-lib'; -import { App, Stack } from 'aws-cdk-lib'; -import { getClusterVersionConfig } from './integ-tests-kubernetes-version'; -import * as eks from 'aws-cdk-lib/aws-eks-v2'; -import { IntegTest } from '@aws-cdk/integ-tests-alpha'; - -/** - * After deployment, verify with: - * - * $ kubectl get po - * - * You should see two pods named `demo` and `demo-external-role` in Completed STATUS. - * - * $ kubectl logs -f demo - * $ kubectl logs -f demo-external-role - * - * Both pods should show a log message with UserId, Account and Arn, - * indicating they are running with the correct eks pod identity defined with ServiceAccount. - * The `demo-external-role` pod uses an externally-created IAM role passed via the `role` prop. - */ - -class EksPodIdentitiesStack extends Stack { - constructor(scope: App, id: string, props?: StackProps) { - super(scope, id, props); - - const vpc = new ec2.Vpc(this, 'VPC', { natGateways: 1 }); - - const cluster = new eks.Cluster(this, 'Cluster', { - vpc, - defaultCapacity: 1, - ...getClusterVersionConfig(this), - }); - - // Case 1: auto-generated IAM role (existing behavior) - const sa = new eks.ServiceAccount(this, 'ServiceAccount', { - cluster, - name: 'test-sa', - namespace: 'default', - identityType: eks.IdentityType.POD_IDENTITY, - }); - - sa.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); - - const pod = cluster.addManifest('demopod', { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'demo' }, - spec: { - serviceAccountName: sa.serviceAccountName, - containers: [ - { - name: 'demo', - image: 'public.ecr.aws/amazonlinux/amazonlinux:2023', - command: ['/bin/bash', '-c', 'yum update -y && yum install -y awscli && aws sts get-caller-identity'], - }, - ], - }, - }); - pod.node.addDependency(sa); - - // Case 2: externally-created IAM role passed via the `role` prop - // The trust policy must allow pods.eks.amazonaws.com with sts:AssumeRole and sts:TagSession. - const externalRole = new iam.Role(this, 'ExternalRole', { - assumedBy: new iam.SessionTagsPrincipal( - new iam.ServicePrincipal('pods.eks.amazonaws.com'), - ), - }); - externalRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); - - const saWithExternalRole = new eks.ServiceAccount(this, 'ServiceAccountWithExternalRole', { - cluster, - name: 'test-sa-external-role', - namespace: 'default', - identityType: eks.IdentityType.POD_IDENTITY, - role: externalRole, - }); - - const podWithExternalRole = cluster.addManifest('demopod-external-role', { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'demo-external-role' }, - spec: { - serviceAccountName: saWithExternalRole.serviceAccountName, - containers: [ - { - name: 'demo', - image: 'public.ecr.aws/amazonlinux/amazonlinux:2023', - command: ['/bin/bash', '-c', 'yum update -y && yum install -y awscli && aws sts get-caller-identity'], - }, - ], - }, - }); - podWithExternalRole.node.addDependency(saWithExternalRole); - } -} - -const app = new App({ - postCliContext: { - '@aws-cdk/aws-lambda:useCdkManagedLogGroup': false, - '@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy': false, - }, -}); - -const stack = new EksPodIdentitiesStack(app, 'eks-pod-identities-v2'); - -new IntegTest(app, 'integ-eks-pod-identities-v2', { - testCases: [stack], -}); -``` - -### ポイント - -- **Case 1**: 既存動作の確認。`role` prop なしで auto-generate されたロールが正しく機能すること -- **Case 2**: 新機能の確認。`SessionTagsPrincipal(ServicePrincipal('pods.eks.amazonaws.com'))` で事前作成したロールを `role` prop で渡し、Pod Identity Association が正しく作成されること -- 両ケースとも Pod が `Completed` になり `aws sts get-caller-identity` の出力が確認できることが検証の基準 - ---- - -## 変更 8: ユニットテスト追加 - -**ファイル**: `service-account.test.ts` の `describe('Service Account with eks.IdentityType.POD_IDENTITY')` ブロック内に追記 - -```typescript -test('uses provided role when role prop is specified', () => { - // GIVEN - const { stack, cluster } = testFixtureCluster(); - const existingRole = new iam.Role(stack, 'ExistingRole', { - assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), - }); - - // WHEN - new eks.ServiceAccount(stack, 'MyServiceAccount', { - cluster, - identityType: eks.IdentityType.POD_IDENTITY, - role: existingRole, - }); - const t = Template.fromStack(stack); - - // THEN - // provided role ARN が PodIdentityAssociation に使われること - t.hasResourceProperties('AWS::EKS::PodIdentityAssociation', { - ClusterName: { Ref: 'ClusterEB0386A7' }, - Namespace: 'default', - RoleArn: { 'Fn::GetAtt': ['ExistingRole...', 'Arn'] }, - ServiceAccount: 'stackmyserviceaccount58b9529e', - }); - // ServiceAccount 用の IAM Role が auto-generate されないこと(ExistingRole のみ存在) - t.resourceCountIs('AWS::IAM::Role', 1); - // Pod Identity Agent addon が作られること - t.hasResourceProperties('AWS::EKS::Addon', { - AddonName: 'eks-pod-identity-agent', - }); -}); - -test('throws if role is specified without POD_IDENTITY identity type', () => { - // GIVEN - const { stack, cluster } = testFixtureCluster(); - const existingRole = new iam.Role(stack, 'ExistingRole', { - assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), - }); - - // WHEN / THEN - expect(() => new eks.ServiceAccount(stack, 'MyServiceAccount', { - cluster, - identityType: eks.IdentityType.IRSA, - role: existingRole, - })).toThrow('The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.'); -}); - -test('throws if role is specified with default identity type (IRSA)', () => { - // GIVEN - const { stack, cluster } = testFixtureCluster(); - const existingRole = new iam.Role(stack, 'ExistingRole', { - assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), - }); - - // WHEN / THEN - expect(() => new eks.ServiceAccount(stack, 'MyServiceAccount', { - cluster, - // identityType 未指定 → デフォルト IRSA - role: existingRole, - })).toThrow('The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.'); -}); - -test('sa.role getter returns the provided L2 role', () => { - // GIVEN - const { stack, cluster } = testFixtureCluster(); - const existingRole = new iam.Role(stack, 'ExistingRole', { - assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'), - }); - - // WHEN - const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { - cluster, - identityType: eks.IdentityType.POD_IDENTITY, - role: existingRole, - }); - - // THEN: getter が提供した L2 ロールをそのまま返すこと - expect(sa.role).toBe(existingRole); -}); - -test('ServiceAccount creation succeeds when L1 CfnRole is provided, but sa.role getter throws', () => { - // GIVEN - const { stack, cluster } = testFixtureCluster(); - const cfnRole = new iam.CfnRole(stack, 'CfnRole', { - assumeRolePolicyDocument: { - Statement: [{ - Action: ['sts:AssumeRole', 'sts:TagSession'], - Effect: 'Allow', - Principal: { Service: 'pods.eks.amazonaws.com' }, - }], - }, - }); - - // WHEN: ServiceAccount の作成自体は成功すること(コンストラクタでエラーにならない) - const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { - cluster, - identityType: eks.IdentityType.POD_IDENTITY, - role: cfnRole, - }); - - // THEN: PodIdentityAssociation は正常に作成されること - Template.fromStack(stack).hasResourceProperties('AWS::EKS::PodIdentityAssociation', { - ClusterName: { Ref: 'ClusterEB0386A7' }, - Namespace: 'default', - ServiceAccount: 'stackmyserviceaccount58b9529e', - }); - - // THEN: sa.role にアクセスすると getter でエラーになること - expect(() => sa.role).toThrow( - 'The provided role is not an instance of IRole.', - ); -}); - -test('sa.role getter returns auto-generated role when no role prop is provided with POD_IDENTITY', () => { - // GIVEN - const { stack, cluster } = testFixtureCluster(); - - // WHEN - const sa = new eks.ServiceAccount(stack, 'MyServiceAccount', { - cluster, - identityType: eks.IdentityType.POD_IDENTITY, - }); - - // THEN: getter が IRole を返すこと - expect(sa.role).toBeDefined(); - // addToPrincipalPolicy が呼べること(IRole として機能すること) - expect(() => sa.addToPrincipalPolicy( - new iam.PolicyStatement({ actions: ['s3:GetObject'], resources: ['*'] }), - )).not.toThrow(); -}); -``` - ---- - -## 変更のポイント - -| 項目 | 詳細 | -|---|---| -| **`props.role` の型** | `IRoleRef`(`IRole` より広い型。`IRole` は `IRoleRef` を継承しているため渡せる) | -| **`this.role` の実装** | `readonly` プロパティから getter に変更。TypeScript API 互換性は維持される | -| **後方互換性** | `role` 未指定時は従来通りロールを自動生成する | -| **バリデーション** | IRSA(またはデフォルト)で `role` を指定するとコンストラクタ内でエラー | -| **既存ロール使用時の trust policy** | 変更しない(ユーザー責任) | -| **`CfnPodIdentityAssociation`** | 既存ロール・自動生成どちらでも必ず作成される | -| **`cluster.eksPodIdentityAgent`** | 既存ロール・自動生成どちらでも必ず呼ばれる | -| **ARN 参照** | `role.roleArn` ではなく `_podIdentityRole.roleRef.roleArn` を使う | -| **L1 CfnRole を渡した場合** | ServiceAccount・PodIdentityAssociation の作成は成功。`sa.role` アクセス時のみ getter でエラー | - ---- - -## 変更 9: README 追記 - -**ファイル**: `packages/aws-cdk-lib/aws-eks-v2/README.md` - -### 追記箇所 - -Service Accounts セクション(L830〜)の末尾、`#### Migrating from the deprecated eks.OpenIdConnectProvider` セクションの直前に追記する。 - -v2 README には Pod Identities セクションが完全に存在しないため、新設する。 - -### 追記内容 - -```markdown -### Pod Identities - -[Amazon EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) is a feature that simplifies how -Kubernetes applications running on Amazon EKS can obtain AWS IAM credentials. It provides a way to associate an IAM role with a -Kubernetes service account, allowing pods to retrieve temporary AWS credentials without the need -to manage IAM roles and policies directly. - -By default, `ServiceAccount` creates an `OpenIdConnectProvider` for -[IRSA (IAM roles for service accounts)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) if -`identityType` is `undefined` or `IdentityType.IRSA`. - -You may opt in to Amazon EKS Pod Identities as below: - -```ts -declare const cluster: eks.Cluster; - -new eks.ServiceAccount(this, 'ServiceAccount', { - cluster, - name: 'test-sa', - namespace: 'default', - identityType: eks.IdentityType.POD_IDENTITY, -}); -``` - -When you create the `ServiceAccount` with the `identityType` set to `POD_IDENTITY`, -the `ServiceAccount` construct will perform the following actions behind the scenes: - -1. It will create an IAM role with the necessary trust policy to allow the `pods.eks.amazonaws.com` principal to assume the role. - This trust policy grants the EKS service the permission to retrieve temporary AWS credentials on behalf of the pods using this service account. - -2. It will enable the "Amazon EKS Pod Identity Agent" add-on on the EKS cluster. This add-on is responsible for managing the temporary - AWS credentials and making them available to the pods. - -3. It will create an association between the IAM role and the Kubernetes service account. This association allows the pods using this - service account to obtain the temporary AWS credentials from the associated IAM role. - -#### Using an existing IAM role with Pod Identity - -If you want to manage IAM roles centrally (e.g., in a dedicated `IamConstruct`) or reuse an existing role created via -`iam.Role.fromRoleArn()`, you can pass it to `ServiceAccount` via the `role` property. - -The `role` property accepts any `IRoleRef`, including `iam.Role`, `iam.Role.fromRoleArn()`, and L1 `iam.CfnRole`. -**This option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.** - -The caller is responsible for configuring the trust policy of the role correctly. For Pod Identity, the role must allow -`pods.eks.amazonaws.com` to perform `sts:AssumeRole` and `sts:TagSession`. - -```ts -import * as iam from 'aws-cdk-lib/aws-iam'; -declare const cluster: eks.Cluster; - -// Create and manage the IAM role separately -const appRole = new iam.Role(this, 'AppRole', { - assumedBy: new iam.SessionTagsPrincipal( - new iam.ServicePrincipal('pods.eks.amazonaws.com'), - ), -}); -appRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); - -// Pass the existing role to ServiceAccount -new eks.ServiceAccount(this, 'AppServiceAccount', { - cluster, - name: 'app-sa', - namespace: 'production', - identityType: eks.IdentityType.POD_IDENTITY, - role: appRole, -}); -``` - -When `role` is specified, the auto-generation of an IAM role is skipped. -The provided role's ARN is used directly in the `PodIdentityAssociation`. - -> **Note:** If you pass an L1 construct (`iam.CfnRole`) as the `role`, the `ServiceAccount` and `PodIdentityAssociation` -> are created successfully. However, accessing `serviceAccount.role` to call methods such as `grant()` or -> `addManagedPolicy()` will throw an error, as those methods are only available on L2 `IRole` instances. -``` - ---- - -## 注意事項 - -- `IRoleRef` の import を追加する必要がある(現在は `IRole` のみ import 済み) -- `ServiceAccountProps extends ServiceAccountOptions` のため、`ServiceAccountProps` にも自動的に `role` が追加される -- `props` を private メソッドから参照するため、コンストラクタ引数の扱いを調整する(メソッドに引数として渡すか、クラスフィールドとして保持するか、実装時に判断) -- `aws-eks`(v1)パッケージへの同様の対応は別途実施 - ---- - -## 検討経緯: IRSA への `role` プロパティ対応について - -### 検討内容 - -POD_IDENTITY だけでなく IRSA でも既存 IAM ロールを受け入れるべきか検討した。 - -### IRSA における実装上の特性 - -`service-account.ts` の IRSA ブロックを確認すると、Trust Policy の条件式は以下のようにコンストラクタ内で動的に生成されている: - -```typescript -const conditions = new CfnJson(this, 'ConditionJson', { - value: { - [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:aud`]: 'sts.amazonaws.com', - [`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:sub`]: - `system:serviceaccount:${this.serviceAccountNamespace}:${this.serviceAccountName}`, - }, -}); -``` - -つまり、OIDC Issuer URL・namespace・SA 名はいずれも `ServiceAccount` コンストラクト内で解決される値であり、**クラスターが存在して初めて確定する**。 - -### 結論: 今回は POD_IDENTITY のみ対応 - -| 観点 | POD_IDENTITY | IRSA | -|---|---|---| -| Trust Policy に必要な情報 | 静的(`pods.eks.amazonaws.com`) | 動的(OIDC Issuer URL・namespace・SA 名) | -| クラスター外での事前ロール作成 | ✅ 容易 | ❌ クラスター作成後でないと OIDC Issuer URL が確定しない | -| 原 issue の要求スコープ | ✅ 明示的に要求 | ❌ 要求外 | - -IRSA の場合、ユーザーが外で IAM ロールを作成するには OIDC Issuer URL を別途取得する必要があり、CDK を使うメリットが薄れる。実装難易度は低いが、**ユーザーにとっての実用性が限られる**ため、今回のスコープから外した。 - -IRSA 対応が必要な場合は別途 issue/PR として扱う。 diff --git a/.serena/.gitignore b/.serena/.gitignore deleted file mode 100644 index 2e510aff5855b..0000000000000 --- a/.serena/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/cache -/project.local.yml diff --git a/.serena/.serena/.gitignore b/.serena/.serena/.gitignore deleted file mode 100644 index 2e510aff5855b..0000000000000 --- a/.serena/.serena/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/cache -/project.local.yml diff --git a/.serena/.serena/memories/code_style_and_conventions.md b/.serena/.serena/memories/code_style_and_conventions.md deleted file mode 100644 index b6c8d35e92c93..0000000000000 --- a/.serena/.serena/memories/code_style_and_conventions.md +++ /dev/null @@ -1,54 +0,0 @@ -# コードスタイルと規約 - -## TypeScript / jsii 規約 -- **jsii 互換**: 公開 API は jsii の型制約に従う (Java/Python/C# バインディング生成のため) - - `any` 型を公開 API に使わない - - 非直列化可能な型 (関数型など) を公開 API に使わない - - `interface` は `I` プレフィックス (例: `IBucket`, `ILambdaFunction`) -- **命名規則**: - - クラス: PascalCase (例: `Bucket`, `BucketPolicy`) - - インターフェース: `I` + PascalCase (例: `IBucket`) - - メソッド/プロパティ: camelCase - - 定数/Enum: PascalCase -- **型ヒント**: TypeScript なので型を明示する。戻り値型も省略しない -- **JSDoc**: 公開 API には JSDoc コメントを必ず記述する (`/** ... */`) - - `@attribute` タグ: CloudFormation 属性にマッピングされるプロパティ - - `@deprecated` タグ: 廃止予定 API - -## インポートスタイル -```typescript -// 同一パッケージ内: 相対インポート -import { BucketPolicy } from './bucket-policy'; -import type { IBucketNotificationDestination } from './destination'; - -// aws-cdk-lib 内の他モジュール: 相対インポート (../../) -import * as iam from '../../aws-iam'; -import * as kms from '../../aws-kms'; -import { Stack, Resource } from '../../core'; - -// 型のみのインポートには import type を使用 -import type { IGrantable } from '../../aws-iam'; -``` - -## 自動生成ファイル -- `*.generated.ts` (例: `s3.generated.ts`) は CloudFormation spec から自動生成。手動編集禁止。 -- `*.d.ts`, `*.js` はビルド成果物。手動編集禁止。 - -## Breaking Changes の扱い -- 公開 API シグネチャの変更は原則禁止 -- 変更が必要な場合は `allowed-breaking-changes.txt` を確認 -- 動作変更は `cx-api` の Feature Flag 経由で導入 (既存動作はデフォルトで維持) - -## テストスタイル -- フレームワーク: Jest -- テストファイル: `test/*.test.ts` -- `Template.fromStack(stack).hasResourceProperties(...)` パターンでアサーション -- インテグレーションテスト: `integ.*.ts` ファイル (AWS アカウント必要) - -## PR コミットメッセージ形式 -conventional commits 形式: -``` -feat(aws-s3): add new bucket property -fix(aws-lambda): fix memory size validation -chore: update dependencies -``` diff --git a/.serena/.serena/memories/project_overview.md b/.serena/.serena/memories/project_overview.md deleted file mode 100644 index 10fca2cf8a08b..0000000000000 --- a/.serena/.serena/memories/project_overview.md +++ /dev/null @@ -1,48 +0,0 @@ -# AWS CDK プロジェクト概要 - -## 目的 -AWS Cloud Development Kit (AWS CDK) - TypeScript 製のオープンソース IaC フレームワーク。 -CloudFormation テンプレートをプログラマブルに生成できる。jsii により Java/Python/C#/.NET 向けバインディングも生成。 - -## 技術スタック -- 言語: TypeScript (jsii) -- パッケージ管理: Yarn (v1) + Lerna + Nx -- テスト: Jest -- Linter: ESLint + pkglint + awslint -- ビルド: cdk-build-tools (tools/@aws-cdk/cdk-build-tools) -- Node.js: >= 20.x - -## リポジトリ構成 -``` -packages/ - aws-cdk-lib/ # メインライブラリ (全サービスモジュール統合) - aws-s3/ # 例: S3 モジュール (lib/, test/, index.ts) - aws-lambda/ - core/ # CDK コア (Construct, Stack, App など) - cx-api/ # Cloud Assembly API / Feature Flags - ... # 200以上の AWS サービスモジュール - @aws-cdk/ # 個別パッケージ (alpha/experimental) - @aws-cdk-testing/ # テストユーティリティ - aws-cdk/ # CDK CLI - cdk/ # CDK CLI エイリアス -tools/ - @aws-cdk/cdk-build-tools/ # ビルドツール - @aws-cdk/pkglint/ # パッケージ規約チェック - @aws-cdk/eslint-config/ # ESLint 設定 - @aws-cdk/spec2cdk/ # CloudFormation spec → CDK コード生成 -scripts/ # CI/CD スクリプト -design/ # RFC・設計ドキュメント -``` - -## モジュール構造 (例: aws-cdk-lib/aws-s3) -``` -aws-s3/ - lib/ - bucket.ts # メイン実装 - bucket-policy.ts - s3.generated.ts # CloudFormation spec から自動生成 (編集不可) - test/ - bucket.test.ts # Jest ユニットテスト - index.ts # 公開 API のエクスポート - README.md -``` diff --git a/.serena/.serena/memories/suggested_commands.md b/.serena/.serena/memories/suggested_commands.md deleted file mode 100644 index 2ca20d6f45600..0000000000000 --- a/.serena/.serena/memories/suggested_commands.md +++ /dev/null @@ -1,45 +0,0 @@ -# 主要コマンド一覧 - -## セットアップ -```bash -yarn install # 依存パッケージのインストール -``` - -## ビルド -```bash -./build.sh # 全体ビルド (時間がかかる) -cd packages/aws-cdk-lib && yarn build # aws-cdk-lib だけビルド -yarn watch # ウォッチモード (変更時に再ビルド) -``` - -## テスト -```bash -# 特定モジュールのユニットテスト -cd packages/aws-cdk-lib && yarn test aws-lambda -cd packages/aws-cdk-lib && yarn test aws-s3 - -# 特定テストファイル -cd packages/aws-cdk-lib && yarn test aws-s3/test/bucket.test.ts - -# インテグレーションテスト (AWS アカウント必要) -cd packages/@aws-cdk-testing/framework-integ -yarn integ test/aws-lambda/test/integ.lambda.js --update-on-failed -``` - -## Lint / フォーマット -```bash -cd packages/aws-cdk-lib && yarn lint # ESLint -yarn pkglint # パッケージ規約チェック -``` - -## その他 -```bash -yarn build+test # ビルド + テスト -./scripts/run-rosetta.sh # README サンプルコードのコンパイル確認 -``` - -## Nx (モノレポ向け) -```bash -npx nx build aws-cdk-lib # Nx 経由でビルド -npx nx test aws-cdk-lib # Nx 経由でテスト -``` diff --git a/.serena/.serena/memories/task_completion_checklist.md b/.serena/.serena/memories/task_completion_checklist.md deleted file mode 100644 index f0cd329f1e43e..0000000000000 --- a/.serena/.serena/memories/task_completion_checklist.md +++ /dev/null @@ -1,41 +0,0 @@ -# タスク完了時のチェックリスト - -## コード変更後に必ず実施すること - -1. **ビルド確認** - ```bash - cd packages/aws-cdk-lib && yarn build - # または変更したパッケージのディレクトリで - yarn build - ``` - -2. **ユニットテスト実行** - ```bash - cd packages/aws-cdk-lib && yarn test <モジュール名> - # 例: yarn test aws-s3 - ``` - -3. **Lint チェック** - ```bash - cd packages/aws-cdk-lib && yarn lint - ``` - -4. **pkglint チェック** (package.json 変更時) - ```bash - yarn pkglint - ``` - -5. **README サンプルコード確認** (README 変更時) - ```bash - ./scripts/run-rosetta.sh - ``` - -6. **自動生成ファイルの確認** - - `*.generated.ts` を手動編集していないか確認 - - `.jsii` ファイルがビルドで正しく生成されているか確認 - -## PR 作成前チェック -- [ ] 新機能には JSDoc コメントがある -- [ ] 公開 API の breaking change がない (または `allowed-breaking-changes.txt` に記載) -- [ ] ユニットテストが追加・更新されている -- [ ] コミットメッセージが conventional commits 形式 diff --git a/.serena/.serena/project.yml b/.serena/.serena/project.yml deleted file mode 100644 index deb8941047e4f..0000000000000 --- a/.serena/.serena/project.yml +++ /dev/null @@ -1,138 +0,0 @@ -# the name by which the project can be referenced within Serena -project_name: "aws-cdk" - - -# list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp -# csharp_omnisharp dart elixir elm erlang -# fortran fsharp go groovy haskell -# java julia kotlin lua markdown -# matlab nix pascal perl php -# php_phpactor powershell python python_jedi r -# rego ruby ruby_solargraph rust scala -# swift terraform toml typescript typescript_vts -# vue yaml zig -# (This list may be outdated. For the current list, see values of Language enum here: -# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py -# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) -# Note: -# - For C, use cpp -# - For JavaScript, use typescript -# - For Free Pascal/Lazarus, use pascal -# Special requirements: -# Some languages require additional setup/installations. -# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers -# When using multiple languages, the first language server that supports a given file will be used for that file. -# The first language is the default language and the respective language server will be used as a fallback. -# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. -languages: -- typescript - -# the encoding used by text files in the project -# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings -encoding: "utf-8" - -# line ending convention to use when writing source files. -# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) -# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. -line_ending: - -# The language backend to use for this project. -# If not set, the global setting from serena_config.yml is used. -# Valid values: LSP, JetBrains -# Note: the backend is fixed at startup. If a project with a different backend -# is activated post-init, an error will be returned. -language_backend: - -# whether to use project's .gitignore files to ignore files -ignore_all_files_in_gitignore: true - -# list of additional paths to ignore in this project. -# Same syntax as gitignore, so you can use * and **. -# Note: global ignored_paths from serena_config.yml are also applied additively. -ignored_paths: [] - -# whether the project is in read-only mode -# If set to true, all editing tools will be disabled and attempts to use them will result in an error -# Added on 2025-04-18 -read_only: false - -# list of tool names to exclude. -# This extends the existing exclusions (e.g. from the global configuration) -# -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. -excluded_tools: [] - -# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). -# This extends the existing inclusions (e.g. from the global configuration). -included_optional_tools: [] - -# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. -# This cannot be combined with non-empty excluded_tools or included_optional_tools. -fixed_tools: [] - -# list of mode names to that are always to be included in the set of active modes -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this setting overrides the global configuration. -# Set this to [] to disable base modes for this project. -# Set this to a list of mode names to always include the respective modes for this project. -base_modes: - -# list of mode names that are to be activated by default. -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this overrides the setting from the global configuration (serena_config.yml). -# This setting can, in turn, be overridden by CLI parameters (--mode). -default_modes: - -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: "" - -# time budget (seconds) per tool call for the retrieval of additional symbol information -# such as docstrings or parameter information. -# This overrides the corresponding setting in the global configuration; see the documentation there. -# If null or missing, use the setting from the global configuration. -symbol_info_budget: - -# list of regex patterns which, when matched, mark a memory entry as read‑only. -# Extends the list from the global configuration, merging the two lists. -read_only_memory_patterns: [] diff --git a/.serena/memories/code_style_and_conventions.md b/.serena/memories/code_style_and_conventions.md deleted file mode 100644 index b6c8d35e92c93..0000000000000 --- a/.serena/memories/code_style_and_conventions.md +++ /dev/null @@ -1,54 +0,0 @@ -# コードスタイルと規約 - -## TypeScript / jsii 規約 -- **jsii 互換**: 公開 API は jsii の型制約に従う (Java/Python/C# バインディング生成のため) - - `any` 型を公開 API に使わない - - 非直列化可能な型 (関数型など) を公開 API に使わない - - `interface` は `I` プレフィックス (例: `IBucket`, `ILambdaFunction`) -- **命名規則**: - - クラス: PascalCase (例: `Bucket`, `BucketPolicy`) - - インターフェース: `I` + PascalCase (例: `IBucket`) - - メソッド/プロパティ: camelCase - - 定数/Enum: PascalCase -- **型ヒント**: TypeScript なので型を明示する。戻り値型も省略しない -- **JSDoc**: 公開 API には JSDoc コメントを必ず記述する (`/** ... */`) - - `@attribute` タグ: CloudFormation 属性にマッピングされるプロパティ - - `@deprecated` タグ: 廃止予定 API - -## インポートスタイル -```typescript -// 同一パッケージ内: 相対インポート -import { BucketPolicy } from './bucket-policy'; -import type { IBucketNotificationDestination } from './destination'; - -// aws-cdk-lib 内の他モジュール: 相対インポート (../../) -import * as iam from '../../aws-iam'; -import * as kms from '../../aws-kms'; -import { Stack, Resource } from '../../core'; - -// 型のみのインポートには import type を使用 -import type { IGrantable } from '../../aws-iam'; -``` - -## 自動生成ファイル -- `*.generated.ts` (例: `s3.generated.ts`) は CloudFormation spec から自動生成。手動編集禁止。 -- `*.d.ts`, `*.js` はビルド成果物。手動編集禁止。 - -## Breaking Changes の扱い -- 公開 API シグネチャの変更は原則禁止 -- 変更が必要な場合は `allowed-breaking-changes.txt` を確認 -- 動作変更は `cx-api` の Feature Flag 経由で導入 (既存動作はデフォルトで維持) - -## テストスタイル -- フレームワーク: Jest -- テストファイル: `test/*.test.ts` -- `Template.fromStack(stack).hasResourceProperties(...)` パターンでアサーション -- インテグレーションテスト: `integ.*.ts` ファイル (AWS アカウント必要) - -## PR コミットメッセージ形式 -conventional commits 形式: -``` -feat(aws-s3): add new bucket property -fix(aws-lambda): fix memory size validation -chore: update dependencies -``` diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md deleted file mode 100644 index 10fca2cf8a08b..0000000000000 --- a/.serena/memories/project_overview.md +++ /dev/null @@ -1,48 +0,0 @@ -# AWS CDK プロジェクト概要 - -## 目的 -AWS Cloud Development Kit (AWS CDK) - TypeScript 製のオープンソース IaC フレームワーク。 -CloudFormation テンプレートをプログラマブルに生成できる。jsii により Java/Python/C#/.NET 向けバインディングも生成。 - -## 技術スタック -- 言語: TypeScript (jsii) -- パッケージ管理: Yarn (v1) + Lerna + Nx -- テスト: Jest -- Linter: ESLint + pkglint + awslint -- ビルド: cdk-build-tools (tools/@aws-cdk/cdk-build-tools) -- Node.js: >= 20.x - -## リポジトリ構成 -``` -packages/ - aws-cdk-lib/ # メインライブラリ (全サービスモジュール統合) - aws-s3/ # 例: S3 モジュール (lib/, test/, index.ts) - aws-lambda/ - core/ # CDK コア (Construct, Stack, App など) - cx-api/ # Cloud Assembly API / Feature Flags - ... # 200以上の AWS サービスモジュール - @aws-cdk/ # 個別パッケージ (alpha/experimental) - @aws-cdk-testing/ # テストユーティリティ - aws-cdk/ # CDK CLI - cdk/ # CDK CLI エイリアス -tools/ - @aws-cdk/cdk-build-tools/ # ビルドツール - @aws-cdk/pkglint/ # パッケージ規約チェック - @aws-cdk/eslint-config/ # ESLint 設定 - @aws-cdk/spec2cdk/ # CloudFormation spec → CDK コード生成 -scripts/ # CI/CD スクリプト -design/ # RFC・設計ドキュメント -``` - -## モジュール構造 (例: aws-cdk-lib/aws-s3) -``` -aws-s3/ - lib/ - bucket.ts # メイン実装 - bucket-policy.ts - s3.generated.ts # CloudFormation spec から自動生成 (編集不可) - test/ - bucket.test.ts # Jest ユニットテスト - index.ts # 公開 API のエクスポート - README.md -``` diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md deleted file mode 100644 index 2ca20d6f45600..0000000000000 --- a/.serena/memories/suggested_commands.md +++ /dev/null @@ -1,45 +0,0 @@ -# 主要コマンド一覧 - -## セットアップ -```bash -yarn install # 依存パッケージのインストール -``` - -## ビルド -```bash -./build.sh # 全体ビルド (時間がかかる) -cd packages/aws-cdk-lib && yarn build # aws-cdk-lib だけビルド -yarn watch # ウォッチモード (変更時に再ビルド) -``` - -## テスト -```bash -# 特定モジュールのユニットテスト -cd packages/aws-cdk-lib && yarn test aws-lambda -cd packages/aws-cdk-lib && yarn test aws-s3 - -# 特定テストファイル -cd packages/aws-cdk-lib && yarn test aws-s3/test/bucket.test.ts - -# インテグレーションテスト (AWS アカウント必要) -cd packages/@aws-cdk-testing/framework-integ -yarn integ test/aws-lambda/test/integ.lambda.js --update-on-failed -``` - -## Lint / フォーマット -```bash -cd packages/aws-cdk-lib && yarn lint # ESLint -yarn pkglint # パッケージ規約チェック -``` - -## その他 -```bash -yarn build+test # ビルド + テスト -./scripts/run-rosetta.sh # README サンプルコードのコンパイル確認 -``` - -## Nx (モノレポ向け) -```bash -npx nx build aws-cdk-lib # Nx 経由でビルド -npx nx test aws-cdk-lib # Nx 経由でテスト -``` diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md deleted file mode 100644 index f0cd329f1e43e..0000000000000 --- a/.serena/memories/task_completion_checklist.md +++ /dev/null @@ -1,41 +0,0 @@ -# タスク完了時のチェックリスト - -## コード変更後に必ず実施すること - -1. **ビルド確認** - ```bash - cd packages/aws-cdk-lib && yarn build - # または変更したパッケージのディレクトリで - yarn build - ``` - -2. **ユニットテスト実行** - ```bash - cd packages/aws-cdk-lib && yarn test <モジュール名> - # 例: yarn test aws-s3 - ``` - -3. **Lint チェック** - ```bash - cd packages/aws-cdk-lib && yarn lint - ``` - -4. **pkglint チェック** (package.json 変更時) - ```bash - yarn pkglint - ``` - -5. **README サンプルコード確認** (README 変更時) - ```bash - ./scripts/run-rosetta.sh - ``` - -6. **自動生成ファイルの確認** - - `*.generated.ts` を手動編集していないか確認 - - `.jsii` ファイルがビルドで正しく生成されているか確認 - -## PR 作成前チェック -- [ ] 新機能には JSDoc コメントがある -- [ ] 公開 API の breaking change がない (または `allowed-breaking-changes.txt` に記載) -- [ ] ユニットテストが追加・更新されている -- [ ] コミットメッセージが conventional commits 形式 diff --git a/.serena/project.yml b/.serena/project.yml deleted file mode 100644 index 0baaf14f225a8..0000000000000 --- a/.serena/project.yml +++ /dev/null @@ -1,152 +0,0 @@ -# the name by which the project can be referenced within Serena -project_name: "aws-cdk" - - -# list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp -# csharp_omnisharp dart elixir elm erlang -# fortran fsharp go groovy haskell -# java julia kotlin lua markdown -# matlab nix pascal perl php -# php_phpactor powershell python python_jedi r -# rego ruby ruby_solargraph rust scala -# swift terraform toml typescript typescript_vts -# vue yaml zig -# (This list may be outdated. For the current list, see values of Language enum here: -# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py -# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) -# Note: -# - For C, use cpp -# - For JavaScript, use typescript -# - For Free Pascal/Lazarus, use pascal -# Special requirements: -# Some languages require additional setup/installations. -# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers -# When using multiple languages, the first language server that supports a given file will be used for that file. -# The first language is the default language and the respective language server will be used as a fallback. -# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. -languages: -- typescript - -# the encoding used by text files in the project -# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings -encoding: "utf-8" - -# line ending convention to use when writing source files. -# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) -# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. -line_ending: - -# The language backend to use for this project. -# If not set, the global setting from serena_config.yml is used. -# Valid values: LSP, JetBrains -# Note: the backend is fixed at startup. If a project with a different backend -# is activated post-init, an error will be returned. -language_backend: - -# whether to use project's .gitignore files to ignore files -ignore_all_files_in_gitignore: true - -# list of additional paths to ignore in this project. -# Same syntax as gitignore, so you can use * and **. -# Note: global ignored_paths from serena_config.yml are also applied additively. -ignored_paths: [] - -# whether the project is in read-only mode -# If set to true, all editing tools will be disabled and attempts to use them will result in an error -# Added on 2025-04-18 -read_only: false - -# list of tool names to exclude. -# This extends the existing exclusions (e.g. from the global configuration) -# -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. -excluded_tools: [] - -# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). -# This extends the existing inclusions (e.g. from the global configuration). -included_optional_tools: [] - -# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. -# This cannot be combined with non-empty excluded_tools or included_optional_tools. -fixed_tools: [] - -# list of mode names to that are always to be included in the set of active modes -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this setting overrides the global configuration. -# Set this to [] to disable base modes for this project. -# Set this to a list of mode names to always include the respective modes for this project. -base_modes: - -# list of mode names that are to be activated by default. -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this overrides the setting from the global configuration (serena_config.yml). -# This setting can, in turn, be overridden by CLI parameters (--mode). -default_modes: - -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: "" - -# time budget (seconds) per tool call for the retrieval of additional symbol information -# such as docstrings or parameter information. -# This overrides the corresponding setting in the global configuration; see the documentation there. -# If null or missing, use the setting from the global configuration. -symbol_info_budget: - -# list of regex patterns which, when matched, mark a memory entry as read‑only. -# Extends the list from the global configuration, merging the two lists. -read_only_memory_patterns: [] - -# list of regex patterns for memories to completely ignore. -# Matching memories will not appear in list_memories or activate_project output -# and cannot be accessed via read_memory or write_memory. -# To access ignored memory files, use the read_file tool on the raw file path. -# Extends the list from the global configuration, merging the two lists. -# Example: ["_archive/.*", "_episodes/.*"] -ignored_memory_patterns: [] - -# advanced configuration option allowing to configure language server-specific options. -# Maps the language key to the options. -# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. -# No documentation on options means no options are available. -ls_specific_settings: {} diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip new file mode 100644 index 0000000000000..662f4594c4908 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d732cb0a962f1d8bf0696f92469e7801b1588cb14281988c3eef80db9946743c +size 21030328 diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/__entrypoint__.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/__entrypoint__.js new file mode 100644 index 0000000000000..cecb7885e3220 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/__entrypoint__.js @@ -0,0 +1,154 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.external = void 0; +exports.handler = handler; +exports.withRetries = withRetries; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + const parsedUrl = url.parse(event.ResponseURL); + const loggingSafeUrl = `${parsedUrl.protocol}//${parsedUrl.hostname}/${parsedUrl.pathname}?***`; + exports.external.log('submit response to cloudformation', loggingSafeUrl, json); + const responseBody = JSON.stringify(json); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'content-type': '', + 'content-length': Buffer.byteLength(responseBody, 'utf8'), + }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, requestBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, (response) => { + response.resume(); // Consume the response but don't care about it + if (!response.statusCode || response.statusCode >= 400) { + reject(new Error(`Unsuccessful HTTP response: ${response.statusCode}`)); + } + else { + resolve(); + } + }); + request.on('error', reject); + request.write(requestBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/index.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/index.js new file mode 100644 index 0000000000000..013bcaffd8fe5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f/index.js @@ -0,0 +1 @@ +"use strict";var I=Object.create;var t=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,l=Object.prototype.hasOwnProperty;var G=(r,e)=>{for(var o in e)t(r,o,{get:e[o],enumerable:!0})},n=(r,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of P(e))!l.call(r,s)&&s!==o&&t(r,s,{get:()=>e[s],enumerable:!(i=y(e,s))||i.enumerable});return r};var R=(r,e,o)=>(o=r!=null?I(g(r)):{},n(e||!r||!r.__esModule?t(o,"default",{value:r,enumerable:!0}):o,r)),S=r=>n(t({},"__esModule",{value:!0}),r);var k={};G(k,{handler:()=>f});module.exports=S(k);var a=R(require("@aws-sdk/client-ec2")),u=new a.EC2({});function c(r,e){return{GroupId:r,IpPermissions:[{UserIdGroupPairs:[{GroupId:r,UserId:e}],IpProtocol:"-1"}]}}function d(r){return{GroupId:r,IpPermissions:[{IpRanges:[{CidrIp:"0.0.0.0/0"}],IpProtocol:"-1"}]}}async function f(r){let e=r.ResourceProperties.DefaultSecurityGroupId,o=r.ResourceProperties.Account;switch(r.RequestType){case"Create":return p(e,o);case"Update":return h(r);case"Delete":return m(e,o)}}async function h(r){let e=r.OldResourceProperties.DefaultSecurityGroupId,o=r.ResourceProperties.DefaultSecurityGroupId;e!==o&&(await m(e,r.ResourceProperties.Account),await p(o,r.ResourceProperties.Account))}async function p(r,e){try{await u.revokeSecurityGroupEgress(d(r))}catch(o){if(o.name!=="InvalidPermission.NotFound")throw o}try{await u.revokeSecurityGroupIngress(c(r,e))}catch(o){if(o.name!=="InvalidPermission.NotFound")throw o}}async function m(r,e){await u.authorizeSecurityGroupIngress(c(r,e)),await u.authorizeSecurityGroupEgress(d(r))}0&&(module.exports={handler}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip new file mode 100644 index 0000000000000..ef66548e9915c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9674535227143fac02de93f9e5696fbdaff09551a042739bc75893da3b4b11c7 +size 34443564 diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/cfn-response.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/cfn-response.js new file mode 100644 index 0000000000000..dbfaccef2e782 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/cfn-response.js @@ -0,0 +1,104 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Retry = exports.includeStackTraces = exports.MISSING_PHYSICAL_ID_MARKER = exports.CREATE_FAILED_PHYSICAL_ID_MARKER = void 0; +exports.submitResponse = submitResponse; +exports.safeHandler = safeHandler; +exports.redactDataFromPayload = redactDataFromPayload; +const url = require("url"); +const outbound_1 = require("./outbound"); +const util_1 = require("./util"); +exports.CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +exports.MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function submitResponse(status, event, options = {}) { + const json = { + Status: status, + Reason: options.reason || status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || exports.MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: options.noEcho, + Data: event.Data, + }; + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const loggingSafeUrl = `${parsedUrl.protocol}//${parsedUrl.hostname}/${parsedUrl.pathname}?***`; + if (options?.noEcho) { + (0, util_1.log)('submit redacted response to cloudformation', loggingSafeUrl, redactDataFromPayload(json)); + } + else { + (0, util_1.log)('submit response to cloudformation', loggingSafeUrl, json); + } + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await (0, util_1.withRetries)(retryOptions, outbound_1.httpRequest)({ + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'content-type': '', + 'content-length': Buffer.byteLength(responseBody, 'utf8'), + }, + }, responseBody); +} +exports.includeStackTraces = true; // for unit tests +function safeHandler(block) { + return async (event) => { + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === exports.CREATE_FAILED_PHYSICAL_ID_MARKER) { + (0, util_1.log)('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + await block(event); + } + catch (e) { + // tell waiter state machine to retry + if (e instanceof Retry) { + (0, util_1.log)('retry requested by handler'); + throw e; + } + if (!event.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + (0, util_1.log)('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + event.PhysicalResourceId = exports.CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + (0, util_1.log)(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify({ ...event, ResponseURL: '...' })}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', event, { + reason: exports.includeStackTraces ? e.stack : e.message, + }); + } + }; +} +function redactDataFromPayload(payload) { + // Create a deep copy of the payload object + const redactedPayload = JSON.parse(JSON.stringify(payload)); + // Redact the data in the copied payload object + if (redactedPayload.Data) { + const keys = Object.keys(redactedPayload.Data); + for (const key of keys) { + redactedPayload.Data[key] = '*****'; + } + } + return redactedPayload; +} +class Retry extends Error { +} +exports.Retry = Retry; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2ZuLXJlc3BvbnNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY2ZuLXJlc3BvbnNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQXVCQSx3Q0FtQ0M7QUFJRCxrQ0EwQ0M7QUFFRCxzREFZQztBQXJIRCwyQkFBMkI7QUFDM0IseUNBQXlDO0FBQ3pDLGlDQUEwQztBQUc3QixRQUFBLGdDQUFnQyxHQUFHLHdEQUF3RCxDQUFDO0FBQzVGLFFBQUEsMEJBQTBCLEdBQUcsOERBQThELENBQUM7QUFnQmxHLEtBQUssVUFBVSxjQUFjLENBQUMsTUFBNEIsRUFBRSxLQUFpQyxFQUFFLFVBQXlDLEVBQUc7SUFDaEosTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLE9BQU8sQ0FBQyxNQUFNLElBQUksTUFBTTtRQUNoQyxPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxrQ0FBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU07UUFDdEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRTFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sY0FBYyxHQUFHLEdBQUcsU0FBUyxDQUFDLFFBQVEsS0FBSyxTQUFTLENBQUMsUUFBUSxJQUFJLFNBQVMsQ0FBQyxRQUFRLE1BQU0sQ0FBQztJQUNoRyxJQUFJLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUNwQixJQUFBLFVBQUcsRUFBQyw0Q0FBNEMsRUFBRSxjQUFjLEVBQUUscUJBQXFCLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUNqRyxDQUFDO1NBQU0sQ0FBQztRQUNOLElBQUEsVUFBRyxFQUFDLG1DQUFtQyxFQUFFLGNBQWMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNqRSxDQUFDO0lBRUQsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLElBQUEsa0JBQVcsRUFBQyxZQUFZLEVBQUUsc0JBQVcsQ0FBQyxDQUFDO1FBQzNDLFFBQVEsRUFBRSxTQUFTLENBQUMsUUFBUTtRQUM1QixJQUFJLEVBQUUsU0FBUyxDQUFDLElBQUk7UUFDcEIsTUFBTSxFQUFFLEtBQUs7UUFDYixPQUFPLEVBQUU7WUFDUCxjQUFjLEVBQUUsRUFBRTtZQUNsQixnQkFBZ0IsRUFBRSxNQUFNLENBQUMsVUFBVSxDQUFDLFlBQVksRUFBRSxNQUFNLENBQUM7U0FDMUQ7S0FDRixFQUFFLFlBQVksQ0FBQyxDQUFDO0FBQ25CLENBQUM7QUFFVSxRQUFBLGtCQUFrQixHQUFHLElBQUksQ0FBQyxDQUFDLGlCQUFpQjtBQUV2RCxTQUFnQixXQUFXLENBQUMsS0FBb0M7SUFDOUQsT0FBTyxLQUFLLEVBQUUsS0FBVSxFQUFFLEVBQUU7UUFDMUIsdUVBQXVFO1FBQ3ZFLHVFQUF1RTtRQUN2RSxhQUFhO1FBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssd0NBQWdDLEVBQUUsQ0FBQztZQUNwRyxJQUFBLFVBQUcsRUFBQyx1REFBdUQsQ0FBQyxDQUFDO1lBQzdELE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsQ0FBQztZQUN2QyxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILE1BQU0sS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3JCLENBQUM7UUFBQyxPQUFPLENBQU0sRUFBRSxDQUFDO1lBQ2hCLHFDQUFxQztZQUNyQyxJQUFJLENBQUMsWUFBWSxLQUFLLEVBQUUsQ0FBQztnQkFDdkIsSUFBQSxVQUFHLEVBQUMsNEJBQTRCLENBQUMsQ0FBQztnQkFDbEMsTUFBTSxDQUFDLENBQUM7WUFDVixDQUFDO1lBRUQsSUFBSSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO2dCQUM5Qix5RUFBeUU7Z0JBQ3pFLG1FQUFtRTtnQkFDbkUsd0VBQXdFO2dCQUN4RSxxRUFBcUU7Z0JBQ3JFLGdDQUFnQztnQkFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRSxDQUFDO29CQUNuQyxJQUFBLFVBQUcsRUFBQyw0R0FBNEcsQ0FBQyxDQUFDO29CQUNsSCxLQUFLLENBQUMsa0JBQWtCLEdBQUcsd0NBQWdDLENBQUM7Z0JBQzlELENBQUM7cUJBQU0sQ0FBQztvQkFDTixrRUFBa0U7b0JBQ2xFLDZEQUE2RDtvQkFDN0QsSUFBQSxVQUFHLEVBQUMsNkRBQTZELElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ3ZILENBQUM7WUFDSCxDQUFDO1lBRUQsbUVBQW1FO1lBQ25FLE1BQU0sY0FBYyxDQUFDLFFBQVEsRUFBRSxLQUFLLEVBQUU7Z0JBQ3BDLE1BQU0sRUFBRSwwQkFBa0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU87YUFDakQsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFnQixxQkFBcUIsQ0FBQyxPQUF3QjtJQUM1RCwyQ0FBMkM7SUFDM0MsTUFBTSxlQUFlLEdBQW9CLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBRTdFLCtDQUErQztJQUMvQyxJQUFJLGVBQWUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN6QixNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMvQyxLQUFLLE1BQU0sR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQ3ZCLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsT0FBTyxDQUFDO1FBQ3RDLENBQUM7SUFDSCxDQUFDO0lBQ0QsT0FBTyxlQUFlLENBQUM7QUFDekIsQ0FBQztBQUVELE1BQWEsS0FBTSxTQUFRLEtBQUs7Q0FBSTtBQUFwQyxzQkFBb0MiLCJzb3VyY2VzQ29udGVudCI6WyJcbmltcG9ydCAqIGFzIHVybCBmcm9tICd1cmwnO1xuaW1wb3J0IHsgaHR0cFJlcXVlc3QgfSBmcm9tICcuL291dGJvdW5kJztcbmltcG9ydCB7IGxvZywgd2l0aFJldHJpZXMgfSBmcm9tICcuL3V0aWwnO1xuaW1wb3J0IHR5cGUgeyBPbkV2ZW50UmVzcG9uc2UgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUiA9ICdBV1NDREs6OkN1c3RvbVJlc291cmNlUHJvdmlkZXJGcmFtZXdvcms6OkNSRUFURV9GQUlMRUQnO1xuZXhwb3J0IGNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2xvdWRGb3JtYXRpb25SZXNwb25zZU9wdGlvbnMge1xuICByZWFkb25seSByZWFzb24/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IG5vRWNobz86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2xvdWRGb3JtYXRpb25FdmVudENvbnRleHQge1xuICBTdGFja0lkOiBzdHJpbmc7XG4gIFJlcXVlc3RJZDogc3RyaW5nO1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIExvZ2ljYWxSZXNvdXJjZUlkOiBzdHJpbmc7XG4gIFJlc3BvbnNlVVJMOiBzdHJpbmc7XG4gIERhdGE/OiBhbnk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzdWJtaXRSZXNwb25zZShzdGF0dXM6ICdTVUNDRVNTJyB8ICdGQUlMRUQnLCBldmVudDogQ2xvdWRGb3JtYXRpb25FdmVudENvbnRleHQsIG9wdGlvbnM6IENsb3VkRm9ybWF0aW9uUmVzcG9uc2VPcHRpb25zID0geyB9KSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBvcHRpb25zLnJlYXNvbiB8fCBzdGF0dXMsXG4gICAgU3RhY2tJZDogZXZlbnQuU3RhY2tJZCxcbiAgICBSZXF1ZXN0SWQ6IGV2ZW50LlJlcXVlc3RJZCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB8fCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUixcbiAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgTm9FY2hvOiBvcHRpb25zLm5vRWNobyxcbiAgICBEYXRhOiBldmVudC5EYXRhLFxuICB9O1xuXG4gIGNvbnN0IHJlc3BvbnNlQm9keSA9IEpTT04uc3RyaW5naWZ5KGpzb24pO1xuXG4gIGNvbnN0IHBhcnNlZFVybCA9IHVybC5wYXJzZShldmVudC5SZXNwb25zZVVSTCk7XG4gIGNvbnN0IGxvZ2dpbmdTYWZlVXJsID0gYCR7cGFyc2VkVXJsLnByb3RvY29sfS8vJHtwYXJzZWRVcmwuaG9zdG5hbWV9LyR7cGFyc2VkVXJsLnBhdGhuYW1lfT8qKipgO1xuICBpZiAob3B0aW9ucz8ubm9FY2hvKSB7XG4gICAgbG9nKCdzdWJtaXQgcmVkYWN0ZWQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBsb2dnaW5nU2FmZVVybCwgcmVkYWN0RGF0YUZyb21QYXlsb2FkKGpzb24pKTtcbiAgfSBlbHNlIHtcbiAgICBsb2coJ3N1Ym1pdCByZXNwb25zZSB0byBjbG91ZGZvcm1hdGlvbicsIGxvZ2dpbmdTYWZlVXJsLCBqc29uKTtcbiAgfVxuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBodHRwUmVxdWVzdCkoe1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7XG4gICAgICAnY29udGVudC10eXBlJzogJycsXG4gICAgICAnY29udGVudC1sZW5ndGgnOiBCdWZmZXIuYnl0ZUxlbmd0aChyZXNwb25zZUJvZHksICd1dGY4JyksXG4gICAgfSxcbiAgfSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuZXhwb3J0IGxldCBpbmNsdWRlU3RhY2tUcmFjZXMgPSB0cnVlOyAvLyBmb3IgdW5pdCB0ZXN0c1xuXG5leHBvcnQgZnVuY3Rpb24gc2FmZUhhbmRsZXIoYmxvY2s6IChldmVudDogYW55KSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiBhc3luYyAoZXZlbnQ6IGFueSkgPT4ge1xuICAgIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gICAgLy8gaW5kaWNhdGVzIHRoYXQgdGhpcyBERUxFVEUgaXMgYSBzdWJzZXF1ZW50IERFTEVURSB0byBhIGZhaWxlZCBDUkVBVEVcbiAgICAvLyBvcGVyYXRpb24uXG4gICAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgICBsb2coJ2lnbm9yaW5nIERFTEVURSBldmVudCBjYXVzZWQgYnkgYSBmYWlsZWQgQ1JFQVRFIGV2ZW50Jyk7XG4gICAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnU1VDQ0VTUycsIGV2ZW50KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgYmxvY2soZXZlbnQpO1xuICAgIH0gY2F0Y2ggKGU6IGFueSkge1xuICAgICAgLy8gdGVsbCB3YWl0ZXIgc3RhdGUgbWFjaGluZSB0byByZXRyeVxuICAgICAgaWYgKGUgaW5zdGFuY2VvZiBSZXRyeSkge1xuICAgICAgICBsb2coJ3JldHJ5IHJlcXVlc3RlZCBieSBoYW5kbGVyJyk7XG4gICAgICAgIHRocm93IGU7XG4gICAgICB9XG5cbiAgICAgIGlmICghZXZlbnQuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAgIC8vIHNwZWNpYWwgY2FzZTogaWYgQ1JFQVRFIGZhaWxzLCB3aGljaCB1c3VhbGx5IGltcGxpZXMsIHdlIHVzdWFsbHkgZG9uJ3RcbiAgICAgICAgLy8gaGF2ZSBhIHBoeXNpY2FsIHJlc291cmNlIGlkLiBpbiB0aGlzIGNhc2UsIHRoZSBzdWJzZXF1ZW50IERFTEVURVxuICAgICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgICAgLy8gYWRkcmVzcyB0aGlzLCB3ZSB1c2UgYSBtYXJrZXIgc28gdGhlIHByb3ZpZGVyIGZyYW1ld29yayBjYW4gc2ltcGx5XG4gICAgICAgIC8vIGlnbm9yZSB0aGUgc3Vic2VxdWVudCBERUxFVEUuXG4gICAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgICBsb2coJ0NSRUFURSBmYWlsZWQsIHJlc3BvbmRpbmcgd2l0aCBhIG1hcmtlciBwaHlzaWNhbCByZXNvdXJjZSBpZCBzbyB0aGF0IHRoZSBzdWJzZXF1ZW50IERFTEVURSB3aWxsIGJlIGlnbm9yZWQnKTtcbiAgICAgICAgICBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPSBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgICAvLyB0ZXJyaWJseSB3cm9uZyBiZWNhdXNlIGFsbCBvdGhlciBldmVudHMgc2hvdWxkIGhhdmUgYW4gSUQuXG4gICAgICAgICAgbG9nKGBFUlJPUjogTWFsZm9ybWVkIGV2ZW50LiBcIlBoeXNpY2FsUmVzb3VyY2VJZFwiIGlzIHJlcXVpcmVkOiAke0pTT04uc3RyaW5naWZ5KHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9KX1gKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyB0aGlzIGlzIGFuIGFjdHVhbCBlcnJvciwgZmFpbCB0aGUgYWN0aXZpdHkgYWx0b2dldGhlciBhbmQgZXhpc3QuXG4gICAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgZXZlbnQsIHtcbiAgICAgICAgcmVhc29uOiBpbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgICAgfSk7XG4gICAgfVxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVkYWN0RGF0YUZyb21QYXlsb2FkKHBheWxvYWQ6IE9uRXZlbnRSZXNwb25zZSkge1xuICAvLyBDcmVhdGUgYSBkZWVwIGNvcHkgb2YgdGhlIHBheWxvYWQgb2JqZWN0XG4gIGNvbnN0IHJlZGFjdGVkUGF5bG9hZDogT25FdmVudFJlc3BvbnNlID0gSlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG5cbiAgLy8gUmVkYWN0IHRoZSBkYXRhIGluIHRoZSBjb3BpZWQgcGF5bG9hZCBvYmplY3RcbiAgaWYgKHJlZGFjdGVkUGF5bG9hZC5EYXRhKSB7XG4gICAgY29uc3Qga2V5cyA9IE9iamVjdC5rZXlzKHJlZGFjdGVkUGF5bG9hZC5EYXRhKTtcbiAgICBmb3IgKGNvbnN0IGtleSBvZiBrZXlzKSB7XG4gICAgICByZWRhY3RlZFBheWxvYWQuRGF0YVtrZXldID0gJyoqKioqJztcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlZGFjdGVkUGF5bG9hZDtcbn1cblxuZXhwb3J0IGNsYXNzIFJldHJ5IGV4dGVuZHMgRXJyb3IgeyB9XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/consts.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/consts.js new file mode 100644 index 0000000000000..31faa077ae313 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/consts.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FRAMEWORK_ON_TIMEOUT_HANDLER_NAME = exports.FRAMEWORK_IS_COMPLETE_HANDLER_NAME = exports.FRAMEWORK_ON_EVENT_HANDLER_NAME = exports.WAITER_STATE_MACHINE_ARN_ENV = exports.USER_IS_COMPLETE_FUNCTION_ARN_ENV = exports.USER_ON_EVENT_FUNCTION_ARN_ENV = void 0; +exports.USER_ON_EVENT_FUNCTION_ARN_ENV = 'USER_ON_EVENT_FUNCTION_ARN'; +exports.USER_IS_COMPLETE_FUNCTION_ARN_ENV = 'USER_IS_COMPLETE_FUNCTION_ARN'; +exports.WAITER_STATE_MACHINE_ARN_ENV = 'WAITER_STATE_MACHINE_ARN'; +exports.FRAMEWORK_ON_EVENT_HANDLER_NAME = 'onEvent'; +exports.FRAMEWORK_IS_COMPLETE_HANDLER_NAME = 'isComplete'; +exports.FRAMEWORK_ON_TIMEOUT_HANDLER_NAME = 'onTimeout'; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY29uc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFhLFFBQUEsOEJBQThCLEdBQUcsNEJBQTRCLENBQUM7QUFDOUQsUUFBQSxpQ0FBaUMsR0FBRywrQkFBK0IsQ0FBQztBQUNwRSxRQUFBLDRCQUE0QixHQUFHLDBCQUEwQixDQUFDO0FBRTFELFFBQUEsK0JBQStCLEdBQUcsU0FBUyxDQUFDO0FBQzVDLFFBQUEsa0NBQWtDLEdBQUcsWUFBWSxDQUFDO0FBQ2xELFFBQUEsaUNBQWlDLEdBQUcsV0FBVyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGNvbnN0IFVTRVJfT05fRVZFTlRfRlVOQ1RJT05fQVJOX0VOViA9ICdVU0VSX09OX0VWRU5UX0ZVTkNUSU9OX0FSTic7XG5leHBvcnQgY29uc3QgVVNFUl9JU19DT01QTEVURV9GVU5DVElPTl9BUk5fRU5WID0gJ1VTRVJfSVNfQ09NUExFVEVfRlVOQ1RJT05fQVJOJztcbmV4cG9ydCBjb25zdCBXQUlURVJfU1RBVEVfTUFDSElORV9BUk5fRU5WID0gJ1dBSVRFUl9TVEFURV9NQUNISU5FX0FSTic7XG5cbmV4cG9ydCBjb25zdCBGUkFNRVdPUktfT05fRVZFTlRfSEFORExFUl9OQU1FID0gJ29uRXZlbnQnO1xuZXhwb3J0IGNvbnN0IEZSQU1FV09SS19JU19DT01QTEVURV9IQU5ETEVSX05BTUUgPSAnaXNDb21wbGV0ZSc7XG5leHBvcnQgY29uc3QgRlJBTUVXT1JLX09OX1RJTUVPVVRfSEFORExFUl9OQU1FID0gJ29uVGltZW91dCc7XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/framework.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/framework.js new file mode 100644 index 0000000000000..739d89d63bf03 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/framework.js @@ -0,0 +1,183 @@ +"use strict"; +/* eslint-disable @cdklabs/no-throw-default-error */ +/* eslint-disable max-len */ +const cfnResponse = require("./cfn-response"); +const consts = require("./consts"); +const outbound_1 = require("./outbound"); +const util_1 = require("./util"); +/** + * The main runtime entrypoint of the async custom resource lambda function. + * + * Any lifecycle event changes to the custom resources will invoke this handler, which will, in turn, + * interact with the user-defined `onEvent` and `isComplete` handlers. + * + * This function will always succeed. If an error occurs, it is logged but an error is not thrown. + * + * @param cfnRequest The cloudformation custom resource event. + */ +async function onEvent(cfnRequest) { + const sanitizedRequest = { ...cfnRequest, ResponseURL: '...' }; + (0, util_1.log)('onEventHandler', sanitizedRequest); + cfnRequest.ResourceProperties = cfnRequest.ResourceProperties || {}; + const onEventResult = await invokeUserFunction(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, sanitizedRequest, cfnRequest.ResponseURL); + if (onEventResult?.NoEcho) { + (0, util_1.log)('redacted onEvent returned:', cfnResponse.redactDataFromPayload(onEventResult)); + } + else { + (0, util_1.log)('onEvent returned:', onEventResult); + } + // merge the request and the result from onEvent to form the complete resource event + // this also performs validation. + const resourceEvent = createResponseEvent(cfnRequest, onEventResult); + const sanitizedEvent = { ...resourceEvent, ResponseURL: '...' }; + if (onEventResult?.NoEcho) { + (0, util_1.log)('readacted event:', cfnResponse.redactDataFromPayload(sanitizedEvent)); + } + else { + (0, util_1.log)('event:', sanitizedEvent); + } + // determine if this is an async provider based on whether we have an isComplete handler defined. + // if it is not defined, then we are basically ready to return a positive response. + if (!process.env[consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV]) { + return cfnResponse.submitResponse('SUCCESS', resourceEvent, { noEcho: resourceEvent.NoEcho }); + } + // ok, we are not complete, so kick off the waiter workflow + const waiter = { + stateMachineArn: (0, util_1.getEnv)(consts.WAITER_STATE_MACHINE_ARN_ENV), + input: JSON.stringify(resourceEvent), + }; + (0, util_1.log)('starting waiter', { + stateMachineArn: (0, util_1.getEnv)(consts.WAITER_STATE_MACHINE_ARN_ENV), + }); + // kick off waiter state machine + await (0, outbound_1.startExecution)(waiter); +} +// invoked a few times until `complete` is true or until it times out. +async function isComplete(event) { + const sanitizedRequest = { ...event, ResponseURL: '...' }; + if (event?.NoEcho) { + (0, util_1.log)('redacted isComplete request', cfnResponse.redactDataFromPayload(sanitizedRequest)); + } + else { + (0, util_1.log)('isComplete', sanitizedRequest); + } + const isCompleteResult = await invokeUserFunction(consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV, sanitizedRequest, event.ResponseURL); + if (event?.NoEcho) { + (0, util_1.log)('redacted user isComplete returned:', cfnResponse.redactDataFromPayload(isCompleteResult)); + } + else { + (0, util_1.log)('user isComplete returned:', isCompleteResult); + } + // if we are not complete, return false, and don't send a response back. + if (!isCompleteResult.IsComplete) { + if (isCompleteResult.Data && Object.keys(isCompleteResult.Data).length > 0) { + throw new Error('"Data" is not allowed if "IsComplete" is "False"'); + } + // This must be the full event, it will be deserialized in `onTimeout` to send the response to CloudFormation + throw new cfnResponse.Retry(JSON.stringify(event)); + } + const response = { + ...event, + ...isCompleteResult, + Data: { + ...event.Data, + ...isCompleteResult.Data, + }, + }; + await cfnResponse.submitResponse('SUCCESS', response, { noEcho: event.NoEcho }); +} +// invoked when completion retries are exhaused. +async function onTimeout(timeoutEvent) { + (0, util_1.log)('timeoutHandler', timeoutEvent); + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + await cfnResponse.submitResponse('FAILED', isCompleteRequest, { + reason: 'Operation timed out', + }); +} +async function invokeUserFunction(functionArnEnv, sanitizedPayload, responseUrl) { + const functionArn = (0, util_1.getEnv)(functionArnEnv); + (0, util_1.log)(`executing user function ${functionArn} with payload`, sanitizedPayload); + // transient errors such as timeouts, throttling errors (429), and other + // errors that aren't caused by a bad request (500 series) are retried + // automatically by the JavaScript SDK. + const resp = await (0, outbound_1.invokeFunction)({ + FunctionName: functionArn, + // Cannot strip 'ResponseURL' here as this would be a breaking change even though the downstream CR doesn't need it + Payload: JSON.stringify({ ...sanitizedPayload, ResponseURL: responseUrl }), + }); + (0, util_1.log)('user function response:', resp, typeof (resp)); + // ParseJsonPayload is very defensive. It should not be possible for `Payload` + // to be anything other than a JSON encoded string (or intarray). Something weird is + // going on if that happens. Still, we should do our best to survive it. + const jsonPayload = (0, util_1.parseJsonPayload)(resp.Payload); + if (resp.FunctionError) { + (0, util_1.log)('user function threw an error:', resp.FunctionError); + const errorMessage = jsonPayload.errorMessage || 'error'; + // parse function name from arn + // arn:${Partition}:lambda:${Region}:${Account}:function:${FunctionName} + const arn = functionArn.split(':'); + const functionName = arn[arn.length - 1]; + // append a reference to the log group. + const message = [ + errorMessage, + '', + `Logs: /aws/lambda/${functionName}`, // cloudwatch log group + '', + ].join('\n'); + const e = new Error(message); + // the output that goes to CFN is what's in `stack`, not the error message. + // if we have a remote trace, construct a nice message with log group information + if (jsonPayload.trace) { + // skip first trace line because it's the message + e.stack = [message, ...jsonPayload.trace.slice(1)].join('\n'); + } + throw e; + } + return jsonPayload; +} +function createResponseEvent(cfnRequest, onEventResult) { + // + // validate that onEventResult always includes a PhysicalResourceId + onEventResult = onEventResult || {}; + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = onEventResult.PhysicalResourceId || defaultPhysicalResourceId(cfnRequest); + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${onEventResult.PhysicalResourceId}" during deletion`); + } + // if we are in UPDATE and physical ID was changed, it's a replacement (just log) + if (cfnRequest.RequestType === 'Update' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + (0, util_1.log)(`UPDATE: changing physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${onEventResult.PhysicalResourceId}"`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...onEventResult, + PhysicalResourceId: physicalResourceId, + }; +} +/** + * Calculates the default physical resource ID based in case user handler did + * not return a PhysicalResourceId. + * + * For "CREATE", it uses the RequestId. + * For "UPDATE" and "DELETE" and returns the current PhysicalResourceId (the one provided in `event`). + */ +function defaultPhysicalResourceId(req) { + switch (req.RequestType) { + case 'Create': + return req.RequestId; + case 'Update': + case 'Delete': + return req.PhysicalResourceId; + default: + throw new Error(`Invalid "RequestType" in request "${JSON.stringify(req)}"`); + } +} +module.exports = { + [consts.FRAMEWORK_ON_EVENT_HANDLER_NAME]: cfnResponse.safeHandler(onEvent), + [consts.FRAMEWORK_IS_COMPLETE_HANDLER_NAME]: cfnResponse.safeHandler(isComplete), + [consts.FRAMEWORK_ON_TIMEOUT_HANDLER_NAME]: onTimeout, +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnJhbWV3b3JrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZnJhbWV3b3JrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxvREFBb0Q7QUFFcEQsNEJBQTRCO0FBRTVCLDhDQUE4QztBQUM5QyxtQ0FBbUM7QUFDbkMseUNBQTREO0FBQzVELGlDQUF1RDtBQVV2RDs7Ozs7Ozs7O0dBU0c7QUFDSCxLQUFLLFVBQVUsT0FBTyxDQUFDLFVBQXVEO0lBQzVFLE1BQU0sZ0JBQWdCLEdBQUcsRUFBRSxHQUFHLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFXLENBQUM7SUFDeEUsSUFBQSxVQUFHLEVBQUMsZ0JBQWdCLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztJQUV4QyxVQUFVLENBQUMsa0JBQWtCLEdBQUcsVUFBVSxDQUFDLGtCQUFrQixJQUFJLEVBQUcsQ0FBQztJQUVyRSxNQUFNLGFBQWEsR0FBRyxNQUFNLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyw4QkFBOEIsRUFBRSxnQkFBZ0IsRUFBRSxVQUFVLENBQUMsV0FBVyxDQUFvQixDQUFDO0lBQ25KLElBQUksYUFBYSxFQUFFLE1BQU0sRUFBRSxDQUFDO1FBQzFCLElBQUEsVUFBRyxFQUFDLDRCQUE0QixFQUFFLFdBQVcsQ0FBQyxxQkFBcUIsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDO0lBQ3RGLENBQUM7U0FBTSxDQUFDO1FBQ04sSUFBQSxVQUFHLEVBQUMsbUJBQW1CLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFDMUMsQ0FBQztJQUVELG9GQUFvRjtJQUNwRixpQ0FBaUM7SUFDakMsTUFBTSxhQUFhLEdBQUcsbUJBQW1CLENBQUMsVUFBVSxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sY0FBYyxHQUFHLEVBQUUsR0FBRyxhQUFhLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDO0lBQ2hFLElBQUksYUFBYSxFQUFFLE1BQU0sRUFBRSxDQUFDO1FBQzFCLElBQUEsVUFBRyxFQUFDLGtCQUFrQixFQUFFLFdBQVcsQ0FBQyxxQkFBcUIsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO0lBQzdFLENBQUM7U0FBTSxDQUFDO1FBQ04sSUFBQSxVQUFHLEVBQUMsUUFBUSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQ2hDLENBQUM7SUFFRCxpR0FBaUc7SUFDakcsbUZBQW1GO0lBQ25GLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxpQ0FBaUMsQ0FBQyxFQUFFLENBQUM7UUFDM0QsT0FBTyxXQUFXLENBQUMsY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLEVBQUUsRUFBRSxNQUFNLEVBQUUsYUFBYSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDaEcsQ0FBQztJQUVELDJEQUEyRDtJQUMzRCxNQUFNLE1BQU0sR0FBRztRQUNiLGVBQWUsRUFBRSxJQUFBLGFBQU0sRUFBQyxNQUFNLENBQUMsNEJBQTRCLENBQUM7UUFDNUQsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsYUFBYSxDQUFDO0tBQ3JDLENBQUM7SUFFRixJQUFBLFVBQUcsRUFBQyxpQkFBaUIsRUFBRTtRQUNyQixlQUFlLEVBQUUsSUFBQSxhQUFNLEVBQUMsTUFBTSxDQUFDLDRCQUE0QixDQUFDO0tBQzdELENBQUMsQ0FBQztJQUVILGdDQUFnQztJQUNoQyxNQUFNLElBQUEseUJBQWMsRUFBQyxNQUFNLENBQUMsQ0FBQztBQUMvQixDQUFDO0FBRUQsc0VBQXNFO0FBQ3RFLEtBQUssVUFBVSxVQUFVLENBQUMsS0FBa0Q7SUFDMUUsTUFBTSxnQkFBZ0IsR0FBRyxFQUFFLEdBQUcsS0FBSyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQVcsQ0FBQztJQUNuRSxJQUFJLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUNsQixJQUFBLFVBQUcsRUFBQyw2QkFBNkIsRUFBRSxXQUFXLENBQUMscUJBQXFCLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDO0lBQzFGLENBQUM7U0FBTSxDQUFDO1FBQ04sSUFBQSxVQUFHLEVBQUMsWUFBWSxFQUFFLGdCQUFnQixDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVELE1BQU0sZ0JBQWdCLEdBQUcsTUFBTSxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsaUNBQWlDLEVBQUUsZ0JBQWdCLEVBQUUsS0FBSyxDQUFDLFdBQVcsQ0FBdUIsQ0FBQztJQUN2SixJQUFJLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUNsQixJQUFBLFVBQUcsRUFBQyxvQ0FBb0MsRUFBRSxXQUFXLENBQUMscUJBQXFCLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDO0lBQ2pHLENBQUM7U0FBTSxDQUFDO1FBQ04sSUFBQSxVQUFHLEVBQUMsMkJBQTJCLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztJQUNyRCxDQUFDO0lBRUQsd0VBQXdFO0lBQ3hFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNqQyxJQUFJLGdCQUFnQixDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUMzRSxNQUFNLElBQUksS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUM7UUFDdEUsQ0FBQztRQUVELDZHQUE2RztRQUM3RyxNQUFNLElBQUksV0FBVyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDckQsQ0FBQztJQUVELE1BQU0sUUFBUSxHQUFHO1FBQ2YsR0FBRyxLQUFLO1FBQ1IsR0FBRyxnQkFBZ0I7UUFDbkIsSUFBSSxFQUFFO1lBQ0osR0FBRyxLQUFLLENBQUMsSUFBSTtZQUNiLEdBQUcsZ0JBQWdCLENBQUMsSUFBSTtTQUN6QjtLQUNGLENBQUM7SUFFRixNQUFNLFdBQVcsQ0FBQyxjQUFjLENBQUMsU0FBUyxFQUFFLFFBQVEsRUFBRSxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztBQUNsRixDQUFDO0FBRUQsZ0RBQWdEO0FBQ2hELEtBQUssVUFBVSxTQUFTLENBQUMsWUFBaUI7SUFDeEMsSUFBQSxVQUFHLEVBQUMsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFFcEMsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLFlBQVksQ0FBZ0QsQ0FBQztJQUNqSSxNQUFNLFdBQVcsQ0FBQyxjQUFjLENBQUMsUUFBUSxFQUFFLGlCQUFpQixFQUFFO1FBQzVELE1BQU0sRUFBRSxxQkFBcUI7S0FDOUIsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELEtBQUssVUFBVSxrQkFBa0IsQ0FBbUMsY0FBc0IsRUFBRSxnQkFBbUIsRUFBRSxXQUFtQjtJQUNsSSxNQUFNLFdBQVcsR0FBRyxJQUFBLGFBQU0sRUFBQyxjQUFjLENBQUMsQ0FBQztJQUMzQyxJQUFBLFVBQUcsRUFBQywyQkFBMkIsV0FBVyxlQUFlLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztJQUU3RSx3RUFBd0U7SUFDeEUsc0VBQXNFO0lBQ3RFLHVDQUF1QztJQUN2QyxNQUFNLElBQUksR0FBRyxNQUFNLElBQUEseUJBQWMsRUFBQztRQUNoQyxZQUFZLEVBQUUsV0FBVztRQUV6QixtSEFBbUg7UUFDbkgsT0FBTyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxHQUFHLGdCQUFnQixFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsQ0FBQztLQUMzRSxDQUFDLENBQUM7SUFFSCxJQUFBLFVBQUcsRUFBQyx5QkFBeUIsRUFBRSxJQUFJLEVBQUUsT0FBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7SUFFbkQsOEVBQThFO0lBQzlFLG9GQUFvRjtJQUNwRix3RUFBd0U7SUFDeEUsTUFBTSxXQUFXLEdBQUcsSUFBQSx1QkFBZ0IsRUFBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbkQsSUFBSSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDdkIsSUFBQSxVQUFHLEVBQUMsK0JBQStCLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBRXpELE1BQU0sWUFBWSxHQUFHLFdBQVcsQ0FBQyxZQUFZLElBQUksT0FBTyxDQUFDO1FBRXpELCtCQUErQjtRQUMvQix3RUFBd0U7UUFDeEUsTUFBTSxHQUFHLEdBQUcsV0FBVyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQyxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztRQUV6Qyx1Q0FBdUM7UUFDdkMsTUFBTSxPQUFPLEdBQUc7WUFDZCxZQUFZO1lBQ1osRUFBRTtZQUNGLHFCQUFxQixZQUFZLEVBQUUsRUFBRSx1QkFBdUI7WUFDNUQsRUFBRTtTQUNILENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRWIsTUFBTSxDQUFDLEdBQUcsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFN0IsMkVBQTJFO1FBQzNFLGlGQUFpRjtRQUNqRixJQUFJLFdBQVcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUN0QixpREFBaUQ7WUFDakQsQ0FBQyxDQUFDLEtBQUssR0FBRyxDQUFDLE9BQU8sRUFBRSxHQUFHLFdBQVcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hFLENBQUM7UUFFRCxNQUFNLENBQUMsQ0FBQztJQUNWLENBQUM7SUFFRCxPQUFPLFdBQVcsQ0FBQztBQUNyQixDQUFDO0FBRUQsU0FBUyxtQkFBbUIsQ0FBQyxVQUF1RCxFQUFFLGFBQThCO0lBQ2xILEVBQUU7SUFDRixtRUFBbUU7SUFFbkUsYUFBYSxHQUFHLGFBQWEsSUFBSSxFQUFHLENBQUM7SUFFckMsc0VBQXNFO0lBQ3RFLHVCQUF1QjtJQUN2QixNQUFNLGtCQUFrQixHQUFHLGFBQWEsQ0FBQyxrQkFBa0IsSUFBSSx5QkFBeUIsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUVyRyxrRUFBa0U7SUFDbEUsSUFBSSxVQUFVLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxrQkFBa0IsS0FBSyxVQUFVLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUNoRyxNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsYUFBYSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0lBQ3JLLENBQUM7SUFFRCxpRkFBaUY7SUFDakYsSUFBSSxVQUFVLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxrQkFBa0IsS0FBSyxVQUFVLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUNoRyxJQUFBLFVBQUcsRUFBQywrQ0FBK0MsVUFBVSxDQUFDLGtCQUFrQixTQUFTLGFBQWEsQ0FBQyxrQkFBa0IsR0FBRyxDQUFDLENBQUM7SUFDaEksQ0FBQztJQUVELDBEQUEwRDtJQUMxRCxPQUFPO1FBQ0wsR0FBRyxVQUFVO1FBQ2IsR0FBRyxhQUFhO1FBQ2hCLGtCQUFrQixFQUFFLGtCQUFrQjtLQUN2QyxDQUFDO0FBQ0osQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQVMseUJBQXlCLENBQUMsR0FBZ0Q7SUFDakYsUUFBUSxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDeEIsS0FBSyxRQUFRO1lBQ1gsT0FBTyxHQUFHLENBQUMsU0FBUyxDQUFDO1FBRXZCLEtBQUssUUFBUSxDQUFDO1FBQ2QsS0FBSyxRQUFRO1lBQ1gsT0FBTyxHQUFHLENBQUMsa0JBQWtCLENBQUM7UUFFaEM7WUFDRSxNQUFNLElBQUksS0FBSyxDQUFDLHFDQUFxQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNqRixDQUFDO0FBQ0gsQ0FBQztBQS9NRCxpQkFBUztJQUNQLENBQUMsTUFBTSxDQUFDLCtCQUErQixDQUFDLEVBQUUsV0FBVyxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUM7SUFDMUUsQ0FBQyxNQUFNLENBQUMsa0NBQWtDLENBQUMsRUFBRSxXQUFXLENBQUMsV0FBVyxDQUFDLFVBQVUsQ0FBQztJQUNoRixDQUFDLE1BQU0sQ0FBQyxpQ0FBaUMsQ0FBQyxFQUFFLFNBQVM7Q0FDdEQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIEBjZGtsYWJzL25vLXRocm93LWRlZmF1bHQtZXJyb3IgKi9cblxuLyogZXNsaW50LWRpc2FibGUgbWF4LWxlbiAqL1xuXG5pbXBvcnQgKiBhcyBjZm5SZXNwb25zZSBmcm9tICcuL2Nmbi1yZXNwb25zZSc7XG5pbXBvcnQgKiBhcyBjb25zdHMgZnJvbSAnLi9jb25zdHMnO1xuaW1wb3J0IHsgaW52b2tlRnVuY3Rpb24sIHN0YXJ0RXhlY3V0aW9uIH0gZnJvbSAnLi9vdXRib3VuZCc7XG5pbXBvcnQgeyBnZXRFbnYsIGxvZywgcGFyc2VKc29uUGF5bG9hZCB9IGZyb20gJy4vdXRpbCc7XG5pbXBvcnQgdHlwZSB7IElzQ29tcGxldGVSZXNwb25zZSwgT25FdmVudFJlc3BvbnNlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG4vLyB1c2UgY29uc3RzIGZvciBoYW5kbGVyIG5hbWVzIHRvIGNvbXBpbGVyLWVuZm9yY2UgdGhlIGNvdXBsaW5nIHdpdGggY29uc3RydWN0aW9uIGNvZGUuXG5leHBvcnQgPSB7XG4gIFtjb25zdHMuRlJBTUVXT1JLX09OX0VWRU5UX0hBTkRMRVJfTkFNRV06IGNmblJlc3BvbnNlLnNhZmVIYW5kbGVyKG9uRXZlbnQpLFxuICBbY29uc3RzLkZSQU1FV09SS19JU19DT01QTEVURV9IQU5ETEVSX05BTUVdOiBjZm5SZXNwb25zZS5zYWZlSGFuZGxlcihpc0NvbXBsZXRlKSxcbiAgW2NvbnN0cy5GUkFNRVdPUktfT05fVElNRU9VVF9IQU5ETEVSX05BTUVdOiBvblRpbWVvdXQsXG59O1xuXG4vKipcbiAqIFRoZSBtYWluIHJ1bnRpbWUgZW50cnlwb2ludCBvZiB0aGUgYXN5bmMgY3VzdG9tIHJlc291cmNlIGxhbWJkYSBmdW5jdGlvbi5cbiAqXG4gKiBBbnkgbGlmZWN5Y2xlIGV2ZW50IGNoYW5nZXMgdG8gdGhlIGN1c3RvbSByZXNvdXJjZXMgd2lsbCBpbnZva2UgdGhpcyBoYW5kbGVyLCB3aGljaCB3aWxsLCBpbiB0dXJuLFxuICogaW50ZXJhY3Qgd2l0aCB0aGUgdXNlci1kZWZpbmVkIGBvbkV2ZW50YCBhbmQgYGlzQ29tcGxldGVgIGhhbmRsZXJzLlxuICpcbiAqIFRoaXMgZnVuY3Rpb24gd2lsbCBhbHdheXMgc3VjY2VlZC4gSWYgYW4gZXJyb3Igb2NjdXJzLCBpdCBpcyBsb2dnZWQgYnV0IGFuIGVycm9yIGlzIG5vdCB0aHJvd24uXG4gKlxuICogQHBhcmFtIGNmblJlcXVlc3QgVGhlIGNsb3VkZm9ybWF0aW9uIGN1c3RvbSByZXNvdXJjZSBldmVudC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gb25FdmVudChjZm5SZXF1ZXN0OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHNhbml0aXplZFJlcXVlc3QgPSB7IC4uLmNmblJlcXVlc3QsIFJlc3BvbnNlVVJMOiAnLi4uJyB9IGFzIGNvbnN0O1xuICBsb2coJ29uRXZlbnRIYW5kbGVyJywgc2FuaXRpemVkUmVxdWVzdCk7XG5cbiAgY2ZuUmVxdWVzdC5SZXNvdXJjZVByb3BlcnRpZXMgPSBjZm5SZXF1ZXN0LlJlc291cmNlUHJvcGVydGllcyB8fCB7IH07XG5cbiAgY29uc3Qgb25FdmVudFJlc3VsdCA9IGF3YWl0IGludm9rZVVzZXJGdW5jdGlvbihjb25zdHMuVVNFUl9PTl9FVkVOVF9GVU5DVElPTl9BUk5fRU5WLCBzYW5pdGl6ZWRSZXF1ZXN0LCBjZm5SZXF1ZXN0LlJlc3BvbnNlVVJMKSBhcyBPbkV2ZW50UmVzcG9uc2U7XG4gIGlmIChvbkV2ZW50UmVzdWx0Py5Ob0VjaG8pIHtcbiAgICBsb2coJ3JlZGFjdGVkIG9uRXZlbnQgcmV0dXJuZWQ6JywgY2ZuUmVzcG9uc2UucmVkYWN0RGF0YUZyb21QYXlsb2FkKG9uRXZlbnRSZXN1bHQpKTtcbiAgfSBlbHNlIHtcbiAgICBsb2coJ29uRXZlbnQgcmV0dXJuZWQ6Jywgb25FdmVudFJlc3VsdCk7XG4gIH1cblxuICAvLyBtZXJnZSB0aGUgcmVxdWVzdCBhbmQgdGhlIHJlc3VsdCBmcm9tIG9uRXZlbnQgdG8gZm9ybSB0aGUgY29tcGxldGUgcmVzb3VyY2UgZXZlbnRcbiAgLy8gdGhpcyBhbHNvIHBlcmZvcm1zIHZhbGlkYXRpb24uXG4gIGNvbnN0IHJlc291cmNlRXZlbnQgPSBjcmVhdGVSZXNwb25zZUV2ZW50KGNmblJlcXVlc3QsIG9uRXZlbnRSZXN1bHQpO1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4ucmVzb3VyY2VFdmVudCwgUmVzcG9uc2VVUkw6ICcuLi4nIH07XG4gIGlmIChvbkV2ZW50UmVzdWx0Py5Ob0VjaG8pIHtcbiAgICBsb2coJ3JlYWRhY3RlZCBldmVudDonLCBjZm5SZXNwb25zZS5yZWRhY3REYXRhRnJvbVBheWxvYWQoc2FuaXRpemVkRXZlbnQpKTtcbiAgfSBlbHNlIHtcbiAgICBsb2coJ2V2ZW50OicsIHNhbml0aXplZEV2ZW50KTtcbiAgfVxuXG4gIC8vIGRldGVybWluZSBpZiB0aGlzIGlzIGFuIGFzeW5jIHByb3ZpZGVyIGJhc2VkIG9uIHdoZXRoZXIgd2UgaGF2ZSBhbiBpc0NvbXBsZXRlIGhhbmRsZXIgZGVmaW5lZC5cbiAgLy8gaWYgaXQgaXMgbm90IGRlZmluZWQsIHRoZW4gd2UgYXJlIGJhc2ljYWxseSByZWFkeSB0byByZXR1cm4gYSBwb3NpdGl2ZSByZXNwb25zZS5cbiAgaWYgKCFwcm9jZXNzLmVudltjb25zdHMuVVNFUl9JU19DT01QTEVURV9GVU5DVElPTl9BUk5fRU5WXSkge1xuICAgIHJldHVybiBjZm5SZXNwb25zZS5zdWJtaXRSZXNwb25zZSgnU1VDQ0VTUycsIHJlc291cmNlRXZlbnQsIHsgbm9FY2hvOiByZXNvdXJjZUV2ZW50Lk5vRWNobyB9KTtcbiAgfVxuXG4gIC8vIG9rLCB3ZSBhcmUgbm90IGNvbXBsZXRlLCBzbyBraWNrIG9mZiB0aGUgd2FpdGVyIHdvcmtmbG93XG4gIGNvbnN0IHdhaXRlciA9IHtcbiAgICBzdGF0ZU1hY2hpbmVBcm46IGdldEVudihjb25zdHMuV0FJVEVSX1NUQVRFX01BQ0hJTkVfQVJOX0VOViksXG4gICAgaW5wdXQ6IEpTT04uc3RyaW5naWZ5KHJlc291cmNlRXZlbnQpLFxuICB9O1xuXG4gIGxvZygnc3RhcnRpbmcgd2FpdGVyJywge1xuICAgIHN0YXRlTWFjaGluZUFybjogZ2V0RW52KGNvbnN0cy5XQUlURVJfU1RBVEVfTUFDSElORV9BUk5fRU5WKSxcbiAgfSk7XG5cbiAgLy8ga2ljayBvZmYgd2FpdGVyIHN0YXRlIG1hY2hpbmVcbiAgYXdhaXQgc3RhcnRFeGVjdXRpb24od2FpdGVyKTtcbn1cblxuLy8gaW52b2tlZCBhIGZldyB0aW1lcyB1bnRpbCBgY29tcGxldGVgIGlzIHRydWUgb3IgdW50aWwgaXQgdGltZXMgb3V0LlxuYXN5bmMgZnVuY3Rpb24gaXNDb21wbGV0ZShldmVudDogQVdTQ0RLQXN5bmNDdXN0b21SZXNvdXJjZS5Jc0NvbXBsZXRlUmVxdWVzdCkge1xuICBjb25zdCBzYW5pdGl6ZWRSZXF1ZXN0ID0geyAuLi5ldmVudCwgUmVzcG9uc2VVUkw6ICcuLi4nIH0gYXMgY29uc3Q7XG4gIGlmIChldmVudD8uTm9FY2hvKSB7XG4gICAgbG9nKCdyZWRhY3RlZCBpc0NvbXBsZXRlIHJlcXVlc3QnLCBjZm5SZXNwb25zZS5yZWRhY3REYXRhRnJvbVBheWxvYWQoc2FuaXRpemVkUmVxdWVzdCkpO1xuICB9IGVsc2Uge1xuICAgIGxvZygnaXNDb21wbGV0ZScsIHNhbml0aXplZFJlcXVlc3QpO1xuICB9XG5cbiAgY29uc3QgaXNDb21wbGV0ZVJlc3VsdCA9IGF3YWl0IGludm9rZVVzZXJGdW5jdGlvbihjb25zdHMuVVNFUl9JU19DT01QTEVURV9GVU5DVElPTl9BUk5fRU5WLCBzYW5pdGl6ZWRSZXF1ZXN0LCBldmVudC5SZXNwb25zZVVSTCkgYXMgSXNDb21wbGV0ZVJlc3BvbnNlO1xuICBpZiAoZXZlbnQ/Lk5vRWNobykge1xuICAgIGxvZygncmVkYWN0ZWQgdXNlciBpc0NvbXBsZXRlIHJldHVybmVkOicsIGNmblJlc3BvbnNlLnJlZGFjdERhdGFGcm9tUGF5bG9hZChpc0NvbXBsZXRlUmVzdWx0KSk7XG4gIH0gZWxzZSB7XG4gICAgbG9nKCd1c2VyIGlzQ29tcGxldGUgcmV0dXJuZWQ6JywgaXNDb21wbGV0ZVJlc3VsdCk7XG4gIH1cblxuICAvLyBpZiB3ZSBhcmUgbm90IGNvbXBsZXRlLCByZXR1cm4gZmFsc2UsIGFuZCBkb24ndCBzZW5kIGEgcmVzcG9uc2UgYmFjay5cbiAgaWYgKCFpc0NvbXBsZXRlUmVzdWx0LklzQ29tcGxldGUpIHtcbiAgICBpZiAoaXNDb21wbGV0ZVJlc3VsdC5EYXRhICYmIE9iamVjdC5rZXlzKGlzQ29tcGxldGVSZXN1bHQuRGF0YSkubGVuZ3RoID4gMCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIkRhdGFcIiBpcyBub3QgYWxsb3dlZCBpZiBcIklzQ29tcGxldGVcIiBpcyBcIkZhbHNlXCInKTtcbiAgICB9XG5cbiAgICAvLyBUaGlzIG11c3QgYmUgdGhlIGZ1bGwgZXZlbnQsIGl0IHdpbGwgYmUgZGVzZXJpYWxpemVkIGluIGBvblRpbWVvdXRgIHRvIHNlbmQgdGhlIHJlc3BvbnNlIHRvIENsb3VkRm9ybWF0aW9uXG4gICAgdGhyb3cgbmV3IGNmblJlc3BvbnNlLlJldHJ5KEpTT04uc3RyaW5naWZ5KGV2ZW50KSk7XG4gIH1cblxuICBjb25zdCByZXNwb25zZSA9IHtcbiAgICAuLi5ldmVudCxcbiAgICAuLi5pc0NvbXBsZXRlUmVzdWx0LFxuICAgIERhdGE6IHtcbiAgICAgIC4uLmV2ZW50LkRhdGEsXG4gICAgICAuLi5pc0NvbXBsZXRlUmVzdWx0LkRhdGEsXG4gICAgfSxcbiAgfTtcblxuICBhd2FpdCBjZm5SZXNwb25zZS5zdWJtaXRSZXNwb25zZSgnU1VDQ0VTUycsIHJlc3BvbnNlLCB7IG5vRWNobzogZXZlbnQuTm9FY2hvIH0pO1xufVxuXG4vLyBpbnZva2VkIHdoZW4gY29tcGxldGlvbiByZXRyaWVzIGFyZSBleGhhdXNlZC5cbmFzeW5jIGZ1bmN0aW9uIG9uVGltZW91dCh0aW1lb3V0RXZlbnQ6IGFueSkge1xuICBsb2coJ3RpbWVvdXRIYW5kbGVyJywgdGltZW91dEV2ZW50KTtcblxuICBjb25zdCBpc0NvbXBsZXRlUmVxdWVzdCA9IEpTT04ucGFyc2UoSlNPTi5wYXJzZSh0aW1lb3V0RXZlbnQuQ2F1c2UpLmVycm9yTWVzc2FnZSkgYXMgQVdTQ0RLQXN5bmNDdXN0b21SZXNvdXJjZS5Jc0NvbXBsZXRlUmVxdWVzdDtcbiAgYXdhaXQgY2ZuUmVzcG9uc2Uuc3VibWl0UmVzcG9uc2UoJ0ZBSUxFRCcsIGlzQ29tcGxldGVSZXF1ZXN0LCB7XG4gICAgcmVhc29uOiAnT3BlcmF0aW9uIHRpbWVkIG91dCcsXG4gIH0pO1xufVxuXG5hc3luYyBmdW5jdGlvbiBpbnZva2VVc2VyRnVuY3Rpb248QSBleHRlbmRzIHsgUmVzcG9uc2VVUkw6ICcuLi4nIH0+KGZ1bmN0aW9uQXJuRW52OiBzdHJpbmcsIHNhbml0aXplZFBheWxvYWQ6IEEsIHJlc3BvbnNlVXJsOiBzdHJpbmcpIHtcbiAgY29uc3QgZnVuY3Rpb25Bcm4gPSBnZXRFbnYoZnVuY3Rpb25Bcm5FbnYpO1xuICBsb2coYGV4ZWN1dGluZyB1c2VyIGZ1bmN0aW9uICR7ZnVuY3Rpb25Bcm59IHdpdGggcGF5bG9hZGAsIHNhbml0aXplZFBheWxvYWQpO1xuXG4gIC8vIHRyYW5zaWVudCBlcnJvcnMgc3VjaCBhcyB0aW1lb3V0cywgdGhyb3R0bGluZyBlcnJvcnMgKDQyOSksIGFuZCBvdGhlclxuICAvLyBlcnJvcnMgdGhhdCBhcmVuJ3QgY2F1c2VkIGJ5IGEgYmFkIHJlcXVlc3QgKDUwMCBzZXJpZXMpIGFyZSByZXRyaWVkXG4gIC8vIGF1dG9tYXRpY2FsbHkgYnkgdGhlIEphdmFTY3JpcHQgU0RLLlxuICBjb25zdCByZXNwID0gYXdhaXQgaW52b2tlRnVuY3Rpb24oe1xuICAgIEZ1bmN0aW9uTmFtZTogZnVuY3Rpb25Bcm4sXG5cbiAgICAvLyBDYW5ub3Qgc3RyaXAgJ1Jlc3BvbnNlVVJMJyBoZXJlIGFzIHRoaXMgd291bGQgYmUgYSBicmVha2luZyBjaGFuZ2UgZXZlbiB0aG91Z2ggdGhlIGRvd25zdHJlYW0gQ1IgZG9lc24ndCBuZWVkIGl0XG4gICAgUGF5bG9hZDogSlNPTi5zdHJpbmdpZnkoeyAuLi5zYW5pdGl6ZWRQYXlsb2FkLCBSZXNwb25zZVVSTDogcmVzcG9uc2VVcmwgfSksXG4gIH0pO1xuXG4gIGxvZygndXNlciBmdW5jdGlvbiByZXNwb25zZTonLCByZXNwLCB0eXBlb2YocmVzcCkpO1xuXG4gIC8vIFBhcnNlSnNvblBheWxvYWQgaXMgdmVyeSBkZWZlbnNpdmUuIEl0IHNob3VsZCBub3QgYmUgcG9zc2libGUgZm9yIGBQYXlsb2FkYFxuICAvLyB0byBiZSBhbnl0aGluZyBvdGhlciB0aGFuIGEgSlNPTiBlbmNvZGVkIHN0cmluZyAob3IgaW50YXJyYXkpLiBTb21ldGhpbmcgd2VpcmQgaXNcbiAgLy8gZ29pbmcgb24gaWYgdGhhdCBoYXBwZW5zLiBTdGlsbCwgd2Ugc2hvdWxkIGRvIG91ciBiZXN0IHRvIHN1cnZpdmUgaXQuXG4gIGNvbnN0IGpzb25QYXlsb2FkID0gcGFyc2VKc29uUGF5bG9hZChyZXNwLlBheWxvYWQpO1xuICBpZiAocmVzcC5GdW5jdGlvbkVycm9yKSB7XG4gICAgbG9nKCd1c2VyIGZ1bmN0aW9uIHRocmV3IGFuIGVycm9yOicsIHJlc3AuRnVuY3Rpb25FcnJvcik7XG5cbiAgICBjb25zdCBlcnJvck1lc3NhZ2UgPSBqc29uUGF5bG9hZC5lcnJvck1lc3NhZ2UgfHwgJ2Vycm9yJztcblxuICAgIC8vIHBhcnNlIGZ1bmN0aW9uIG5hbWUgZnJvbSBhcm5cbiAgICAvLyBhcm46JHtQYXJ0aXRpb259OmxhbWJkYToke1JlZ2lvbn06JHtBY2NvdW50fTpmdW5jdGlvbjoke0Z1bmN0aW9uTmFtZX1cbiAgICBjb25zdCBhcm4gPSBmdW5jdGlvbkFybi5zcGxpdCgnOicpO1xuICAgIGNvbnN0IGZ1bmN0aW9uTmFtZSA9IGFyblthcm4ubGVuZ3RoIC0gMV07XG5cbiAgICAvLyBhcHBlbmQgYSByZWZlcmVuY2UgdG8gdGhlIGxvZyBncm91cC5cbiAgICBjb25zdCBtZXNzYWdlID0gW1xuICAgICAgZXJyb3JNZXNzYWdlLFxuICAgICAgJycsXG4gICAgICBgTG9nczogL2F3cy9sYW1iZGEvJHtmdW5jdGlvbk5hbWV9YCwgLy8gY2xvdWR3YXRjaCBsb2cgZ3JvdXBcbiAgICAgICcnLFxuICAgIF0uam9pbignXFxuJyk7XG5cbiAgICBjb25zdCBlID0gbmV3IEVycm9yKG1lc3NhZ2UpO1xuXG4gICAgLy8gdGhlIG91dHB1dCB0aGF0IGdvZXMgdG8gQ0ZOIGlzIHdoYXQncyBpbiBgc3RhY2tgLCBub3QgdGhlIGVycm9yIG1lc3NhZ2UuXG4gICAgLy8gaWYgd2UgaGF2ZSBhIHJlbW90ZSB0cmFjZSwgY29uc3RydWN0IGEgbmljZSBtZXNzYWdlIHdpdGggbG9nIGdyb3VwIGluZm9ybWF0aW9uXG4gICAgaWYgKGpzb25QYXlsb2FkLnRyYWNlKSB7XG4gICAgICAvLyBza2lwIGZpcnN0IHRyYWNlIGxpbmUgYmVjYXVzZSBpdCdzIHRoZSBtZXNzYWdlXG4gICAgICBlLnN0YWNrID0gW21lc3NhZ2UsIC4uLmpzb25QYXlsb2FkLnRyYWNlLnNsaWNlKDEpXS5qb2luKCdcXG4nKTtcbiAgICB9XG5cbiAgICB0aHJvdyBlO1xuICB9XG5cbiAgcmV0dXJuIGpzb25QYXlsb2FkO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVSZXNwb25zZUV2ZW50KGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIG9uRXZlbnRSZXN1bHQ6IE9uRXZlbnRSZXNwb25zZSk6IEFXU0NES0FzeW5jQ3VzdG9tUmVzb3VyY2UuSXNDb21wbGV0ZVJlcXVlc3Qge1xuICAvL1xuICAvLyB2YWxpZGF0ZSB0aGF0IG9uRXZlbnRSZXN1bHQgYWx3YXlzIGluY2x1ZGVzIGEgUGh5c2ljYWxSZXNvdXJjZUlkXG5cbiAgb25FdmVudFJlc3VsdCA9IG9uRXZlbnRSZXN1bHQgfHwgeyB9O1xuXG4gIC8vIGlmIHBoeXNpY2FsIElEIGlzIG5vdCByZXR1cm5lZCwgd2UgaGF2ZSBzb21lIGRlZmF1bHRzIGZvciB5b3UgYmFzZWRcbiAgLy8gb24gdGhlIHJlcXVlc3QgdHlwZS5cbiAgY29uc3QgcGh5c2ljYWxSZXNvdXJjZUlkID0gb25FdmVudFJlc3VsdC5QaHlzaWNhbFJlc291cmNlSWQgfHwgZGVmYXVsdFBoeXNpY2FsUmVzb3VyY2VJZChjZm5SZXF1ZXN0KTtcblxuICAvLyBpZiB3ZSBhcmUgaW4gREVMRVRFIGFuZCBwaHlzaWNhbCBJRCB3YXMgY2hhbmdlZCwgaXQncyBhbiBlcnJvci5cbiAgaWYgKGNmblJlcXVlc3QuUmVxdWVzdFR5cGUgPT09ICdEZWxldGUnICYmIHBoeXNpY2FsUmVzb3VyY2VJZCAhPT0gY2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYERFTEVURTogY2Fubm90IGNoYW5nZSB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgZnJvbSBcIiR7Y2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWR9XCIgdG8gXCIke29uRXZlbnRSZXN1bHQuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIGR1cmluZyBkZWxldGlvbmApO1xuICB9XG5cbiAgLy8gaWYgd2UgYXJlIGluIFVQREFURSBhbmQgcGh5c2ljYWwgSUQgd2FzIGNoYW5nZWQsIGl0J3MgYSByZXBsYWNlbWVudCAoanVzdCBsb2cpXG4gIGlmIChjZm5SZXF1ZXN0LlJlcXVlc3RUeXBlID09PSAnVXBkYXRlJyAmJiBwaHlzaWNhbFJlc291cmNlSWQgIT09IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgbG9nKGBVUERBVEU6IGNoYW5naW5nIHBoeXNpY2FsIHJlc291cmNlIElEIGZyb20gXCIke2NmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIHRvIFwiJHtvbkV2ZW50UmVzdWx0LlBoeXNpY2FsUmVzb3VyY2VJZH1cImApO1xuICB9XG5cbiAgLy8gbWVyZ2UgcmVxdWVzdCBldmVudCBhbmQgcmVzdWx0IGV2ZW50IChyZXN1bHQgcHJldmFpbHMpLlxuICByZXR1cm4ge1xuICAgIC4uLmNmblJlcXVlc3QsXG4gICAgLi4ub25FdmVudFJlc3VsdCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuLyoqXG4gKiBDYWxjdWxhdGVzIHRoZSBkZWZhdWx0IHBoeXNpY2FsIHJlc291cmNlIElEIGJhc2VkIGluIGNhc2UgdXNlciBoYW5kbGVyIGRpZFxuICogbm90IHJldHVybiBhIFBoeXNpY2FsUmVzb3VyY2VJZC5cbiAqXG4gKiBGb3IgXCJDUkVBVEVcIiwgaXQgdXNlcyB0aGUgUmVxdWVzdElkLlxuICogRm9yIFwiVVBEQVRFXCIgYW5kIFwiREVMRVRFXCIgYW5kIHJldHVybnMgdGhlIGN1cnJlbnQgUGh5c2ljYWxSZXNvdXJjZUlkICh0aGUgb25lIHByb3ZpZGVkIGluIGBldmVudGApLlxuICovXG5mdW5jdGlvbiBkZWZhdWx0UGh5c2ljYWxSZXNvdXJjZUlkKHJlcTogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCk6IHN0cmluZyB7XG4gIHN3aXRjaCAocmVxLlJlcXVlc3RUeXBlKSB7XG4gICAgY2FzZSAnQ3JlYXRlJzpcbiAgICAgIHJldHVybiByZXEuUmVxdWVzdElkO1xuXG4gICAgY2FzZSAnVXBkYXRlJzpcbiAgICBjYXNlICdEZWxldGUnOlxuICAgICAgcmV0dXJuIHJlcS5QaHlzaWNhbFJlc291cmNlSWQ7XG5cbiAgICBkZWZhdWx0OlxuICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIFwiUmVxdWVzdFR5cGVcIiBpbiByZXF1ZXN0IFwiJHtKU09OLnN0cmluZ2lmeShyZXEpfVwiYCk7XG4gIH1cbn1cbiJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/outbound.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/outbound.js new file mode 100644 index 0000000000000..cbd999a911392 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/outbound.js @@ -0,0 +1,82 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.httpRequest = exports.invokeFunction = exports.startExecution = void 0; +/* istanbul ignore file */ +const https = require("https"); +// eslint-disable-next-line import/no-extraneous-dependencies +const client_lambda_1 = require("@aws-sdk/client-lambda"); +// eslint-disable-next-line import/no-extraneous-dependencies +const client_sfn_1 = require("@aws-sdk/client-sfn"); +const FRAMEWORK_HANDLER_TIMEOUT = 900000; // 15 minutes +// In order to honor the overall maximum timeout set for the target process, +// the default 2 minutes from AWS SDK has to be overriden: +// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#httpOptions-property +const awsSdkConfig = { + httpOptions: { timeout: FRAMEWORK_HANDLER_TIMEOUT }, +}; +async function defaultHttpRequest(options, requestBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, (response) => { + response.resume(); // Consume the response but don't care about it + if (!response.statusCode || response.statusCode >= 400) { + reject(new Error(`Unsuccessful HTTP response: ${response.statusCode}`)); + } + else { + resolve(); + } + }); + request.on('error', reject); + request.write(requestBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +let sfn; +let lambda; +async function defaultStartExecution(req) { + if (!sfn) { + sfn = new client_sfn_1.SFN(awsSdkConfig); + } + return sfn.startExecution(req); +} +async function defaultInvokeFunction(req) { + if (!lambda) { + lambda = new client_lambda_1.Lambda(awsSdkConfig); + } + try { + /** + * Try an initial invoke. + * + * When you try to invoke a function that is inactive, the invocation fails and Lambda sets + * the function to pending state until the function resources are recreated. + * If Lambda fails to recreate the resources, the function is set to the inactive state. + * + * We're using invoke first because `waitFor` doesn't trigger an inactive function to do anything, + * it just runs `getFunction` and checks the state. + */ + return await lambda.invoke(req); + } + catch { + /** + * The status of the Lambda function is checked every second for up to 300 seconds. + * Exits the loop on 'Active' state and throws an error on 'Inactive' or 'Failed'. + * + * And now we wait. + */ + await (0, client_lambda_1.waitUntilFunctionActiveV2)({ + client: lambda, + maxWaitTime: 300, + }, { + FunctionName: req.FunctionName, + }); + return lambda.invoke(req); + } +} +exports.startExecution = defaultStartExecution; +exports.invokeFunction = defaultInvokeFunction; +exports.httpRequest = defaultHttpRequest; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0Ym91bmQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJvdXRib3VuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwwQkFBMEI7QUFDMUIsK0JBQStCO0FBRS9CLDZEQUE2RDtBQUM3RCwwREFBMkU7QUFFM0UsNkRBQTZEO0FBQzdELG9EQUEwQztBQUUxQyxNQUFNLHlCQUF5QixHQUFHLE1BQU0sQ0FBQyxDQUFDLGFBQWE7QUFFdkQsNEVBQTRFO0FBQzVFLDBEQUEwRDtBQUMxRCwyRkFBMkY7QUFDM0YsTUFBTSxZQUFZLEdBQUc7SUFDbkIsV0FBVyxFQUFFLEVBQUUsT0FBTyxFQUFFLHlCQUF5QixFQUFFO0NBQ3BELENBQUM7QUFFRixLQUFLLFVBQVUsa0JBQWtCLENBQUMsT0FBNkIsRUFBRSxXQUFtQjtJQUNsRixPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQzNDLElBQUksQ0FBQztZQUNILE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ2xELFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLCtDQUErQztnQkFDbEUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLElBQUksUUFBUSxDQUFDLFVBQVUsSUFBSSxHQUFHLEVBQUUsQ0FBQztvQkFDdkQsTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLCtCQUErQixRQUFRLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUMxRSxDQUFDO3FCQUFNLENBQUM7b0JBQ04sT0FBTyxFQUFFLENBQUM7Z0JBQ1osQ0FBQztZQUNILENBQUMsQ0FBQyxDQUFDO1lBQ0gsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDNUIsT0FBTyxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUMzQixPQUFPLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDaEIsQ0FBQztRQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDWCxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDWixDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDO0FBRUQsSUFBSSxHQUFRLENBQUM7QUFDYixJQUFJLE1BQWMsQ0FBQztBQUVuQixLQUFLLFVBQVUscUJBQXFCLENBQUMsR0FBd0I7SUFDM0QsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ1QsR0FBRyxHQUFHLElBQUksZ0JBQUcsQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBRUQsT0FBTyxHQUFHLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ2pDLENBQUM7QUFFRCxLQUFLLFVBQVUscUJBQXFCLENBQUMsR0FBdUI7SUFDMUQsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxHQUFHLElBQUksc0JBQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUNwQyxDQUFDO0lBRUQsSUFBSSxDQUFDO1FBQ0g7Ozs7Ozs7OztXQVNHO1FBQ0gsT0FBTyxNQUFNLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQOzs7OztXQUtHO1FBQ0gsTUFBTSxJQUFBLHlDQUF5QixFQUFDO1lBQzlCLE1BQU0sRUFBRSxNQUFNO1lBQ2QsV0FBVyxFQUFFLEdBQUc7U0FDakIsRUFBRTtZQUNELFlBQVksRUFBRSxHQUFHLENBQUMsWUFBWTtTQUMvQixDQUFDLENBQUM7UUFDSCxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDNUIsQ0FBQztBQUNILENBQUM7QUFFVSxRQUFBLGNBQWMsR0FBRyxxQkFBcUIsQ0FBQztBQUN2QyxRQUFBLGNBQWMsR0FBRyxxQkFBcUIsQ0FBQztBQUN2QyxRQUFBLFdBQVcsR0FBRyxrQkFBa0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qIGlzdGFuYnVsIGlnbm9yZSBmaWxlICovXG5pbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgdHlwZSB7IEludm9jYXRpb25SZXNwb25zZSwgSW52b2tlQ29tbWFuZElucHV0IH0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LWxhbWJkYSc7XG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgeyBMYW1iZGEsIHdhaXRVbnRpbEZ1bmN0aW9uQWN0aXZlVjIgfSBmcm9tICdAYXdzLXNkay9jbGllbnQtbGFtYmRhJztcbmltcG9ydCB0eXBlIHsgU3RhcnRFeGVjdXRpb25JbnB1dCwgU3RhcnRFeGVjdXRpb25PdXRwdXQgfSBmcm9tICdAYXdzLXNkay9jbGllbnQtc2ZuJztcbi8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBpbXBvcnQvbm8tZXh0cmFuZW91cy1kZXBlbmRlbmNpZXNcbmltcG9ydCB7IFNGTiB9IGZyb20gJ0Bhd3Mtc2RrL2NsaWVudC1zZm4nO1xuXG5jb25zdCBGUkFNRVdPUktfSEFORExFUl9USU1FT1VUID0gOTAwMDAwOyAvLyAxNSBtaW51dGVzXG5cbi8vIEluIG9yZGVyIHRvIGhvbm9yIHRoZSBvdmVyYWxsIG1heGltdW0gdGltZW91dCBzZXQgZm9yIHRoZSB0YXJnZXQgcHJvY2Vzcyxcbi8vIHRoZSBkZWZhdWx0IDIgbWludXRlcyBmcm9tIEFXUyBTREsgaGFzIHRvIGJlIG92ZXJyaWRlbjpcbi8vIGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9BV1NKYXZhU2NyaXB0U0RLL2xhdGVzdC9BV1MvQ29uZmlnLmh0bWwjaHR0cE9wdGlvbnMtcHJvcGVydHlcbmNvbnN0IGF3c1Nka0NvbmZpZyA9IHtcbiAgaHR0cE9wdGlvbnM6IHsgdGltZW91dDogRlJBTUVXT1JLX0hBTkRMRVJfVElNRU9VVCB9LFxufTtcblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdEh0dHBSZXF1ZXN0KG9wdGlvbnM6IGh0dHBzLlJlcXVlc3RPcHRpb25zLCByZXF1ZXN0Qm9keTogc3RyaW5nKSB7XG4gIHJldHVybiBuZXcgUHJvbWlzZTx2b2lkPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlcXVlc3QgPSBodHRwcy5yZXF1ZXN0KG9wdGlvbnMsIChyZXNwb25zZSkgPT4ge1xuICAgICAgICByZXNwb25zZS5yZXN1bWUoKTsgLy8gQ29uc3VtZSB0aGUgcmVzcG9uc2UgYnV0IGRvbid0IGNhcmUgYWJvdXQgaXRcbiAgICAgICAgaWYgKCFyZXNwb25zZS5zdGF0dXNDb2RlIHx8IHJlc3BvbnNlLnN0YXR1c0NvZGUgPj0gNDAwKSB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgVW5zdWNjZXNzZnVsIEhUVFAgcmVzcG9uc2U6ICR7cmVzcG9uc2Uuc3RhdHVzQ29kZX1gKSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHJlcXVlc3Qub24oJ2Vycm9yJywgcmVqZWN0KTtcbiAgICAgIHJlcXVlc3Qud3JpdGUocmVxdWVzdEJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxubGV0IHNmbjogU0ZOO1xubGV0IGxhbWJkYTogTGFtYmRhO1xuXG5hc3luYyBmdW5jdGlvbiBkZWZhdWx0U3RhcnRFeGVjdXRpb24ocmVxOiBTdGFydEV4ZWN1dGlvbklucHV0KTogUHJvbWlzZTxTdGFydEV4ZWN1dGlvbk91dHB1dD4ge1xuICBpZiAoIXNmbikge1xuICAgIHNmbiA9IG5ldyBTRk4oYXdzU2RrQ29uZmlnKTtcbiAgfVxuXG4gIHJldHVybiBzZm4uc3RhcnRFeGVjdXRpb24ocmVxKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdEludm9rZUZ1bmN0aW9uKHJlcTogSW52b2tlQ29tbWFuZElucHV0KTogUHJvbWlzZTxJbnZvY2F0aW9uUmVzcG9uc2U+IHtcbiAgaWYgKCFsYW1iZGEpIHtcbiAgICBsYW1iZGEgPSBuZXcgTGFtYmRhKGF3c1Nka0NvbmZpZyk7XG4gIH1cblxuICB0cnkge1xuICAgIC8qKlxuICAgICAqIFRyeSBhbiBpbml0aWFsIGludm9rZS5cbiAgICAgKlxuICAgICAqIFdoZW4geW91IHRyeSB0byBpbnZva2UgYSBmdW5jdGlvbiB0aGF0IGlzIGluYWN0aXZlLCB0aGUgaW52b2NhdGlvbiBmYWlscyBhbmQgTGFtYmRhIHNldHNcbiAgICAgKiB0aGUgZnVuY3Rpb24gdG8gcGVuZGluZyBzdGF0ZSB1bnRpbCB0aGUgZnVuY3Rpb24gcmVzb3VyY2VzIGFyZSByZWNyZWF0ZWQuXG4gICAgICogSWYgTGFtYmRhIGZhaWxzIHRvIHJlY3JlYXRlIHRoZSByZXNvdXJjZXMsIHRoZSBmdW5jdGlvbiBpcyBzZXQgdG8gdGhlIGluYWN0aXZlIHN0YXRlLlxuICAgICAqXG4gICAgICogV2UncmUgdXNpbmcgaW52b2tlIGZpcnN0IGJlY2F1c2UgYHdhaXRGb3JgIGRvZXNuJ3QgdHJpZ2dlciBhbiBpbmFjdGl2ZSBmdW5jdGlvbiB0byBkbyBhbnl0aGluZyxcbiAgICAgKiBpdCBqdXN0IHJ1bnMgYGdldEZ1bmN0aW9uYCBhbmQgY2hlY2tzIHRoZSBzdGF0ZS5cbiAgICAgKi9cbiAgICByZXR1cm4gYXdhaXQgbGFtYmRhLmludm9rZShyZXEpO1xuICB9IGNhdGNoIHtcbiAgICAvKipcbiAgICAgKiBUaGUgc3RhdHVzIG9mIHRoZSBMYW1iZGEgZnVuY3Rpb24gaXMgY2hlY2tlZCBldmVyeSBzZWNvbmQgZm9yIHVwIHRvIDMwMCBzZWNvbmRzLlxuICAgICAqIEV4aXRzIHRoZSBsb29wIG9uICdBY3RpdmUnIHN0YXRlIGFuZCB0aHJvd3MgYW4gZXJyb3Igb24gJ0luYWN0aXZlJyBvciAnRmFpbGVkJy5cbiAgICAgKlxuICAgICAqIEFuZCBub3cgd2Ugd2FpdC5cbiAgICAgKi9cbiAgICBhd2FpdCB3YWl0VW50aWxGdW5jdGlvbkFjdGl2ZVYyKHtcbiAgICAgIGNsaWVudDogbGFtYmRhLFxuICAgICAgbWF4V2FpdFRpbWU6IDMwMCxcbiAgICB9LCB7XG4gICAgICBGdW5jdGlvbk5hbWU6IHJlcS5GdW5jdGlvbk5hbWUsXG4gICAgfSk7XG4gICAgcmV0dXJuIGxhbWJkYS5pbnZva2UocmVxKTtcbiAgfVxufVxuXG5leHBvcnQgbGV0IHN0YXJ0RXhlY3V0aW9uID0gZGVmYXVsdFN0YXJ0RXhlY3V0aW9uO1xuZXhwb3J0IGxldCBpbnZva2VGdW5jdGlvbiA9IGRlZmF1bHRJbnZva2VGdW5jdGlvbjtcbmV4cG9ydCBsZXQgaHR0cFJlcXVlc3QgPSBkZWZhdWx0SHR0cFJlcXVlc3Q7XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/util.js b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/util.js new file mode 100644 index 0000000000000..07ddd92d97321 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e/util.js @@ -0,0 +1,54 @@ +"use strict"; +/* eslint-disable @cdklabs/no-throw-default-error */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getEnv = getEnv; +exports.log = log; +exports.withRetries = withRetries; +exports.parseJsonPayload = parseJsonPayload; +/* eslint-disable no-console */ +function getEnv(name) { + const value = process.env[name]; + if (!value) { + throw new Error(`The environment variable "${name}" is not defined`); + } + return value; +} +function log(title, ...args) { + console.log('[provider-framework]', title, ...args.map(x => typeof (x) === 'object' ? JSON.stringify(x, undefined, 2) : x)); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +function parseJsonPayload(payload) { + // sdk v3 returns payloads in Uint8Array, either it or a string or Buffer + // can be cast into a buffer and then decoded. + const text = new TextDecoder().decode(Buffer.from(payload ?? '')); + if (!text) { + return {}; + } + try { + return JSON.parse(text); + } + catch { + throw new Error(`return values from user-handlers must be JSON objects. got: "${text}"`); + } +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInV0aWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLG9EQUFvRDs7QUFJcEQsd0JBTUM7QUFFRCxrQkFFQztBQVNELGtDQWdCQztBQU1ELDRDQVVDO0FBckRELCtCQUErQjtBQUUvQixTQUFnQixNQUFNLENBQUMsSUFBWTtJQUNqQyxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2hDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNYLE1BQU0sSUFBSSxLQUFLLENBQUMsNkJBQTZCLElBQUksa0JBQWtCLENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDO0FBRUQsU0FBZ0IsR0FBRyxDQUFDLEtBQVUsRUFBRSxHQUFHLElBQVc7SUFDNUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsRUFBRSxLQUFLLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzdILENBQUM7QUFTRCxTQUFnQixXQUFXLENBQTBCLE9BQXFCLEVBQUUsRUFBNEI7SUFDdEcsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFLLEVBQUUsRUFBRTtRQUN4QixJQUFJLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQ2hDLElBQUksRUFBRSxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUM7UUFDdkIsT0FBTyxJQUFJLEVBQUUsQ0FBQztZQUNaLElBQUksQ0FBQztnQkFDSCxPQUFPLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUM7WUFDekIsQ0FBQztZQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ1gsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQztvQkFDcEIsTUFBTSxDQUFDLENBQUM7Z0JBQ1YsQ0FBQztnQkFDRCxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUM1QyxFQUFFLElBQUksQ0FBQyxDQUFDO1lBQ1YsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLEtBQUssQ0FBQyxFQUFVO0lBQzdCLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxFQUFFLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNqRCxDQUFDO0FBRUQsU0FBZ0IsZ0JBQWdCLENBQUMsT0FBd0Q7SUFDdkYseUVBQXlFO0lBQ3pFLDhDQUE4QztJQUM5QyxNQUFNLElBQUksR0FBRyxJQUFJLFdBQVcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2xFLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUFDLE9BQU8sRUFBRyxDQUFDO0lBQUMsQ0FBQztJQUMxQixJQUFJLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDMUIsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0VBQWdFLElBQUksR0FBRyxDQUFDLENBQUM7SUFDM0YsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiBlc2xpbnQtZGlzYWJsZSBAY2RrbGFicy9uby10aHJvdy1kZWZhdWx0LWVycm9yICovXG5cbi8qIGVzbGludC1kaXNhYmxlIG5vLWNvbnNvbGUgKi9cblxuZXhwb3J0IGZ1bmN0aW9uIGdldEVudihuYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCB2YWx1ZSA9IHByb2Nlc3MuZW52W25hbWVdO1xuICBpZiAoIXZhbHVlKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBUaGUgZW52aXJvbm1lbnQgdmFyaWFibGUgXCIke25hbWV9XCIgaXMgbm90IGRlZmluZWRgKTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2codGl0bGU6IGFueSwgLi4uYXJnczogYW55W10pIHtcbiAgY29uc29sZS5sb2coJ1twcm92aWRlci1mcmFtZXdvcmtdJywgdGl0bGUsIC4uLmFyZ3MubWFwKHggPT4gdHlwZW9mKHgpID09PSAnb2JqZWN0JyA/IEpTT04uc3RyaW5naWZ5KHgsIHVuZGVmaW5lZCwgMikgOiB4KSk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZUpzb25QYXlsb2FkKHBheWxvYWQ6IHN0cmluZyB8IEJ1ZmZlciB8IFVpbnQ4QXJyYXkgfCB1bmRlZmluZWQgfCBudWxsKTogYW55IHtcbiAgLy8gc2RrIHYzIHJldHVybnMgcGF5bG9hZHMgaW4gVWludDhBcnJheSwgZWl0aGVyIGl0IG9yIGEgc3RyaW5nIG9yIEJ1ZmZlclxuICAvLyBjYW4gYmUgY2FzdCBpbnRvIGEgYnVmZmVyIGFuZCB0aGVuIGRlY29kZWQuXG4gIGNvbnN0IHRleHQgPSBuZXcgVGV4dERlY29kZXIoKS5kZWNvZGUoQnVmZmVyLmZyb20ocGF5bG9hZCA/PyAnJykpO1xuICBpZiAoIXRleHQpIHsgcmV0dXJuIHsgfTsgfVxuICB0cnkge1xuICAgIHJldHVybiBKU09OLnBhcnNlKHRleHQpO1xuICB9IGNhdGNoIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYHJldHVybiB2YWx1ZXMgZnJvbSB1c2VyLWhhbmRsZXJzIG11c3QgYmUgSlNPTiBvYmplY3RzLiBnb3Q6IFwiJHt0ZXh0fVwiYCk7XG4gIH1cbn1cbiJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/apply/__init__.py b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/apply/__init__.py new file mode 100644 index 0000000000000..a62a9a0ceb913 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/apply/__init__.py @@ -0,0 +1,93 @@ +import json +import logging +import os +import subprocess + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/kubectl:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + + +def apply_handler(event, context): + logger.info(json.dumps(dict(event, ResponseURL='...'))) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties (all required) + cluster_name = props['ClusterName'] + manifest_text = props['Manifest'] + prune_label = props.get('PruneLabel', None) + overwrite = props.get('Overwrite', 'false').lower() == 'true' + skip_validation = props.get('SkipValidation', 'false').lower() == 'true' + + # "log in" to the cluster + cmd = [ 'aws', 'eks', 'update-kubeconfig', + '--name', cluster_name, + '--kubeconfig', kubeconfig + ] + logger.info(f'Running command: {cmd}') + subprocess.check_call(cmd) + + if os.path.isfile(kubeconfig): + os.chmod(kubeconfig, 0o600) + + # write resource manifests in sequence: { r1 }{ r2 }{ r3 } (this is how + # a stream of JSON objects can be included in a k8s manifest). + manifest_list = json.loads(manifest_text) + manifest_file = os.path.join(outdir, 'manifest.yaml') + with open(manifest_file, "w") as f: + f.writelines(map(lambda obj: json.dumps(obj), manifest_list)) + + logger.info("manifest written to: %s" % manifest_file) + + kubectl_opts = [] + if skip_validation: + kubectl_opts.extend(['--validate=false']) + + if request_type == 'Create': + # if "overwrite" is enabled, then we use "apply" for CREATE operations + # which technically means we can determine the desired state of an + # existing resource. + if overwrite: + kubectl('apply', manifest_file, *kubectl_opts) + else: + # --save-config will allow us to use "apply" later + kubectl_opts.extend(['--save-config']) + kubectl('create', manifest_file, *kubectl_opts) + elif request_type == 'Update': + if prune_label is not None: + kubectl_opts.extend(['--prune', '-l', prune_label]) + + kubectl('apply', manifest_file, *kubectl_opts) + elif request_type == "Delete": + try: + kubectl('delete', manifest_file) + except Exception as e: + logger.info("delete error: %s" % e) + + +def kubectl(verb, file, *opts): + maxAttempts = 3 + retry = maxAttempts + while retry > 0: + try: + cmd = ['kubectl', verb, '--kubeconfig', kubeconfig, '-f', file] + list(opts) + logger.info(f'Running command: {cmd}') + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + output = exc.output + if b'i/o timeout' in output and retry > 0: + retry = retry - 1 + logger.info("kubectl timed out, retries left: %s" % retry) + else: + raise Exception(output) + else: + logger.info(output) + return + raise Exception(f'Operation failed after {maxAttempts} attempts: {output}') diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/get/__init__.py b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/get/__init__.py new file mode 100644 index 0000000000000..2bf22d45f0415 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/get/__init__.py @@ -0,0 +1,86 @@ +import json +import logging +import os +import subprocess +import time + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/kubectl:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + + +def get_handler(event, context): + logger.info(json.dumps(dict(event, ResponseURL='...'))) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties (all required) + cluster_name = props['ClusterName'] + + # "log in" to the cluster + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + if os.path.isfile(kubeconfig): + os.chmod(kubeconfig, 0o600) + + object_type = props['ObjectType'] + object_name = props['ObjectName'] + object_namespace = props['ObjectNamespace'] + json_path = props['JsonPath'] + timeout_seconds = props['TimeoutSeconds'] + + # json path should be surrouded with '{}' + path = '{{{0}}}'.format(json_path) + if request_type == 'Create' or request_type == 'Update': + output = wait_for_output(['get', '-n', object_namespace, object_type, object_name, "-o=jsonpath='{{{0}}}'".format(json_path)], int(timeout_seconds)) + return {'Data': {'Value': output}} + elif request_type == 'Delete': + pass + else: + raise Exception("invalid request type %s" % request_type) + +def wait_for_output(args, timeout_seconds): + + end_time = time.time() + timeout_seconds + error = None + + while time.time() < end_time: + try: + # the output is surrounded with '', so we unquote + output = kubectl(args).decode('utf-8')[1:-1] + if output: + return output + except Exception as e: + error = str(e) + # also a recoverable error + if 'NotFound' in error: + pass + time.sleep(10) + + raise RuntimeError(f'Timeout waiting for output from kubectl command: {args} (last_error={error})') + +def kubectl(args): + retry = 3 + while retry > 0: + try: + cmd = [ 'kubectl', '--kubeconfig', kubeconfig ] + args + output = subprocess.check_output(cmd, stderr=subprocess.PIPE) + except subprocess.CalledProcessError as exc: + output = exc.output + exc.stderr + if b'i/o timeout' in output and retry > 0: + logger.info("kubectl timed out, retries left: %s" % retry) + retry = retry - 1 + else: + raise Exception(output) + else: + logger.info(output) + return output diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/helm/__init__.py b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/helm/__init__.py new file mode 100644 index 0000000000000..c8c2979e0fce9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/helm/__init__.py @@ -0,0 +1,255 @@ +import json +import logging +import os +import re +import subprocess +import shutil +import tempfile +import zipfile +import boto3 + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/helm:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + +def get_chart_asset_from_url(chart_asset_url): + chart_zip = os.path.join(outdir, 'chart.zip') + shutil.rmtree(chart_zip, ignore_errors=True) + subprocess.check_call(['aws', 's3', 'cp', chart_asset_url, chart_zip]) + chart_dir = os.path.join(outdir, 'chart') + shutil.rmtree(chart_dir, ignore_errors=True) + os.mkdir(chart_dir) + with zipfile.ZipFile(chart_zip, 'r') as zip_ref: + zip_ref.extractall(chart_dir) + return chart_dir + +def is_ecr_public_available(region): + s = boto3.Session() + return s.get_partition_for_region(region) == 'aws' + +def helm_handler(event, context): + logger.info(json.dumps(dict(event, ResponseURL='...'))) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties + cluster_name = props['ClusterName'] + release = props['Release'] + chart = props.get('Chart', None) + chart_asset_url = props.get('ChartAssetURL', None) + version = props.get('Version', None) + wait = props.get('Wait', False) + atomic = props.get('Atomic', False) + timeout = props.get('Timeout', None) + namespace = props.get('Namespace', None) + create_namespace = props.get('CreateNamespace', None) + repository = props.get('Repository', None) + values_text = props.get('Values', None) + skip_crds = props.get('SkipCrds', False) + + # "log in" to the cluster + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + if os.path.isfile(kubeconfig): + os.chmod(kubeconfig, 0o600) + + # Write out the values to a file and include them with the install and upgrade + values_file = None + if not request_type == "Delete" and not values_text is None: + values = json.loads(values_text) + values_file = os.path.join(outdir, 'values.yaml') + with open(values_file, "w") as f: + f.write(json.dumps(values, indent=2)) + + if request_type == 'Create' or request_type == 'Update': + # Ensure chart or chart_asset_url are set + if chart == None and chart_asset_url == None: + raise RuntimeError(f'chart or chartAsset must be specified') + + if chart_asset_url != None: + assert(chart==None) + assert(repository==None) + assert(version==None) + if not chart_asset_url.startswith('s3://'): + raise RuntimeError(f'ChartAssetURL must point to as s3 location but is {chart_asset_url}') + # future work: support versions from s3 assets + chart = get_chart_asset_from_url(chart_asset_url) + + if repository is not None and repository.startswith('oci://'): + tmpdir = tempfile.TemporaryDirectory() + chart_dir = get_chart_from_oci(tmpdir.name, repository, version) + chart = chart_dir + # Chart is now local — clear repository and version so helm() doesn't + # pass --repo/--version to "helm upgrade". Helm v4 (kubectl-v35+) + # rejects --repo with oci:// URLs ("invalid reference"), unlike v3. + repository = None + version = None + + helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout, create_namespace, atomic=atomic) + elif request_type == "Delete": + try: + helm('uninstall', release, namespace=namespace, wait=wait, timeout=timeout) + except Exception as e: + logger.info("delete error: %s" % e) + + +def get_oci_cmd(repository, version): + # Generates OCI command based on pattern. Public ECR vs Private ECR are treated differently. + private_ecr_pattern = 'oci://(?P\d+\.dkr\.ecr\.(?P[a-z0-9\-]+)\.(?P[a-z0-9\.-]+))*' + public_ecr_pattern = 'oci://(?Ppublic\.ecr\.aws)*' + + private_registry = re.match(private_ecr_pattern, repository).groupdict() + public_registry = re.match(public_ecr_pattern, repository).groupdict() + + # Build helm pull command as array + helm_cmd = ['helm', 'pull', repository, '--version', version , '--untar'] + + if private_registry['registry'] is not None: + logger.info("Found AWS private repository") + ecr_login = ['aws', 'ecr', 'get-login-password', '--region', private_registry['region']] + helm_registry_login = ['helm', 'registry', 'login', '--username', 'AWS', '--password-stdin', private_registry['registry']] + return {'ecr_login': ecr_login, 'helm_registry_login': helm_registry_login, 'helm': helm_cmd} + elif public_registry['registry'] is not None: + logger.info("Found AWS public repository, will use default region as deployment") + region = os.environ.get('AWS_REGION', 'us-east-1') + + if is_ecr_public_available(region): + # Public ECR auth is always in us-east-1: https://docs.aws.amazon.com/AmazonECR/latest/public/public-registry-auth.html + ecr_login = ['aws', 'ecr-public', 'get-login-password', '--region', 'us-east-1'] + helm_registry_login = ['helm', 'registry', 'login', '--username', 'AWS', '--password-stdin', public_registry['registry']] + return {'ecr_login': ecr_login, 'helm_registry_login': helm_registry_login, 'helm': helm_cmd} + else: + # No login required for public ECR in non-aws regions + # see https://helm.sh/docs/helm/helm_registry_login/ + return {'helm': helm_cmd} + else: + logger.error("OCI repository format not recognized, falling back to helm pull") + return {'helm': helm_cmd} + + +def get_chart_from_oci(tmpdir, repository=None, version=None): + from subprocess import Popen, PIPE + + commands = get_oci_cmd(repository, version) + + maxAttempts = 3 + retry = maxAttempts + while retry > 0: + try: + # Execute login commands if needed + if 'ecr_login' in commands and 'helm_registry_login' in commands: + logger.info("Running login command: %s", commands['ecr_login']) + logger.info("Running registry login command: %s", commands['helm_registry_login']) + + # Start first process: aws ecr get-login-password + # NOTE: We do NOT call p1.wait() here before starting p2. + # Doing so could deadlock if p1's output fills the pipe buffer + # before p2 starts reading. Instead, start p2 immediately so it + # can consume p1's stdout as it's produced. + p1 = Popen(commands['ecr_login'], stdout=PIPE, stderr=PIPE, cwd=tmpdir) + + # Start second process: helm registry login + p2 = Popen(commands['helm_registry_login'], stdin=p1.stdout, stdout=PIPE, stderr=PIPE, cwd=tmpdir) + p1.stdout.close() # Allow p1 to receive SIGPIPE if p2 exits early + + # Wait for p2 to finish first (ensures full pipeline completes) + _, p2_err = p2.communicate() + + # Now wait for p1 so we have a complete stderr and an exit code + p1.wait() + + # Handle p1 failure + if p1.returncode != 0: + p1_err = p1.stderr.read().decode('utf-8', errors='replace') if p1.stderr else '' + logger.error( + "ECR get-login-password failed for repository %s. Error: %s", + repository, + p1_err or "No error details" + ) + raise subprocess.CalledProcessError(p1.returncode, commands['ecr_login'], p1_err.encode()) + + # Handle p2 failure + if p2.returncode != 0: + logger.error( + "Helm registry authentication failed for repository %s. Error: %s", + repository, + p2_err.decode('utf-8', errors='replace') or "No error details" + ) + raise subprocess.CalledProcessError(p2.returncode, commands['helm_registry_login'], p2_err) + + # Execute helm pull command + logger.info("Running helm command: %s", commands['helm']) + output = subprocess.check_output(commands['helm'], stderr=subprocess.STDOUT, cwd=tmpdir) + logger.info(output) + + # effectively returns "$tmpDir/$lastPartOfOCIUrl", because this is how helm pull saves OCI artifact. + # Eg. if we have oci://9999999999.dkr.ecr.us-east-1.amazonaws.com/foo/bar/pet-service repository, helm saves artifact under $tmpDir/pet-service + return os.path.join(tmpdir, repository.rpartition('/')[-1]) + + except subprocess.CalledProcessError as exc: + output = exc.output + if b'Broken pipe' in output: + retry = retry - 1 + logger.info("Broken pipe, retries left: %s" % retry) + else: + raise Exception(output) + + raise Exception(f'Operation failed after {maxAttempts} attempts: {output}') + + +def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None, create_namespace = None, skip_crds = False, atomic = False): + cmnd = ['helm', verb, release] + if not chart is None: + cmnd.append(chart) + if verb == 'upgrade': + cmnd.append('--install') + if create_namespace: + cmnd.append('--create-namespace') + if not repo is None: + cmnd.extend(['--repo', repo]) + if not file is None: + cmnd.extend(['--values', file]) + if not version is None: + cmnd.extend(['--version', version]) + if not namespace is None: + cmnd.extend(['--namespace', namespace]) + if wait: + cmnd.append('--wait') + if skip_crds: + cmnd.append('--skip-crds') + if not timeout is None: + cmnd.extend(['--timeout', timeout]) + if atomic: + cmnd.append('--atomic') + cmnd.extend(['--kubeconfig', kubeconfig]) + + # Log the full helm command for better troubleshooting + logger.info("Running command: %s", cmnd) + + maxAttempts = 3 + retry = maxAttempts + while retry > 0: + try: + output = subprocess.check_output(cmnd, stderr=subprocess.STDOUT, cwd=outdir) + logger.info(output.decode('utf-8', errors='replace')) + return + except subprocess.CalledProcessError as exc: + output = exc.output + if b'Broken pipe' in output: + retry = retry - 1 + logger.info("Broken pipe, retries left: %s" % retry) + else: + error_message = output.decode('utf-8', errors='replace') + logger.error("Command failed: %s", cmnd) + logger.error("Error output: %s", error_message) + raise Exception(output) + raise Exception(f'Operation failed after {maxAttempts} attempts: {output.decode("utf-8", errors="replace")}') diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/index.py b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/index.py new file mode 100644 index 0000000000000..188ef37d8e1c1 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/index.py @@ -0,0 +1,26 @@ +import json +import logging + +from apply import apply_handler +from helm import helm_handler +from patch import patch_handler +from get import get_handler + +def handler(event, context): + print(json.dumps(dict(event, ResponseURL='...'))) + + resource_type = event['ResourceType'] + if resource_type == 'Custom::AWSCDK-EKS-KubernetesResource': + return apply_handler(event, context) + + if resource_type == 'Custom::AWSCDK-EKS-HelmChart': + return helm_handler(event, context) + + if resource_type == 'Custom::AWSCDK-EKS-KubernetesPatch': + return patch_handler(event, context) + + if resource_type == 'Custom::AWSCDK-EKS-KubernetesObjectValue': + return get_handler(event, context) + + raise Exception("unknown resource type %s" % resource_type) + \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/patch/__init__.py b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/patch/__init__.py new file mode 100644 index 0000000000000..a8ba4a13cbd06 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8/patch/__init__.py @@ -0,0 +1,68 @@ +import json +import logging +import os +import subprocess + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/kubectl:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + + +def patch_handler(event, context): + logger.info(json.dumps(dict(event, ResponseURL='...'))) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties (all required) + cluster_name = props['ClusterName'] + + # "log in" to the cluster + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + if os.path.isfile(kubeconfig): + os.chmod(kubeconfig, 0o600) + + resource_name = props['ResourceName'] + resource_namespace = props['ResourceNamespace'] + apply_patch_json = props['ApplyPatchJson'] + restore_patch_json = props['RestorePatchJson'] + patch_type = props['PatchType'] + + patch_json = None + if request_type == 'Create' or request_type == 'Update': + patch_json = apply_patch_json + elif request_type == 'Delete': + patch_json = restore_patch_json + else: + raise Exception("invalid request type %s" % request_type) + + kubectl([ 'patch', resource_name, '-n', resource_namespace, '-p', patch_json, '--type', patch_type ]) + + +def kubectl(args): + maxAttempts = 3 + retry = maxAttempts + while retry > 0: + try: + cmd = [ 'kubectl', '--kubeconfig', kubeconfig ] + args + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + output = exc.output + if b'i/o timeout' in output and retry > 0: + retry = retry - 1 + logger.info("kubectl timed out, retries left: %s" % retry) + else: + raise Exception(output) + else: + logger.info(output) + return + raise Exception(f'Operation failed after {maxAttempts} attempts: {output}') \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/cdk.out new file mode 100644 index 0000000000000..60aa68e157090 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"53.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.assets.json new file mode 100644 index 0000000000000..c1c63be4c51de --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.assets.json @@ -0,0 +1,90 @@ +{ + "version": "53.0.0", + "files": { + "55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f": { + "displayName": "eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider Code", + "source": { + "path": "asset.55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region-65a3ca72": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112": { + "displayName": "kubectlLayer/Code", + "source": { + "path": "asset.6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-bb629a4c": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8": { + "displayName": "Cluster/KubectlProvider/Handler/Code", + "source": { + "path": "asset.d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region-982e4a52": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18": { + "displayName": "Cluster/KubectlProvider/AwsCliLayer/Code", + "source": { + "path": "asset.0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-3bd41744": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e": { + "displayName": "Cluster/KubectlProvider/Provider/framework-onEvent/Code", + "source": { + "path": "asset.d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region-9e5c0bf1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "b19e9bfac053081966b8dde0eb743485e9c2905a10b559c852bfdb495a688372": { + "displayName": "eks-pod-identities-v2 Template", + "source": { + "path": "eks-pod-identities-v2.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d1f8d5cd": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b19e9bfac053081966b8dde0eb743485e9c2905a10b559c852bfdb495a688372.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.metadata.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.metadata.json new file mode 100644 index 0000000000000..084b6049395f5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.metadata.json @@ -0,0 +1,964 @@ +{ + "/eks-pod-identities-v2/VPC": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "natGateways": "*" + } + } + ], + "/eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider": [ + { + "type": "aws:cdk:is-custom-resource-handler-customResourceProvider", + "data": true + } + ], + "/eks-pod-identities-v2/kubectlLayer": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "vpc": "*", + "defaultCapacityType": 0, + "defaultCapacity": "*", + "version": "*", + "kubectlProviderOptions": {} + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "grantClusterAdmin": [ + "*", + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addNodegroupCapacity": [ + "*", + { + "instanceTypes": "*", + "minSize": "*" + } + ] + } + } + ], + "/eks-pod-identities-v2/ExternalRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + } + ], + "/eks-pod-identities-v2/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/eks-pod-identities-v2/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ], + "/eks-pod-identities-v2/VPC/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCB9E5F0B4" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": true, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": true, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:method", + "data": {} + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addNatGateway": [ + "*" + ] + } + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet2": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": true, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": true, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:method", + "data": {} + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet1": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": false, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": false, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:method", + "data": {} + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet2": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": false, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:construct", + "data": { + "availabilityZone": "*", + "vpcId": "*", + "cidrBlock": "*", + "mapPublicIpOnLaunch": false, + "ipv6CidrBlock": "*", + "assignIpv6AddressOnCreation": "*" + } + }, + { + "type": "aws:cdk:analytics:method", + "data": {} + } + ], + "/eks-pod-identities-v2/VPC/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCIGWB7E252D3" + } + ], + "/eks-pod-identities-v2/VPC/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCVPCGW99B986DC" + } + ], + "/eks-pod-identities-v2/VPC/RestrictDefaultSecurityGroupCustomResource": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0" + } + ], + "/eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E" + } + ], + "/eks-pod-identities-v2/kubectlLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "kubectlLayer44321E08" + } + ], + "/eks-pod-identities-v2/Cluster/Role": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "managedPolicies": [ + { + "managedPolicyArn": "*" + } + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/ControlPlaneSecurityGroup": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "vpc": "*", + "description": "*" + } + } + ], + "/eks-pod-identities-v2/Cluster/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterEB0386A7" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlReadyBarrier": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlReadyBarrier200052AF" + } + ], + "/eks-pod-identities-v2/Cluster/ClusterAdminRoleAccess": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "principal": "*", + "cluster": "*", + "accessPolicies": [ + { + "policy": "*", + "accessScope": { + "type": "cluster", + "namespaces": "*" + } + } + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "cluster": "*", + "instanceTypes": "*", + "minSize": "*" + } + } + ], + "/eks-pod-identities-v2/Cluster/EksPodIdentityAgentAddon": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "cluster": "*", + "addonName": "*", + "removalPolicy": "*" + } + } + ], + "/eks-pod-identities-v2/ServiceAccount/Role": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + } + ], + "/eks-pod-identities-v2/ServiceAccount/Association": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceAccountAssociation24AB2A05" + } + ], + "/eks-pod-identities-v2/ExternalRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ExternalRole6A2601E5" + } + ], + "/eks-pod-identities-v2/ServiceAccountWithExternalRole/Association": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceAccountWithExternalRoleAssociation9C038708" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1SubnetB4246D30" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1RouteTableFEE4B781" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1RouteTableAssociation0B0896DC" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1DefaultRoute91CEF279" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1EIP6AD938E8" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1NATGatewayE0556630" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2Subnet74179F39" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2RouteTable6F1A15F1" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2RouteTableAssociation5A808732" + } + ], + "/eks-pod-identities-v2/VPC/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2DefaultRouteB7481BBA" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1Subnet8BCA10E0" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1RouteTableBE8A6027" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1RouteTableAssociation347902D1" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1DefaultRouteAE1D6490" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2RouteTable0A19E10E" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2RouteTableAssociation0C73D413" + } + ], + "/eks-pod-identities-v2/VPC/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2DefaultRouteF4F5CFD2" + } + ], + "/eks-pod-identities-v2/VPC/RestrictDefaultSecurityGroupCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCRestrictDefaultSecurityGroupCustomResource59474679" + } + ], + "/eks-pod-identities-v2/kubectlLayer/Code/AssetBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterRoleFA261979" + } + ], + "/eks-pod-identities-v2/Cluster/ControlPlaneSecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterControlPlaneSecurityGroupD274242C" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "timeout": "*", + "description": "*", + "memorySize": "*", + "environment": "*", + "role": "*", + "code": "*", + "handler": "*", + "runtime": "*", + "vpc": "*", + "securityGroups": [ + "*" + ], + "vpcSubnets": { + "subnets": [ + "*", + "*" + ] + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addEnvironment": [ + "*", + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addLayers": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addLayers": [ + "*" + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer": [ + { + "type": "aws:cdk:analytics:construct", + "data": {} + } + ], + "/eks-pod-identities-v2/Cluster/ClusterAdminRoleAccess/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterClusterAdminRoleAccessF2BFF759" + } + ], + "/eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity/NodeGroupRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterNodegroupDefaultCapacityDA0920A3" + } + ], + "/eks-pod-identities-v2/Cluster/EksPodIdentityAgentAddon/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterEksPodIdentityAgentAddonEF717E32" + } + ], + "/eks-pod-identities-v2/Cluster/manifest-demopod/Resource": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster/manifest-demopod-external-role/Resource": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/ServiceAccount/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceAccountRoleFA0EEF4F" + } + ], + "/eks-pod-identities-v2/ServiceAccount/manifest-ServiceAccountServiceAccountResource/Resource": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/ServiceAccountWithExternalRole/manifest-ServiceAccountWithExternalRoleServiceAccountResource/Resource": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "managedPolicies": [ + { + "managedPolicyArn": "*" + }, + { + "managedPolicyArn": "*" + } + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + { + "managedPolicyArn": "*" + } + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addManagedPolicy": [ + "*" + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderHandler2E05C68A" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/HasEcrPublic": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderHandlerHasEcrPublic69E09706" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderAwsCliLayer24064B0B" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "code": "*", + "description": "*", + "runtime": "*", + "handler": "*", + "timeout": "*", + "loggingFormat": "JSON", + "applicationLogLevelV2": "FATAL", + "logGroup": "*", + "vpc": "*", + "vpcSubnets": { + "subnets": [ + "*", + "*" + ] + }, + "securityGroups": [ + "*" + ], + "role": "*", + "functionName": "*", + "environmentEncryption": "*" + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addEnvironment": [ + "*", + "*" + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity/NodeGroupRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterNodegroupDefaultCapacityNodeGroupRole55953B04" + } + ], + "/eks-pod-identities-v2/Cluster/manifest-demopod/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "Clustermanifestdemopod4389851E" + } + ], + "/eks-pod-identities-v2/Cluster/manifest-demopod-external-role/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ClustermanifestdemopodexternalroleE5DB8C74" + } + ], + "/eks-pod-identities-v2/ServiceAccount/manifest-ServiceAccountServiceAccountResource/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceAccountmanifestServiceAccountServiceAccountResourceB533ED3E" + } + ], + "/eks-pod-identities-v2/ServiceAccountWithExternalRole/manifest-ServiceAccountWithExternalRoleServiceAccountResource/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceAccountWithExternalRolemanifestServiceAccountWithExternalRoleServiceAccountResource393DF898" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderHandlerServiceRoleB460AA6D" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/Code/AssetBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer/Code/AssetBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "managedPolicies": [ + { + "managedPolicyArn": "*" + }, + { + "managedPolicyArn": "*" + } + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderframeworkonEvent68E0CF80" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderHandlerServiceRoleDefaultPolicy77317198" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/Code/AssetBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterKubectlProviderframeworkonEventServiceRoleDefaultPolicyA4F24629" + } + ] +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.template.json new file mode 100644 index 0000000000000..7ca13afddf1bc --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/eks-pod-identities-v2.template.json @@ -0,0 +1,1409 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + }, + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "VPCPublicSubnet1DefaultRoute91CEF279", + "VPCPublicSubnet1RouteTableAssociation0B0896DC" + ] + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PublicSubnet2" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PublicSubnet2" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + }, + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PrivateSubnet1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PrivateSubnet1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + }, + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PrivateSubnet2" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC/PrivateSubnet2" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + }, + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "eks-pod-identities-v2/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + }, + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCRestrictDefaultSecurityGroupCustomResource59474679": { + "Type": "Custom::VpcRestrictDefaultSG", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E", + "Arn" + ] + }, + "DefaultSecurityGroupId": { + "Fn::GetAtt": [ + "VPCB9E5F0B4", + "DefaultSecurityGroup" + ] + }, + "Account": { + "Ref": "AWS::AccountId" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:RevokeSecurityGroupEgress" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ec2:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":security-group/", + { + "Fn::GetAtt": [ + "VPCB9E5F0B4", + "DefaultSecurityGroup" + ] + } + ] + ] + } + ] + } + ] + } + } + ] + } + }, + "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "55549d0bfa9628b306c349544b7d95017221e259e17875f3d38f2a3d2da5043f.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Description": "Lambda function for removing all inbound/outbound rules from the VPC default security group" + }, + "DependsOn": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0" + ] + }, + "kubectlLayer44321E08": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip" + }, + "Description": "/opt/kubectl/kubectl 1.32.3; /opt/helm/helm 3.17.2", + "LicenseInfo": "Apache-2.0" + } + }, + "ClusterRoleFA261979": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + } + ] + } + }, + "ClusterControlPlaneSecurityGroupD274242C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "ClusterEB0386A7": { + "Type": "AWS::EKS::Cluster", + "Properties": { + "AccessConfig": { + "AuthenticationMode": "API" + }, + "ComputeConfig": { + "Enabled": false + }, + "KubernetesNetworkConfig": { + "ElasticLoadBalancing": { + "Enabled": false + }, + "IpFamily": "ipv4" + }, + "ResourcesVpcConfig": { + "EndpointPrivateAccess": true, + "EndpointPublicAccess": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterRoleFA261979", + "Arn" + ] + }, + "StorageConfig": { + "BlockStorage": { + "Enabled": false + } + }, + "Version": "1.32" + }, + "DependsOn": [ + "VPCIGWB7E252D3", + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableBE8A6027", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet1Subnet8BCA10E0", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTable0A19E10E", + "VPCPrivateSubnet2RouteTableAssociation0C73D413", + "VPCPrivateSubnet2SubnetCFCDAA7A", + "VPCPublicSubnet1DefaultRoute91CEF279", + "VPCPublicSubnet1EIP6AD938E8", + "VPCPublicSubnet1NATGatewayE0556630", + "VPCPublicSubnet1RouteTableFEE4B781", + "VPCPublicSubnet1RouteTableAssociation0B0896DC", + "VPCPublicSubnet1SubnetB4246D30", + "VPCPublicSubnet2DefaultRouteB7481BBA", + "VPCPublicSubnet2RouteTable6F1A15F1", + "VPCPublicSubnet2RouteTableAssociation5A808732", + "VPCPublicSubnet2Subnet74179F39", + "VPCB9E5F0B4", + "VPCRestrictDefaultSecurityGroupCustomResource59474679", + "VPCVPCGW99B986DC" + ] + }, + "ClusterKubectlReadyBarrier200052AF": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "aws:cdk:eks:kubectl-ready" + }, + "DependsOn": [ + "ClusterClusterAdminRoleAccessF2BFF759", + "ClusterEB0386A7" + ] + }, + "ClusterKubectlProviderHandlerServiceRoleB460AA6D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + }, + { + "Fn::If": [ + "ClusterKubectlProviderHandlerHasEcrPublic69E09706", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonElasticContainerRegistryPublicReadOnly" + ] + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + }, + "DependsOn": [ + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTableAssociation0C73D413" + ] + }, + "ClusterKubectlProviderHandlerServiceRoleDefaultPolicy77317198": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "eks:DescribeCluster", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ClusterEB0386A7", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ClusterKubectlProviderHandlerServiceRoleDefaultPolicy77317198", + "Roles": [ + { + "Ref": "ClusterKubectlProviderHandlerServiceRoleB460AA6D" + } + ] + }, + "DependsOn": [ + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTableAssociation0C73D413" + ] + }, + "ClusterKubectlProviderHandler2E05C68A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8.zip" + }, + "Description": "onEvent handler for EKS kubectl resource provider", + "Environment": { + "Variables": { + "AWS_STS_REGIONAL_ENDPOINTS": "regional" + } + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "ClusterKubectlProviderAwsCliLayer24064B0B" + }, + { + "Ref": "kubectlLayer44321E08" + } + ], + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "ClusterKubectlProviderHandlerServiceRoleB460AA6D", + "Arn" + ] + }, + "Runtime": "python3.13", + "Timeout": 900, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterEB0386A7", + "ClusterSecurityGroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "DependsOn": [ + "ClusterKubectlProviderHandlerServiceRoleDefaultPolicy77317198", + "ClusterKubectlProviderHandlerServiceRoleB460AA6D", + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTableAssociation0C73D413" + ] + }, + "ClusterKubectlProviderAwsCliLayer24064B0B": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" + ] + ] + } + ] + }, + "DependsOn": [ + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTableAssociation0C73D413" + ] + }, + "ClusterKubectlProviderframeworkonEventServiceRoleDefaultPolicyA4F24629": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ClusterKubectlProviderHandler2E05C68A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ClusterKubectlProviderHandler2E05C68A", + "Arn" + ] + }, + ":*" + ] + ] + } + ] + }, + { + "Action": "lambda:GetFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ClusterKubectlProviderHandler2E05C68A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ClusterKubectlProviderframeworkonEventServiceRoleDefaultPolicyA4F24629", + "Roles": [ + { + "Ref": "ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5" + } + ] + }, + "DependsOn": [ + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTableAssociation0C73D413" + ] + }, + "ClusterKubectlProviderframeworkonEvent68E0CF80": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e.zip" + }, + "Description": "AWS CDK resource provider framework - onEvent (eks-pod-identities-v2/Cluster/KubectlProvider/Provider)", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "ClusterKubectlProviderHandler2E05C68A", + "Arn" + ] + } + } + }, + "Handler": "framework.onEvent", + "LoggingConfig": { + "ApplicationLogLevel": "FATAL", + "LogFormat": "JSON" + }, + "Role": { + "Fn::GetAtt": [ + "ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Timeout": 900, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterEB0386A7", + "ClusterSecurityGroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "DependsOn": [ + "ClusterKubectlProviderframeworkonEventServiceRoleDefaultPolicyA4F24629", + "ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5", + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet1RouteTableAssociation347902D1", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet2RouteTableAssociation0C73D413" + ] + }, + "ClusterClusterAdminRoleAccessF2BFF759": { + "Type": "AWS::EKS::AccessEntry", + "Properties": { + "AccessPolicies": [ + { + "AccessScope": { + "Type": "cluster" + }, + "PolicyArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + ] + ] + } + } + ], + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "PrincipalArn": { + "Fn::GetAtt": [ + "ClusterKubectlProviderHandlerServiceRoleB460AA6D", + "Arn" + ] + } + } + }, + "ClusterNodegroupDefaultCapacityNodeGroupRole55953B04": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ] + } + }, + "ClusterNodegroupDefaultCapacityDA0920A3": { + "Type": "AWS::EKS::Nodegroup", + "Properties": { + "AmiType": "AL2_x86_64", + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "ForceUpdateEnabled": true, + "InstanceTypes": [ + "m5.large" + ], + "NodeRole": { + "Fn::GetAtt": [ + "ClusterNodegroupDefaultCapacityNodeGroupRole55953B04", + "Arn" + ] + }, + "ScalingConfig": { + "DesiredSize": 1, + "MaxSize": 1, + "MinSize": 1 + }, + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "ClusterEksPodIdentityAgentAddonEF717E32": { + "Type": "AWS::EKS::Addon", + "Properties": { + "AddonName": "eks-pod-identity-agent", + "ClusterName": { + "Ref": "ClusterEB0386A7" + } + } + }, + "Clustermanifestdemopod4389851E": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ClusterKubectlProviderframeworkonEvent68E0CF80", + "Arn" + ] + }, + "Manifest": "[{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"name\":\"demo\",\"labels\":{\"aws.cdk.eks/prune-c8a22d9f5ece7fbafcfe9cf11ed74672a3ab05f8ab\":\"\"}},\"spec\":{\"serviceAccountName\":\"test-sa\",\"containers\":[{\"name\":\"demo\",\"image\":\"public.ecr.aws/amazonlinux/amazonlinux:2023\",\"command\":[\"/bin/bash\",\"-c\",\"yum update -y && yum install -y awscli && aws sts get-caller-identity\"]}]}}]", + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "PruneLabel": "aws.cdk.eks/prune-c8a22d9f5ece7fbafcfe9cf11ed74672a3ab05f8ab" + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF", + "ServiceAccountAssociation24AB2A05", + "ServiceAccountmanifestServiceAccountServiceAccountResourceB533ED3E", + "ServiceAccountRoleFA0EEF4F" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClustermanifestdemopodexternalroleE5DB8C74": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ClusterKubectlProviderframeworkonEvent68E0CF80", + "Arn" + ] + }, + "Manifest": "[{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"name\":\"demo-external-role\",\"labels\":{\"aws.cdk.eks/prune-c80afea47745e629e80878777602ae8462c3c838e2\":\"\"}},\"spec\":{\"serviceAccountName\":\"test-sa-external-role\",\"containers\":[{\"name\":\"demo\",\"image\":\"public.ecr.aws/amazonlinux/amazonlinux:2023\",\"command\":[\"/bin/bash\",\"-c\",\"yum update -y && yum install -y awscli && aws sts get-caller-identity\"]}]}}]", + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "PruneLabel": "aws.cdk.eks/prune-c80afea47745e629e80878777602ae8462c3c838e2" + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF", + "ServiceAccountWithExternalRoleAssociation9C038708", + "ServiceAccountWithExternalRolemanifestServiceAccountWithExternalRoleServiceAccountResource393DF898" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ServiceAccountRoleFA0EEF4F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ], + "Effect": "Allow", + "Principal": { + "Service": "pods.eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonS3ReadOnlyAccess" + ] + ] + } + ] + } + }, + "ServiceAccountAssociation24AB2A05": { + "Type": "AWS::EKS::PodIdentityAssociation", + "Properties": { + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "Namespace": "default", + "RoleArn": { + "Fn::GetAtt": [ + "ServiceAccountRoleFA0EEF4F", + "Arn" + ] + }, + "ServiceAccount": "test-sa" + } + }, + "ServiceAccountmanifestServiceAccountServiceAccountResourceB533ED3E": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ClusterKubectlProviderframeworkonEvent68E0CF80", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"test-sa\",\"namespace\":\"default\",\"labels\":{\"aws.cdk.eks/prune-c8b31e6b895bea014893f82aea691368cc3f383ced\":\"\",\"app.kubernetes.io/name\":\"test-sa\"},\"annotations\":{\"eks.amazonaws.com/role-arn\":\"", + { + "Fn::GetAtt": [ + "ServiceAccountRoleFA0EEF4F", + "Arn" + ] + }, + "\"}}}]" + ] + ] + }, + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "PruneLabel": "aws.cdk.eks/prune-c8b31e6b895bea014893f82aea691368cc3f383ced" + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ExternalRole6A2601E5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ], + "Effect": "Allow", + "Principal": { + "Service": "pods.eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonS3ReadOnlyAccess" + ] + ] + } + ] + } + }, + "ServiceAccountWithExternalRoleAssociation9C038708": { + "Type": "AWS::EKS::PodIdentityAssociation", + "Properties": { + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "Namespace": "default", + "RoleArn": { + "Fn::GetAtt": [ + "ExternalRole6A2601E5", + "Arn" + ] + }, + "ServiceAccount": "test-sa-external-role" + } + }, + "ServiceAccountWithExternalRolemanifestServiceAccountWithExternalRoleServiceAccountResource393DF898": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ClusterKubectlProviderframeworkonEvent68E0CF80", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"test-sa-external-role\",\"namespace\":\"default\",\"labels\":{\"aws.cdk.eks/prune-c8d64a5df326c6f6cd0de2137016a33a826dea248c\":\"\",\"app.kubernetes.io/name\":\"test-sa-external-role\"},\"annotations\":{\"eks.amazonaws.com/role-arn\":\"", + { + "Fn::GetAtt": [ + "ExternalRole6A2601E5", + "Arn" + ] + }, + "\"}}}]" + ] + ] + }, + "ClusterName": { + "Ref": "ClusterEB0386A7" + }, + "PruneLabel": "aws.cdk.eks/prune-c8d64a5df326c6f6cd0de2137016a33a826dea248c" + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Conditions": { + "ClusterKubectlProviderHandlerHasEcrPublic69E09706": { + "Fn::Equals": [ + { + "Ref": "AWS::Partition" + }, + "aws" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integ.json new file mode 100644 index 0000000000000..f2aad604f04d4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "53.0.0", + "testCases": { + "integ-eks-pod-identities-v2/DefaultTest": { + "stacks": [ + "eks-pod-identities-v2" + ], + "assertionStack": "integ-eks-pod-identities-v2/DefaultTest/DeployAssert", + "assertionStackName": "integekspodidentitiesv2DefaultTestDeployAssert83452A00" + } + }, + "minimumCliVersion": "2.1108.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets.json new file mode 100644 index 0000000000000..b19769815ea25 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets.json @@ -0,0 +1,20 @@ +{ + "version": "53.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "integekspodidentitiesv2DefaultTestDeployAssert83452A00 Template", + "source": { + "path": "integekspodidentitiesv2DefaultTestDeployAssert83452A00.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.metadata.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.metadata.json new file mode 100644 index 0000000000000..1dafbaebc7547 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.metadata.json @@ -0,0 +1,14 @@ +{ + "/integ-eks-pod-identities-v2/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-eks-pod-identities-v2/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/integekspodidentitiesv2DefaultTestDeployAssert83452A00.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/manifest.json new file mode 100644 index 0000000000000..e420e91c434ba --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/manifest.json @@ -0,0 +1,594 @@ +{ + "version": "53.0.0", + "artifacts": { + "eks-pod-identities-v2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "eks-pod-identities-v2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "eks-pod-identities-v2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "eks-pod-identities-v2.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b19e9bfac053081966b8dde0eb743485e9c2905a10b559c852bfdb495a688372.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "eks-pod-identities-v2.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "eks-pod-identities-v2.assets" + ], + "additionalMetadataFile": "eks-pod-identities-v2.metadata.json", + "displayName": "eks-pod-identities-v2" + }, + "integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integekspodidentitiesv2DefaultTestDeployAssert83452A00": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integekspodidentitiesv2DefaultTestDeployAssert83452A00.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integekspodidentitiesv2DefaultTestDeployAssert83452A00.assets" + ], + "additionalMetadataFile": "integekspodidentitiesv2DefaultTestDeployAssert83452A00.metadata.json", + "displayName": "integ-eks-pod-identities-v2/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "userValue": true, + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": { + "userValue": true, + "recommendedValue": true, + "explanation": "Disable implicit openListener when custom security groups are provided" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-eks:useNativeOidcProvider": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, EKS V2 clusters will use the native OIDC provider resource AWS::IAM::OIDCProvider instead of creating the OIDCProvider with a custom resource (iam.OpenIDConnectProvider)." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": false, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + }, + "@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, Network Load Balancer will be created with a security group by default." + }, + "@aws-cdk/aws-stepfunctions-tasks:httpInvokeDynamicJsonPathEndpoint": { + "recommendedValue": true, + "explanation": "When enabled, allows using a dynamic apiEndpoint with JSONPath format in HttpInvoke tasks.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, ECS patterns will generate unique target group IDs to prevent conflicts during load balancer replacement" + }, + "@aws-cdk/aws-route53-patterns:useDistribution": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the `Distribution` resource instead of `CloudFrontWebDistribution`" + }, + "@aws-cdk/aws-cloudfront:defaultFunctionRuntimeV2_0": { + "recommendedValue": true, + "explanation": "Use cloudfront-js-2.0 as the default runtime for CloudFront Functions" + }, + "@aws-cdk/aws-elasticloadbalancingv2:usePostQuantumTlsPolicy": { + "recommendedValue": true, + "explanation": "When enabled, HTTPS/TLS listeners use post-quantum TLS policy by default" + }, + "@aws-cdk/core:automaticL1Traits": { + "recommendedValue": true, + "explanation": "Automatically use the default L1 traits for L1 constructs`", + "unconfiguredBehavesLike": { + "v2": true + } + } + } + } + } + }, + "minimumCliVersion": "2.1108.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/tree.json new file mode 100644 index 0000000000000..a762b847956c4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"eks-pod-identities-v2":{"id":"eks-pod-identities-v2","path":"eks-pod-identities-v2","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"VPC":{"id":"VPC","path":"eks-pod-identities-v2/VPC","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.Vpc","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/VPC/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPC","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPC","aws:cdk:cloudformation:props":{"cidrBlock":"10.0.0.0/16","enableDnsHostnames":true,"enableDnsSupport":true,"instanceTenancy":"default","tags":[{"key":"Name","value":"eks-pod-identities-v2/VPC"}]}}},"PublicSubnet1":{"id":"PublicSubnet1","path":"eks-pod-identities-v2/VPC/PublicSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0"},"children":{"Subnet":{"id":"Subnet","path":"eks-pod-identities-v2/VPC/PublicSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.0.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"kubernetes.io/role/elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PublicSubnet1"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"Acl":{"id":"Acl","path":"eks-pod-identities-v2/VPC/PublicSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"RouteTable":{"id":"RouteTable","path":"eks-pod-identities-v2/VPC/PublicSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"kubernetes.io/role/elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PublicSubnet1"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"eks-pod-identities-v2/VPC/PublicSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VPCPublicSubnet1RouteTableFEE4B781"},"subnetId":{"Ref":"VPCPublicSubnet1SubnetB4246D30"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"eks-pod-identities-v2/VPC/PublicSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VPCIGWB7E252D3"},"routeTableId":{"Ref":"VPCPublicSubnet1RouteTableFEE4B781"}}}},"EIP":{"id":"EIP","path":"eks-pod-identities-v2/VPC/PublicSubnet1/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"kubernetes.io/role/elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PublicSubnet1"}]}}},"NATGateway":{"id":"NATGateway","path":"eks-pod-identities-v2/VPC/PublicSubnet1/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VPCPublicSubnet1EIP6AD938E8","AllocationId"]},"subnetId":{"Ref":"VPCPublicSubnet1SubnetB4246D30"},"tags":[{"key":"kubernetes.io/role/elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PublicSubnet1"}]}}}}},"PublicSubnet2":{"id":"PublicSubnet2","path":"eks-pod-identities-v2/VPC/PublicSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0"},"children":{"Subnet":{"id":"Subnet","path":"eks-pod-identities-v2/VPC/PublicSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.64.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"kubernetes.io/role/elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PublicSubnet2"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"Acl":{"id":"Acl","path":"eks-pod-identities-v2/VPC/PublicSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"RouteTable":{"id":"RouteTable","path":"eks-pod-identities-v2/VPC/PublicSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"kubernetes.io/role/elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PublicSubnet2"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"eks-pod-identities-v2/VPC/PublicSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VPCPublicSubnet2RouteTable6F1A15F1"},"subnetId":{"Ref":"VPCPublicSubnet2Subnet74179F39"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"eks-pod-identities-v2/VPC/PublicSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VPCIGWB7E252D3"},"routeTableId":{"Ref":"VPCPublicSubnet2RouteTable6F1A15F1"}}}}}},"PrivateSubnet1":{"id":"PrivateSubnet1","path":"eks-pod-identities-v2/VPC/PrivateSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0"},"children":{"Subnet":{"id":"Subnet","path":"eks-pod-identities-v2/VPC/PrivateSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.128.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"kubernetes.io/role/internal-elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PrivateSubnet1"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"Acl":{"id":"Acl","path":"eks-pod-identities-v2/VPC/PrivateSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"RouteTable":{"id":"RouteTable","path":"eks-pod-identities-v2/VPC/PrivateSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"kubernetes.io/role/internal-elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PrivateSubnet1"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"eks-pod-identities-v2/VPC/PrivateSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VPCPrivateSubnet1RouteTableBE8A6027"},"subnetId":{"Ref":"VPCPrivateSubnet1Subnet8BCA10E0"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"eks-pod-identities-v2/VPC/PrivateSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VPCPublicSubnet1NATGatewayE0556630"},"routeTableId":{"Ref":"VPCPrivateSubnet1RouteTableBE8A6027"}}}}}},"PrivateSubnet2":{"id":"PrivateSubnet2","path":"eks-pod-identities-v2/VPC/PrivateSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0"},"children":{"Subnet":{"id":"Subnet","path":"eks-pod-identities-v2/VPC/PrivateSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.192.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"kubernetes.io/role/internal-elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PrivateSubnet2"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"Acl":{"id":"Acl","path":"eks-pod-identities-v2/VPC/PrivateSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"RouteTable":{"id":"RouteTable","path":"eks-pod-identities-v2/VPC/PrivateSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"kubernetes.io/role/internal-elb","value":"1"},{"key":"Name","value":"eks-pod-identities-v2/VPC/PrivateSubnet2"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"eks-pod-identities-v2/VPC/PrivateSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VPCPrivateSubnet2RouteTable0A19E10E"},"subnetId":{"Ref":"VPCPrivateSubnet2SubnetCFCDAA7A"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"eks-pod-identities-v2/VPC/PrivateSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VPCPublicSubnet1NATGatewayE0556630"},"routeTableId":{"Ref":"VPCPrivateSubnet2RouteTable0A19E10E"}}}}}},"IGW":{"id":"IGW","path":"eks-pod-identities-v2/VPC/IGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnInternetGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::InternetGateway","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"eks-pod-identities-v2/VPC"}]}}},"VPCGW":{"id":"VPCGW","path":"eks-pod-identities-v2/VPC/VPCGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPCGatewayAttachment","aws:cdk:cloudformation:props":{"internetGatewayId":{"Ref":"VPCIGWB7E252D3"},"vpcId":{"Ref":"VPCB9E5F0B4"}}}},"RestrictDefaultSecurityGroupCustomResource":{"id":"RestrictDefaultSecurityGroupCustomResource","path":"eks-pod-identities-v2/VPC/RestrictDefaultSecurityGroupCustomResource","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"eks-pod-identities-v2/VPC/RestrictDefaultSecurityGroupCustomResource/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}},"Custom::VpcRestrictDefaultSGCustomResourceProvider":{"id":"Custom::VpcRestrictDefaultSGCustomResourceProvider","path":"eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider","constructInfo":{"fqn":"aws-cdk-lib.CustomResourceProviderBase","version":"0.0.0"},"children":{"Staging":{"id":"Staging","path":"eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Staging","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"Role":{"id":"Role","path":"eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}},"Handler":{"id":"Handler","path":"eks-pod-identities-v2/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}},"kubectlLayer":{"id":"kubectlLayer","path":"eks-pod-identities-v2/kubectlLayer","constructInfo":{"fqn":"@aws-cdk/lambda-layer-kubectl-v32.KubectlV32Layer","version":"2.1.0"},"children":{"Code":{"id":"Code","path":"eks-pod-identities-v2/kubectlLayer/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"eks-pod-identities-v2/kubectlLayer/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"eks-pod-identities-v2/kubectlLayer/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0"}}}},"Resource":{"id":"Resource","path":"eks-pod-identities-v2/kubectlLayer/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnLayerVersion","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::LayerVersion","aws:cdk:cloudformation:props":{"content":{"s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"6094cb0ff874f89ab5ab24fb6b9417df0fdeb6966645f90c88ec1d7e28130112.zip"},"description":"/opt/kubectl/kubectl 1.32.3; /opt/helm/helm 3.17.2","licenseInfo":"Apache-2.0"}}}}},"Cluster":{"id":"Cluster","path":"eks-pod-identities-v2/Cluster","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.Cluster","version":"0.0.0"},"children":{"Role":{"id":"Role","path":"eks-pod-identities-v2/Cluster/Role","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/Role/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"eks.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonEKSClusterPolicy"]]}]}}}}},"ControlPlaneSecurityGroup":{"id":"ControlPlaneSecurityGroup","path":"eks-pod-identities-v2/Cluster/ControlPlaneSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/ControlPlaneSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"EKS Control Plane Security Group","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"vpcId":{"Ref":"VPCB9E5F0B4"}}}}}},"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_eks.CfnCluster","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EKS::Cluster","aws:cdk:cloudformation:props":{"accessConfig":{"authenticationMode":"API"},"computeConfig":{"enabled":false},"kubernetesNetworkConfig":{"ipFamily":"ipv4","elasticLoadBalancing":{"enabled":false}},"resourcesVpcConfig":{"securityGroupIds":[{"Fn::GetAtt":["ClusterControlPlaneSecurityGroupD274242C","GroupId"]}],"subnetIds":[{"Ref":"VPCPublicSubnet1SubnetB4246D30"},{"Ref":"VPCPublicSubnet2Subnet74179F39"},{"Ref":"VPCPrivateSubnet1Subnet8BCA10E0"},{"Ref":"VPCPrivateSubnet2SubnetCFCDAA7A"}],"endpointPrivateAccess":true,"endpointPublicAccess":true},"roleArn":{"Fn::GetAtt":["ClusterRoleFA261979","Arn"]},"storageConfig":{"blockStorage":{"enabled":false}},"version":"1.32"}}},"KubectlReadyBarrier":{"id":"KubectlReadyBarrier","path":"eks-pod-identities-v2/Cluster/KubectlReadyBarrier","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}},"ClusterSecurityGroup":{"id":"ClusterSecurityGroup","path":"eks-pod-identities-v2/Cluster/ClusterSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"KubectlProvider":{"id":"KubectlProvider","path":"eks-pod-identities-v2/Cluster/KubectlProvider","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.KubectlProvider","version":"0.0.0"},"children":{"Handler":{"id":"Handler","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0"},"children":{"ServiceRole":{"id":"ServiceRole","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"]]},{"Fn::If":["ClusterKubectlProviderHandlerHasEcrPublic69E09706",{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonElasticContainerRegistryPublicReadOnly"]]},{"Ref":"AWS::NoValue"}]}]}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/ServiceRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"eks:DescribeCluster","Effect":"Allow","Resource":{"Fn::GetAtt":["ClusterEB0386A7","Arn"]}}],"Version":"2012-10-17"},"policyName":"ClusterKubectlProviderHandlerServiceRoleDefaultPolicy77317198","roles":[{"Ref":"ClusterKubectlProviderHandlerServiceRoleB460AA6D"}]}}}}}}},"Code":{"id":"Code","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0"}}}},"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"d1f0dc4bbd7f19a0c9d47ff23ed15d3972cdb7bd292c1c20c0f442d0021d9eb8.zip"},"description":"onEvent handler for EKS kubectl resource provider","environment":{"variables":{"AWS_STS_REGIONAL_ENDPOINTS":"regional"}},"handler":"index.handler","layers":[{"Ref":"ClusterKubectlProviderAwsCliLayer24064B0B"},{"Ref":"kubectlLayer44321E08"}],"memorySize":1024,"role":{"Fn::GetAtt":["ClusterKubectlProviderHandlerServiceRoleB460AA6D","Arn"]},"runtime":"python3.13","timeout":900,"vpcConfig":{"subnetIds":[{"Ref":"VPCPrivateSubnet1Subnet8BCA10E0"},{"Ref":"VPCPrivateSubnet2SubnetCFCDAA7A"}],"securityGroupIds":[{"Fn::GetAtt":["ClusterEB0386A7","ClusterSecurityGroupId"]}]}}}},"HasEcrPublic":{"id":"HasEcrPublic","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Handler/HasEcrPublic","constructInfo":{"fqn":"aws-cdk-lib.CfnCondition","version":"0.0.0"}}}},"AwsCliLayer":{"id":"AwsCliLayer","path":"eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer","constructInfo":{"fqn":"aws-cdk-lib.lambda_layer_awscli.AwsCliLayer","version":"0.0.0"},"children":{"Code":{"id":"Code","path":"eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0"}}}},"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/AwsCliLayer/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnLayerVersion","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::LayerVersion","aws:cdk:cloudformation:props":{"content":{"s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"0cfdecad2260a3a84ad0c2d08a77e03c9d25e26c7b52f26b1e1faf97aef92f18.zip"},"description":"/opt/awscli/aws"}}}}},"ConditionalPolicyArn":{"id":"ConditionalPolicyArn","path":"eks-pod-identities-v2/Cluster/KubectlProvider/ConditionalPolicyArn","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"conditionalPolicy":{"id":"conditionalPolicy","path":"eks-pod-identities-v2/Cluster/KubectlProvider/conditionalPolicy","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0"}},"Provider":{"id":"Provider","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider","constructInfo":{"fqn":"aws-cdk-lib.custom_resources.Provider","version":"0.0.0"},"children":{"framework-onEvent":{"id":"framework-onEvent","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0"},"children":{"ServiceRole":{"id":"ServiceRole","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"]]}]}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/ServiceRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"lambda:InvokeFunction","Effect":"Allow","Resource":[{"Fn::GetAtt":["ClusterKubectlProviderHandler2E05C68A","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["ClusterKubectlProviderHandler2E05C68A","Arn"]},":*"]]}]},{"Action":"lambda:GetFunction","Effect":"Allow","Resource":{"Fn::GetAtt":["ClusterKubectlProviderHandler2E05C68A","Arn"]}}],"Version":"2012-10-17"},"policyName":"ClusterKubectlProviderframeworkonEventServiceRoleDefaultPolicyA4F24629","roles":[{"Ref":"ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5"}]}}}}}}},"Code":{"id":"Code","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0"}}}},"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/KubectlProvider/Provider/framework-onEvent/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"d14e70c8c1d5d6c32025ea07c4b9ed67be449820cf578659c9deb07160688c0e.zip"},"description":"AWS CDK resource provider framework - onEvent (eks-pod-identities-v2/Cluster/KubectlProvider/Provider)","environment":{"variables":{"USER_ON_EVENT_FUNCTION_ARN":{"Fn::GetAtt":["ClusterKubectlProviderHandler2E05C68A","Arn"]}}},"handler":"framework.onEvent","loggingConfig":{"logFormat":"JSON","applicationLogLevel":"FATAL"},"role":{"Fn::GetAtt":["ClusterKubectlProviderframeworkonEventServiceRoleFD0BA8C5","Arn"]},"runtime":"nodejs22.x","timeout":900,"vpcConfig":{"subnetIds":[{"Ref":"VPCPrivateSubnet1Subnet8BCA10E0"},{"Ref":"VPCPrivateSubnet2SubnetCFCDAA7A"}],"securityGroupIds":[{"Fn::GetAtt":["ClusterEB0386A7","ClusterSecurityGroupId"]}]}}}}}}}}}},"ClusterAdminRoleAccess":{"id":"ClusterAdminRoleAccess","path":"eks-pod-identities-v2/Cluster/ClusterAdminRoleAccess","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.AccessEntry","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/ClusterAdminRoleAccess/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_eks.CfnAccessEntry","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EKS::AccessEntry","aws:cdk:cloudformation:props":{"accessPolicies":[{"accessScope":{"type":"cluster"},"policyArn":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"]]}}],"clusterName":{"Ref":"ClusterEB0386A7"},"principalArn":{"Fn::GetAtt":["ClusterKubectlProviderHandlerServiceRoleB460AA6D","Arn"]}}}}}},"NodegroupDefaultCapacity":{"id":"NodegroupDefaultCapacity","path":"eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.Nodegroup","version":"0.0.0"},"children":{"NodeGroupRole":{"id":"NodeGroupRole","path":"eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity/NodeGroupRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity/NodeGroupRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonEKSWorkerNodePolicy"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonEKS_CNI_Policy"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"]]}]}}}}},"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/NodegroupDefaultCapacity/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_eks.CfnNodegroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EKS::Nodegroup","aws:cdk:cloudformation:props":{"amiType":"AL2_x86_64","clusterName":{"Ref":"ClusterEB0386A7"},"forceUpdateEnabled":true,"instanceTypes":["m5.large"],"nodeRole":{"Fn::GetAtt":["ClusterNodegroupDefaultCapacityNodeGroupRole55953B04","Arn"]},"scalingConfig":{"desiredSize":1,"maxSize":1,"minSize":1},"subnets":[{"Ref":"VPCPrivateSubnet1Subnet8BCA10E0"},{"Ref":"VPCPrivateSubnet2SubnetCFCDAA7A"}]}}}}},"EksPodIdentityAgentAddon":{"id":"EksPodIdentityAgentAddon","path":"eks-pod-identities-v2/Cluster/EksPodIdentityAgentAddon","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.Addon","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/EksPodIdentityAgentAddon/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_eks.CfnAddon","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EKS::Addon","aws:cdk:cloudformation:props":{"addonName":"eks-pod-identity-agent","clusterName":{"Ref":"ClusterEB0386A7"}}}}}},"manifest-demopod":{"id":"manifest-demopod","path":"eks-pod-identities-v2/Cluster/manifest-demopod","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.KubernetesManifest","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/manifest-demopod/Resource","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"eks-pod-identities-v2/Cluster/manifest-demopod/Resource/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}},"manifest-demopod-external-role":{"id":"manifest-demopod-external-role","path":"eks-pod-identities-v2/Cluster/manifest-demopod-external-role","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.KubernetesManifest","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/Cluster/manifest-demopod-external-role/Resource","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"eks-pod-identities-v2/Cluster/manifest-demopod-external-role/Resource/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}}}},"ServiceAccount":{"id":"ServiceAccount","path":"eks-pod-identities-v2/ServiceAccount","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.ServiceAccount","version":"0.0.0"},"children":{"Role":{"id":"Role","path":"eks-pod-identities-v2/ServiceAccount/Role","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/ServiceAccount/Role/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":["sts:AssumeRole","sts:TagSession"],"Effect":"Allow","Principal":{"Service":"pods.eks.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonS3ReadOnlyAccess"]]}]}}}}},"Association":{"id":"Association","path":"eks-pod-identities-v2/ServiceAccount/Association","constructInfo":{"fqn":"aws-cdk-lib.aws_eks.CfnPodIdentityAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EKS::PodIdentityAssociation","aws:cdk:cloudformation:props":{"clusterName":{"Ref":"ClusterEB0386A7"},"namespace":"default","roleArn":{"Fn::GetAtt":["ServiceAccountRoleFA0EEF4F","Arn"]},"serviceAccount":"test-sa"}}},"manifest-ServiceAccountServiceAccountResource":{"id":"manifest-ServiceAccountServiceAccountResource","path":"eks-pod-identities-v2/ServiceAccount/manifest-ServiceAccountServiceAccountResource","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.KubernetesManifest","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/ServiceAccount/manifest-ServiceAccountServiceAccountResource/Resource","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"eks-pod-identities-v2/ServiceAccount/manifest-ServiceAccountServiceAccountResource/Resource/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}}}},"ExternalRole":{"id":"ExternalRole","path":"eks-pod-identities-v2/ExternalRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/ExternalRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":["sts:AssumeRole","sts:TagSession"],"Effect":"Allow","Principal":{"Service":"pods.eks.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonS3ReadOnlyAccess"]]}]}}}}},"ServiceAccountWithExternalRole":{"id":"ServiceAccountWithExternalRole","path":"eks-pod-identities-v2/ServiceAccountWithExternalRole","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.ServiceAccount","version":"0.0.0"},"children":{"Association":{"id":"Association","path":"eks-pod-identities-v2/ServiceAccountWithExternalRole/Association","constructInfo":{"fqn":"aws-cdk-lib.aws_eks.CfnPodIdentityAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EKS::PodIdentityAssociation","aws:cdk:cloudformation:props":{"clusterName":{"Ref":"ClusterEB0386A7"},"namespace":"default","roleArn":{"Fn::GetAtt":["ExternalRole6A2601E5","Arn"]},"serviceAccount":"test-sa-external-role"}}},"manifest-ServiceAccountWithExternalRoleServiceAccountResource":{"id":"manifest-ServiceAccountWithExternalRoleServiceAccountResource","path":"eks-pod-identities-v2/ServiceAccountWithExternalRole/manifest-ServiceAccountWithExternalRoleServiceAccountResource","constructInfo":{"fqn":"aws-cdk-lib.aws_eks_v2.KubernetesManifest","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"eks-pod-identities-v2/ServiceAccountWithExternalRole/manifest-ServiceAccountWithExternalRoleServiceAccountResource/Resource","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"eks-pod-identities-v2/ServiceAccountWithExternalRole/manifest-ServiceAccountWithExternalRoleServiceAccountResource/Resource/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"eks-pod-identities-v2/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"eks-pod-identities-v2/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"integ-eks-pod-identities-v2":{"id":"integ-eks-pod-identities-v2","path":"integ-eks-pod-identities-v2","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"integ-eks-pod-identities-v2/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"integ-eks-pod-identities-v2/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.5.1"}},"DeployAssert":{"id":"DeployAssert","path":"integ-eks-pod-identities-v2/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"integ-eks-pod-identities-v2/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"integ-eks-pod-identities-v2/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.5.1"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts index e0170c8d9d4cf..0ce4c381cc67a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks-v2/test/integ.eks-pod-identities.ts @@ -2,7 +2,7 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as iam from 'aws-cdk-lib/aws-iam'; import type { StackProps } from 'aws-cdk-lib'; import { App, Stack } from 'aws-cdk-lib'; -import { getClusterVersionConfig } from './integ-tests-kubernetes-version'; +import { KubectlV32Layer } from '@aws-cdk/lambda-layer-kubectl-v32'; import * as eks from 'aws-cdk-lib/aws-eks-v2'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; @@ -29,8 +29,12 @@ class EksPodIdentitiesStack extends Stack { const cluster = new eks.Cluster(this, 'Cluster', { vpc, + defaultCapacityType: eks.DefaultCapacityType.NODEGROUP, defaultCapacity: 1, - ...getClusterVersionConfig(this), + version: eks.KubernetesVersion.V1_32, + kubectlProviderOptions: { + kubectlLayer: new KubectlV32Layer(this, 'kubectlLayer'), + }, }); // Case 1: auto-generated IAM role (existing behavior) From 49e64dcede4ffec511dc93b065e68f78487e2c37 Mon Sep 17 00:00:00 2001 From: letsgomeow Date: Sun, 29 Mar 2026 13:57:01 +0900 Subject: [PATCH 4/5] feat(aws-eks-v2): allow providing an existing IAM role to ServiceAccount with POD_IDENTITY Add an optional `role` property to `ServiceAccountOptions` so that `ServiceAccount` with `IdentityType.POD_IDENTITY` can accept an externally-created IAM role instead of always auto-generating one. --- packages/aws-cdk-lib/aws-eks-v2/README.md | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/aws-cdk-lib/aws-eks-v2/README.md b/packages/aws-cdk-lib/aws-eks-v2/README.md index 9384bbef5b787..b2147f548ee2c 100644 --- a/packages/aws-cdk-lib/aws-eks-v2/README.md +++ b/packages/aws-cdk-lib/aws-eks-v2/README.md @@ -912,6 +912,81 @@ bucket.grantReadWrite(serviceAccount); Note that adding service accounts requires running `kubectl` commands against the cluster which requires you to provide `kubectlProviderOptions` in the cluster props to create the `kubectl` provider. See [Kubectl Support](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-eks-v2-readme.html#kubectl-support) +### Pod Identities + +[Amazon EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) is a feature that simplifies how +Kubernetes applications running on Amazon EKS can obtain AWS IAM credentials. It provides a way to associate an IAM role with a +Kubernetes service account, allowing pods to retrieve temporary AWS credentials without the need +to manage IAM roles and policies directly. + +By default, `ServiceAccount` creates an `OpenIdConnectProvider` for +[IRSA (IAM roles for service accounts)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) if +`identityType` is `undefined` or `IdentityType.IRSA`. + +You may opt in to Amazon EKS Pod Identities as below: + +```ts +declare const cluster: eks.Cluster; + +new eks.ServiceAccount(this, 'ServiceAccount', { + cluster, + name: 'test-sa', + namespace: 'default', + identityType: eks.IdentityType.POD_IDENTITY, +}); +``` + +When you create the `ServiceAccount` with the `identityType` set to `POD_IDENTITY`, +the `ServiceAccount` construct will perform the following actions behind the scenes: + +1. It will create an IAM role with the necessary trust policy to allow the `pods.eks.amazonaws.com` principal to assume the role. + This trust policy grants the EKS service the permission to retrieve temporary AWS credentials on behalf of the pods using this service account. + +2. It will enable the "Amazon EKS Pod Identity Agent" add-on on the EKS cluster. This add-on is responsible for managing the temporary + AWS credentials and making them available to the pods. + +3. It will create an association between the IAM role and the Kubernetes service account. This association allows the pods using this + service account to obtain the temporary AWS credentials from the associated IAM role. + +#### Using an existing IAM role with Pod Identity + +If you want to manage IAM roles centrally (e.g., in a dedicated `IamConstruct`) or reuse an existing role created via +`iam.Role.fromRoleArn()`, you can pass it to `ServiceAccount` via the `role` property. + +The `role` property accepts any `IRoleRef`, including `iam.Role`, `iam.Role.fromRoleArn()`, and L1 `iam.CfnRole`. +**This option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.** + +The caller is responsible for configuring the trust policy of the role correctly. For Pod Identity, the role must allow +`pods.eks.amazonaws.com` to perform `sts:AssumeRole` and `sts:TagSession`. + +```ts +import * as iam from 'aws-cdk-lib/aws-iam'; +declare const cluster: eks.Cluster; + +// Create and manage the IAM role separately +const appRole = new iam.Role(this, 'AppRole', { + assumedBy: new iam.SessionTagsPrincipal( + new iam.ServicePrincipal('pods.eks.amazonaws.com'), + ), +}); +appRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + +// Pass the existing role to ServiceAccount +new eks.ServiceAccount(this, 'AppServiceAccount', { + cluster, + name: 'app-sa', + namespace: 'production', + identityType: eks.IdentityType.POD_IDENTITY, + role: appRole, +}); +``` + +When `role` is specified, the auto-generation of an IAM role is skipped. +The provided role's ARN is used directly in the `PodIdentityAssociation`. + +> **Note:** If you pass an L1 construct (`iam.CfnRole`) as the `role`, the `ServiceAccount` and `PodIdentityAssociation` +> are created successfully. However, accessing `serviceAccount.role` to call methods such as `grant()` or +> `addManagedPolicy()` will throw an error, as those methods are only available on L2 `IRole` instances. #### Migrating from the deprecated eks.OpenIdConnectProvider to eks.OidcProviderNative From f7e492145cba38a5186e2ccad7e9e5dbd31ed74e Mon Sep 17 00:00:00 2001 From: letsgomeow Date: Sun, 5 Apr 2026 15:49:45 +0900 Subject: [PATCH 5/5] fix(eks-v2): use lit template tag for ValidationError codes in ServiceAccount --- packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts b/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts index afb6e098d092d..001a87f9cd514 100644 --- a/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts +++ b/packages/aws-cdk-lib/aws-eks-v2/lib/service-account.ts @@ -9,6 +9,7 @@ import { } from '../../aws-iam'; import type { RemovalPolicy } from '../../core'; import { CfnJson, Names, RemovalPolicies, ValidationError } from '../../core'; +import { lit } from '../../core/lib/private/literal-string'; // import { FargateCluster } from './index'; /** @@ -150,7 +151,7 @@ export class ServiceAccount extends Construct implements IPrincipal { return this._podIdentityRole as IRole; } throw new ValidationError( - 'ServiceAccountRole', + lit`ServiceAccountRole`, 'The provided role is not an instance of IRole. ' + 'Cannot access role grants when using an L1 construct (e.g. CfnRole) as the role.', this, @@ -203,7 +204,7 @@ export class ServiceAccount extends Construct implements IPrincipal { if (props.role !== undefined && props.identityType !== IdentityType.POD_IDENTITY) { throw new ValidationError( - 'ServiceAccountRoleOption', + lit`ServiceAccountRoleOption`, 'The `role` option is only valid when `identityType` is `IdentityType.POD_IDENTITY`.', this, );