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

fix: aws multi function support #546

Merged
merged 2 commits into from
Jul 22, 2020
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
28 changes: 16 additions & 12 deletions packages/faas-cli-plugin-aws/resource/aws-stack-http-template.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,38 @@
"Transform": "AWS::Serverless-2016-10-31",
"Description": "The Midway Serverless function deploy specification template.",
"Resources": {
"hellocurl": {
<% functions.forEach(function (opt) { %>
"<%= opt.name %>": {
"Type": "AWS::Serverless::Function",
"Properties": {
"FunctionName": "<%= options.name %>",
"Handler": "<%= options.handler || "index.handler" %>",
"Runtime": "<%= options.runtime || "nodejs12.x" %>",
"Description": "<%= options.description || "" %>",
"MemorySize": <%- options.memorySize || 128 %>,
"Timeout": <%= options.timeout || 3 %>,
"FunctionName": "<%= opt.name %>",
"Handler": "<%= opt.handler || "index.handler" %>",
"Runtime": "<%= opt.runtime || "nodejs12.x" %>",
"Description": "<%= opt.description || "" %>",
"MemorySize": <%- opt.memorySize || 128 %>,
"Timeout": <%= opt.timeout || 3 %>,
"Role": "arn:aws:iam::752677612709:role/service-role/hello-curl-role-5tk89mye",
"CodeUri": {
"Bucket": "<%= options.codeBucket %>",
"Key": "<%= options.codeKey %>"
"Bucket": "<%= opt.codeBucket %>",
"Key": "<%= opt.codeKey %>"
},
"Events": {
"Api1": {
<% opt.events.forEach(function (event, idx) { %>
"Api<%= idx + opt.name + 1 %>": {
"Type": "Api",
"Properties": {
"Path": "<%= options.path %>",
"Method": "ANY",
"Path": "<%= event.path %>",
"Method": "<%= event.method || 'ANY' %>",
"RestApiId": {
"Ref": "MyApi"
}
}
}
<% }) %>
}
}
},
<% }) %>
"MyApi": {
"Type": "AWS::Serverless::Api",
"Properties": {
Expand Down
182 changes: 94 additions & 88 deletions packages/faas-cli-plugin-aws/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import {
} from './profile';
import {
S3UploadResult,
LambdaFunctionOptions,
// LambdaFunctionOptions,
StackEvents,
StackResourcesDetail,
MFunctions,
} from './interface';

import { get } from 'lodash';
Expand Down Expand Up @@ -129,9 +130,8 @@ export class AWSLambdaPlugin extends BasePlugin {
}

async generateStackJson(
name: string,
handler = 'index.handler',
path = '/hello',
fns: MFunctions[],
stage: string,
bucket: string,
key: string
) {
Expand All @@ -140,22 +140,43 @@ export class AWSLambdaPlugin extends BasePlugin {
const tpl = readFileSync(
join(__dirname, '../resource/aws-stack-http-template.ejs')
).toString();
const params: { options: LambdaFunctionOptions } = {
const params: {
functions: Array<{
name: string;
handler: string;
runtime?: string;
description?: string;
memorySize?: number;
timeout?: number;
codeBucket: string;
codeKey: string;
events: { path: string; method: string }[];
}>;
options?: { stage: string };
} = {
functions: fns.map(item =>
Object.assign(
{},
{
name: item.name,
handler: item.handler,
events: item.events,
codeBucket: bucket,
codeKey: key,
}
)
),
options: {
name,
handler,
path,
codeBucket: bucket,
codeKey: key,
stage,
},
};
return render(tpl, params);
}

async createStack(
credentials,
name: string,
handler: string,
fns: MFunctions[],
stage: string,
bucket: S3UploadResult
): Promise<{ StackId: string }> {
this.core.cli.log('Start stack create');
Expand All @@ -172,14 +193,13 @@ export class AWSLambdaPlugin extends BasePlugin {
*/
const service = new CloudFormation(credentials);
const TemplateBody = await this.generateStackJson(
name,
handler,
'/hello',
fns,
stage,
bucket.Bucket,
bucket.Key
);
const params = {
StackName: 'my-test-stack',
StackName: 'ms-stack-' + this.core.service.service.name,
OnFailure: 'DELETE',
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_AUTO_EXPAND'],
Parameters: [],
Expand Down Expand Up @@ -216,22 +236,15 @@ export class AWSLambdaPlugin extends BasePlugin {
credentials,
bucket: string,
key: string,
name: string
fns: MFunctions[],
stage
): Promise<{ StackId: string }> {
this.core.cli.log(' - stack already exists, do stack update');
// TODO support multi function;
const names = Object.keys(this.core.service.functions);
const handler = this.core.service.functions[names[0]].handler;
const service = new CloudFormation(credentials);
const TemplateBody = await this.generateStackJson(
name,
handler,
'/hello',
bucket,
key
);
const TemplateBody = await this.generateStackJson(fns, stage, bucket, key);
const params = {
StackName: 'my-test-stack',
StackName: 'ms-stack-' + this.core.service.service.name,
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_AUTO_EXPAND'],
Parameters: [],
TemplateBody,
Expand All @@ -251,9 +264,9 @@ export class AWSLambdaPlugin extends BasePlugin {
credentials,
stackId: string,
stage = 'v1',
path = '/hello'
fns: MFunctions[]
) {
this.core.cli.log(' - wait stack ready');
this.core.cli.log(' - wait stack ready', stackId);
const service = new CloudFormation(credentials);
const params = {
StackName: stackId,
Expand Down Expand Up @@ -307,20 +320,20 @@ export class AWSLambdaPlugin extends BasePlugin {
);

const { StackResources } = result;
const data = StackResources.find(
const datas = StackResources.filter(
res => res.ResourceType === 'AWS::ApiGateway::RestApi'
);

// https://wsqd4ni6i5.execute-api.us-east-1.amazonaws.com/Prod/hello-curl
const api = `https://${data.PhysicalResourceId}.execute-api.${credentials.region}.amazonaws.com/${stage}${path}`;
return {
api,
};
fns.map(fn => {
const api = `https://${datas[0].PhysicalResourceId}.execute-api.${credentials.region}.amazonaws.com/${stage}${fn.events[0].path}`;
console.log(fn.name, 'test url', api);
});
}

async updateFunction(
credentials,
name: string,
fns: MFunctions[],
bucket: S3UploadResult
): Promise<any> {
this.core.cli.log(' - upadte function');
Expand All @@ -331,41 +344,20 @@ export class AWSLambdaPlugin extends BasePlugin {
// (err, data) => err ? reject(err) : resolve(data));
// });

// TODO support multi function;
const params = {
FunctionName: name,
S3Bucket: bucket.Bucket,
S3Key: bucket.Key,
};
/**
* {
* FunctionName: 'serverless-hello-world-index',
* FunctionArn: 'arn:aws:lambda:us-east-1:752677612709:function:serverless-hello-world-index',
* Runtime: 'nodejs12.x',
* Role: 'arn:aws:iam::752677612709:role/service-role/hello-curl-role-5tk89mye',
* Handler: 'index.handler',
* CodeSize: 311,
* Description: '',
* Timeout: 3,
* MemorySize: 128,
* LastModified: '2020-07-12T18:10:09.191+0000',
* CodeSha256: 'pg5zZr5JSWbuN14CoyCzcz5tu0mZA7mxAoIgdC5+dL0=',
* Version: '$LATEST',
* KMSKeyArn: null,
* TracingConfig: { Mode: 'PassThrough' },
* MasterArn: null,
* RevisionId: '7d0b02dc-cde7-4680-9db1-95792282f96c',
* State: 'Active',
* StateReason: null,
* StateReasonCode: null,
* LastUpdateStatus: 'Successful',
* LastUpdateStatusReason: null,
* LastUpdateStatusReasonCode: null
* }
*/
await new Promise((resolve, reject) =>
service.updateFunctionCode(params, err => (err ? reject(err) : resolve()))
);
const tasks = fns.map(fn => {
const params = {
FunctionName: fn.name,
S3Bucket: bucket.Bucket,
S3Key: bucket.Key,
};
return new Promise((resolve, reject) =>
service.updateFunctionCode(params, err =>
err ? reject(err) : resolve()
)
);
});
tasks.push();
await Promise.all(tasks);
this.core.cli.log(' - upadte over');
}

Expand All @@ -389,12 +381,36 @@ export class AWSLambdaPlugin extends BasePlugin {
writeFileSync(awsCredentialsPath, text);
}

getFunctions(): MFunctions[] {
const obj: {
[key: string]: {
handler: string;
events: Array<{
[key: string]: {
method: string;
path: string;
};
}>;
};
} = this.core.service.functions as any;
return Object.keys(obj).map(name => ({
name,
handler: obj[name].handler || 'index.handler',
events: obj[name].events.reduce((arr, item) => {
arr.push(
...Object.keys(item).map(type => ({
type,
...item[type],
}))
);
return arr;
}, [] as { type: string; method: string; path: string }[]),
}));
}

async deploy() {
const names = Object.keys(this.core.service.functions);
const handler = this.core.service.functions[names[0]].handler;
const name = `${this.core.service.service.name}-${names[0]}`;
const stage = 'v1';
const path = '/hello';
const fns = this.getFunctions();

await this.package();

Expand Down Expand Up @@ -432,26 +448,16 @@ export class AWSLambdaPlugin extends BasePlugin {

let stackData: { StackId: string } = null;
try {
stackData = await this.createStack(
credentials,
name,
handler,
artifactRes
);
stackData = await this.createStack(credentials, fns, stage, artifactRes);
} catch (err) {
if (err.message.includes('already exists')) {
await this.updateFunction(credentials, name, artifactRes);
await this.updateFunction(credentials, fns, artifactRes);
return;
}
throw err;
}
const result = await this.monitorStackResult(
credentials,
stackData.StackId,
stage,
path
);
this.core.cli.log('Deploy over, test url:', result.api);
await this.monitorStackResult(credentials, stackData.StackId, stage, fns);
this.core.cli.log('Deploy over');
}

/**
Expand Down
10 changes: 10 additions & 0 deletions packages/faas-cli-plugin-aws/src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export interface MFunctions {
name: string;
handler: string;
events: {
type: string;
method: string;
path: string;
}[];
}

export interface S3UploadResult {
ETag: string;
ServerSideEncryption?: string;
Expand Down