UNPKG

@aws-amplify/amplify-category-api

Version:
359 lines (309 loc) 11.7 kB
import { $TSContext, exitOnNextTick, ResourceDoesNotExistError } from '@aws-amplify/amplify-cli-core'; import { printer } from '@aws-amplify/amplify-prompts'; import inquirer from 'inquirer'; import { category } from '../../../category-constants'; import { DEPLOYMENT_MECHANISM } from '../base-api-stack'; import { GitHubSourceActionInfo } from '../pipeline-with-awaiter'; import { getAllDefaults } from '../default-values/containers-defaults'; const serviceName = 'ElasticContainer'; /** * Designed to be backwards compatible with the old way of representing dependencies as * { * category: string * resourceName: string * attributes: string[] * } * and auto-generating environment variable names based on this info * When attributeEnvMap is specified, it can specify a custom environment variable name for a dependency attribute * If no mapping is found for an attribute in the map, then it falls back to the autogenerated value */ export interface ResourceDependency { category: string; // resource category of the dependency resourceName: string; // name of the dependency attributes: string[]; // attributes that this function depends on (must be outputs of the dependencies CFN template) attributeEnvMap?: { [name: string]: string }; // optional attributes to environment variable names map that will be exposed to the function } export enum API_TYPE { GRAPHQL = 'GRAPHQL', REST = 'REST', } export type ServiceConfiguration = { resourceName: string; imageSource: { type: IMAGE_SOURCE_TYPE; template?: string }; gitHubPath: string; authName: string; gitHubToken: string; deploymentMechanism: DEPLOYMENT_MECHANISM; restrictAccess: boolean; dependsOn?: ResourceDependency[]; // resources this function depends on categoryPolicies?: any[]; // IAM policies that should be applied to this lambda mutableParametersState?: any; // Contains the object that is written to function-parameters.json. Kindof a hold-over from older code environmentMap?: Record<string, any>; // Existing function environment variable map. Should refactor to use dependsOn directly, gitHubInfo?: GitHubSourceActionInfo; }; export async function serviceWalkthrough(context: $TSContext, apiType: API_TYPE): Promise<Partial<ServiceConfiguration>> { const allDefaultValues = getAllDefaults(); const resourceName = await askResourceName(context, allDefaultValues); const containerInfo = await askContainerSource(context, resourceName, apiType); return { resourceName, ...containerInfo }; } async function askResourceName(context: $TSContext, allDefaultValues: Record<string, any>) { const { amplify } = context; const { resourceName } = await inquirer.prompt([ { name: 'resourceName', type: 'input', message: 'Provide a friendly name for your resource to be used as a label for this category in the project:', default: allDefaultValues.resourceName, validate: amplify.inputValidation({ validation: { operator: 'regex', value: '^[a-z0-9]+$', onErrorMsg: 'Resource name should be alphanumeric with no uppercase letters', }, required: true, }), }, ]); return resourceName; } async function askContainerSource(context: $TSContext, resourceName: string, apiType: API_TYPE): Promise<Partial<ServiceConfiguration>> { return newContainer(context, resourceName, apiType); } export enum IMAGE_SOURCE_TYPE { TEMPLATE = 'TEMPLATE', CUSTOM = 'CUSTOM', } async function newContainer(context: $TSContext, resourceName: string, apiType: API_TYPE): Promise<Partial<ServiceConfiguration>> { let imageSource: { type: IMAGE_SOURCE_TYPE; template?: string }; let choices = []; if (apiType === API_TYPE.GRAPHQL) { choices.push({ name: 'ExpressJS - GraphQL template', value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'graphql-express' }, }); } if (apiType === API_TYPE.REST) { choices.push({ name: 'ExpressJS - REST template', value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'dockerfile-rest-express' }, }); choices.push({ name: 'Docker Compose - ExpressJS + Flask template', value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'dockercompose-rest-express' }, }); } choices = choices.concat([ { name: 'Custom (bring your own Dockerfile or docker-compose.yml)', value: { type: IMAGE_SOURCE_TYPE.CUSTOM }, }, { name: 'Learn More', value: undefined, }, ]); do { ({ imageSource } = await inquirer.prompt([ { name: 'imageSource', type: 'list', message: 'What image would you like to use', choices, default: 'express_hello_world', }, ])); } while (imageSource === undefined); let deploymentMechanismQuestion; const deploymentMechanismChoices = [ { name: 'On every "amplify push" (Fully managed container source)', value: DEPLOYMENT_MECHANISM.FULLY_MANAGED, }, ]; if (imageSource.type === IMAGE_SOURCE_TYPE.CUSTOM) { deploymentMechanismChoices.push({ name: 'On every Github commit (Independently managed container source)', value: DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED, }); } deploymentMechanismChoices.push({ name: 'Advanced: Self-managed (Learn more: docs.amplify.aws/cli/usage/containers)', value: DEPLOYMENT_MECHANISM.SELF_MANAGED, }); do { deploymentMechanismQuestion = await inquirer.prompt([ { name: 'deploymentMechanism', type: 'list', message: 'When do you want to build & deploy the Fargate task', choices: deploymentMechanismChoices, }, ]); } while (deploymentMechanismQuestion.deploymentMechanism === 'Learn More'); let gitHubPath: string; let gitHubToken: string; if (deploymentMechanismQuestion.deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) { printer.info('We need a Github Personal Access Token to automatically build & deploy your Fargate task on every Github commit.'); printer.info( 'Learn more about Github Personal Access Token here: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token', ); const gitHubQuestions = await inquirer.prompt([ { name: 'github_access_token', type: 'password', message: 'GitHub Personal Access Token:', }, { name: 'github_path', type: 'input', message: 'Path to your repo:', }, ]); gitHubPath = gitHubQuestions.github_path; gitHubToken = gitHubQuestions.github_access_token; } const meta = context.amplify.getProjectDetails().amplifyMeta; const hasAccessableResources = ['storage', 'function'].some((categoryName) => { return Object.keys(meta[categoryName] ?? {}).length > 0; }); let rolePermissions: any = {}; if ( hasAccessableResources && (await context.amplify.confirmPrompt('Do you want to access other resources in this project from your api?')) ) { rolePermissions = await context.amplify.invokePluginMethod(context, 'function', undefined, 'askExecRolePermissionsQuestions', [ context, resourceName, undefined, undefined, category, serviceName, ]); } const { categoryPolicies, environmentMap, dependsOn, mutableParametersState } = rolePermissions; const restrictApiQuestion = await inquirer.prompt({ name: 'rescrict_access', type: 'confirm', message: 'Do you want to restrict API access', default: true, }); return { imageSource, gitHubPath, gitHubToken, deploymentMechanism: deploymentMechanismQuestion.deploymentMechanism, restrictAccess: restrictApiQuestion.rescrict_access, categoryPolicies, environmentMap, dependsOn, mutableParametersState, }; } export async function updateWalkthrough(context: $TSContext, apiType: API_TYPE) { const { allResources } = await context.amplify.getResourceStatus(); const resources = allResources .filter( (resource) => resource.category === category && resource.service === serviceName && !!resource.providerPlugin && resource.apiType === apiType, ) .map((resource) => resource.resourceName); // There can only be one appsync resource if (resources.length === 0) { const errMessage = `No ${apiType} API resource to update. Use "amplify add api" command to create a new ${apiType} API`; printer.error(errMessage); await context.usageData.emitError(new ResourceDoesNotExistError(errMessage)); exitOnNextTick(0); return; } const question = [ { name: 'resourceName', message: 'Please select the API you would want to update', type: 'list', choices: resources, }, ]; const { resourceName } = await inquirer.prompt(question); const resourceSettings = allResources.find( (resource) => resource.resourceName === resourceName && resource.category === category && resource.service === serviceName && !!resource.providerPlugin, ); let { gitHubInfo: { path = undefined } = {} } = resourceSettings; let gitHubToken; if (resourceSettings.deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) { if (await confirm('Would you like to change your GitHub access token')) { const gitHubQuestion = await inquirer.prompt({ name: 'gitHubAccessToken', type: 'password', message: 'GitHub Personal Access Token:', }); gitHubToken = gitHubQuestion.gitHubAccessToken; } if (await confirm('Would you like to change your GitHub Path to your repo')) { const gitHubQuestion = await inquirer.prompt({ name: 'gitHubPath', type: 'input', message: 'Path to your repo:', default: path, }); path = gitHubQuestion.gitHubPath; } } const { environmentMap = {}, mutableParametersState = {} } = resourceSettings; const meta = context.amplify.getProjectDetails().amplifyMeta; const hasAccessableResources = ['storage', 'function'].some((categoryName) => { return Object.keys(meta[categoryName] ?? {}).length > 0; }); let rolePermissions: any = {}; if ( hasAccessableResources && (await context.amplify.confirmPrompt('Do you want to access other resources in this project from your api?')) ) { rolePermissions = await context.amplify.invokePluginMethod(context, 'function', undefined, 'askExecRolePermissionsQuestions', [ context, resourceName, mutableParametersState.permissions, environmentMap, category, serviceName, ]); } const { categoryPolicies = [], environmentMap: newEnvironmentMap, dependsOn: newDependsOn = [], mutableParametersState: newMutableParametersState, } = rolePermissions; const { restrict_access: restrictAccess } = await inquirer.prompt({ name: 'restrict_access', type: 'confirm', message: 'Do you want to restrict API access', default: resourceSettings.restrictAccess, }); return { ...resourceSettings, restrictAccess, environmentMap: newEnvironmentMap, mutableParametersState: newMutableParametersState, dependsOn: newDependsOn, categoryPolicies, gitHubPath: path, gitHubToken, }; } async function confirm(question: string) { const { confirm } = await inquirer.prompt({ type: 'confirm', default: false, message: question, name: 'confirm', }); return confirm; } export async function getPermissionPolicies(context, service, resourceName, crudOptions) { throw new Error('IAM access not available for this resource'); }