UNPKG

@aws-amplify/amplify-category-storage

Version:

amplify-cli storage plugin

510 lines • 25.8 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.getIAMPolicies = exports.checkIfAuthExists = exports.resourceAlreadyExists = exports.S3CLITriggerStateEvent = exports.S3CLITriggerFlow = exports.createNewLambdaAndUpdateCFN = exports.addTrigger = exports.buildShortUUID = exports.migrateStorageCategory = exports.isMigrateStorageRequired = exports.updateWalkthrough = exports.addWalkthrough = void 0; const amplify_cli_core_1 = require("@aws-amplify/amplify-cli-core"); const amplify_prompts_1 = require("@aws-amplify/amplify-prompts"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const uuid_1 = require("uuid"); const s3_stack_transform_1 = require("../cdk-stack-builder/s3-stack-transform"); const s3_defaults_1 = require("../default-values/s3-defaults"); const s3_user_input_types_1 = require("../service-walkthrough-types/s3-user-input-types"); const s3_auth_api_1 = require("./s3-auth-api"); const s3_errors_1 = require("./s3-errors"); const s3_questions_1 = require("./s3-questions"); const s3_resource_api_1 = require("./s3-resource-api"); const s3_user_input_state_1 = require("./s3-user-input-state"); async function addWalkthrough(context, defaultValuesFilename, serviceMetadata, options) { const { amplify } = context; const amplifyMeta = amplify_cli_core_1.stateManager.getMeta(); try { const authMigrationAccepted = await (0, s3_auth_api_1.migrateAuthDependencyResource)(context); if (!authMigrationAccepted) { (0, amplify_cli_core_1.exitOnNextTick)(0); } } catch (error) { await (0, s3_errors_1.printErrorAuthResourceMigrationFailed)(context); (0, amplify_cli_core_1.exitOnNextTick)(0); } await (0, s3_questions_1.askAndInvokeAuthWorkflow)(context); const resourceName = await getS3ResourceNameFromMeta(amplifyMeta); if (resourceName) { await (0, s3_errors_1.printErrorAlreadyCreated)(context); (0, amplify_cli_core_1.exitOnNextTick)(0); } else { const policyID = buildShortUUID(); const defaultValues = (0, s3_defaults_1.getAllDefaults)(amplify.getProjectDetails(), policyID); const storageResourceName = await (0, s3_questions_1.askResourceNameQuestion)(context, defaultValues); const bucketName = await (0, s3_questions_1.askBucketNameQuestion)(context, defaultValues); let cliInputs = Object.assign({}, defaultValues); cliInputs.policyUUID = policyID; cliInputs.resourceName = storageResourceName; cliInputs.bucketName = bucketName; const userPoolGroupList = context.amplify.getUserPoolGroupList(); if (userPoolGroupList && userPoolGroupList.length > 0) { cliInputs = await (0, s3_questions_1.askGroupOrIndividualAccessFlow)(userPoolGroupList, context, cliInputs); } else { cliInputs.storageAccess = await await (0, s3_questions_1.askWhoHasAccessQuestion)(context, defaultValues); cliInputs.authAccess = await (0, s3_questions_1.askAuthPermissionQuestion)(context, defaultValues); cliInputs.guestAccess = await await (0, s3_questions_1.conditionallyAskGuestPermissionQuestion)(cliInputs.storageAccess, context, defaultValues); } const triggerFunction = await startAddTriggerFunctionFlow(context, storageResourceName, policyID, undefined); cliInputs.triggerFunction = triggerFunction ? triggerFunction : 'NONE'; const allowUnauthenticatedIdentities = cliInputs.guestAccess && cliInputs.guestAccess.length > 0; await (0, s3_auth_api_1.checkStorageAuthenticationRequirements)(context, storageResourceName, allowUnauthenticatedIdentities); const cliInputsState = new s3_user_input_state_1.S3InputState(context, cliInputs.resourceName, cliInputs); await cliInputsState.saveCliInputPayload(cliInputs); const stackGenerator = new s3_stack_transform_1.AmplifyS3ResourceStackTransform(cliInputs.resourceName, context); await stackGenerator.transform(amplify_cli_core_1.CLISubCommandType.ADD); const dependsOn = stackGenerator.getS3DependsOn(); if (dependsOn) { options.dependsOn = dependsOn; } return cliInputs.resourceName; } return undefined; } exports.addWalkthrough = addWalkthrough; async function updateWalkthrough(context) { var _a, _b, _c; const amplifyMeta = amplify_cli_core_1.stateManager.getMeta(); const storageResourceName = await getS3ResourceNameFromMeta(amplifyMeta); if (storageResourceName === undefined) { await (0, s3_errors_1.printErrorNoResourcesToUpdate)(context); (0, amplify_cli_core_1.exitOnNextTick)(0); } else { if (amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE][storageResourceName].serviceType === 'imported') { amplify_prompts_1.printer.error('Updating of an imported storage resource is not supported.'); return undefined; } const cliInputsState = new s3_user_input_state_1.S3InputState(context, storageResourceName, undefined); const headlessMigrate = ((_a = context.input.options) === null || _a === void 0 ? void 0 : _a.yes) || ((_b = context.input.options) === null || _b === void 0 ? void 0 : _b.forcePush) || ((_c = context.input.options) === null || _c === void 0 ? void 0 : _c.headless); if (!cliInputsState.cliInputFileExists()) { if (headlessMigrate || (await amplify_prompts_1.prompter.yesOrNo((0, amplify_cli_core_1.getMigrateResourceMessageForOverride)(amplify_cli_core_1.AmplifyCategories.STORAGE, storageResourceName), true))) { await cliInputsState.migrate(context); const stackGenerator = new s3_stack_transform_1.AmplifyS3ResourceStackTransform(storageResourceName, context); await stackGenerator.transform(amplify_cli_core_1.CLISubCommandType.UPDATE); } else { return undefined; } } const previousUserInput = cliInputsState.getUserInput(); let cliInputs = Object.assign({}, previousUserInput); const userPoolGroupList = context.amplify.getUserPoolGroupList(); if (userPoolGroupList && userPoolGroupList.length > 0) { cliInputs = await (0, s3_questions_1.askGroupOrIndividualAccessFlow)(userPoolGroupList, context, cliInputs); } else { cliInputs.storageAccess = await (0, s3_questions_1.askWhoHasAccessQuestion)(context, previousUserInput); cliInputs.authAccess = await (0, s3_questions_1.askAuthPermissionQuestion)(context, previousUserInput); cliInputs.guestAccess = await (0, s3_questions_1.conditionallyAskGuestPermissionQuestion)(cliInputs.storageAccess, context, previousUserInput); } if (previousUserInput.triggerFunction && previousUserInput.triggerFunction != 'NONE') { cliInputs.triggerFunction = await startUpdateTriggerFunctionFlow(context, storageResourceName, previousUserInput.policyUUID, previousUserInput.triggerFunction); } else { cliInputs.triggerFunction = await startAddTriggerFunctionFlow(context, storageResourceName, previousUserInput.policyUUID, undefined); } const allowUnauthenticatedIdentities = cliInputs.guestAccess && cliInputs.guestAccess.length > 0; await (0, s3_auth_api_1.checkStorageAuthenticationRequirements)(context, storageResourceName, allowUnauthenticatedIdentities); await cliInputsState.saveCliInputPayload(cliInputs); const stackGenerator = new s3_stack_transform_1.AmplifyS3ResourceStackTransform(cliInputs.resourceName, context); await stackGenerator.transform(amplify_cli_core_1.CLISubCommandType.UPDATE); return cliInputs.resourceName; } return undefined; } exports.updateWalkthrough = updateWalkthrough; function isMigrateStorageRequired(context, resourceName) { const projectBackendDirPath = amplify_cli_core_1.pathManager.getBackendDirPath(); const cliInputsFilePath = path.resolve(path.join(projectBackendDirPath, amplify_cli_core_1.AmplifyCategories.STORAGE, resourceName, 'cli-inputs.json')); return !fs.existsSync(cliInputsFilePath); } exports.isMigrateStorageRequired = isMigrateStorageRequired; async function migrateStorageCategory(context, resourceName) { const cliInputsState = new s3_user_input_state_1.S3InputState(context, resourceName, undefined); if (!cliInputsState.cliInputFileExists()) { await cliInputsState.migrate(context); const stackGenerator = new s3_stack_transform_1.AmplifyS3ResourceStackTransform(resourceName, context); await stackGenerator.transform(amplify_cli_core_1.CLISubCommandType.MIGRATE); return stackGenerator.getCFN(); } else { return undefined; } } exports.migrateStorageCategory = migrateStorageCategory; function buildShortUUID() { const [shortId] = (0, uuid_1.v4)().split('-'); return shortId; } exports.buildShortUUID = buildShortUUID; async function startAddTriggerFunctionFlow(context, resourceName, policyID, existingTriggerFunction) { const enableLambdaTriggerOnS3 = await amplify_prompts_1.prompter.yesOrNo('Do you want to add a Lambda Trigger for your S3 Bucket?', false); let triggerFunction = undefined; if (enableLambdaTriggerOnS3) { try { triggerFunction = await addTrigger(S3CLITriggerFlow.ADD, context, resourceName, policyID, existingTriggerFunction); } catch (e) { amplify_prompts_1.printer.error(e.message); } } return triggerFunction; } async function startUpdateTriggerFunctionFlow(context, resourceName, policyID, existingTriggerFunction) { let triggerFunction = existingTriggerFunction; let continueWithTriggerOperationQuestion = true; do { const triggerOperationAnswer = await (0, s3_questions_1.askUpdateTriggerSelection)(existingTriggerFunction); switch (triggerOperationAnswer) { case s3_questions_1.S3CLITriggerUpdateMenuOptions.ADD: case s3_questions_1.S3CLITriggerUpdateMenuOptions.UPDATE: { try { triggerFunction = await addTrigger(S3CLITriggerFlow.UPDATE, context, resourceName, policyID, existingTriggerFunction); continueWithTriggerOperationQuestion = false; } catch (e) { amplify_prompts_1.printer.error(e.message); continueWithTriggerOperationQuestion = true; } break; } case s3_questions_1.S3CLITriggerUpdateMenuOptions.REMOVE: { if (triggerFunction) { await removeTriggerPolicy(context, resourceName, triggerFunction); triggerFunction = undefined; continueWithTriggerOperationQuestion = false; } break; } case s3_questions_1.S3CLITriggerUpdateMenuOptions.SKIP: { continueWithTriggerOperationQuestion = false; break; } default: amplify_prompts_1.printer.error(`${triggerOperationAnswer} not supported`); continueWithTriggerOperationQuestion = false; } } while (continueWithTriggerOperationQuestion); return triggerFunction; } async function addTrigger(triggerFlowType, context, resourceName, policyID, existingTriggerFunction) { const triggerStateEvent = getCLITriggerStateEvent(triggerFlowType, existingTriggerFunction); let triggerFunction = existingTriggerFunction; switch (triggerStateEvent) { case S3CLITriggerStateEvent.ERROR: throw new Error("Lambda Trigger is already enabled, please use 'amplify update storage'"); case S3CLITriggerStateEvent.ADD_NEW_TRIGGER: { const existingLambdaResources = await getExistingFunctionsForTrigger(context, existingTriggerFunction, false); if (existingLambdaResources && existingLambdaResources.length > 0) { triggerFunction = await interactiveAskTriggerTypeFlow(context, policyID, existingTriggerFunction, existingLambdaResources); } else { triggerFunction = await interactiveCreateNewLambdaAndUpdateCFN(context); } break; } case S3CLITriggerStateEvent.REPLACE_TRIGGER: triggerFunction = await interactiveAskTriggerTypeFlow(context, policyID, existingTriggerFunction); break; case S3CLITriggerStateEvent.DELETE_TRIGGER: triggerFunction = undefined; break; } return triggerFunction; } exports.addTrigger = addTrigger; async function removeTriggerPolicy(context, resourceName, triggerFunction) { const projectBackendDirPath = context.amplify.pathManager.getBackendDirPath(); const functionCFNFilePath = path.join(projectBackendDirPath, 'function', triggerFunction, `${triggerFunction}-cloudformation-template.json`); if (fs.existsSync(functionCFNFilePath)) { const functionCFNFile = context.amplify.readJsonFile(functionCFNFilePath); delete functionCFNFile.Resources[`${resourceName}TriggerPolicy`]; delete functionCFNFile.Resources[`${resourceName}Trigger`]; const functionCFNString = JSON.stringify(functionCFNFile, null, 4); fs.writeFileSync(functionCFNFilePath, functionCFNString, 'utf8'); } return triggerFunction; } async function getS3ResourceNameFromMeta(amplifyMeta) { const storageResources = getS3ResourcesFromAmplifyMeta(amplifyMeta); if (storageResources) { if (Object.keys(storageResources).length === 0) { return undefined; } const [resourceName] = Object.keys(storageResources); return resourceName; } return undefined; } function getS3ResourcesFromAmplifyMeta(amplifyMeta) { if (!Object.prototype.hasOwnProperty.call(amplifyMeta, amplify_cli_core_1.AmplifyCategories.STORAGE)) { return undefined; } const resources = {}; Object.keys(amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE]).forEach((resourceName) => { if (amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE][resourceName].service === amplify_cli_core_1.AmplifySupportedService.S3 && amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE][resourceName].mobileHubMigrated !== true && amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE][resourceName].serviceType !== 'imported') { resources[resourceName] = amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE][resourceName]; } }); return resources; } async function createNewLambdaAndUpdateCFN(context, triggerFunctionName, policyUUID) { const targetDir = context.amplify.pathManager.getBackendDirPath(); const newShortUUID = policyUUID ? policyUUID : buildShortUUID(); const newFunctionName = triggerFunctionName ? triggerFunctionName : `S3Trigger${newShortUUID}`; const pluginDir = __dirname; const defaults = { functionName: `${newFunctionName}`, roleName: `${newFunctionName}LambdaRole${newShortUUID}`, }; const copyJobs = [ { dir: pluginDir, template: path.join('..', '..', '..', '..', 'resources', 'triggers', 's3', 'lambda-cloudformation-template.json.ejs'), target: path.join(targetDir, 'function', newFunctionName, `${newFunctionName}-cloudformation-template.json`), }, { dir: pluginDir, template: path.join('..', '..', '..', '..', 'resources', 'triggers', 's3', 'event.json'), target: path.join(targetDir, 'function', newFunctionName, 'src', 'event.json'), }, { dir: pluginDir, template: path.join('..', '..', '..', '..', 'resources', 'triggers', 's3', 'index.js'), target: path.join(targetDir, 'function', newFunctionName, 'src', 'index.js'), }, { dir: pluginDir, template: path.join('..', '..', '..', '..', 'resources', 'triggers', 's3', 'package.json.ejs'), target: path.join(targetDir, 'function', newFunctionName, 'src', 'package.json'), }, ]; await context.amplify.copyBatch(context, copyJobs, defaults); const backendConfigs = { service: amplify_cli_core_1.AmplifySupportedService.LAMBDA, providerPlugin: 'awscloudformation', build: true, }; await context.amplify.updateamplifyMetaAfterResourceAdd('function', newFunctionName, backendConfigs); amplify_prompts_1.printer.success(`Successfully added resource ${newFunctionName} locally`); return newFunctionName; } exports.createNewLambdaAndUpdateCFN = createNewLambdaAndUpdateCFN; async function getExistingFunctionsForTrigger(context, excludeFunctionName, isInteractive) { const excludeFunctionList = excludeFunctionName ? [excludeFunctionName] : []; const adminTriggerFunction = await (0, s3_resource_api_1.s3GetAdminTriggerFunctionName)(context); if (adminTriggerFunction && adminTriggerFunction != 'NONE') { excludeFunctionList.push(adminTriggerFunction); } let lambdaResourceNames = await getLambdaFunctionList(context); if (excludeFunctionList.length > 0 && lambdaResourceNames && lambdaResourceNames.length > 0) { lambdaResourceNames = lambdaResourceNames.filter((lambdaResourceName) => !excludeFunctionList.includes(lambdaResourceName)); } if (lambdaResourceNames.length === 0 && isInteractive) { throw new Error("No functions were found in the project. Use 'amplify add function' to add a new function."); } return lambdaResourceNames; } var S3CLITriggerFlow; (function (S3CLITriggerFlow) { S3CLITriggerFlow["ADD"] = "TRIGGER_ADD_FLOW"; S3CLITriggerFlow["UPDATE"] = "TRIGGER_UPDATE_FLOW"; S3CLITriggerFlow["REMOVE"] = "TRIGGER_REMOVE_FLOW"; })(S3CLITriggerFlow = exports.S3CLITriggerFlow || (exports.S3CLITriggerFlow = {})); var S3CLITriggerStateEvent; (function (S3CLITriggerStateEvent) { S3CLITriggerStateEvent["ADD_NEW_TRIGGER"] = "ADD_NEW_TRIGGER"; S3CLITriggerStateEvent["REPLACE_TRIGGER"] = "REPLACE_TRIGGER"; S3CLITriggerStateEvent["DELETE_TRIGGER"] = "DELETE_TRIGGER"; S3CLITriggerStateEvent["ERROR"] = "TRIGGER_ERROR"; S3CLITriggerStateEvent["NO_OP"] = "TRIGGER_NO_OP"; })(S3CLITriggerStateEvent = exports.S3CLITriggerStateEvent || (exports.S3CLITriggerStateEvent = {})); function getCLITriggerStateEvent(triggerFlowType, existingTriggerFunction) { if (triggerFlowType === S3CLITriggerFlow.ADD) { if (existingTriggerFunction) { return S3CLITriggerStateEvent.ERROR; } else { return S3CLITriggerStateEvent.ADD_NEW_TRIGGER; } } else { if (triggerFlowType === S3CLITriggerFlow.UPDATE) { return S3CLITriggerStateEvent.REPLACE_TRIGGER; } else { if (existingTriggerFunction) { return S3CLITriggerStateEvent.DELETE_TRIGGER; } else { return S3CLITriggerStateEvent.NO_OP; } } } } async function interactiveCreateNewLambdaAndUpdateCFN(context) { const newTriggerFunction = await createNewLambdaAndUpdateCFN(context, undefined, undefined); await (0, s3_questions_1.askAndOpenFunctionEditor)(context, newTriggerFunction); return newTriggerFunction; } async function interactiveAddExistingLambdaAndUpdateCFN(context, existingTriggerFunction = undefined, existingLambdaResources = undefined) { const lambdaResources = existingLambdaResources ? existingLambdaResources : await getExistingFunctionsForTrigger(context, existingTriggerFunction, true); const selectedFunction = await (0, s3_questions_1.askSelectExistingFunctionToAddTrigger)(lambdaResources); return selectedFunction; } async function interactiveAskTriggerTypeFlow(context, _policyID, existingTriggerFunction, existingLambdaResources = undefined) { const triggerTypeAnswer = await (0, s3_questions_1.askTriggerFunctionTypeQuestion)(); switch (triggerTypeAnswer) { case s3_user_input_types_1.S3TriggerFunctionType.EXISTING_FUNCTION: { const selectedFunction = await interactiveAddExistingLambdaAndUpdateCFN(context, existingTriggerFunction, existingLambdaResources); return selectedFunction; } case s3_user_input_types_1.S3TriggerFunctionType.NEW_FUNCTION: { const newTriggerFunction = await interactiveCreateNewLambdaAndUpdateCFN(context); return newTriggerFunction; } } return undefined; } async function getLambdaFunctionList(context) { var _a; const { allResources } = await context.amplify.getResourceStatus(); const lambdaResources = allResources && allResources.length > 0 ? (_a = allResources === null || allResources === void 0 ? void 0 : allResources.filter((resource) => resource.service === amplify_cli_core_1.AmplifySupportedService.LAMBDA)) === null || _a === void 0 ? void 0 : _a.map((resource) => resource.resourceName) : []; return lambdaResources ? lambdaResources : []; } const resourceAlreadyExists = () => { const amplifyMeta = amplify_cli_core_1.stateManager.getMeta(); let resourceName; if (amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE]) { const categoryResources = amplifyMeta[amplify_cli_core_1.AmplifyCategories.STORAGE]; Object.keys(categoryResources).forEach((resource) => { if (categoryResources[resource].service === amplify_cli_core_1.AmplifySupportedService.S3) { resourceName = resource; } }); } return resourceName; }; exports.resourceAlreadyExists = resourceAlreadyExists; function checkIfAuthExists() { const amplifyMeta = amplify_cli_core_1.stateManager.getMeta(); let authExists = false; const authServiceName = 'Cognito'; const authCategory = 'auth'; if (amplifyMeta[authCategory] && Object.keys(amplifyMeta[authCategory]).length > 0) { const categoryResources = amplifyMeta[authCategory]; Object.keys(categoryResources).forEach((resource) => { if (categoryResources[resource].service === authServiceName) { authExists = true; } }); } return authExists; } exports.checkIfAuthExists = checkIfAuthExists; function getIAMPolicies(resourceName, crudOptions) { const policy = []; let actions = new Set(); crudOptions.forEach((crudOption) => { switch (crudOption) { case 'create': actions.add('s3:PutObject'); break; case 'update': actions.add('s3:PutObject'); break; case 'read': actions.add('s3:GetObject'); actions.add('s3:ListBucket'); break; case 'delete': actions.add('s3:DeleteObject'); break; default: amplify_prompts_1.printer.info(`${crudOption} not supported`); } }); actions = Array.from(actions); if (actions.includes('s3:ListBucket')) { let listBucketPolicy = {}; listBucketPolicy = { Effect: 'Allow', Action: 's3:ListBucket', Resource: [ { 'Fn::Join': [ '', [ 'arn:aws:s3:::', { Ref: `${amplify_cli_core_1.AmplifyCategories.STORAGE}${resourceName}BucketName`, }, ], ], }, ], }; actions = actions.filter((action) => action != 's3:ListBucket'); policy.push(listBucketPolicy); } const s3ObjectPolicy = { Effect: 'Allow', Action: actions, Resource: [ { 'Fn::Join': [ '', [ 'arn:aws:s3:::', { Ref: `${amplify_cli_core_1.AmplifyCategories.STORAGE}${resourceName}BucketName`, }, '/*', ], ], }, ], }; policy.push(s3ObjectPolicy); const attributes = ['BucketName']; return { policy, attributes }; } exports.getIAMPolicies = getIAMPolicies; //# sourceMappingURL=s3-walkthrough.js.map