diff --git a/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap b/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap index fe2c35ecf6..048b06eaa5 100644 --- a/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap +++ b/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap @@ -53,30 +53,3 @@ $util.toJson($ctx.result) "Query.echo.res.vtl": "$util.toJson($ctx.prev.result)", } `; - -exports[`it generates the expected resources 1`] = ` -Object { - "InvokeEchofunctionLambdaDataSource.req.vtl": "## [Start] Invoke AWS Lambda data source: EchofunctionLambdaDataSource. ** -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"Invoke\\", - \\"payload\\": { - \\"typeName\\": $util.toJson($ctx.stash.get(\\"typeName\\")), - \\"fieldName\\": $util.toJson($ctx.stash.get(\\"fieldName\\")), - \\"arguments\\": $util.toJson($ctx.arguments), - \\"identity\\": $util.toJson($ctx.identity), - \\"source\\": $util.toJson($ctx.source), - \\"request\\": $util.toJson($ctx.request), - \\"prev\\": $util.toJson($ctx.prev) - } -} -## [End] Invoke AWS Lambda data source: EchofunctionLambdaDataSource. **", - "InvokeEchofunctionLambdaDataSource.res.vtl": "## [Start] Handle error or return result. ** -#if( $ctx.error ) - $util.error($ctx.error.message, $ctx.error.type) -#end -$util.toJson($ctx.result) -## [End] Handle error or return result. **", - "Query.echo.res.vtl": "$util.toJson($ctx.prev.result)", -} -`; diff --git a/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts b/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts index acd55f87e2..8ebf122f5e 100644 --- a/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts +++ b/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts @@ -11,10 +11,13 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { AuthorizationType } from '@aws-cdk/aws-appsync'; import * as cdk from '@aws-cdk/core'; import { obj, str, ref, printBlock, compoundExpression, qref, raw, iff, Expression } from 'graphql-mapping-template'; -import { FunctionDirectiveConfig, FunctionResourceIDs, ResolverResourceIDs, ResourceConstants } from 'graphql-transformer-common'; +import { FunctionResourceIDs, ResolverResourceIDs, ResourceConstants } from 'graphql-transformer-common'; import { DirectiveNode, ObjectTypeDefinitionNode, InterfaceTypeDefinitionNode, FieldDefinitionNode } from 'graphql'; -interface FunctionDirectiveWithResolverConfig extends FunctionDirectiveConfig { +type FunctionDirectiveConfiguration = { + name: string; + region: string | undefined; + accountId: string | undefined; resolverTypeName: string; resolverFieldName: string; } @@ -25,7 +28,7 @@ const directiveDefinition = /* GraphQL */ ` `; export class FunctionTransformer extends TransformerPluginBase { - private resolverGroups: Map = new Map(); + private resolverGroups: Map = new Map(); constructor() { super('amplify-function-transformer', directiveDefinition); @@ -41,7 +44,7 @@ export class FunctionTransformer extends TransformerPluginBase { const args = directiveWrapped.getArguments({ resolverTypeName: parent.name.value, resolverFieldName: definition.name.value, - }, generateGetArgumentsInput(acc.featureFlags)) as FunctionDirectiveWithResolverConfig; + } as FunctionDirectiveConfiguration, generateGetArgumentsInput(acc.featureFlags)); let resolver = this.resolverGroups.get(definition); if (resolver === undefined) { @@ -71,13 +74,13 @@ export class FunctionTransformer extends TransformerPluginBase { this.resolverGroups.forEach((resolverFns, fieldDefinition) => { resolverFns.forEach(config => { // Create data sources that register Lambdas and IAM roles. - const dataSourceId = FunctionResourceIDs.FunctionDataSourceID(config); + const dataSourceId = FunctionResourceIDs.FunctionDataSourceID(config.name, config.region, config.accountId); if (!createdResources.has(dataSourceId)) { const dataSource = context.api.host.addLambdaDataSource( dataSourceId, lambda.Function.fromFunctionAttributes(stack, `${dataSourceId}Function`, { - functionArn: lambdaArnResource(env, config), + functionArn: lambdaArnResource(env, config.name, config.region, config.accountId), }), {}, stack, @@ -86,7 +89,7 @@ export class FunctionTransformer extends TransformerPluginBase { } // Create AppSync functions. - const functionId = FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(config); + const functionId = FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(config.name, config.region, config.accountId); let func = createdResources.get(functionId); if (func === undefined) { @@ -179,21 +182,18 @@ export class FunctionTransformer extends TransformerPluginBase { }; } -function lambdaArnResource(env: cdk.CfnParameter, fdConfig: FunctionDirectiveConfig): string { +function lambdaArnResource(env: cdk.CfnParameter, name: string, region?: string, accountId?: string): string { const substitutions: { [key: string]: string } = {}; - if (fdConfig.name.includes('${env}')) { + if (name.includes('${env}')) { substitutions.env = env as unknown as string; } return cdk.Fn.conditionIf( ResourceConstants.CONDITIONS.HasEnvironmentParameter, - cdk.Fn.sub(lambdaArnKey(fdConfig), substitutions), - cdk.Fn.sub(lambdaArnKey({ - ...fdConfig, - name: fdConfig.name.replace(/(-\${env})/, ''), - })), + cdk.Fn.sub(lambdaArnKey(name, region, accountId), substitutions), + cdk.Fn.sub(lambdaArnKey(name.replace(/(-\${env})/, ''), region, accountId)), ).toString(); } -function lambdaArnKey({ name, region, accountId }: FunctionDirectiveConfig): string { - return `arn:aws:lambda:${region ?? '${AWS::Region}'}:${accountId ?? '${AWS::AccountId}'}:function:${name}`; +function lambdaArnKey(name: string, region?: string, accountId?: string): string { + return `arn:aws:lambda:${region ? region : '${AWS::Region}'}:${accountId ? accountId : '${AWS::AccountId}'}:function:${name}`; } diff --git a/packages/graphql-function-transformer/src/FunctionTransformer.ts b/packages/graphql-function-transformer/src/FunctionTransformer.ts index 6681e0ca82..b8d3059e25 100644 --- a/packages/graphql-function-transformer/src/FunctionTransformer.ts +++ b/packages/graphql-function-transformer/src/FunctionTransformer.ts @@ -1,11 +1,9 @@ -import { Transformer, gql, TransformerContext } from 'graphql-transformer-core'; +import { Transformer, gql, TransformerContext, getDirectiveArguments, TransformerContractError } from 'graphql-transformer-core'; import { obj, str, ref, printBlock, compoundExpression, qref, raw, iff } from 'graphql-mapping-template'; import { ResolverResourceIDs, FunctionResourceIDs, ResourceConstants, - parseFunctionDirective, - FunctionDirectiveConfig, } from 'graphql-transformer-common'; import { ObjectTypeDefinitionNode, FieldDefinitionNode, DirectiveNode } from 'graphql'; import { AppSync, IAM, Fn } from 'cloudform-types'; @@ -29,26 +27,29 @@ export class FunctionTransformer extends Transformer { * Add the required resources to invoke a lambda function for this field. */ field = (parent: ObjectTypeDefinitionNode, definition: FieldDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => { - const fdConfig = parseFunctionDirective(directive); + const { name, region, accountId } = getDirectiveArguments(directive); + if (!name) { + throw new TransformerContractError(`Must supply a 'name' to @function.`); + } // Add the iam role if it does not exist. - const iamRoleKey = FunctionResourceIDs.FunctionIAMRoleID(fdConfig); + const iamRoleKey = FunctionResourceIDs.FunctionIAMRoleID(name, region, accountId); if (!ctx.getResource(iamRoleKey)) { - ctx.setResource(iamRoleKey, this.role(fdConfig)); + ctx.setResource(iamRoleKey, this.role(name, region, accountId)); ctx.mapResourceToStack(FUNCTION_DIRECTIVE_STACK, iamRoleKey); } // Add the data source if it does not exist. - const lambdaDataSourceKey = FunctionResourceIDs.FunctionDataSourceID(fdConfig); + const lambdaDataSourceKey = FunctionResourceIDs.FunctionDataSourceID(name, region, accountId); if (!ctx.getResource(lambdaDataSourceKey)) { - ctx.setResource(lambdaDataSourceKey, this.datasource(fdConfig)); + ctx.setResource(lambdaDataSourceKey, this.datasource(name, region, accountId)); ctx.mapResourceToStack(FUNCTION_DIRECTIVE_STACK, lambdaDataSourceKey); } // Add function that invokes the lambda function - const functionConfigurationKey = FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(fdConfig); + const functionConfigurationKey = FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(name, region, accountId); if (!ctx.getResource(functionConfigurationKey)) { - ctx.setResource(functionConfigurationKey, this.function(fdConfig)); + ctx.setResource(functionConfigurationKey, this.function(name, region, accountId)); ctx.mapResourceToStack(FUNCTION_DIRECTIVE_STACK, functionConfigurationKey); } @@ -58,12 +59,12 @@ export class FunctionTransformer extends Transformer { const resolverKey = ResolverResourceIDs.ResolverResourceID(typeName, fieldName); const resolver = ctx.getResource(resolverKey); if (!resolver) { - ctx.setResource(resolverKey, this.resolver(typeName, fieldName, fdConfig)); + ctx.setResource(resolverKey, this.resolver(typeName, fieldName, name, region, accountId)); ctx.mapResourceToStack(FUNCTION_DIRECTIVE_STACK, resolverKey); } else if (resolver.Properties.Kind === 'PIPELINE') { ctx.setResource( resolverKey, - this.appendFunctionToResolver(resolver, FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(fdConfig)) + this.appendFunctionToResolver(resolver, FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(name, region, accountId)) ); } }; @@ -71,17 +72,17 @@ export class FunctionTransformer extends Transformer { /** * Create a role that allows our AppSync API to talk to our Lambda function. */ - role = (fdConfig: FunctionDirectiveConfig): any => { + role = (name: string, region: string, accountId?: string): any => { return new IAM.Role({ RoleName: Fn.If( ResourceConstants.CONDITIONS.HasEnvironmentParameter, Fn.Join('-', [ - FunctionResourceIDs.FunctionIAMRoleName(fdConfig.name, true), // max of 64. 64-10-26-28 = 0 + FunctionResourceIDs.FunctionIAMRoleName(name, true), // max of 64. 64-10-26-28 = 0 Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'), // 26 Fn.Ref(ResourceConstants.PARAMETERS.Env), // 10 ]), Fn.Join('-', [ - FunctionResourceIDs.FunctionIAMRoleName(fdConfig.name, false), // max of 64. 64-26-38 = 0 + FunctionResourceIDs.FunctionIAMRoleName(name, false), // max of 64. 64-26-38 = 0 Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'), // 26 ]) ), @@ -106,7 +107,7 @@ export class FunctionTransformer extends Transformer { { Effect: 'Allow', Action: ['lambda:InvokeFunction'], - Resource: lambdaArnResource(fdConfig), + Resource: lambdaArnResource(name, region, accountId), }, ], }, @@ -118,28 +119,28 @@ export class FunctionTransformer extends Transformer { /** * Creates a lambda data source that registers the lambda function and associated role. */ - datasource = (fdConfig: FunctionDirectiveConfig): any => { + datasource = (name: string, region: string, accountId?: string): any => { return new AppSync.DataSource({ ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId), - Name: FunctionResourceIDs.FunctionDataSourceID(fdConfig), + Name: FunctionResourceIDs.FunctionDataSourceID(name, region, accountId), Type: 'AWS_LAMBDA', - ServiceRoleArn: Fn.GetAtt(FunctionResourceIDs.FunctionIAMRoleID(fdConfig), 'Arn'), + ServiceRoleArn: Fn.GetAtt(FunctionResourceIDs.FunctionIAMRoleID(name, region, accountId), 'Arn'), LambdaConfig: { - LambdaFunctionArn: lambdaArnResource(fdConfig), + LambdaFunctionArn: lambdaArnResource(name, region, accountId), }, - }).dependsOn(FunctionResourceIDs.FunctionIAMRoleID(fdConfig)); + }).dependsOn(FunctionResourceIDs.FunctionIAMRoleID(name, region, accountId)); }; /** * Create a new pipeline function that calls out to the lambda function and returns the value. */ - function = (fdConfig: FunctionDirectiveConfig): any => { + function = (name: string, region: string, accountId?: string): any => { return new AppSync.FunctionConfiguration({ ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId), - Name: FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(fdConfig), - DataSourceName: FunctionResourceIDs.FunctionDataSourceID(fdConfig), + Name: FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(name, region, accountId), + DataSourceName: FunctionResourceIDs.FunctionDataSourceID(name, region, accountId), FunctionVersion: '2018-05-29', - RequestMappingTemplate: printBlock(`Invoke AWS Lambda data source: ${FunctionResourceIDs.FunctionDataSourceID(fdConfig)}`)( + RequestMappingTemplate: printBlock(`Invoke AWS Lambda data source: ${FunctionResourceIDs.FunctionDataSourceID(name, region, accountId)}`)( obj({ version: str('2018-05-29'), operation: str('Invoke'), @@ -160,26 +161,26 @@ export class FunctionTransformer extends Transformer { raw('$util.toJson($ctx.result)'), ]) ), - }).dependsOn(FunctionResourceIDs.FunctionDataSourceID(fdConfig)); + }).dependsOn(FunctionResourceIDs.FunctionDataSourceID(name, region, accountId)); }; /** * Create a resolver of one that calls the "function" function. */ - resolver = (type: string, field: string, fdConfig: FunctionDirectiveConfig): any => { + resolver = (type: string, field: string, name: string, region?: string, accountId?: string): any => { return new AppSync.Resolver({ ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId), TypeName: type, FieldName: field, Kind: 'PIPELINE', PipelineConfig: { - Functions: [Fn.GetAtt(FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(fdConfig), 'FunctionId')], + Functions: [Fn.GetAtt(FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(name, region, accountId), 'FunctionId')], }, RequestMappingTemplate: printBlock('Stash resolver specific context.')( compoundExpression([qref(`$ctx.stash.put("typeName", "${type}")`), qref(`$ctx.stash.put("fieldName", "${field}")`), obj({})]) ), ResponseMappingTemplate: '$util.toJson($ctx.prev.result)', - }).dependsOn(FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(fdConfig)); + }).dependsOn(FunctionResourceIDs.FunctionAppSyncFunctionConfigurationID(name, region, accountId)); }; appendFunctionToResolver(resolver: any, functionId: string) { diff --git a/packages/graphql-function-transformer/src/lambdaArns.ts b/packages/graphql-function-transformer/src/lambdaArns.ts index 1d1eadbcd0..085c0ee25e 100644 --- a/packages/graphql-function-transformer/src/lambdaArns.ts +++ b/packages/graphql-function-transformer/src/lambdaArns.ts @@ -1,24 +1,21 @@ -import { Fn, Refs } from 'cloudform-types'; -import { FunctionDirectiveConfig, ResourceConstants } from 'graphql-transformer-common'; +import { Fn } from 'cloudform-types'; +import { ResourceConstants } from 'graphql-transformer-common'; -export function lambdaArnResource(fdConfig: FunctionDirectiveConfig) { +export function lambdaArnResource(name: string, region?: string, accountId?:string) { const substitutions = {}; - if (referencesEnv(fdConfig.name)) { + if (referencesEnv(name)) { substitutions['env'] = Fn.Ref(ResourceConstants.PARAMETERS.Env); } return Fn.If( ResourceConstants.CONDITIONS.HasEnvironmentParameter, - Fn.Sub(lambdaArnKey(fdConfig), substitutions), - Fn.Sub(lambdaArnKey({ - ...fdConfig, - name: removeEnvReference(fdConfig.name), - }), {}) + Fn.Sub(lambdaArnKey(name, region, accountId), substitutions), + Fn.Sub(lambdaArnKey(removeEnvReference(name), region, accountId), {}) ); } -export function lambdaArnKey({ name, region, accountId }: FunctionDirectiveConfig) { - const regionSubstr: string = region ?? '${AWS::Region}'; - const accountIdSubstr: string = accountId ?? '${AWS::AccountId}'; +export function lambdaArnKey(name: string, region?: string, accountId?:string) { + const regionSubstr: string = region ? region : '${AWS::Region}'; + const accountIdSubstr: string = accountId ? accountId : '${AWS::AccountId}'; return `arn:aws:lambda:${regionSubstr}:${accountIdSubstr}:function:${name}`; } diff --git a/packages/graphql-transformer-common/src/FunctionDirectiveConfig.ts b/packages/graphql-transformer-common/src/FunctionDirectiveConfig.ts deleted file mode 100644 index e7dea9eb57..0000000000 --- a/packages/graphql-transformer-common/src/FunctionDirectiveConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface FunctionDirectiveConfig { - name: string; - region?: string; - accountId?: string; -} diff --git a/packages/graphql-transformer-common/src/FunctionResourceIDs.ts b/packages/graphql-transformer-common/src/FunctionResourceIDs.ts index aff9add06c..c13f3502cd 100644 --- a/packages/graphql-transformer-common/src/FunctionResourceIDs.ts +++ b/packages/graphql-transformer-common/src/FunctionResourceIDs.ts @@ -1,27 +1,13 @@ import { simplifyName } from './util'; import md5 from 'md5'; -import { DirectiveNode } from 'graphql'; -import { getDirectiveArguments, TransformerContractError } from 'graphql-transformer-core'; - -import { FunctionDirectiveConfig } from './FunctionDirectiveConfig'; - -export function parseFunctionDirective(directive: DirectiveNode): FunctionDirectiveConfig { - const { name, region, accountId } = getDirectiveArguments(directive); - - if (!name) { - throw new TransformerContractError(`Must supply a 'name' to @function.`); - } - - return { name, region, accountId }; -} export class FunctionResourceIDs { - static FunctionDataSourceID({ name, region, accountId }: FunctionDirectiveConfig): string { + static FunctionDataSourceID(name: string, region?: string, accountId?:string): string { return `${simplifyName(name)}${simplifyName(region || '')}${accountId || ''}LambdaDataSource`; } - static FunctionIAMRoleID(fdConfig: FunctionDirectiveConfig): string { - return `${FunctionResourceIDs.FunctionDataSourceID(fdConfig)}Role`; + static FunctionIAMRoleID(name: string, region?: string, accountId?:string): string { + return `${FunctionResourceIDs.FunctionDataSourceID(name, region, accountId)}Role`; } static FunctionIAMRoleName(name: string, withEnv: boolean = false): string { @@ -31,7 +17,7 @@ export class FunctionResourceIDs { return `${simplifyName(name).slice(0, 32)}${md5(name).slice(0, 4)}`; } - static FunctionAppSyncFunctionConfigurationID(fdConfig: FunctionDirectiveConfig): string { - return `Invoke${FunctionResourceIDs.FunctionDataSourceID(fdConfig)}`; + static FunctionAppSyncFunctionConfigurationID(name: string, region?: string, accountId?:string): string { + return `Invoke${FunctionResourceIDs.FunctionDataSourceID(name, region, accountId)}`; } } diff --git a/packages/graphql-transformer-common/src/index.ts b/packages/graphql-transformer-common/src/index.ts index babc15572d..b93882805c 100644 --- a/packages/graphql-transformer-common/src/index.ts +++ b/packages/graphql-transformer-common/src/index.ts @@ -6,7 +6,6 @@ export * from './ModelResourceIDs'; export * from './SearchableResourceIDs'; export * from './nodeUtils'; export * from './HttpResourceIDs'; -export * from './FunctionDirectiveConfig'; export * from './FunctionResourceIDs'; export * from './connectionUtils'; export * from './dynamodbUtils';