diff --git a/packages/amplify-category-predictions/provider-utils/awscloudformation/prediction-category-walkthroughs/identify-walkthrough.js b/packages/amplify-category-predictions/provider-utils/awscloudformation/prediction-category-walkthroughs/identify-walkthrough.js index 3d7497d5349..09586568574 100644 --- a/packages/amplify-category-predictions/provider-utils/awscloudformation/prediction-category-walkthroughs/identify-walkthrough.js +++ b/packages/amplify-category-predictions/provider-utils/awscloudformation/prediction-category-walkthroughs/identify-walkthrough.js @@ -1,14 +1,22 @@ +import { JSONUtilities, stateManager, pathManager } from 'amplify-cli-core'; import { - addTextractPolicies, generateLambdaAccessForRekognition, - generateStorageAccessForRekognition, generateStorageCFNForAdditionalLambda, generateStorageCFNForLambda, removeTextractPolicies + addTextractPolicies, + generateLambdaAccessForRekognition, + generateStorageAccessForRekognition, + removeTextractPolicies } from '../assets/identifyCFNGenerate'; import identifyAssets from '../assets/identifyQuestions'; import regionMapper from '../assets/regionMapping'; import getAllDefaults from '../default-values/identify-defaults'; import { enableGuestAuth } from './enable-guest-auth'; -import { invokeS3GetResourceName, invokeS3GetAllDefaults, - invokeS3AddResource, invokeS3GetUserInputs, invokeS3RemoveAdminLambdaTrigger, - invokeS3RegisterAdminTrigger} from './storage-api'; +import { + invokeS3AddResource, + invokeS3GetAllDefaults, + invokeS3GetResourceName, + invokeS3GetUserInputs, + invokeS3RegisterAdminTrigger, + invokeS3RemoveAdminLambdaTrigger +} from './storage-api'; const { ResourceDoesNotExistError, ResourceAlreadyExistsError, exitOnNextTick } = require('amplify-cli-core'); const inquirer = require('inquirer'); const path = require('path'); @@ -36,9 +44,9 @@ const prefixForAdminTrigger = 'protected/predictions/index-faces/'; // TODO support appsync const PREDICTIONS_WALKTHROUGH_MODE = { - ADD : 'ADD', - UPDATE : 'UPDATE' -} + ADD: 'ADD', + UPDATE: 'UPDATE', +}; async function addWalkthrough(context) { while (!checkIfAuthExists(context)) { @@ -89,27 +97,27 @@ async function updateWalkthrough(context) { resourceObj = resourceAnswer.resource; } - return await configure(context, resourceObj, PREDICTIONS_WALKTHROUGH_MODE.ADD ); + return await configure(context, resourceObj, PREDICTIONS_WALKTHROUGH_MODE.UPDATE); } - -async function createAndRegisterAdminLambdaS3Trigger( context, predictionsResourceName, s3ResourceName ){ - let predictionsTriggerFunctionName = await createNewFunction(context, predictionsResourceName, s3ResourceName); +async function createAndRegisterAdminLambdaS3Trigger(context, predictionsResourceName, s3ResourceName, configMode) { + //In Add mode, predictions cloudformation is not yet created, hence do not use this in add-function + const predictionsResourceSavedName = configMode === PREDICTIONS_WALKTHROUGH_MODE.ADD ? undefined : predictionsResourceName; + let predictionsTriggerFunctionName = await createNewFunction(context, predictionsResourceSavedName, s3ResourceName); // adding additinal lambda trigger - const adminTriggerFunctionParams = { + const adminTriggerFunctionParams = { tag: 'adminTriggerFunction', - category : 'predictions', //function is owned by storage category - permissions : ['CREATE_AND_UPDATE', 'READ', 'DELETE'], //permissions to access S3 - triggerFunction : predictionsTriggerFunctionName, - triggerEvents : ['s3:ObjectCreated:*', 's3:ObjectRemoved:*'], //s3 events to trigger S3 - triggerPrefix : [{ prefix : prefixForAdminTrigger, prefixTransform : 'NONE'}] //trigger only if events are seen on the p + category: 'predictions', //function is owned by storage category + permissions: ['CREATE_AND_UPDATE', 'READ', 'DELETE'], //permissions to access S3 + triggerFunction: predictionsTriggerFunctionName, + triggerEvents: ['s3:ObjectCreated:*', 's3:ObjectRemoved:*'], //s3 events to trigger S3 + triggerPrefix: [{ prefix: prefixForAdminTrigger, prefixTransform: 'NONE' }], //trigger only if events are seen on the p }; - const s3UserInputs = await invokeS3RegisterAdminTrigger( context, s3ResourceName, adminTriggerFunctionParams ); + const s3UserInputs = await invokeS3RegisterAdminTrigger(context, s3ResourceName, adminTriggerFunctionParams); return s3UserInputs; } - -async function configure(context, predictionsResourceObj, configMode /*add/update*/ ) { +async function configure(context, predictionsResourceObj, configMode /*add/update*/) { const { amplify } = context; const defaultValues = getAllDefaults(amplify.getProjectDetails()); const projectBackendDirPath = context.amplify.pathManager.getBackendDirPath(); @@ -160,8 +168,8 @@ async function configure(context, predictionsResourceObj, configMode /*add/upda let s3Resource = {}; let predictionsTriggerFunctionName; - if ( answers.adminTask ) { - const s3ResourceName = await invokeS3GetResourceName(context); + if (answers.adminTask) { + const s3ResourceName = await invokeS3GetResourceName(context); const predictionsResourceName = parameters.resourceName; // Check is storage already exists in the project @@ -171,8 +179,8 @@ async function configure(context, predictionsResourceObj, configMode /*add/upda s3Resource.resourceName = s3UserInputs.resourceName; // Check if any lambda triggers are already existing in the project. if (!s3UserInputs.adminTriggerFunction) { - s3UserInputs = await createAndRegisterAdminLambdaS3Trigger( context, predictionsResourceName, s3Resource.resourceName ) - predictionsTriggerFunctionName = s3UserInputs.adminTriggerFunction.triggerFunction + s3UserInputs = await createAndRegisterAdminLambdaS3Trigger(context, predictionsResourceName, s3Resource.resourceName, configMode); + predictionsTriggerFunctionName = s3UserInputs.adminTriggerFunction.triggerFunction; } else { predictionsTriggerFunctionName = s3UserInputs.adminTriggerFunction.triggerFunction; } @@ -180,8 +188,13 @@ async function configure(context, predictionsResourceObj, configMode /*add/upda //create S3 bucket s3Resource = await addS3ForIdentity(context, answers.access, undefined, predictionsResourceName); //create admin lamda and register with s3 as trigger - const s3UserInputs = await createAndRegisterAdminLambdaS3Trigger( context, predictionsResourceName, s3Resource.resourceName ); - predictionsTriggerFunctionName = s3UserInputs.adminTriggerFunction.triggerFunction + const s3UserInputs = await createAndRegisterAdminLambdaS3Trigger( + context, + predictionsResourceName, + s3Resource.resourceName, + configMode, + ); + predictionsTriggerFunctionName = s3UserInputs.adminTriggerFunction.triggerFunction; } s3Resource.functionName = predictionsTriggerFunctionName; @@ -203,9 +216,11 @@ async function configure(context, predictionsResourceObj, configMode /*add/upda const s3ResourceName = s3ResourceAlreadyExists(context); if (s3ResourceName) { let s3UserInputs = await invokeS3GetUserInputs(context, s3ResourceName); - if ( s3UserInputs.adminLambdaTrigger && - s3UserInputs.adminLambdaTrigger.triggerFunction && - s3UserInputs.adminLambdaTrigger.triggerFunction !== 'NONE'){ + if ( + s3UserInputs.adminLambdaTrigger && + s3UserInputs.adminLambdaTrigger.triggerFunction && + s3UserInputs.adminLambdaTrigger.triggerFunction !== 'NONE' + ) { await invokeS3RemoveAdminLambdaTrigger(context, s3ResourceName); } } @@ -237,7 +252,7 @@ async function configure(context, predictionsResourceObj, configMode /*add/upda attributes: ['BucketName'], }); - if (answers.folderPolicies === 'app' && parameters.resourceName) { + if (answers.folderPolicies === 'app' && parameters.resourceName && configMode != PREDICTIONS_WALKTHROUGH_MODE.ADD) { addStorageIAMResourcestoIdentifyCFNFile(context, parameters.resourceName, s3Resource.resourceName); } } @@ -248,7 +263,7 @@ async function configure(context, predictionsResourceObj, configMode /*add/upda Object.assign(defaultValues, options); const { dependsOn } = defaultValues; - const amplifyMetaValues = { + let amplifyMetaValues = { resourceName, service, dependsOn, @@ -415,8 +430,7 @@ async function addS3ForIdentity(context, storageAccess, bucketName, predictionsR } else { s3UserInputs = getAllAuthDefaultPerm(s3UserInputs); } - s3UserInputs.adminTriggerFunction = 'NONE'; - s3UserInputs.triggerFunction = 'NONE'; + /** * Create S3 bucket and add admin trigger. */ @@ -465,7 +479,7 @@ async function addS3ForIdentity(context, storageAccess, bucketName, predictionsR return { bucketName: resultS3UserInput.bucketName, resourceName: resultS3UserInput.resourceName, - functionName: resultS3UserInput.adminTriggerFunction.triggerFunction, + functionName: resultS3UserInput.adminTriggerFunction ? resultS3UserInput.adminTriggerFunction.triggerFunction : undefined, }; } @@ -486,8 +500,39 @@ function s3ResourceAlreadyExists(context) { return resourceName; } +async function postCFNGenUpdateLambdaResourceInPredictions(context, predictionsResourceName, functionName, s3ResourceName) { + const projectBackendDirPath = context.amplify.pathManager.getBackendDirPath(); + const identifyCFNFilePath = path.join( + projectBackendDirPath, + category, + predictionsResourceName, + `${predictionsResourceName}-template.json`, + ); + let identifyCFNFile; + identifyCFNFile = JSONUtilities.readJson(identifyCFNFilePath); + + identifyCFNFile = generateLambdaAccessForRekognition(identifyCFNFile, functionName, s3ResourceName); + JSONUtilities.writeJson(identifyCFNFilePath, identifyCFNFile); + + const amplifyMeta = stateManager.getMeta(); + const dependsOnResources = amplifyMeta.predictions[predictionsResourceName].dependsOn; + dependsOnResources.push({ + category: functionCategory, + resourceName: functionName, + attributes: ['Name', 'Arn', 'LambdaExecutionRole'], + }); + dependsOnResources.push({ + category: storageCategory, + resourceName: s3ResourceName, + attributes: ['BucketName'], + }); + + // Update DependsOn + context.amplify.updateamplifyMetaAfterResourceUpdate(category, predictionsResourceName, 'dependsOn', dependsOnResources); +} + async function createNewFunction(context, predictionsResourceName, s3ResourceName) { - const targetDir = context.amplify.pathManager.getBackendDirPath(); + const targetDir = pathManager.getBackendDirPath(); const [shortId] = uuid().split('-'); const functionName = `RekognitionIndexFacesTrigger${shortId}`; const pluginDir = __dirname; @@ -522,43 +567,9 @@ async function createNewFunction(context, predictionsResourceName, s3ResourceNam // copy over the files await context.amplify.copyBatch(context, copyJobs, defaults); - if (predictionsResourceName) { - const projectBackendDirPath = context.amplify.pathManager.getBackendDirPath(); - const identifyCFNFilePath = path.join( - projectBackendDirPath, - category, - predictionsResourceName, - `${predictionsResourceName}-template.json`, - ); - let identifyCFNFile; - try { - identifyCFNFile = context.amplify.readJsonFile(identifyCFNFilePath); - } catch (fileNotFoundError ){ - //generate template and switch to update mode ? - } - identifyCFNFile = generateLambdaAccessForRekognition(identifyCFNFile, functionName, s3ResourceName); - const identifyCFNString = JSON.stringify(identifyCFNFile, null, 4); - fs.writeFileSync(identifyCFNFilePath, identifyCFNString, 'utf8'); - - const amplifyMetaFilePath = path.join(projectBackendDirPath, amplifyMetaFilename); - const amplifyMetaFile = context.amplify.readJsonFile(amplifyMetaFilePath); - const dependsOnResources = amplifyMetaFile.predictions[predictionsResourceName].dependsOn; - dependsOnResources.push({ - category: functionCategory, - resourceName: functionName, - attributes: ['Name', 'Arn', 'LambdaExecutionRole'], - }); - dependsOnResources.push({ - category: storageCategory, - resourceName: s3ResourceName, - attributes: ['BucketName'], - }); - - // Update DependsOn - context.amplify.updateamplifyMetaAfterResourceUpdate(category, predictionsResourceName, 'dependsOn', dependsOnResources); + await postCFNGenUpdateLambdaResourceInPredictions(context, predictionsResourceName, functionName, s3ResourceName); } - // Update amplify-meta and backend-config const backendConfigs = { @@ -581,54 +592,10 @@ function addStorageIAMResourcestoIdentifyCFNFile(context, predictionsResourceNam predictionsResourceName, `${predictionsResourceName}-template.json`, ); - let identifyCFNFile = context.amplify.readJsonFile(identifyCFNFilePath); + let identifyCFNFile = JSONUtilities.readJson(identifyCFNFilePath); identifyCFNFile = generateStorageAccessForRekognition(identifyCFNFile, s3ResourceName, prefixForAdminTrigger); const identifyCFNString = JSON.stringify(identifyCFNFile, null, 4); fs.writeFileSync(identifyCFNFilePath, identifyCFNString, 'utf8'); } -function removeS3AdminLambdaTrigger(storageCFNFile, adminTriggerFunction) { - let modifyOnlyFilters = false; - const lambdaConfigurations = []; - storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations.forEach(triggers => { - if ( - !( - triggers.Filter && - typeof triggers.Filter.S3Key.Rules[0].Value === 'string' && - triggers.Filter.S3Key.Rules[0].Value.includes('index-faces') - ) - ) { - modifyOnlyFilters = true; - lambdaConfigurations.push(triggers); - } - }); - - storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations = lambdaConfigurations; - delete storageCFNFile.Resources.AdminTriggerPermissions; - delete storageCFNFile.Parameters.adminTriggerFunction; - delete storageCFNFile.Parameters[`function${adminTriggerFunction}Arn`]; - delete storageCFNFile.Parameters[`function${adminTriggerFunction}Name`]; - delete storageCFNFile.Parameters[`function${adminTriggerFunction}LambdaExecutionRole`]; - const index = storageCFNFile.Resources.S3Bucket.DependsOn.indexOf('AdminTriggerPermissions'); - if (index > -1) { - storageCFNFile.Resources.S3Bucket.DependsOn.splice(index, 1); - } - const roles = []; - storageCFNFile.Resources.S3TriggerBucketPolicy.Properties.Roles.forEach(role => { - if (!role.Ref.includes(adminTriggerFunction)) { - roles.push(role); - } - }); - storageCFNFile.Resources.S3TriggerBucketPolicy.Properties.Roles = roles; - - if (!modifyOnlyFilters) { - // Remove reference for triggerFunction - delete storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration; - delete storageCFNFile.Resources.S3TriggerBucketPolicy; - delete storageCFNFile.Resources.S3Bucket.DependsOn; - } - - return storageCFNFile; -} - -module.exports = { addWalkthrough, updateWalkthrough, removeS3AdminLambdaTrigger }; +module.exports = { addWalkthrough, updateWalkthrough }; diff --git a/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.test.ts b/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.test.ts index 4a8b178a03c..95f4b5fbce1 100644 --- a/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.test.ts +++ b/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.test.ts @@ -61,7 +61,7 @@ describe('add s3 walkthrough tests', () => { }); it('addWalkthrough() simple-auth test', async () => { - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation( async () => {return} ); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); jest.spyOn(s3AuthAPI, 'migrateAuthDependencyResource').mockReturnValue(new Promise((resolve, _reject)=>{ process.nextTick(() => resolve(undefined)); @@ -93,7 +93,7 @@ describe('add s3 walkthrough tests', () => { expect(S3InputState.prototype.saveCliInputPayload).toHaveBeenCalledWith(expectedCLIInputsJSON); }); it('addWalkthrough() simple-auth+guest test', async () => { - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); const mockDataBuilder = new S3MockDataBuilder(undefined); @@ -123,7 +123,7 @@ describe('add s3 walkthrough tests', () => { expect(S3InputState.prototype.saveCliInputPayload).toHaveBeenCalledWith(expectedCLIInputsJSON); }); it('addWalkthrough() simple-auth + trigger (new function) test', async () => { - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); const mockDataBuilder = new S3MockDataBuilder(undefined); @@ -156,7 +156,7 @@ describe('add s3 walkthrough tests', () => { expect(S3InputState.prototype.saveCliInputPayload).toHaveBeenCalledWith(expectedCLIInputsJSON); }); it('addWalkthrough() simple-auth + trigger (existing function) test', async () => { - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //Add Existing Lambda functions in resource status mockContext.amplify.getResourceStatus = () => { @@ -246,7 +246,7 @@ describe('update s3 permission walkthrough tests', () => { const currentCLIInputs = mockDataBuilder.removeMockTriggerFunction().getCLIInputs(); jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(()=> currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set Auth permissions in Expected Output (without Delete permissions) @@ -270,7 +270,7 @@ describe('update s3 permission walkthrough tests', () => { const currentCLIInputs = mockDataBuilder.removeMockTriggerFunction().getCLIInputs(); jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(()=> currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set Auth permissions in Expected Output (without Delete permissions) @@ -302,7 +302,7 @@ describe('update s3 permission walkthrough tests', () => { .getCLIInputs(); jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(()=> currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set Auth permissions in Expected Output (without Delete permissions) @@ -337,7 +337,7 @@ describe('update s3 permission walkthrough tests', () => { jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(()=> currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set Auth permissions in Expected Output (without Delete permissions) @@ -371,7 +371,7 @@ describe('update s3 permission walkthrough tests', () => { jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(()=> currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //** Add GuestAccess, GroupAccess @@ -450,7 +450,7 @@ describe('update s3 lambda-trigger walkthrough tests', () => { const currentCLIInputs = mockDataBuilder.removeMockTriggerFunction().getCLIInputs(); jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(() => currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set Auth permissions in Expected Output (without Delete permissions) @@ -486,7 +486,7 @@ describe('update s3 lambda-trigger walkthrough tests', () => { const currentCLIInputs = existingDataBuilder.addMockTriggerFunction(undefined).getCLIInputs(); jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(() => currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set simple auth and remove triggerFunction @@ -522,7 +522,7 @@ describe('update s3 lambda-trigger walkthrough tests', () => { jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(() => currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set simple-auth and set mockExistingFunctionName1 as the new trigger function @@ -563,7 +563,7 @@ describe('update s3 lambda-trigger walkthrough tests', () => { jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(() => currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation((async ()=>{ return })); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set simple-auth and set mockExistingFunctionName1 as the new trigger function @@ -606,7 +606,7 @@ describe('update s3 lambda-trigger walkthrough tests', () => { jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => true); //CLI Input exists jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(() => currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set simple-auth and set mockExistingFunctionName1 as the new trigger function @@ -703,7 +703,7 @@ describe('migrate s3 and update s3 permission walkthrough tests', () => { jest.spyOn(S3InputState.prototype, 'getOldS3ParamsForMigration').mockImplementation(()=>oldParams); jest.spyOn(S3InputState.prototype, 'cliInputFileExists').mockImplementation(() => false); //CLI Input doesnt exist - requires migration jest.spyOn(S3InputState.prototype, 'getUserInput').mockImplementation(()=> currentCLIInputs); //simple-auth - jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(() => true); + jest.spyOn(S3InputState.prototype, 'saveCliInputPayload').mockImplementation(async ()=>{ return }); jest.spyOn(AmplifyS3ResourceStackTransform.prototype, 'transform').mockImplementation(() => Promise.resolve()); //**Set Auth permissions in Expected Output (without Delete permissions) diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-questions.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-questions.ts index d57281298c7..4f248d59c6b 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-questions.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-questions.ts @@ -119,6 +119,26 @@ export async function askWhoHasAccessQuestion(context: $TSContext, defaultValues return answer as S3AccessType; } +/** + * Normalize User Access Role for View + * @param role + * @param groupName + * @returns + */ +function normalizeUserRole( role : S3UserAccessRole , groupName :string | undefined){ + switch(role){ + case S3UserAccessRole.AUTH :{ + return 'Authenticated'; + } + case S3UserAccessRole.GUEST :{ + return S3UserAccessRole.GUEST; + } + case S3UserAccessRole.GROUP:{ + return groupName + } + } +} + export async function askCRUDQuestion( role: S3UserAccessRole, groupName: string | undefined = undefined, @@ -126,7 +146,7 @@ export async function askCRUDQuestion( defaultValues: S3UserInputs, ): Promise> { const roleDefaultValues = getRoleAccessDefaultValues(role, groupName, defaultValues); - const userRole = role === S3UserAccessRole.GROUP ? groupName : role; + const userRole = normalizeUserRole(role, groupName); const message = `What kind of access do you want for ${userRole} users?`; const choices = possibleCRUDOperations; const initialIndexes = getIndexArrayByValue(possibleCRUDOperations, roleDefaultValues); diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-resource-api.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-resource-api.ts index 578810cee0a..bf264e8b346 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-resource-api.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-resource-api.ts @@ -90,7 +90,7 @@ export async function s3AddStorageLambdaTrigger( } let s3UserInput = cliInputsState.getUserInput(); s3UserInput.triggerFunction = storageLambdaTrigger.triggerFunction; - cliInputsState.saveCliInputPayload(s3UserInput); + await cliInputsState.saveCliInputPayload(s3UserInput); await createNewLambdaAndUpdateCFN(context, s3UserInput.triggerFunction, undefined /* generate unique uuid*/); await s3APIHelperTransformAndSaveState(context, s3UserInput, CLISubCommandType.UPDATE); return s3UserInput; @@ -136,7 +136,6 @@ export async function s3RemoveStorageLambdaTrigger(context: $TSContext, s3Resour s3ResourceName: string, adminLambdaTrigger: S3UserInputTriggerFunctionParams, ) { - console.log("s3RegisterAdminTrigger : s3ResourceName: ", s3ResourceName , " adminLambdaTrigger: ", adminLambdaTrigger ); let cliInputsState = new S3InputState(s3ResourceName, undefined); //Check if migration is required if (!cliInputsState.cliInputFileExists()) { @@ -202,7 +201,7 @@ async function s3APIHelperTransformAndSaveState(context: $TSContext, storageInpu } else { cliInputsState = new S3InputState(storageInput.resourceName as string, undefined ); } - cliInputsState.saveCliInputPayload(storageInput); + await cliInputsState.saveCliInputPayload(storageInput); //Generate Cloudformation const stackGenerator = new AmplifyS3ResourceStackTransform(storageInput.resourceName as string, context); diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-user-input-state.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-user-input-state.ts index 1e9cad8c53b..010acc0b7ec 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-user-input-state.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-user-input-state.ts @@ -201,7 +201,7 @@ export class S3InputState { } const oldS3Params: MigrationParams = this.getOldS3ParamsForMigration(); const cliInputs: S3UserInputs = this.genInputParametersForMigration(oldS3Params); - this.saveCliInputPayload(cliInputs); + await this.saveCliInputPayload(cliInputs); this.removeOldS3ConfigFiles(oldS3Params); } @@ -310,7 +310,7 @@ export class S3InputState { return this._inputPayload; } - public async isCLIInputsValid(cliInputs?: S3UserInputs) { + public async isCLIInputsValid(cliInputs?: S3UserInputs) : Promise { if (!cliInputs) { cliInputs = this.getCliInputPayload(); } @@ -470,8 +470,8 @@ export class S3InputState { return undefined; } - public saveCliInputPayload(cliInputs: S3UserInputs): void { - this.isCLIInputsValid(cliInputs); + public async saveCliInputPayload(cliInputs: S3UserInputs): Promise { + await this.isCLIInputsValid(cliInputs); this._inputPayload = cliInputs; fs.ensureDirSync(path.join(pathManager.getBackendDirPath(), this._category, this._resourceName)); diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts index d6e9b13b248..c6788eac828 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts @@ -91,7 +91,7 @@ export async function addWalkthrough(context: $TSContext, defaultValuesFilename: //Save CLI Inputs payload const cliInputsState = new S3InputState(cliInputs.resourceName as string, cliInputs); - cliInputsState.saveCliInputPayload(cliInputs); + await cliInputsState.saveCliInputPayload(cliInputs); //Generate Cloudformation const stackGenerator = new AmplifyS3ResourceStackTransform(cliInputs.resourceName as string, context); @@ -171,7 +171,7 @@ export async function updateWalkthrough(context: $TSContext) { } //Save CLI Inputs payload - cliInputsState.saveCliInputPayload(cliInputs); + await cliInputsState.saveCliInputPayload(cliInputs); //Generate Cloudformation const stackGenerator = new AmplifyS3ResourceStackTransform(cliInputs.resourceName as string, context); await stackGenerator.transform(CLISubCommandType.UPDATE); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts index 50760e7780f..d2dce2046e2 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts @@ -2,6 +2,7 @@ import { Input } from '../../../domain/input'; import { constructMockPluginPlatform } from './mock-plugin-platform'; import { constructContext } from '../../../context-manager'; import { getCategoryPluginInfo } from '../../../extensions/amplify-helpers/get-category-pluginInfo'; +import { $TSContext } from 'amplify-cli-core'; test('getCategoryPluginInfo returns the first pluginInfo to match category', () => { const mockPluginPlatform = constructMockPluginPlatform(); @@ -13,7 +14,7 @@ test('getCategoryPluginInfo returns the first pluginInfo to match category', () ]; const mockInput = new Input(mockProcessArgv); - const mockContext = constructContext(mockPluginPlatform, mockInput); + const mockContext = constructContext(mockPluginPlatform, mockInput) as unknown as $TSContext; const hostingPluginInfo = getCategoryPluginInfo(mockContext, 'hosting'); expect(hostingPluginInfo).toBeDefined(); }); @@ -28,7 +29,7 @@ test('getCategoryPluginInfo returns pluginInfo when plugin matches category and ]; const mockInput = new Input(mockProcessArgv); - const mockContext = constructContext(mockPluginPlatform, mockInput); + const mockContext = constructContext(mockPluginPlatform, mockInput) as unknown as $TSContext; const hostingAmplifyhostingPluginInfo = getCategoryPluginInfo(mockContext, 'hosting', 'amplifyhosting'); expect(hostingAmplifyhostingPluginInfo).toBeDefined(); }); @@ -43,7 +44,7 @@ test('getCategoryPluginInfo returns the first pluginInfo to match only category ]; const mockInput = new Input(mockProcessArgv); - const mockContext = constructContext(mockPluginPlatform, mockInput); + const mockContext = constructContext(mockPluginPlatform, mockInput) as unknown as $TSContext; const hostingPluginInfo = getCategoryPluginInfo(mockContext, 'hosting', 'S3'); expect(hostingPluginInfo).toBeDefined(); }); diff --git a/packages/amplify-e2e-core/src/categories/storage.ts b/packages/amplify-e2e-core/src/categories/storage.ts index c49987bd373..ac60309a62f 100644 --- a/packages/amplify-e2e-core/src/categories/storage.ts +++ b/packages/amplify-e2e-core/src/categories/storage.ts @@ -541,7 +541,7 @@ export function addS3StorageWithIdpAuth(projectDir: string): Promise { return new Promise((resolve, reject) => { let chain = spawn(getCLIPath(), ['add', 'storage'], { cwd: projectDir, stripColors: true }); - singleSelect(chain.wait('Please select from one of the below mentioned services:'), 'Content (Images, audio, video, etc.)', [ + singleSelect(chain.wait('Select from one of the below mentioned services:'), 'Content (Images, audio, video, etc.)', [ 'Content (Images, audio, video, etc.)', 'NoSQL Database', ]);