@aws/nx-plugin 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/src/infra/app/generator.js +4 -1
  3. package/src/infra/app/generator.js.map +1 -1
  4. package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +60 -0
  5. package/src/py/fast-api/react/__snapshots__/generator.spec.ts.snap +1 -0
  6. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +310 -0
  7. package/src/py/lambda-function/generator.js +15 -25
  8. package/src/py/lambda-function/generator.js.map +1 -1
  9. package/src/py/lambda-function/schema.d.ts +1 -0
  10. package/src/py/lambda-function/schema.json +8 -0
  11. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +60 -0
  12. package/src/trpc/react/__snapshots__/generator.spec.ts.snap +1 -0
  13. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +310 -0
  14. package/src/ts/lambda-function/generator.js +15 -27
  15. package/src/ts/lambda-function/generator.js.map +1 -1
  16. package/src/ts/lambda-function/schema.d.ts +1 -0
  17. package/src/ts/lambda-function/schema.json +8 -0
  18. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +756 -0
  19. package/src/ts/react-website/app/generator.js +42 -43
  20. package/src/ts/react-website/app/generator.js.map +1 -1
  21. package/src/ts/react-website/app/schema.d.ts +1 -0
  22. package/src/ts/react-website/app/schema.json +8 -0
  23. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +259 -0
  24. package/src/ts/react-website/cognito-auth/generator.js +10 -13
  25. package/src/ts/react-website/cognito-auth/generator.js.map +1 -1
  26. package/src/ts/react-website/cognito-auth/schema.d.ts +1 -0
  27. package/src/ts/react-website/cognito-auth/schema.json +8 -0
  28. package/src/ts/react-website/runtime-config/__snapshots__/generator.spec.ts.snap +0 -40
  29. package/src/ts/react-website/runtime-config/generator.js +0 -2
  30. package/src/ts/react-website/runtime-config/generator.js.map +1 -1
  31. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +10 -0
  32. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +10 -0
  33. package/src/utils/files/terraform/src/core/runtime-config/entry/entry.tf.template +119 -0
  34. package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +28 -0
  35. package/src/{py/lambda-function/files/common/constructs/src/app/lambda-functions/__constructFunctionKebabCase__.ts.template → utils/function-constructs/files/cdk/app/lambda-functions/__functionNameKebabCase__.ts.template} +5 -5
  36. package/src/utils/function-constructs/files/terraform/app/lambda-functions/__functionNameKebabCase__/__functionNameKebabCase__.tf.template +150 -0
  37. package/src/utils/function-constructs/function-constructs.d.ts +19 -0
  38. package/src/utils/function-constructs/function-constructs.js +57 -0
  39. package/src/utils/function-constructs/function-constructs.js.map +1 -0
  40. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +123 -0
  41. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +421 -0
  42. package/src/utils/identity-constructs/files/terraform/core/user-identity/main.tf.template +47 -0
  43. package/src/utils/identity-constructs/identity-constructs.d.ts +15 -0
  44. package/src/utils/identity-constructs/identity-constructs.js +84 -0
  45. package/src/utils/identity-constructs/identity-constructs.js.map +1 -0
  46. package/src/utils/metrics.js +1 -1
  47. package/src/utils/metrics.js.map +1 -1
  48. package/src/utils/shared-constructs.js +24 -0
  49. package/src/utils/shared-constructs.js.map +1 -1
  50. package/src/utils/website-constructs/files/terraform/app/static-websites/__websiteNameKebabCase__/__websiteNameKebabCase__.tf.template +42 -0
  51. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +709 -0
  52. package/src/utils/website-constructs/website-constructs.d.ts +18 -0
  53. package/src/utils/website-constructs/website-constructs.js +61 -0
  54. package/src/utils/website-constructs/website-constructs.js.map +1 -0
  55. package/src/ts/lambda-function/files/common/constructs/src/app/lambda-functions/__constructFunctionNameKebabCase__.ts.template +0 -24
  56. /package/src/{ts/react-website/cognito-auth/files/common/constructs/src → utils/identity-constructs/files/cdk}/core/user-identity.ts.template +0 -0
  57. /package/src/{ts/react-website/app/files/common/constructs/src → utils/website-constructs/files/cdk}/app/static-websites/__websiteNameKebabCase__.ts.template +0 -0
  58. /package/src/{ts/react-website/app/files/common/constructs/src → utils/website-constructs/files/cdk}/core/static-website.ts.template +0 -0
@@ -258,6 +258,16 @@ resource "aws_apigatewayv2_route" "proxy_routes" {
258
258
  depends_on = [aws_apigatewayv2_integration.lambda_integration<% if (auth === 'Cognito') { %>, aws_apigatewayv2_authorizer.cognito_authorizer<% } %>]
259
259
  }
260
260
 
261
+ # Add API url to runtime config
262
+ module "add_url_to_runtime_config" {
263
+ source = "../../../core/runtime-config/entry"
264
+
265
+ key_path = "apis.<%- apiNameClassName %>"
266
+ value = module.http_api.stage_invoke_url
267
+
268
+ depends_on = [module.http_api]
269
+ }
270
+
261
271
  # Lambda permission for API Gateway to invoke the function
262
272
  resource "aws_lambda_permission" "api_gateway_invoke" {
263
273
  statement_id = "AllowExecutionFromAPIGateway"
@@ -385,6 +385,16 @@ resource "aws_lambda_permission" "api_gateway_invoke" {
385
385
  depends_on = [module.rest_api, aws_lambda_function.api_lambda]
386
386
  }
387
387
 
388
+ # Add API url to runtime config
389
+ module "add_url_to_runtime_config" {
390
+ source = "../../../core/runtime-config/entry"
391
+
392
+ key_path = "apis.<%- apiNameClassName %>"
393
+ value = aws_api_gateway_stage.api_stage.invoke_url
394
+
395
+ depends_on = [aws_api_gateway_stage.api_stage]
396
+ }
397
+
388
398
  # Outputs
389
399
 
390
400
  # API Gateway Outputs (from core module)
@@ -0,0 +1,119 @@
1
+ terraform {
2
+ required_providers {
3
+ local = {
4
+ source = "hashicorp/local"
5
+ version = "~> 2.0"
6
+ }
7
+ }
8
+ }
9
+
10
+ # Variables
11
+ variable "key_path" {
12
+ description = "Dot-separated path for the configuration key (e.g., 'apis.FooApi', 'cognito.userPoolId')"
13
+ type = string
14
+ }
15
+
16
+ variable "value" {
17
+ description = "Value to set at the key path"
18
+ type = any
19
+ }
20
+
21
+ locals {
22
+ config_file_path = "${path.module}/../../../../../../../dist/packages/common/terraform/runtime-config.json"
23
+ }
24
+
25
+ # This module writes an entry to runtime-config.json which is provided to any static websites
26
+ # as a way to pass deploy-time values to the UI such as API urls
27
+
28
+ data "external" "updated_config" {
29
+ program = ["uv", "run", "python", "-c", <<-EOT
30
+ import json
31
+ import sys
32
+ import os
33
+ import time
34
+ import fcntl
35
+ from pathlib import Path
36
+
37
+ # Read input from Terraform
38
+ input_data = json.load(sys.stdin)
39
+ config_file = input_data['config_file']
40
+ key_path = input_data['key_path']
41
+ value = json.loads(input_data['value'])
42
+
43
+ # Create lock file path
44
+ lock_file = config_file + '.lock'
45
+
46
+ # Ensure directory exists
47
+ Path(config_file).parent.mkdir(parents=True, exist_ok=True)
48
+
49
+ # Acquire exclusive lock with retry mechanism
50
+ max_retries = 30
51
+ retry_delay = 1.0
52
+
53
+ for attempt in range(max_retries):
54
+ try:
55
+ # Open lock file for exclusive access
56
+ with open(lock_file, 'w') as lock_fd:
57
+ # Try to acquire exclusive lock (non-blocking)
58
+ fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
59
+
60
+ # Critical section - file operations under lock
61
+ try:
62
+ # Create empty base config if file doesn't exist
63
+ if not os.path.exists(config_file):
64
+ base_config = {}
65
+ with open(config_file, 'w') as f:
66
+ json.dump(base_config, f, indent=2)
67
+
68
+ # Read current config
69
+ with open(config_file, 'r') as f:
70
+ config = json.load(f)
71
+
72
+ # Set the nested key using dot notation
73
+ keys = key_path.split('.')
74
+ current = config
75
+ for key in keys[:-1]:
76
+ if key not in current:
77
+ current[key] = {}
78
+ current = current[key]
79
+ current[keys[-1]] = value
80
+
81
+ # Write updated config back to file atomically
82
+ temp_file = config_file + '.tmp'
83
+ with open(temp_file, 'w') as f:
84
+ json.dump(config, f, indent=2)
85
+ os.rename(temp_file, config_file)
86
+
87
+ # Output the updated config for Terraform
88
+ print(json.dumps({"updated_json": json.dumps(config)}))
89
+
90
+ # Success - break out of retry loop
91
+ break
92
+
93
+ finally:
94
+ # Lock is automatically released when file is closed
95
+ pass
96
+
97
+ except (IOError, OSError) as e:
98
+ if attempt < max_retries - 1:
99
+ # Wait before retrying
100
+ time.sleep(retry_delay)
101
+ continue
102
+ else:
103
+ # Final attempt failed
104
+ raise Exception(f"Failed to acquire lock after {max_retries} attempts: {e}")
105
+
106
+ # Clean up lock file if it exists
107
+ try:
108
+ os.remove(lock_file)
109
+ except OSError:
110
+ pass
111
+ EOT
112
+ ]
113
+
114
+ query = {
115
+ config_file = local.config_file_path
116
+ key_path = var.key_path
117
+ value = jsonencode(var.value)
118
+ }
119
+ }
@@ -0,0 +1,28 @@
1
+ terraform {
2
+ required_providers {
3
+ local = {
4
+ source = "hashicorp/local"
5
+ version = "~> 2.0"
6
+ }
7
+ }
8
+ }
9
+
10
+ locals {
11
+ config_file_path = "${path.module}/../../../../../../../dist/packages/common/terraform/runtime-config.json"
12
+ }
13
+
14
+ # Read the runtime config file - will fail gracefully if file doesn't exist
15
+ data "local_file" "runtime_config" {
16
+ filename = local.config_file_path
17
+ }
18
+
19
+ # Outputs
20
+ output "config" {
21
+ description = "Runtime configuration object"
22
+ value = jsondecode(data.local_file.runtime_config.content)
23
+ }
24
+
25
+ output "config_json" {
26
+ description = "Runtime configuration as JSON string"
27
+ value = data.local_file.runtime_config.content
28
+ }
@@ -3,15 +3,15 @@ import * as url from 'url';
3
3
  import { Code, Function, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
4
4
  import { Duration } from 'aws-cdk-lib';
5
5
 
6
- export class <%= constructFunctionClassName %> extends Function {
6
+ export class <%= functionNameClassName %> extends Function {
7
7
  constructor(scope: Construct, id: string) {
8
8
  super(scope, id, {
9
9
  timeout: Duration.seconds(30),
10
- runtime: Runtime.PYTHON_3_12,
11
- handler: '<%= constructHandlerFilePath %>',
10
+ runtime: <%= runtime %>,
11
+ handler: '<%= handler %>',
12
12
  code: Code.fromAsset(url.fileURLToPath(
13
13
  new URL(
14
- '../../../../../../dist/<%= dir %>/bundle',
14
+ '../../../../../../<%- bundlePathFromRoot %>',
15
15
  import.meta.url
16
16
  )
17
17
  )),
@@ -21,4 +21,4 @@ export class <%= constructFunctionClassName %> extends Function {
21
21
  },
22
22
  });
23
23
  }
24
- }
24
+ }
@@ -0,0 +1,150 @@
1
+ variable "tags" {
2
+ description = "Tags to apply to resources"
3
+ type = map(string)
4
+ default = {}
5
+ }
6
+
7
+ variable "env" {
8
+ description = "Additional environment variables for the Lambda function"
9
+ type = map(string)
10
+ default = {}
11
+ }
12
+
13
+ variable "additional_iam_policy_statements" {
14
+ description = "Additional IAM policy statements to attach to the Lambda role"
15
+ type = list(object({
16
+ Effect = string
17
+ Action = list(string)
18
+ Resource = list(string)
19
+ }))
20
+ default = []
21
+ }
22
+
23
+ data "aws_caller_identity" "current" {}
24
+ data "aws_region" "current" {}
25
+
26
+ locals {
27
+ aws_account_id = data.aws_caller_identity.current.account_id
28
+ aws_region = data.aws_region.current.name
29
+ }
30
+
31
+ resource "random_string" "suffix" {
32
+ length = 8
33
+ special = false
34
+ upper = false
35
+ }
36
+
37
+ resource "aws_iam_role" "lambda_role" {
38
+ name = "<%- functionNameKebabCase %>-role-${random_string.suffix.result}"
39
+
40
+ assume_role_policy = jsonencode({
41
+ Version = "2012-10-17"
42
+ Statement = [
43
+ {
44
+ Sid = "LambdaAssumeRolePolicy"
45
+ Effect = "Allow"
46
+ Principal = {
47
+ Service = "lambda.amazonaws.com"
48
+ }
49
+ Action = "sts:AssumeRole"
50
+ }
51
+ ]
52
+ })
53
+
54
+ tags = var.tags
55
+ }
56
+
57
+ resource "aws_iam_policy" "lambda_policy" {
58
+ name = "<%- functionNameKebabCase %>-policy-${random_string.suffix.result}"
59
+ description = "Policy for <%- functionNameKebabCase %> Lambda function"
60
+
61
+ policy = jsonencode({
62
+ Version = "2012-10-17"
63
+ Statement = concat([
64
+ {
65
+ Sid = "CloudWatchLogsAccess"
66
+ Effect = "Allow"
67
+ Action = [
68
+ "logs:CreateLogGroup",
69
+ "logs:CreateLogStream",
70
+ "logs:PutLogEvents"
71
+ ]
72
+ Resource = [
73
+ "arn:aws:logs:${local.aws_region}:${local.aws_account_id}:log-group:/aws/lambda/<%- functionNameKebabCase %>-${random_string.suffix.result}:*"
74
+ ]
75
+ },
76
+ {
77
+ Sid = "XRayAccess"
78
+ Effect = "Allow"
79
+ Action = [
80
+ "xray:PutTraceSegments",
81
+ "xray:PutTelemetryRecords"
82
+ ]
83
+ Resource = ["*"]
84
+ }
85
+ ], var.additional_iam_policy_statements)
86
+ })
87
+
88
+ tags = var.tags
89
+ }
90
+
91
+ # Attach the policy to the role
92
+ resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
93
+ role = aws_iam_role.lambda_role.name
94
+ policy_arn = aws_iam_policy.lambda_policy.arn
95
+ }
96
+
97
+ data "archive_file" "lambda_zip" {
98
+ type = "zip"
99
+ source_dir = "${path.module}/../../../../../../../<%- bundlePathFromRoot %>"
100
+ output_path = "${path.module}/../../../../../../../dist/packages/common/terraform/lambda-functions/<%- functionNameKebabCase %>/lambda.zip"
101
+ }
102
+
103
+ resource "aws_lambda_function" "lambda_function" {
104
+ #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
105
+ #checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple use case
106
+ #checkov:skip=CKV_AWS_272:Code signing not required for this use case
107
+ #checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
108
+ #checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
109
+ filename = data.archive_file.lambda_zip.output_path
110
+ function_name = "<%- functionNameKebabCase %>-${random_string.suffix.result}"
111
+ role = aws_iam_role.lambda_role.arn
112
+ handler = "<%- handler %>"
113
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
114
+ runtime = "<%- runtime %>"
115
+ timeout = 30
116
+
117
+ tracing_config {
118
+ mode = "Active"
119
+ }
120
+
121
+ environment {
122
+ variables = merge({
123
+ AWS_CONNECTION_REUSE_ENABLED = "1"
124
+ }, var.env)
125
+ }
126
+
127
+ tags = var.tags
128
+
129
+ depends_on = [aws_iam_role_policy_attachment.lambda_policy_attachment]
130
+ }
131
+
132
+ output "function_name" {
133
+ description = "Name of the Lambda function"
134
+ value = aws_lambda_function.lambda_function.function_name
135
+ }
136
+
137
+ output "function_arn" {
138
+ description = "ARN of the Lambda function"
139
+ value = aws_lambda_function.lambda_function.arn
140
+ }
141
+
142
+ output "role_arn" {
143
+ description = "ARN of the Lambda execution role"
144
+ value = aws_iam_role.lambda_role.arn
145
+ }
146
+
147
+ output "role_name" {
148
+ description = "Name of the Lambda execution role"
149
+ value = aws_iam_role.lambda_role.name
150
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { Tree } from '@nx/devkit';
6
+ export interface AddLambdaFunctionConstructOptions {
7
+ functionProjectName: string;
8
+ functionNameClassName: string;
9
+ functionNameKebabCase: string;
10
+ bundlePathFromRoot: string;
11
+ handler: string;
12
+ runtime: 'node' | 'python';
13
+ }
14
+ /**
15
+ * Add infrastructure for a lambda function
16
+ */
17
+ export declare const addLambdaFunctionInfra: (tree: Tree, options: AddLambdaFunctionConstructOptions & {
18
+ iacProvider: "CDK" | "Terraform";
19
+ }) => void;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addLambdaFunctionInfra = void 0;
4
+ /**
5
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ const devkit_1 = require("@nx/devkit");
9
+ const shared_constructs_constants_1 = require("../shared-constructs-constants");
10
+ const ast_1 = require("../ast");
11
+ /**
12
+ * Add infrastructure for a lambda function
13
+ */
14
+ const addLambdaFunctionInfra = (tree, options) => {
15
+ if (options.iacProvider === 'CDK') {
16
+ addLambdaFunctionCdkConstructs(tree, options);
17
+ }
18
+ else if (options.iacProvider === 'Terraform') {
19
+ addLambdaFunctionTerraformModules(tree, options);
20
+ }
21
+ else {
22
+ throw new Error(`Unsupported iacProvider ${options.iacProvider}`);
23
+ }
24
+ (0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(shared_constructs_constants_1.PACKAGES_DIR, options.iacProvider === 'CDK'
25
+ ? shared_constructs_constants_1.SHARED_CONSTRUCTS_DIR
26
+ : shared_constructs_constants_1.SHARED_TERRAFORM_DIR, 'project.json'), (config) => {
27
+ var _a;
28
+ if (!config.targets) {
29
+ config.targets = {};
30
+ }
31
+ if (!config.targets.build) {
32
+ config.targets.build = {};
33
+ }
34
+ config.targets.build.dependsOn = [
35
+ ...((_a = config.targets.build.dependsOn) !== null && _a !== void 0 ? _a : []),
36
+ `${options.functionProjectName}:build`,
37
+ ];
38
+ return config;
39
+ });
40
+ };
41
+ exports.addLambdaFunctionInfra = addLambdaFunctionInfra;
42
+ const addLambdaFunctionCdkConstructs = (tree, options) => {
43
+ // Generate app specific CDK construct
44
+ (0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, 'files', 'cdk', 'app', 'lambda-functions'), (0, devkit_1.joinPathFragments)(shared_constructs_constants_1.PACKAGES_DIR, shared_constructs_constants_1.SHARED_CONSTRUCTS_DIR, 'src', 'app', 'lambda-functions'), Object.assign(Object.assign({}, options), { runtime: `Runtime.${options.runtime === 'python' ? 'PYTHON_3_12' : 'NODEJS_LATEST'}` }), {
45
+ overwriteStrategy: devkit_1.OverwriteStrategy.KeepExisting,
46
+ });
47
+ // Export app specific CDK construct
48
+ (0, ast_1.addStarExport)(tree, (0, devkit_1.joinPathFragments)(shared_constructs_constants_1.PACKAGES_DIR, shared_constructs_constants_1.SHARED_CONSTRUCTS_DIR, 'src', 'app', 'lambda-functions', 'index.ts'), `./${options.functionNameKebabCase}.js`);
49
+ (0, ast_1.addStarExport)(tree, (0, devkit_1.joinPathFragments)(shared_constructs_constants_1.PACKAGES_DIR, shared_constructs_constants_1.SHARED_CONSTRUCTS_DIR, 'src', 'app', 'index.ts'), './lambda-functions/index.js');
50
+ };
51
+ const addLambdaFunctionTerraformModules = (tree, options) => {
52
+ // Generate app specific terraform module
53
+ (0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, 'files', 'terraform', 'app', 'lambda-functions'), (0, devkit_1.joinPathFragments)(shared_constructs_constants_1.PACKAGES_DIR, shared_constructs_constants_1.SHARED_TERRAFORM_DIR, 'src', 'app', 'lambda-functions'), Object.assign(Object.assign({}, options), { runtime: options.runtime === 'python' ? 'python3.12' : 'nodejs22.x' }), {
54
+ overwriteStrategy: devkit_1.OverwriteStrategy.KeepExisting,
55
+ });
56
+ };
57
+ //# sourceMappingURL=function-constructs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-constructs.js","sourceRoot":"","sources":["../../../../../../packages/nx-plugin/src/utils/function-constructs/function-constructs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ terraform {
2
+ required_providers {
3
+ aws = {
4
+ source = "hashicorp/aws"
5
+ version = "~> 6.0"
6
+ }
7
+ }
8
+ }
9
+
10
+ # Variables
11
+ variable "callback_url" {
12
+ description = "Callback URL to add (e.g., https://d123456789.cloudfront.net)"
13
+ type = string
14
+ }
15
+
16
+ # Read runtime config to get user pool client details
17
+ module "runtime_config_reader" {
18
+ source = "../../runtime-config/read"
19
+ }
20
+
21
+ # Extract cognito details from runtime config
22
+ locals {
23
+ cognito_props = try(module.runtime_config_reader.config.cognitoProps, null)
24
+
25
+ user_pool_id = local.cognito_props != null ? local.cognito_props.userPoolId : null
26
+ user_pool_client_id = local.cognito_props != null ? local.cognito_props.userPoolWebClientId : null
27
+ }
28
+
29
+ # Validation: Ensure cognito props exist in runtime config
30
+ resource "terraform_data" "validate_cognito_props" {
31
+ lifecycle {
32
+ precondition {
33
+ condition = local.cognito_props != null
34
+ error_message = "ERROR: cognitoProps not found in runtime config. Ensure user-identity module has been deployed first and has added cognitoProps to the runtime configuration."
35
+ }
36
+
37
+ precondition {
38
+ condition = local.user_pool_id != null && local.user_pool_id != ""
39
+ error_message = "ERROR: cognitoProps.userPoolId is missing or empty in runtime config. Check that user-identity module completed successfully."
40
+ }
41
+
42
+ precondition {
43
+ condition = local.user_pool_client_id != null && local.user_pool_client_id != ""
44
+ error_message = "ERROR: cognitoProps.userPoolWebClientId is missing or empty in runtime config. Check that user-identity module completed successfully."
45
+ }
46
+ }
47
+ }
48
+
49
+
50
+ # Update the user pool client with additional callback URL
51
+ resource "null_resource" "add_callback_url" {
52
+ triggers = {
53
+ callback_url = var.callback_url
54
+ user_pool_id = local.user_pool_id
55
+ user_pool_client_id = local.user_pool_client_id
56
+ }
57
+
58
+ provisioner "local-exec" {
59
+ command = <<-EOT
60
+ uv run --with boto3 python -c "
61
+ import boto3
62
+ import sys
63
+
64
+ # Configuration
65
+ user_pool_id = '${local.user_pool_id}'
66
+ client_id = '${local.user_pool_client_id}'
67
+ new_callback_url = '${var.callback_url}'
68
+
69
+ # Initialize Cognito client
70
+ cognito = boto3.client('cognito-idp')
71
+
72
+ try:
73
+ # Get current user pool client configuration
74
+ response = cognito.describe_user_pool_client(
75
+ UserPoolId=user_pool_id,
76
+ ClientId=client_id
77
+ )
78
+
79
+ client_config = response['UserPoolClient']
80
+ current_callback_urls = client_config.get('CallbackURLs', [])
81
+ current_logout_urls = client_config.get('LogoutURLs', [])
82
+
83
+ # Check if URL already exists
84
+ if new_callback_url in current_callback_urls:
85
+ print(f'Callback URL {new_callback_url} already exists')
86
+ else:
87
+ # Add new URL to both callback and logout URLs
88
+ updated_callback_urls = current_callback_urls + [new_callback_url]
89
+ updated_logout_urls = current_logout_urls + [new_callback_url]
90
+
91
+ # Update the user pool client
92
+ # Only include valid update parameters (exclude read-only fields and ones we're setting)
93
+ valid_update_params = [
94
+ 'ClientName', 'RefreshTokenValidity', 'AccessTokenValidity', 'IdTokenValidity',
95
+ 'TokenValidityUnits', 'ReadAttributes', 'WriteAttributes', 'ExplicitAuthFlows',
96
+ 'SupportedIdentityProviders', 'DefaultRedirectURI', 'AllowedOAuthFlows',
97
+ 'AllowedOAuthScopes', 'AllowedOAuthFlowsUserPoolClient', 'AnalyticsConfiguration',
98
+ 'PreventUserExistenceErrors', 'EnableTokenRevocation',
99
+ 'EnablePropagateAdditionalUserContextData', 'AuthSessionValidity', 'RefreshTokenRotation'
100
+ ]
101
+
102
+ update_config = {k: v for k, v in client_config.items() if k in valid_update_params}
103
+
104
+ cognito.update_user_pool_client(
105
+ UserPoolId=user_pool_id,
106
+ ClientId=client_id,
107
+ CallbackURLs=updated_callback_urls,
108
+ LogoutURLs=updated_logout_urls,
109
+ **update_config
110
+ )
111
+
112
+ print(f'Successfully added callback URL: {new_callback_url}')
113
+
114
+ except Exception as e:
115
+ print(f'Error updating callback URLs: {e}')
116
+ sys.exit(1)
117
+ "
118
+ EOT
119
+ }
120
+
121
+ depends_on = [terraform_data.validate_cognito_props]
122
+ }
123
+