Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully populate mock function environment variables #6551

Merged
merged 6 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export async function updateFunctionResource(context, category, service, paramet
}

if (!parameters || (parameters && !parameters.skipEdit)) {
const breadcrumb = context.amplify.readBreadcrumbs(categoryName, parameters.resourceName);
const breadcrumb = context.amplify.readBreadcrumbs(context, categoryName, parameters.resourceName);
const displayName = 'trigger' in parameters ? parameters.resourceName : undefined;
await openEditor(context, category, parameters.resourceName, { defaultEditorFile: breadcrumb.defaultEditorFile }, displayName, false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const startLambda = (request: InvocationRequest, portNumber: number, lambda: { e

const lambdaProcess: ExecaChildProcess = execa.command(lambda.executable, {
env: envVars,
extendEnv: false,
cwd: lambda.cwd,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const invokeResource = async (request: InvocationRequest, context: any) =
],
{
input: request.event,
env: request.envVars,
extendEnv: false,
},
);
childProcess.stdout.pipe(process.stdout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export type InvokeOptions = {
export const getLambdaChildProcess = (environment: any, functionName: string = 'execute.js'): execa.ExecaChildProcess => {
return execa.node(path.join(__dirname, functionName), [], {
env: environment || {},
extendEnv: false,
});
}
};
7 changes: 7 additions & 0 deletions packages/amplify-provider-awscloudformation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import { CognitoUserPoolService, createCognitoUserPoolService } from './aws-util
import { IdentityPoolService, createIdentityPoolService } from './aws-utils/IdentityPoolService';
import { S3Service, createS3Service } from './aws-utils/S3Service';
import { DynamoDBService, createDynamoDBService } from './aws-utils/DynamoDBService';
import { resolveAppId } from './utils/resolve-appId';
import { loadConfigurationForEnv } from './configuration-manager';

export { resolveAppId } from './utils/resolve-appId';
export { loadConfigurationForEnv } from './configuration-manager';

function init(context) {
return initializer.run(context);
Expand Down Expand Up @@ -128,4 +133,6 @@ module.exports = {
createS3Service,
DynamoDBService,
createDynamoDBService,
resolveAppId,
loadConfigurationForEnv,
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export async function pythonInvoke(context: any, request: InvocationRequest): Pr
throw new Error(`Could not find 'python3' or 'python' executable in the PATH.`);
}

const childProcess = execa('pipenv', ['run', pyBinary, shimPath, handlerFile + '.py', handlerName]);
const childProcess = execa('pipenv', ['run', pyBinary, shimPath, handlerFile + '.py', handlerName], {
env: request.envVars,
extendEnv: false,
});

childProcess.stdout.pipe(process.stdout);
childProcess.stdin.write(JSON.stringify({ event: request.event, context: {} }) + '\n');
Expand Down
14 changes: 7 additions & 7 deletions packages/amplify-util-mock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"build": "tsc",
"build-tests": "tsc --build tsconfig.tests.json",
"watch": "tsc -w",
"clean": "rimraf ./lib"
"clean": "rimraf lib tsconfig.tsbuildinfo"
},
"dependencies": {
"@hapi/topo": "^5.0.0",
Expand All @@ -31,20 +31,25 @@
"amplify-cli-core": "1.16.0",
"amplify-codegen": "2.21.1",
"amplify-dynamodb-simulator": "1.17.8",
"amplify-provider-awscloudformation": "4.38.0",
"amplify-storage-simulator": "1.5.1",
"chokidar": "^3.3.1",
"detect-port": "^1.3.0",
"dotenv": "^8.2.0",
"execa": "^4.1.0",
"fs-extra": "^8.1.0",
"lodash": "^4.17.19",
"semver": "^7.1.3",
"which": "^2.0.2"
},
"devDependencies": {
"@types/detect-port": "^1.3.0",
"@types/lodash": "^4.14.149",
"@types/node": "^10.17.13",
"@types/semver": "^7.1.0",
"@types/which": "^1.3.2",
"amplify-function-plugin-interface": "1.6.0",
"amplify-nodejs-function-runtime-provider": "1.4.3",
"aws-appsync": "^2.0.2",
"aws-sdk": "^2.765.0",
"aws-sdk-mock": "^5.1.0",
Expand Down Expand Up @@ -90,12 +95,7 @@
"jsx",
"json",
"node"
],
"globals": {
"ts-jest": {
"tsconfig": "<rootDir>/tsconfig.tests.json"
}
}
]
},
"jest-junit": {
"outputDirectory": "reports/junit/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const matcher = new RegExp('(?<apiId>.+):GetAtt:(?<modelName>.+)Table:Name');

/**
* Resolves Fn::ImportValue instances that reference a table generated by @model
*
* This is a bit of a hack. The "right" way to do it would be to load the physical ids of the tables as exports
* and pass that context in when parsing the stacks but we don't currently have a good way to resolve those physical ids
* before processing the stacks
* @param val The input to Fn::ImportValue
* @param env The current environment
* @returns if val matches the expected pattern, the expected export value of the table; otherwise val
*/
export const importModelTableResolver = (val: string, env: string): string => {
const match = matcher.exec(val);
if (!match) {
return val;
}
return [match.groups.modelName, match.groups.apiId, env].join('-');
};
29 changes: 10 additions & 19 deletions packages/amplify-util-mock/src/CFNParser/intrinsic-functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CloudFormationParseContext } from './types';
import { isPlainObject } from 'lodash';
import { importModelTableResolver } from './import-model-table-resolver';

export function cfnJoin(valNode: [string, string[]], { params, conditions, resources, exports }: CloudFormationParseContext, processValue) {
if (!(Array.isArray(valNode) && valNode.length === 2 && Array.isArray(valNode[1]))) {
Expand All @@ -12,13 +13,7 @@ export function cfnJoin(valNode: [string, string[]], { params, conditions, resou

export function cfnSub(valNode, { params, conditions, resources, exports }: CloudFormationParseContext, processValue) {
if (typeof valNode === 'string') {
exports[valNode] = templateReplace(valNode, params);
return processValue(valNode, {
params,
conditions,
resources,
exports,
});
return templateReplace(valNode, params);
}
if (!Array.isArray(valNode) && valNode.length !== 2) {
throw new Error(`FN::Sub expects an array with 2 elements instead got ${JSON.stringify(valNode)}`);
Expand Down Expand Up @@ -117,20 +112,20 @@ export function cfnRef(valNode, { params, resources }: CloudFormationParseContex
return key;
}

export function cfnSelect(valNode, { params, conditions, resources, exports }: CloudFormationParseContext, processValue) {
export function cfnSelect(valNode, parseContext: CloudFormationParseContext, processValue) {
if (!Array.isArray(valNode) && valNode.length !== 2) {
throw new Error(`FN::Select expects an array with 2 elements instead got ${JSON.stringify(valNode)}`);
}

const index = parseInt(valNode[0], 10);
if (!Array.isArray(valNode[1])) {
throw new Error(`FN::Select expects list item to be an array instead got ${JSON.stringify(valNode)}`);
const selectionList = Array.isArray[valNode[1]] ? valNode[1] : processValue(valNode[1], parseContext);
if (!Array.isArray(selectionList)) {
throw new Error(`FN::Select expects list item to be an array instead got ${JSON.stringify(selectionList)}`);
}
if (index >= valNode[1].length) {
throw new Error(`FN::Select expects index tp be less than or equal to size of listOfObject ${JSON.stringify(valNode)}`);
if (index >= selectionList.length) {
throw new Error(`FN::Select expects index to be less than or equal to the length of list: ${JSON.stringify(selectionList)}`);
}
const map = valNode[1].map(item => processValue(item, { params, conditions, resources, exports }));
return map[index];
return processValue(selectionList[index]);
}

export function cfnIf(valNode, { params, conditions, resources, exports }: CloudFormationParseContext, processValue) {
Expand Down Expand Up @@ -189,11 +184,7 @@ export function cfnImportValue(valNode, { params, conditions, resources, exports
throw new Error(`FN::ImportValue expects an array with 1 elements instead got ${JSON.stringify(valNode)}`);
}
const key = processValue(valNode, { params, conditions, resources, exports });
if (!Object.keys(exports).includes(key)) {
console.warn(`Fn::ImportValue could not find ${key} in exports. Using unsubstituted value.`);
return key;
}
return exports[key];
return exports[key] ?? importModelTableResolver(key, params.env);
}

export function cfnCondition(valNode, { conditions }: CloudFormationParseContext, processValue) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { lambdaFunctionHandler } from '../lambda-resource-processor';
import { CloudFormationResource } from '../stack/types';
import { $TSAny } from 'amplify-cli-core';
import { lambdaFunctionHandler } from './lambda';
import { CloudFormationResource, ProcessedLambdaFunction } from '../stack/types';
import { CloudFormationParseContext } from '../types';
import {
appSyncAPIKeyResourceHandler,
Expand All @@ -16,7 +17,7 @@ export type CloudFormationResourceProcessorFn = (
resourceName: string,
resource: CloudFormationResource,
cfnContext: CloudFormationParseContext,
) => any; //CloudFormationProcessedResourceResult;
) => ProcessedLambdaFunction | $TSAny; // TODO should type the rest of the handler responses

const resourceProcessorMapping: Record<string, CloudFormationResourceProcessorFn> = {};
export function getResourceProcessorFor(resourceType: string): CloudFormationResourceProcessorFn {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { parseValue } from '../field-parser';
import { CloudFormationParseContext } from '../types';
import { CloudFormationProcessedResourceResult } from '../stack/types';
import { parseValue } from '../field-parser';
import { CloudFormationResource, CloudFormationResourceProperty, ProcessedLambdaFunction } from '../stack/types';

export type LambdaFunctionConfig = CloudFormationProcessedResourceResult & {
name: string;
handler: string;
basePath?: string;
environment?: object;
};
export function lambdaFunctionHandler(resourceName, resource, cfnContext: CloudFormationParseContext): LambdaFunctionConfig {
/**
* Handles the parsing of a lambda CFN resource into relevant bits of information
* @param _ (resourceName) not used, but required to satisfy the CloudFormationResourceProcessorFn interface
* @param resource The CFN resource as a JSON object
* @param cfnContext The parameters, exports and other context required to parse the CFN
*/
export const lambdaFunctionHandler = (
_,
resource: CloudFormationResource,
cfnContext: CloudFormationParseContext,
): ProcessedLambdaFunction => {
const name: string = parseValue(resource.Properties.FunctionName, cfnContext);
const handler = parseValue(resource.Properties.Handler, cfnContext);
const environment =
resource.Properties.Environment && resource.Properties.Environment.Variables
? Object.entries(resource.Properties.Environment.Variables).reduce(
(acc, [varName, varValue]) => ({
...acc,
[varName]: parseValue(varValue, cfnContext),
}),
{},
)
: {};
const cfnEnvVars = (resource?.Properties?.Environment as CloudFormationResourceProperty)?.Variables || {};
const environment = Object.entries(cfnEnvVars).reduce(
(acc, [varName, varVal]) => ({
...acc,
[varName]: parseValue(varVal, cfnContext),
}),
{} as Record<string, string>,
);
return {
cfnExposedAttributes: { Arn: 'arn' },
arn: `arn:aws:lambda:{aws-region}:{aws-account-number}:function/${name}/LATEST`,
Expand All @@ -29,4 +31,4 @@ export function lambdaFunctionHandler(resourceName, resource, cfnContext: CloudF
handler,
environment,
};
}
};
6 changes: 6 additions & 0 deletions packages/amplify-util-mock/src/CFNParser/stack/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,9 @@ export type CloudFormationProcessedResource = {
export type CloudFormationTemplateFetcher = {
getCloudFormationStackTemplate: (templateName: string) => CloudFormationTemplate;
};

export type ProcessedLambdaFunction = CloudFormationProcessedResourceResult & {
name: string;
handler: string;
environment: Record<string, string>;
};
4 changes: 2 additions & 2 deletions packages/amplify-util-mock/src/CFNParser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export type CloudFormationParsedResource = {
};

export type CloudFormationParseContext = {
params: any;
params: Record<string, string>;
conditions: object;
resources: Record<string, CloudFormationParsedResource>;
exports: object;
exports: Record<string, string>;
};

export type CloudFormationWalkContext = CloudFormationParseContext & {
Expand Down
10 changes: 7 additions & 3 deletions packages/amplify-util-mock/src/__e2e__/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import * as path from 'path';
import { v4 } from 'uuid';
import { processTransformerStacks } from '../../CFNParser/appsync-resource-processor';
import { configureDDBDataSource, createAndUpdateTable } from '../../utils/dynamo-db';
import { invoke } from '../../utils/lambda/invoke';
import { getFunctionDetails } from './lambda-helper';
import { DynamoDB } from 'aws-sdk';
import { functionRuntimeContributorFactory } from 'amplify-nodejs-function-runtime-provider';

const invoke = functionRuntimeContributorFactory({}).invoke;

jest.mock('amplify-cli-core', () => ({
pathManager: {
Expand Down Expand Up @@ -73,8 +75,10 @@ async function configureLambdaDataSource(config) {
d.invoke = payload => {
logDebug('Invoking lambda with config', lambdaConfig);
return invoke({
...lambdaConfig,
event: payload,
srcRoot: lambdaConfig.packageFolder,
runtime: 'nodejs',
handler: `${functionName}.${lambdaConfig.handler}`,
event: JSON.stringify(payload),
});
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as fs from 'fs-extra';

export function getFunctionDetails(fnName: string) {
const lambdaFolder = path.join(__dirname, 'lambda_functions');
if (!fs.existsSync(path.join(lambdaFolder, `${fnName}.js`))) {
if (!fs.existsSync(path.join(lambdaFolder, 'src', `${fnName}.js`))) {
throw new Error(`Can not find lambda function ${fnName}`);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { importModelTableResolver } from '../../CFNParser/import-model-table-resolver';

describe('import model table resolver', () => {
it('replaces matched imports', () => {
expect(importModelTableResolver('1234:GetAtt:MyModelTable:Name', 'dev')).toEqual('MyModel-1234-dev');
});

it('identity function if no match', () => {
expect(importModelTableResolver('not a match', 'dev')).toEqual('not a match');
});
});
Loading