UNPKG

@aws-amplify/cli-internal

Version:
248 lines • 12.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GeoResourceGenerator = void 0; const path = __importStar(require("path")); const fs = __importStar(require("fs/promises")); const cdk_from_cfn = __importStar(require("cdk-from-cfn")); const cfn_condition_resolver_1 = require("../../../refactor/resolvers/cfn-condition-resolver"); const client_cloudformation_1 = require("@aws-sdk/client-cloudformation"); const amplify_cli_core_1 = require("@aws-amplify/amplify-cli-core"); const ts_1 = require("../../ts"); const geo_resource_renderer_1 = require("./geo-resource.renderer"); class GeoResourceGenerator { constructor(gen1App, outputDir, resource, geoGenerator, logger) { this.renderer = new geo_resource_renderer_1.GeoResourceRenderer(); this.gen1App = gen1App; this.outputDir = outputDir; this.resource = resource; this.geoGenerator = geoGenerator; this.logger = logger; } async plan() { const resourceName = this.resource.resourceName; const resourceMeta = this.gen1App.resourceMeta(this.resource); const geoDir = path.join(this.outputDir, 'amplify', 'geo'); return [ { resource: this.resource, validate: () => undefined, describe: async () => [`Generate amplify/geo/${resourceName}/resource.ts`], execute: async () => { const { props, parameters } = await this.generateBase(resourceMeta.providerMetadata.logicalId); const resource = this.addResource(props, parameters); this.logger.info(`Rendering geo/${resourceName}/resource.ts`); const nodes = this.renderer.render(resource); const content = ts_1.TS.printNodes(nodes); const resourceDir = path.join(geoDir, resourceName); await fs.mkdir(resourceDir, { recursive: true }); await fs.writeFile(path.join(resourceDir, 'resource.ts'), content, 'utf-8'); }, }, ]; } async generateBase(nestedStackLogicalId) { var _a; const constructFileName = `${this.resource.resourceName}-construct`; const filePath = path.join(this.outputDir, 'amplify', 'geo', this.resource.resourceName, `${constructFileName}.ts`); const template = this.gen1App.json(`geo/${this.resource.resourceName}/${this.resource.resourceName}-cloudformation-template.json`); this.logger.debug(`Fetching nested stack parameters for geo resource '${this.resource.resourceName}'`); const parameters = await this.getNestedStackParameters(nestedStackLogicalId); const finalTemplate = await this.preTransmute(template, nestedStackLogicalId); const tsFile = cdk_from_cfn.transmute(JSON.stringify(finalTemplate), 'typescript', nestedStackLogicalId, 'construct'); const fixedTsFile = this.postTransmute(tsFile); await fs.mkdir(path.dirname(filePath), { recursive: true }); const prettier = await Promise.resolve().then(() => __importStar(require('prettier'))); const formatted = prettier.format(fixedTsFile, { parser: 'typescript', singleQuote: true, tabWidth: 2, printWidth: 80, }); const fileWriter = async (content, fp) => { await fs.mkdir(path.dirname(fp), { recursive: true }); await fs.writeFile(fp, content, 'utf-8'); }; await fileWriter(formatted, filePath); const classNameMatch = fixedTsFile.match(/export class (\w+) extends/); if (!classNameMatch) { throw new amplify_cli_core_1.AmplifyFault('RegexMatchingFault', { message: `Failed to extract class name from generated construct for geo resource: ${this.resource.resourceName}`, }); } const constructClassName = classNameMatch[1]; const paramMap = new Map(parameters .filter((p) => p.ParameterKey !== undefined && p.ParameterValue !== undefined) .map((p) => [p.ParameterKey, p.ParameterValue])); let userPoolIdParamName = ''; const groupRoles = []; for (const [key] of paramMap) { if (key.startsWith('auth') && key.endsWith('UserPoolId')) { userPoolIdParamName = key; } else if (key.startsWith('authuserPoolGroups') && key.endsWith('GroupRole')) { const groupName = key.slice('authuserPoolGroups'.length, -'GroupRole'.length); groupRoles.push({ paramName: key, groupName }); } } const gen1ResourceName = `${this.resource.resourceName}-${this.gen1App.envName}`; const gen1Actions = extractGen1Actions(template); const base = { constructClassName, constructFileName, resourceName: this.resource.resourceName, userPoolIdParamName, groupRoles, isDefault: (_a = paramMap.get('isDefault')) !== null && _a !== void 0 ? _a : 'false', needsAuthAndUnauthRoles: false, serviceSpecificProps: [], gen1ResourceName, gen1Actions, serviceName: 'Map', }; return { props: base, parameters: paramMap }; } async getNestedStackPhysicalName(logicalId) { const rootStackName = this.gen1App.rootStackName; if (!rootStackName) return undefined; return this.gen1App.aws.findResourcePhysicalId(rootStackName, logicalId); } async getNestedStackParameters(logicalId) { var _a, _b, _c; const cfnClient = this.gen1App.clients.cloudFormation; const rootStackName = this.gen1App.rootStackName; if (!cfnClient || !rootStackName) return []; const nestedStackName = await this.getNestedStackPhysicalName(logicalId); if (!nestedStackName) return []; const response = await cfnClient.send(new client_cloudformation_1.DescribeStacksCommand({ StackName: nestedStackName })); return (_c = (_b = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Parameters) !== null && _c !== void 0 ? _c : []; } async preTransmute(template, logicalId) { var _a; const result = JSON.parse(JSON.stringify(template)); if ((_a = result.Parameters) === null || _a === void 0 ? void 0 : _a.env) { result.Parameters['branchName'] = result.Parameters.env; delete result.Parameters.env; } const updateRefs = (obj) => { if (typeof obj === 'object' && obj !== null) { const record = obj; if (record.Ref === 'env') { record.Ref = 'branchName'; } Object.values(record).forEach(updateRefs); } }; updateRefs(result.Resources); if (result.Parameters) { const groupRoleParams = Object.keys(result.Parameters).filter((key) => key.startsWith('authuserPoolGroups') && key.endsWith('GroupRole')); const replaceGroupRoleJoins = (obj) => { if (typeof obj !== 'object' || obj === null) return; const record = obj; for (const [key, value] of Object.entries(record)) { if (typeof value === 'object' && value !== null && 'Fn::Join' in value) { const joinValue = value; const [separator, parts] = joinValue['Fn::Join']; if (separator === '-' && Array.isArray(parts) && parts.length === 2 && typeof parts[0] === 'object' && parts[0] !== null && 'Ref' in parts[0] && typeof parts[1] === 'string' && parts[1].endsWith('GroupRole')) { const groupRoleSuffix = parts[1]; const matchingParam = groupRoleParams.find((p) => p.endsWith(groupRoleSuffix)); if (matchingParam) { record[key] = { Ref: matchingParam }; } } } else { replaceGroupRoleJoins(value); } } }; replaceGroupRoleJoins(result.Resources); } const parameters = await this.getNestedStackParameters(logicalId); if (parameters.length > 0) { const resolved = (0, cfn_condition_resolver_1.resolveConditions)(result, parameters); delete resolved.Conditions; return resolved; } return result; } postTransmute(tsCode) { const mappingVarNames = []; let result = tsCode.replace(/const (\w+):\s*Record<string,\s*Record<string,\s*string>>\s*=\s*\{([\s\S]*?)\n(\s*)\};/g, (_match, varName, mappingBody, indent) => { mappingVarNames.push(varName); const constructId = varName.charAt(0).toUpperCase() + varName.slice(1); return `const ${varName} = new cdk.CfnMapping(this, '${constructId}', {\n${indent} mapping: {${mappingBody}\n${indent} },\n${indent}});`; }); for (const varName of mappingVarNames) { const lookupRegex = new RegExp(`${varName}\\[([^\\]]+)\\]\\[(['"])([^'"]+)\\2\\]`, 'g'); result = result.replace(lookupRegex, (_match, expr, quote, key) => { return `${varName}.findInMap(${expr}, ${quote}${key}${quote})`; }); } return result; } } exports.GeoResourceGenerator = GeoResourceGenerator; const GEO_LIFECYCLE_ACTIONS = new Set([ 'geo:CreateMap', 'geo:UpdateMap', 'geo:DeleteMap', 'geo:CreatePlaceIndex', 'geo:UpdatePlaceIndex', 'geo:DeletePlaceIndex', 'geo:CreateGeofenceCollection', 'geo:UpdateGeofenceCollection', 'geo:DeleteGeofenceCollection', ]); function extractGen1Actions(template) { var _a, _b, _c, _d; const resources = (_a = template.Resources) !== null && _a !== void 0 ? _a : {}; for (const resource of Object.values(resources)) { const res = resource; if (res.Type !== 'AWS::IAM::Policy') continue; const statements = (_d = (_c = (_b = res.Properties) === null || _b === void 0 ? void 0 : _b.PolicyDocument) === null || _c === void 0 ? void 0 : _c.Statement) !== null && _d !== void 0 ? _d : []; for (const stmt of statements) { const actions = Array.isArray(stmt.Action) ? stmt.Action : [stmt.Action]; const userFacing = actions.filter((a) => typeof a === 'string' && !GEO_LIFECYCLE_ACTIONS.has(a)); if (userFacing.length > 0 && userFacing.length === actions.length) { return userFacing; } } } return []; } //# sourceMappingURL=geo-resource.generator.js.map