UNPKG

limgen

Version:

Infrastructure as Code generator

231 lines (230 loc) 9.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureTunnelScript = exports.renderIndex = exports.envPull = exports.collectInput = exports.inputs = exports.dependsOn = void 0; const ejs_1 = __importDefault(require("ejs")); const path_1 = __importDefault(require("path")); const prompts_1 = __importDefault(require("prompts")); const promises_1 = __importDefault(require("fs/promises")); const schema_1 = require("../../schema"); const files_1 = require("../../files"); const project_1 = require("../../project"); const exec_1 = require("../../exec"); const dependsOn = (opts) => __awaiter(void 0, void 0, void 0, function* () { const files = [ 'components/app-fargate.ts', 'components/cdn-cloudfront.ts', 'components/lb-alb-public.ts', 'components/vpc-public.ts', 'utils/deep-merge.ts', 'utils/prefixed.ts', ]; const packages = [ '@pulumi/aws', '@pulumi/awsx', '@pulumi/pulumi', 'zod', ]; if (opts.includeStorage) { files.push('components/storage-s3.ts'); } if (opts.includeDb) { files.push('components/db-postgres-rds.ts'); packages.push('@pulumi/random'); } if (opts.networkType === 'private') { files.push('components/bastion-ec2.ts'); } return { files, packages, }; }); exports.dependsOn = dependsOn; const inputs = () => __awaiter(void 0, void 0, void 0, function* () { return [ { name: 'includeStorage', message: 'Include storage', schema: (0, schema_1.cliBoolean)(), }, { name: 'includeDb', message: 'Include a database', schema: (0, schema_1.cliBoolean)(), }, { name: 'networkType', message: 'Network type', schema: (0, schema_1.cliEnum)(['public', 'private', 'unknown']).optional(), }, { name: 'storageAccess', messsage: 'Storage access', schema: (0, schema_1.cliEnum)(['private', 'public', 'unknown']).optional(), }, { name: 'port', message: 'Port to expose', schema: (0, schema_1.cliInteger)().optional().default('3000'), } ]; }); exports.inputs = inputs; const collectInput = (cmdArgs, projectArgs) => __awaiter(void 0, void 0, void 0, function* () { let includeStorage; if (projectArgs.includeStorage === 'unknown') { const answer = yield (0, prompts_1.default)({ type: 'confirm', name: 'includeStorage', message: 'Include storage?', initial: true, }); includeStorage = answer.includeStorage; } else { includeStorage = projectArgs.includeStorage === 'true'; } let storageAccess = projectArgs.storageAccess; if (includeStorage && projectArgs.storageAccess === 'unknown') { const answer = yield (0, prompts_1.default)({ type: 'select', name: 'storageAccess', message: 'Storage access', choices: [ { title: 'Public (useful for static content / media)', value: 'public' }, { title: 'Private (better for sensitive / personal data)', value: 'private' }, ], initial: 0, }); storageAccess = answer.storageAccess; } let includeDb; if (projectArgs.includeDb === 'unknown') { const answer = yield (0, prompts_1.default)({ type: 'confirm', name: 'includeDb', message: 'Include a database?', initial: true, }); includeDb = answer.includeDb; } else { includeDb = projectArgs.includeDb === 'true'; } let networkType = projectArgs.networkType; if (projectArgs.networkType === 'unknown') { const answer = yield (0, prompts_1.default)({ type: 'select', name: 'networkType', message: 'Network type', choices: [ { title: 'Public (simpler, suitable for development)', value: 'public' }, { title: 'Private (more secure, requires tunnel to access database)', value: 'private' }, ], initial: 0, }); networkType = answer.networkType; } return { includeStorage, includeDb, networkType, port: projectArgs.port, storageAccess, }; }); exports.collectInput = collectInput; const envPull = ({ projectName, stack, }) => __awaiter(void 0, void 0, void 0, function* () { const metadata = yield (0, project_1.readProjectMetadata)({ projectName }); const projectInputs = metadata.projectInputs; const properties = {}; if (projectInputs.includeStorage) { const { stdout: BUCKET_NAME } = yield (0, exec_1.execPromise)(`pulumi stack output objectStorageBucket --stack ${stack}`, { cwd: path_1.default.join('infrastructure', 'projects', projectName) }); Object.assign(properties, { BUCKET_NAME }); } if (projectInputs.includeDb) { const { stdout: secretId } = yield (0, exec_1.execPromise)(`pulumi stack output dbSecret --stack ${stack}`, { cwd: path_1.default.join('infrastructure', 'projects', projectName) }); const { stdout: secretResponse } = yield (0, exec_1.execPromise)(`aws secretsmanager get-secret-value --secret-id ${secretId}`); const DATABASE_URL = JSON.parse(secretResponse).SecretString; if (!DATABASE_URL) { throw new Error(`Could not get secret value for ${secretId}`); } Object.assign(properties, { DATABASE_URL }); } yield promises_1.default.writeFile('.env', Object.entries(properties).map(([key, value]) => `${key}=${value}`.trim()).join('\n')); }); exports.envPull = envPull; function fullstackAWSProject(inputs) { return __awaiter(this, void 0, void 0, function* () { const [indexContents] = yield Promise.all([ (0, exports.renderIndex)(inputs), updateDockerignore(), (0, exports.ensureTunnelScript)(inputs), ]); return indexContents; }); } exports.default = fullstackAWSProject; const updateDockerignore = () => __awaiter(void 0, void 0, void 0, function* () { if (yield (0, files_1.fileExists)('.dockerignore')) { // see if infrastructure is already in the .dockerignore file const dockerignore = yield promises_1.default.readFile('.dockerignore', 'utf-8'); if (!dockerignore.includes('infrastructure')) { yield promises_1.default.appendFile('.dockerignore', '\ninfrastructure\n'); } } else { yield promises_1.default.writeFile('.dockerignore', ` Dockerfile .dockerignore node_modules npm-debug.log README.md .next .git .env* infrastructure/`.trim()); } }); const renderIndex = (inputs) => __awaiter(void 0, void 0, void 0, function* () { return ejs_1.default.renderFile(path_1.default.join(__dirname, 'index.ejs.t'), inputs); }); exports.renderIndex = renderIndex; const ensureTunnelScript = (inputs) => __awaiter(void 0, void 0, void 0, function* () { if (!(inputs.includeDb && inputs.networkType === 'private')) { return; } // copy the scripts from the scripts folder yield promises_1.default.mkdir('infrastructure/scripts', { recursive: true }); yield promises_1.default.copyFile(path_1.default.join(__dirname, 'scripts', 'db-tunnel.sh'), 'infrastructure/scripts/db-tunnel.sh'); // does the current working directory contain a package.json file? if (yield (0, files_1.fileExists)('package.json')) { // read the package.json file const packageJson = JSON.parse(yield promises_1.default.readFile('package.json', 'utf-8')); // add the start script packageJson.scripts = packageJson.scripts || {}; if (packageJson.scripts.tunnel) { // TODO: Is there something better than a dead-end here we can help the user with? // What if their tunnel script is different? // What if it's outdated version and they want to update it? // What they have multiple tunnels they need to manage? return; } packageJson.scripts.tunnel = `infrastructure/scripts/db-tunnel.sh ${inputs.projectName}`; // write the package.json file yield promises_1.default.writeFile('package.json', JSON.stringify(packageJson, null, 2)); } }); exports.ensureTunnelScript = ensureTunnelScript;