UNPKG

@aws-amplify/amplify-category-api

Version:
332 lines (281 loc) 13.1 kB
import * as path from 'path'; import { $TSContext, AmplifySupportedService, exitOnNextTick, NotImplementedError } from '@aws-amplify/amplify-cli-core'; import { UpdateApiRequest } from 'amplify-headless-interface'; import { printer } from '@aws-amplify/amplify-prompts'; import inquirer from 'inquirer'; import { category } from '../../category-constants'; import { ApigwInputState } from './apigw-input-state'; import { getCfnApiArtifactHandler } from './cfn-api-artifact-handler'; import { addResource as addContainer, updateResource as updateContainer } from './containers-handler'; import { legacyAddResource } from './legacy-add-resource'; import { API_TYPE, getPermissionPolicies as getContainerPermissionPolicies, ServiceConfiguration, } from './service-walkthroughs/containers-walkthrough'; import { datasourceMetadataFor, getServiceWalkthrough, serviceMetadataFor } from './utils/dynamic-imports'; import { editSchemaFlow } from './utils/edit-schema-flow'; import { serviceWalkthroughResultToAddApiRequest } from './utils/service-walkthrough-result-to-add-api-request'; export async function addAdminQueriesApi( context: $TSContext, apiProps: { apiName: string; functionName: string; authResourceName: string; dependsOn: Record<string, any>[] }, ) { const apigwInputState = new ApigwInputState(context, apiProps.apiName); return apigwInputState.addAdminQueriesResource(apiProps); } export async function updateAdminQueriesApi( context: $TSContext, apiProps: { apiName: string; functionName: string; authResourceName: string; dependsOn: Record<string, any>[] }, ) { const apigwInputState = new ApigwInputState(context, apiProps.apiName); // Check for migration if (!apigwInputState.cliInputsFileExists()) { await apigwInputState.migrateAdminQueries(apiProps); } else { return apigwInputState.updateAdminQueriesResource(apiProps); } } export async function console(context: $TSContext, service: string) { const { serviceWalkthroughFilename } = await serviceMetadataFor(service); const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename); const { openConsole } = await import(serviceWalkthroughSrc); if (!openConsole) { const errMessage = 'Opening console functionality not available for this option'; printer.error(errMessage); await context.usageData.emitError(new NotImplementedError(errMessage)); exitOnNextTick(0); } return openConsole(context); } async function addContainerResource(context: $TSContext, service: string, options, apiType: API_TYPE) { const serviceWalkthroughFilename = 'containers-walkthrough.js'; const serviceWalkthrough = await getServiceWalkthrough(serviceWalkthroughFilename); const serviceWalkthroughPromise: Promise<any> = serviceWalkthrough(context, apiType); return await addContainer(serviceWalkthroughPromise, context, category, service, options, apiType); } async function addNonContainerResource(context: $TSContext, service: string, options) { const serviceMetadata = await serviceMetadataFor(service); const { serviceWalkthroughFilename, defaultValuesFilename } = serviceMetadata; const serviceWalkthrough = await getServiceWalkthrough(serviceWalkthroughFilename); const serviceWalkthroughPromise: Promise<any> = serviceWalkthrough(context, serviceMetadata); switch (service) { case AmplifySupportedService.APPSYNC: { const walkthroughResult = await serviceWalkthroughPromise; const askToEdit = walkthroughResult.askToEdit; const apiName = await getCfnApiArtifactHandler(context).createArtifacts(serviceWalkthroughResultToAddApiRequest(walkthroughResult)); if (askToEdit) { await editSchemaFlow(context, apiName); } return apiName; } case AmplifySupportedService.APIGW: { const apigwInputState = new ApigwInputState(context); return apigwInputState.addApigwResource(serviceWalkthroughPromise, options); } default: return legacyAddResource(serviceWalkthroughPromise, context, category, service, options); } } export async function addResource(context: $TSContext, service: string, options) { let useContainerResource = false; let apiType = API_TYPE.GRAPHQL; if (isContainersEnabled(context)) { switch (service) { case AmplifySupportedService.APPSYNC: useContainerResource = await isGraphQLContainer(); apiType = API_TYPE.GRAPHQL; break; case AmplifySupportedService.APIGW: useContainerResource = await isRestContainer(); apiType = API_TYPE.REST; break; default: throw new Error(`${service} not exists`); } } return useContainerResource ? addContainerResource(context, service, options, apiType) : addNonContainerResource(context, service, options); } function isContainersEnabled(context: $TSContext) { const { frontend } = context.amplify.getProjectConfig(); if (frontend) { const { config: { ServerlessContainers = false } = {} } = context.amplify.getProjectConfig()[frontend] || {}; return ServerlessContainers; } return false; } async function isGraphQLContainer(): Promise<boolean> { const { graphqlSelection } = await inquirer.prompt({ name: 'graphqlSelection', message: 'Which service would you like to use', type: 'list', choices: [ { name: AmplifySupportedService.APPSYNC, value: false, }, { name: 'AWS Fargate (Container-based)', value: true, }, ], }); return graphqlSelection; } async function isRestContainer() { const { restSelection } = await inquirer.prompt({ name: 'restSelection', message: 'Which service would you like to use', type: 'list', choices: [ { name: 'API Gateway + Lambda', value: false, }, { name: 'API Gateway + AWS Fargate (Container-based)', value: true, }, ], }); return restSelection; } export async function updateResource(context: $TSContext, category: string, service: string, options) { const allowContainers = options?.allowContainers ?? true; let useContainerResource = false; let apiType = API_TYPE.GRAPHQL; if (allowContainers && isContainersEnabled(context)) { const { hasAPIGatewayContainerResource, hasAPIGatewayLambdaResource, hasGraphQLAppSyncResource, hasGraphqlContainerResource } = await describeApiResourcesBySubCategory(context); switch (service) { case AmplifySupportedService.APPSYNC: if (hasGraphQLAppSyncResource && hasGraphqlContainerResource) { useContainerResource = await isGraphQLContainer(); } else if (hasGraphqlContainerResource) { useContainerResource = true; } else { useContainerResource = false; } apiType = API_TYPE.GRAPHQL; break; case AmplifySupportedService.APIGW: if (hasAPIGatewayContainerResource && hasAPIGatewayLambdaResource) { useContainerResource = await isRestContainer(); } else if (hasAPIGatewayContainerResource) { useContainerResource = true; } else { useContainerResource = false; } apiType = API_TYPE.REST; break; default: throw new Error(`${service} not exists`); } } return useContainerResource ? updateContainerResource(context, category, service, apiType) : updateNonContainerResource(context, service); } async function describeApiResourcesBySubCategory(context: $TSContext) { const { allResources } = await context.amplify.getResourceStatus(); const resources = allResources.filter((resource) => resource.category === category && resource.mobileHubMigrated !== true); let hasAPIGatewayContainerResource = false; let hasAPIGatewayLambdaResource = false; let hasGraphQLAppSyncResource = false; let hasGraphqlContainerResource = false; resources.forEach((resource) => { hasAPIGatewayContainerResource = hasAPIGatewayContainerResource || (resource.service === 'ElasticContainer' && resource.apiType === API_TYPE.REST); hasAPIGatewayLambdaResource = hasAPIGatewayLambdaResource || resource.service === AmplifySupportedService.APIGW; hasGraphQLAppSyncResource = hasGraphQLAppSyncResource || resource.service === AmplifySupportedService.APPSYNC; hasGraphqlContainerResource = hasGraphqlContainerResource || (resource.service === 'ElasticContainer' && resource.apiType === API_TYPE.GRAPHQL); }); return { hasAPIGatewayLambdaResource, hasAPIGatewayContainerResource, hasGraphQLAppSyncResource, hasGraphqlContainerResource, }; } async function updateContainerResource(context: $TSContext, category: string, service: string, apiType: API_TYPE) { const serviceWalkthroughFilename = 'containers-walkthrough'; const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename); const { updateWalkthrough } = await import(serviceWalkthroughSrc); if (!updateWalkthrough) { const errMessage = 'Update functionality not available for this option'; printer.error(errMessage); await context.usageData.emitError(new NotImplementedError(errMessage)); exitOnNextTick(0); } const updateWalkthroughPromise: Promise<ServiceConfiguration> = updateWalkthrough(context, apiType); await updateContainer(updateWalkthroughPromise, context, category); } async function updateNonContainerResource(context: $TSContext, service: string) { const serviceMetadata = await serviceMetadataFor(service); const { defaultValuesFilename, serviceWalkthroughFilename } = serviceMetadata; const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename); const { updateWalkthrough } = await import(serviceWalkthroughSrc); if (!updateWalkthrough) { const errMessage = 'Update functionality not available for this option'; printer.error(errMessage); await context.usageData.emitError(new NotImplementedError(errMessage)); exitOnNextTick(0); } const updateWalkthroughPromise: Promise<UpdateApiRequest> = updateWalkthrough(context, defaultValuesFilename, serviceMetadata); switch (service) { case AmplifySupportedService.APPSYNC: return updateWalkthroughPromise.then(getCfnApiArtifactHandler(context).updateArtifacts); default: { const apigwInputState = new ApigwInputState(context); return apigwInputState.updateApigwResource(updateWalkthroughPromise); } } } export async function migrateResource(context: $TSContext, projectPath: string, service: string, resourceName: string) { if (service === 'ElasticContainer') { return migrateResourceContainer(context, projectPath, service, resourceName); } else { return migrateResourceNonContainer(context, projectPath, service, resourceName); } } async function migrateResourceContainer(context: $TSContext, projectPath: string, service: string, resourceName: string) { printer.info(`No migration required for ${resourceName}`); return; } async function migrateResourceNonContainer(context: $TSContext, projectPath: string, service: string, resourceName: string) { const serviceMetadata = await serviceMetadataFor(service); const { serviceWalkthroughFilename } = serviceMetadata; const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename); const { migrate } = await import(serviceWalkthroughSrc); if (!migrate) { printer.info(`No migration required for ${resourceName}`); return; } return await migrate(context, projectPath, resourceName); } export async function addDatasource(context: $TSContext, category, datasource) { const serviceMetadata = await datasourceMetadataFor(datasource); const { serviceWalkthroughFilename } = serviceMetadata; return (await getServiceWalkthrough(serviceWalkthroughFilename))(context, serviceMetadata); } export async function getPermissionPolicies(context: $TSContext, service: string, resourceName: string, crudOptions) { if (service === 'ElasticContainer') { return getPermissionPoliciesContainer(context, service, resourceName, crudOptions); } else { return getPermissionPoliciesNonContainer(service, resourceName, crudOptions); } } async function getPermissionPoliciesContainer(context: $TSContext, service: string, resourceName: string, crudOptions) { return getContainerPermissionPolicies(context, service, resourceName, crudOptions); } async function getPermissionPoliciesNonContainer(service: string, resourceName: string, crudOptions: string[]) { const serviceMetadata = await serviceMetadataFor(service); const { serviceWalkthroughFilename } = serviceMetadata; const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename); const { getIAMPolicies } = await import(serviceWalkthroughSrc); if (!getIAMPolicies) { printer.info(`No policies found for ${resourceName}`); return; } return getIAMPolicies(resourceName, crudOptions); }