"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.constructAuthFilterStatement = exports.constructFieldMappingInput = exports.constructArrayFieldsStatement = exports.constructNonScalarFieldsStatement = exports.getArrayFields = exports.getNonScalarFields = exports.generateDefaultLambdaResponseMappingTemplate = exports.generateGetLambdaResponseTemplate = exports.generateLambdaRequestTemplate = exports.createRdsPatchingLambdaRole = exports.createRdsLambdaRole = exports.createRdsPatchingLambda = exports.createLayerVersionCustomResource = exports.createRdsLambda = exports.setRDSLayerMappings = exports.CredentialStorageMethod = void 0;
const path_1 = __importDefault(require("path"));
const aws_cdk_lib_1 = require("aws-cdk-lib");
const graphql_mapping_template_1 = require("graphql-mapping-template");
const graphql_transformer_common_1 = require("graphql-transformer-common");
const graphql_transformer_core_1 = require("@aws-amplify/graphql-transformer-core");
const graphql_transformer_interfaces_1 = require("@aws-amplify/graphql-transformer-interfaces");
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
const graphql_1 = require("graphql");
const aws_ec2_1 = require("aws-cdk-lib/aws-ec2");
const custom_resources_1 = require("aws-cdk-lib/custom-resources");
var CredentialStorageMethod;
(function (CredentialStorageMethod) {
    CredentialStorageMethod["SSM"] = "SSM";
    CredentialStorageMethod["SECRETS_MANAGER"] = "SECRETS_MANAGER";
})(CredentialStorageMethod = exports.CredentialStorageMethod || (exports.CredentialStorageMethod = {}));
const OPERATION_KEY = '__operation';
const setRDSLayerMappings = (scope, mapping, resourceNames) => new aws_cdk_lib_1.CfnMapping(scope, resourceNames.sqlLayerVersionMapping, {
    mapping,
});
exports.setRDSLayerMappings = setRDSLayerMappings;
const createRdsLambda = (scope, apiGraphql, lambdaRole, layerVersionArn, resourceNames, credentialStorageMethod, environment, sqlLambdaVpcConfig, sqlLambdaProvisionedConcurrencyConfig) => {
    var _a, _b;
    const lambdaEnvironment = {
        ...environment,
    };
    if (credentialStorageMethod === CredentialStorageMethod.SSM) {
        let ssmEndpoint = aws_cdk_lib_1.Fn.join('', ['ssm.', aws_cdk_lib_1.Fn.ref('AWS::Region'), '.amazonaws.com']);
        if (sqlLambdaVpcConfig) {
            const services = ['ssm', 'ssmmessages', 'ec2', 'ec2messages', 'kms'];
            const endpoints = addVpcEndpoints(scope, sqlLambdaVpcConfig, resourceNames, services);
            const endpointEntries = (_a = endpoints.find((endpoint) => endpoint.service === 'ssm')) === null || _a === void 0 ? void 0 : _a.endpoint.attrDnsEntries;
            if (endpointEntries) {
                ssmEndpoint = aws_cdk_lib_1.Fn.select(0, endpointEntries);
            }
        }
        lambdaEnvironment.SSM_ENDPOINT = ssmEndpoint;
        lambdaEnvironment.CREDENTIAL_STORAGE_METHOD = CredentialStorageMethod.SSM;
    }
    else if (credentialStorageMethod === CredentialStorageMethod.SECRETS_MANAGER) {
        let secretsManagerEndpoint = aws_cdk_lib_1.Fn.join('', ['secretsmanager.', aws_cdk_lib_1.Fn.ref('AWS::Region'), '.amazonaws.com']);
        if (sqlLambdaVpcConfig) {
            const services = ['secretsmanager'];
            const endpoints = addVpcEndpoints(scope, sqlLambdaVpcConfig, resourceNames, services);
            const endpointEntries = (_b = endpoints.find((endpoint) => endpoint.service === 'secretsmanager')) === null || _b === void 0 ? void 0 : _b.endpoint.attrDnsEntries;
            if (endpointEntries) {
                secretsManagerEndpoint = aws_cdk_lib_1.Fn.select(0, endpointEntries);
            }
        }
        lambdaEnvironment.SECRETS_MANAGER_ENDPOINT = secretsManagerEndpoint;
        lambdaEnvironment.CREDENTIAL_STORAGE_METHOD = CredentialStorageMethod.SECRETS_MANAGER;
    }
    else {
        throw new Error('Unable to determine if SSM or Secrets Manager should be used for credentials.');
    }
    const fn = apiGraphql.host.addLambdaFunction(resourceNames.sqlLambdaFunction, `functions/${resourceNames.sqlLambdaFunction}.zip`, 'handler.run', path_1.default.resolve(__dirname, '..', '..', '..', 'lib', 'rds-lambda.zip'), aws_lambda_1.Runtime.NODEJS_18_X, [aws_lambda_1.LayerVersion.fromLayerVersionArn(scope, resourceNames.sqlLambdaLayerVersion, layerVersionArn)], lambdaRole, lambdaEnvironment, aws_cdk_lib_1.Duration.seconds(30), scope, sqlLambdaVpcConfig);
    if (sqlLambdaProvisionedConcurrencyConfig) {
        const { provisionedConcurrentExecutions } = sqlLambdaProvisionedConcurrencyConfig;
        const alias = new aws_lambda_1.Alias(scope, resourceNames.sqlLambdaAliasLogicalId, {
            aliasName: resourceNames.sqlLambdaAliasName,
            version: fn.currentVersion,
            provisionedConcurrentExecutions,
        });
        (0, graphql_transformer_core_1.setResourceName)(alias, { name: resourceNames.sqlLambdaAliasLogicalId, setOnDefaultChild: true });
        return alias;
    }
    return fn;
};
exports.createRdsLambda = createRdsLambda;
const createLayerVersionCustomResource = (scope, resourceNames) => {
    const { SQLLayerVersionManifestBucket, SQLLayerVersionManifestBucketRegion, SQLLayerVersionManifestKeyPrefix } = graphql_transformer_common_1.ResourceConstants.RESOURCES;
    const key = aws_cdk_lib_1.Fn.join('', [SQLLayerVersionManifestKeyPrefix, aws_cdk_lib_1.Fn.ref('AWS::Region')]);
    const manifestArn = `arn:aws:s3:::${SQLLayerVersionManifestBucket}/${key}`;
    const resourceName = resourceNames.sqlLayerVersionResolverCustomResource;
    const customResource = new custom_resources_1.AwsCustomResource(scope, resourceName, {
        resourceType: 'Custom::SQLLayerVersionCustomResource',
        onUpdate: {
            service: 'S3',
            action: 'getObject',
            region: SQLLayerVersionManifestBucketRegion,
            parameters: {
                Bucket: SQLLayerVersionManifestBucket,
                Key: key,
            },
            physicalResourceId: custom_resources_1.PhysicalResourceId.of(`${resourceName}-${Date.now().toString()}`),
        },
        policy: custom_resources_1.AwsCustomResourcePolicy.fromSdkCalls({
            resources: [manifestArn],
        }),
    });
    return customResource;
};
exports.createLayerVersionCustomResource = createLayerVersionCustomResource;
const addVpcEndpoint = (scope, sqlLambdaVpcConfig, serviceSuffix, resourceNames) => {
    const serviceEndpointPrefix = 'com.amazonaws';
    const endpoint = new aws_ec2_1.CfnVPCEndpoint(scope, `${resourceNames.sqlVpcEndpointPrefix}${serviceSuffix}`, {
        serviceName: aws_cdk_lib_1.Fn.join('', [serviceEndpointPrefix, '.', aws_cdk_lib_1.Fn.ref('AWS::Region'), '.', serviceSuffix]),
        vpcEndpointType: 'Interface',
        vpcId: sqlLambdaVpcConfig.vpcId,
        subnetIds: extractSubnetForVpcEndpoint(sqlLambdaVpcConfig.subnetAvailabilityZoneConfig),
        securityGroupIds: sqlLambdaVpcConfig.securityGroupIds,
        privateDnsEnabled: false,
    });
    (0, graphql_transformer_core_1.setResourceName)(endpoint, { name: endpoint.logicalId, setOnDefaultChild: true });
    return endpoint;
};
const addVpcEndpoints = (scope, sqlLambdaVpcConfig, resourceNames, services) => {
    return services.map((service) => {
        return {
            service,
            endpoint: addVpcEndpoint(scope, sqlLambdaVpcConfig, service, resourceNames),
        };
    });
};
const extractSubnetForVpcEndpoint = (avaliabilityZoneMappings) => {
    const avaliabilityZones = [];
    const result = [];
    for (const subnet of avaliabilityZoneMappings) {
        if (!avaliabilityZones.includes(subnet.availabilityZone)) {
            avaliabilityZones.push(subnet.availabilityZone);
            result.push(subnet.subnetId);
        }
    }
    return result;
};
const createRdsPatchingLambda = (scope, apiGraphql, lambdaRole, resourceNames, environment, sqlLambdaVpcConfig) => {
    return apiGraphql.host.addLambdaFunction(resourceNames.sqlPatchingLambdaFunction, `functions/${resourceNames.sqlPatchingLambdaFunction}.zip`, 'index.handler', path_1.default.resolve(__dirname, '..', '..', '..', 'lib', 'rds-patching-lambda.zip'), aws_lambda_1.Runtime.NODEJS_18_X, [], lambdaRole, environment, aws_cdk_lib_1.Duration.minutes(6), scope, sqlLambdaVpcConfig);
};
exports.createRdsPatchingLambda = createRdsPatchingLambda;
const createRdsLambdaRole = (roleName, scope, secretEntry, resourceNames) => {
    const role = new aws_iam_1.Role(scope, resourceNames.sqlLambdaExecutionRole, {
        assumedBy: new aws_iam_1.ServicePrincipal('lambda.amazonaws.com'),
        roleName,
    });
    (0, graphql_transformer_core_1.setResourceName)(role, { name: resourceNames.sqlLambdaExecutionRole, setOnDefaultChild: true });
    const policyStatements = [
        new aws_iam_1.PolicyStatement({
            actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
            effect: aws_iam_1.Effect.ALLOW,
            resources: ['arn:aws:logs:*:*:*'],
        }),
    ];
    if (secretEntry) {
        if ((0, graphql_transformer_interfaces_1.isSqlModelDataSourceSsmDbConnectionConfig)(secretEntry)) {
            policyStatements.push(new aws_iam_1.PolicyStatement({
                actions: ['ssm:GetParameter', 'ssm:GetParameters'],
                effect: aws_iam_1.Effect.ALLOW,
                resources: [
                    `arn:aws:ssm:*:*:parameter${secretEntry.usernameSsmPath}`,
                    `arn:aws:ssm:*:*:parameter${secretEntry.passwordSsmPath}`,
                    `arn:aws:ssm:*:*:parameter${secretEntry.hostnameSsmPath}`,
                    `arn:aws:ssm:*:*:parameter${secretEntry.databaseNameSsmPath}`,
                    `arn:aws:ssm:*:*:parameter${secretEntry.portSsmPath}`,
                ],
            }));
        }
        else if ((0, graphql_transformer_interfaces_1.isSqlModelDataSourceSecretsManagerDbConnectionConfig)(secretEntry)) {
            policyStatements.push(new aws_iam_1.PolicyStatement({
                actions: ['secretsmanager:GetSecretValue'],
                effect: aws_iam_1.Effect.ALLOW,
                resources: [secretEntry.secretArn],
            }));
            if (secretEntry.keyArn) {
                policyStatements.push(new aws_iam_1.PolicyStatement({
                    actions: ['kms:Decrypt'],
                    effect: aws_iam_1.Effect.ALLOW,
                    resources: [secretEntry.keyArn],
                }));
            }
        }
        else {
            throw new Error('Unable to determine if SSM or Secrets Manager should be used for credentials.');
        }
    }
    role.attachInlinePolicy(new aws_iam_1.Policy(scope, resourceNames.sqlLambdaExecutionRolePolicy, {
        statements: policyStatements,
        policyName: `${roleName}Policy`,
    }));
    role.addToPolicy(new aws_iam_1.PolicyStatement({
        effect: aws_iam_1.Effect.ALLOW,
        resources: ['*'],
        actions: ['ec2:CreateNetworkInterface', 'ec2:DescribeNetworkInterfaces', 'ec2:DeleteNetworkInterface'],
    }));
    return role;
};
exports.createRdsLambdaRole = createRdsLambdaRole;
const createRdsPatchingLambdaRole = (roleName, scope, functionArn, resourceNames) => {
    const role = new aws_iam_1.Role(scope, resourceNames.sqlPatchingLambdaExecutionRole, {
        assumedBy: new aws_iam_1.ServicePrincipal('lambda.amazonaws.com'),
        roleName,
    });
    (0, graphql_transformer_core_1.setResourceName)(role, { name: resourceNames.sqlPatchingLambdaExecutionRole, setOnDefaultChild: true });
    const policyStatements = [
        new aws_iam_1.PolicyStatement({
            actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
            effect: aws_iam_1.Effect.ALLOW,
            resources: ['arn:aws:logs:*:*:*'],
        }),
        new aws_iam_1.PolicyStatement({
            actions: ['lambda:UpdateFunctionConfiguration'],
            effect: aws_iam_1.Effect.ALLOW,
            resources: [functionArn],
        }),
        new aws_iam_1.PolicyStatement({
            actions: ['lambda:GetLayerVersion', 'lambda:GetLayerVersionPolicy'],
            effect: aws_iam_1.Effect.ALLOW,
            resources: ['*'],
        }),
    ];
    role.attachInlinePolicy(new aws_iam_1.Policy(scope, resourceNames.sqlPatchingLambdaExecutionRolePolicy, {
        statements: policyStatements,
        policyName: `${roleName}Policy`,
    }));
    role.addToPolicy(new aws_iam_1.PolicyStatement({
        effect: aws_iam_1.Effect.ALLOW,
        resources: ['*'],
        actions: ['ec2:CreateNetworkInterface', 'ec2:DescribeNetworkInterfaces', 'ec2:DeleteNetworkInterface'],
    }));
    return role;
};
exports.createRdsPatchingLambdaRole = createRdsPatchingLambdaRole;
const generateLambdaRequestTemplate = (tableName, operation, operationName, ctx) => {
    const mappedTableName = ctx.resourceHelper.getModelNameMapping(tableName);
    return (0, graphql_mapping_template_1.printBlock)('Invoke RDS Lambda data source')((0, graphql_mapping_template_1.compoundExpression)([
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput'), (0, graphql_mapping_template_1.obj)({})),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args'), (0, graphql_mapping_template_1.obj)({})),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.table'), (0, graphql_mapping_template_1.str)(mappedTableName)),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.operation'), (0, graphql_mapping_template_1.str)(operation)),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.operationName'), (0, graphql_mapping_template_1.str)(operationName)),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata'), (0, graphql_mapping_template_1.obj)({})),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata.keys'), (0, graphql_mapping_template_1.list)([])),
        (0, exports.constructAuthFilterStatement)('lambdaInput.args.metadata.authFilter'),
        (0, exports.constructNonScalarFieldsStatement)(tableName, ctx),
        (0, exports.constructArrayFieldsStatement)(tableName, ctx),
        (0, exports.constructFieldMappingInput)(),
        (0, graphql_mapping_template_1.qref)((0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata.keys.addAll'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.defaultIfNull'), (0, graphql_mapping_template_1.ref)('ctx.stash.keys'), (0, graphql_mapping_template_1.list)([])))),
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args.input'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.defaultIfNull'), (0, graphql_mapping_template_1.ref)('ctx.stash.defaultValues'), (0, graphql_mapping_template_1.obj)({}))),
        (0, graphql_mapping_template_1.qref)((0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('lambdaInput.args.input.putAll'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.defaultIfNull'), (0, graphql_mapping_template_1.ref)('context.arguments'), (0, graphql_mapping_template_1.obj)({})))),
        (0, graphql_mapping_template_1.obj)({
            version: (0, graphql_mapping_template_1.str)('2018-05-29'),
            operation: (0, graphql_mapping_template_1.str)('Invoke'),
            payload: (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.toJson'), (0, graphql_mapping_template_1.ref)('lambdaInput')),
        }),
    ]));
};
exports.generateLambdaRequestTemplate = generateLambdaRequestTemplate;
const generateGetLambdaResponseTemplate = (isSyncEnabled) => {
    const statements = [];
    if (isSyncEnabled) {
        statements.push((0, graphql_mapping_template_1.ifElse)((0, graphql_mapping_template_1.ref)('ctx.error'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.error'), (0, graphql_mapping_template_1.ref)('ctx.error.message'), (0, graphql_mapping_template_1.ref)('ctx.error.type'), (0, graphql_mapping_template_1.ref)('ctx.result')), (0, graphql_mapping_template_1.toJson)((0, graphql_mapping_template_1.ref)('ctx.result'))));
    }
    else {
        statements.push((0, graphql_mapping_template_1.ifElse)((0, graphql_mapping_template_1.ref)('ctx.error'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.error'), (0, graphql_mapping_template_1.ref)('ctx.error.message'), (0, graphql_mapping_template_1.ref)('ctx.error.type')), (0, graphql_mapping_template_1.toJson)((0, graphql_mapping_template_1.ref)('ctx.result'))));
    }
    return (0, graphql_mapping_template_1.printBlock)('ResponseTemplate')((0, graphql_mapping_template_1.compoundExpression)(statements));
};
exports.generateGetLambdaResponseTemplate = generateGetLambdaResponseTemplate;
const generateDefaultLambdaResponseMappingTemplate = (isSyncEnabled, mutation = false) => {
    const statements = [];
    if (mutation)
        statements.push((0, graphql_mapping_template_1.qref)((0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('ctx.result.put'), (0, graphql_mapping_template_1.str)(OPERATION_KEY), (0, graphql_mapping_template_1.str)('Mutation'))));
    if (isSyncEnabled) {
        statements.push((0, graphql_mapping_template_1.ifElse)((0, graphql_mapping_template_1.ref)('ctx.error'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.error'), (0, graphql_mapping_template_1.ref)('ctx.error.message'), (0, graphql_mapping_template_1.ref)('ctx.error.type'), (0, graphql_mapping_template_1.ref)('ctx.result')), (0, graphql_mapping_template_1.toJson)((0, graphql_mapping_template_1.ref)('ctx.result'))));
    }
    else {
        statements.push((0, graphql_mapping_template_1.ifElse)((0, graphql_mapping_template_1.ref)('ctx.error'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.error'), (0, graphql_mapping_template_1.ref)('ctx.error.message'), (0, graphql_mapping_template_1.ref)('ctx.error.type')), (0, graphql_mapping_template_1.toJson)((0, graphql_mapping_template_1.ref)('ctx.result'))));
    }
    return (0, graphql_mapping_template_1.printBlock)('ResponseTemplate')((0, graphql_mapping_template_1.compoundExpression)(statements));
};
exports.generateDefaultLambdaResponseMappingTemplate = generateDefaultLambdaResponseMappingTemplate;
const getNonScalarFields = (object, ctx) => {
    var _a;
    if (!object) {
        return [];
    }
    const enums = ctx.output.getTypeDefinitionsOfKind(graphql_1.Kind.ENUM_TYPE_DEFINITION);
    return ((_a = object.fields) === null || _a === void 0 ? void 0 : _a.filter((f) => (0, graphql_transformer_common_1.isArrayOrObject)(f.type, enums)).map((f) => f.name.value)) || [];
};
exports.getNonScalarFields = getNonScalarFields;
const getArrayFields = (object, ctx) => {
    var _a;
    if (!object) {
        return [];
    }
    return ((_a = object.fields) === null || _a === void 0 ? void 0 : _a.filter((f) => (0, graphql_transformer_common_1.isListType)(f.type)).map((f) => f.name.value)) || [];
};
exports.getArrayFields = getArrayFields;
const constructNonScalarFieldsStatement = (tableName, ctx) => (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata.nonScalarFields'), (0, graphql_mapping_template_1.list)((0, exports.getNonScalarFields)(ctx.output.getObject(tableName), ctx).map(graphql_mapping_template_1.str)));
exports.constructNonScalarFieldsStatement = constructNonScalarFieldsStatement;
const constructArrayFieldsStatement = (tableName, ctx) => (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata.arrayFields'), (0, graphql_mapping_template_1.list)((0, exports.getArrayFields)(ctx.output.getObject(tableName), ctx).map(graphql_mapping_template_1.str)));
exports.constructArrayFieldsStatement = constructArrayFieldsStatement;
const constructFieldMappingInput = () => {
    return (0, graphql_mapping_template_1.compoundExpression)([
        (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata.fieldMap'), (0, graphql_mapping_template_1.obj)({})),
        (0, graphql_mapping_template_1.qref)((0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('lambdaInput.args.metadata.fieldMap.putAll'), (0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.defaultIfNull'), (0, graphql_mapping_template_1.ref)('context.stash.fieldMap'), (0, graphql_mapping_template_1.obj)({})))),
    ]);
};
exports.constructFieldMappingInput = constructFieldMappingInput;
const constructAuthFilterStatement = (keyName) => (0, graphql_mapping_template_1.iff)((0, graphql_mapping_template_1.not)((0, graphql_mapping_template_1.methodCall)((0, graphql_mapping_template_1.ref)('util.isNullOrEmpty'), (0, graphql_mapping_template_1.ref)('ctx.stash.authFilter'))), (0, graphql_mapping_template_1.set)((0, graphql_mapping_template_1.ref)(keyName), (0, graphql_mapping_template_1.ref)('ctx.stash.authFilter')));
exports.constructAuthFilterStatement = constructAuthFilterStatement;
//# sourceMappingURL=resolver.js.map