Skip to content

Commit

Permalink
fix: refactor dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
phani-srikar committed Nov 9, 2022
1 parent 87bfb6b commit f773765
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -25,7 +28,7 @@ const directiveDefinition = /* GraphQL */ `
`;

export class FunctionTransformer extends TransformerPluginBase {
private resolverGroups: Map<FieldDefinitionNode, FunctionDirectiveWithResolverConfig[]> = new Map();
private resolverGroups: Map<FieldDefinitionNode, FunctionDirectiveConfiguration[]> = new Map();

constructor() {
super('amplify-function-transformer', directiveDefinition);
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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}`;
}
59 changes: 30 additions & 29 deletions packages/graphql-function-transformer/src/FunctionTransformer.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
}

Expand All @@ -58,30 +59,30 @@ 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))
);
}
};

/**
* 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
])
),
Expand All @@ -106,7 +107,7 @@ export class FunctionTransformer extends Transformer {
{
Effect: 'Allow',
Action: ['lambda:InvokeFunction'],
Resource: lambdaArnResource(fdConfig),
Resource: lambdaArnResource(name, region, accountId),
},
],
},
Expand All @@ -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'),
Expand All @@ -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) {
Expand Down
21 changes: 9 additions & 12 deletions packages/graphql-function-transformer/src/lambdaArns.ts
Original file line number Diff line number Diff line change
@@ -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}`;
}

Expand Down

This file was deleted.

24 changes: 5 additions & 19 deletions packages/graphql-transformer-common/src/FunctionResourceIDs.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)}`;
}
}
1 change: 0 additions & 1 deletion packages/graphql-transformer-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit f773765

Please sign in to comment.