serverless-aliyun-function-compute-wnj
Version:
Provider plugin for the Serverless Framework v1.x which adds support for Aliyun Function Compute
1,463 lines (1,307 loc) • 40.4 kB
JavaScript
/*global beforeEach, expect*/
'use strict';
const path = require('path');
const fs = require('fs');
const os = require('os');
const ini = require('ini');
const co = require('co');
const BbPromise = require('bluebird');
const _ = require('lodash');
const FCClient = require('@alicloud/fc');
const OSS = require('ali-oss');
const CloudAPI = require('@alicloud/cloudapi');
const RAM = require('@alicloud/ram');
const SLS = require('@alicloud/log');
const TableStore = require('tablestore');
const utils = require('../shared/utils');
const constants = {
providerName: 'aliyun',
};
const keySym = Symbol('key');
const fcClientSym = Symbol('fc-client');
const agClientSym = Symbol('ag-client');
const ossClientSym = Symbol('oss-client');
const ramClientSym = Symbol('ram-client');
const slsClientSym = Symbol('sls-client');
const tsClientSym = Symbol('ts-client');
const PROJECT_DELAY = 20000;
class AliyunProvider {
static getProviderName() {
return constants.providerName;
}
constructor(serverless, options) {
this.serverless = serverless;
this.provider = this;
this.serverless.setProvider(constants.providerName, this);
this.options = options;
utils.setDefaults.call(this);
}
get PROJECT_DELAY() {
console.log("PROJECT_DELAY:" + PROJECT_DELAY)
return PROJECT_DELAY;
}
get key() {
if (this[keySym]) {
return this[keySym];
}
let credentials = this.serverless.service.provider.credentials;
const credParts = credentials.split('/');
if (credParts[0] === '~') {
credParts[0] = os.homedir();
credentials = credParts.reduce((memo, part) => path.join(memo, part), '');
}
const keyFileContent = fs.readFileSync(credentials, 'utf-8').toString();
// TODO(joyeecheung) support profiles other than [default]
this[keySym] = ini.parse(keyFileContent).default;
[
'aliyun_account_id',
'aliyun_access_key_id',
'aliyun_access_key_secret'
].forEach((field) => {
if (!this[keySym][field]) {
throw new Error(`Credentials in ${credentials} does not contain ${field}`);
}
});
return this[keySym];
}
get fcClient() {
if (this[fcClientSym]) {
return this[fcClientSym];
}
const key = this.key;
this[fcClientSym] = new FCClient(key.aliyun_account_id, {
accessKeyID: key.aliyun_access_key_id,
accessKeySecret: key.aliyun_access_key_secret,
region: this.options.region
});
return this[fcClientSym];
}
get agClient() {
if (this[agClientSym]) {
return this[agClientSym];
}
const key = this.key;
this[agClientSym] = new CloudAPI({
accessKeyId: key.aliyun_access_key_id,
accessKeySecret: key.aliyun_access_key_secret,
endpoint: `http://apigateway.${this.options.region}.aliyuncs.com`
});
return this[agClientSym];
}
get ramClient() {
if (this[ramClientSym]) {
return this[ramClientSym];
}
const key = this.key;
this[ramClientSym] = new RAM({
accessKeyId: key.aliyun_access_key_id,
accessKeySecret: key.aliyun_access_key_secret,
endpoint: 'https://ram.aliyuncs.com'
});
return this[ramClientSym];
}
get ossClient() {
if (this[ossClientSym]) {
return this[ossClientSym];
}
const key = this.key;
this[ossClientSym] = OSS({
accessKeyId: key.aliyun_access_key_id,
accessKeySecret: key.aliyun_access_key_secret,
region: this.getOssRegion()
});
return this[ossClientSym];
}
resetOssClient(bucketName) {
const key = this.key;
this[ossClientSym] = OSS({
accessKeyId: key.aliyun_access_key_id,
accessKeySecret: key.aliyun_access_key_secret,
bucket: bucketName,
region: this.getOssRegion()
});
return this[ossClientSym];
}
get slsClient() {
if (this[slsClientSym]) {
return this[slsClientSym];
}
const key = this.key;
this[slsClientSym] = new SLS({
accessKeyId: key.aliyun_access_key_id,
accessKeySecret: key.aliyun_access_key_secret,
region: this.options.region
});
return this[slsClientSym];
}
getTsClient(instanceName) {
if (this[tsClientSym] && this[tsClientSym].get(instanceName)) {
return this[tsClientSym].get(instanceName);
}
const key = this.key;
this[tsClientSym] = new Map();
const client = new TableStore.Client({
accessKeyId: key.aliyun_access_key_id,
secretAccessKey: key.aliyun_access_key_secret,
endpoint: `https://${instanceName}.${this.options.region}.ots.aliyuncs.com`,
instancename: `${instanceName}`,
maxRetries: 20
});
this[tsClientSym].set(instanceName, client);
return this[tsClientSym].get(instanceName);
}
getStorageBucketId() {
return 'sls-storage-bucket';
}
getOssRegion(region) {
return `oss-${this.options.region}`;
}
getStorageObjectId() {
return 'sls-storage-object';
}
getServiceId() {
return 'sls-function-service';
}
getServiceName() {
return `${this.serverless.service.service}-${this.options.stage}`;
}
getApiGroupLogicalId() {
return 'sls-api-group';
}
getInvokeRoleLogicalId() {
return 'sls-fc-invoke-role';
}
getExecRoleLogicalId() {
return 'sls-fc-exec-role';
}
getLogProjectId() {
return 'sls-log-project';
}
getLogStoreId() {
return 'sls-log-store';
}
getLogIndexId() {
return 'sls-log-index';
}
getFunctionResources_TableStoresId() {
return 'sls-tablestore-tables';
}
getApiGroupName() {
return `${this.getServiceName()}-api`.replace(/-/g, '_');
}
getLogProjectName() {
const service = this.serverless.service.service;
const stage = this.options.stage;
return `sls-${service}-${stage}-logs`.replace(/_/g, '-');
}
getLogStoreName() {
const service = this.serverless.service.service;
return `${service}-${this.options.stage}`;
}
getDeploymentBucketName() {
const service = this.serverless.service.service;
return `sls-${service}-${this.options.stage}`;
}
// If a function is going to be reused by multiple endpoints,
// the user should create multiple functions instead
// So the API name is identified by just
// eventType and funcName
getEventName(eventType, funcName) {
return `sls-${eventType}-${funcName}`.replace(/-/g, '_');
}
getFunctionLogicalId(name) {
return `sls-${name}`;
}
getApiGroupDesc() {
const service = this.getServiceName();
return `API group for Function Compute service ${service}, generated by ` +
'the Serverless framework.';
}
getApiDesc(eventType, funcName) {
const service = this.getServiceName();
return `API for Function Compute function ${funcName} of service ` +
`${service}, triggered by ${eventType} event, generated by the ` +
'Serverless framework.';
}
isApiType(type) {
return type === 'ALIYUN::API::HTTP';
}
isTriggerType(type) {
return type === 'ALIYUN::FC::Trigger';
}
isLogStoreType(type) {
return type === 'ALIYUN::SLS::Store';
}
isFunctionType(type) {
return type === 'ALIYUN::FC::Function';
}
isTableType(type) {
return type === 'ALIYUN::TS::Table';
}
isOSSBucketType(type) {
return type === 'ALIYUN::OSS::Bucket';
}
isRAMRoleType(type) {
return type === 'ALIYUN::RAM::Role';
}
getArtifactDirectoryPrefix() {
const service = this.serverless.service.service;
const stage = this.options.stage;
return `serverless/${service}/${stage}`;
}
getArtifactDirectoryName() {
const prefix = this.getArtifactDirectoryPrefix();
const date = new Date();
const dateString = `${date.getTime().toString()}-${date.toISOString()}`;
return `${prefix}/${dateString}`;
}
initializeTemplate() {
const deploymentTemplate = this.serverless.utils.readFileSync(
path.join(
__dirname,
'templates',
'core-configuration-template.json'));
this.serverless.service.provider.compiledConfigurationTemplate = deploymentTemplate;
return deploymentTemplate;
}
getStorageBucketResource() {
return {
'Type': 'ALIYUN::OSS:Bucket',
'Properties': {
'BucketName': this.getDeploymentBucketName(),
'Region': this.options.region
}
};
}
getLogProjectResource() {
const projectName = this.getLogProjectName();
const service = this.serverless.service.service;
return {
'Type': 'ALIYUN::SLS::Project',
'Properties': {
'projectName': projectName,
'description': 'Log project for serverless service ' + service + ', generated by the Serverless framework'
}
};
}
getLogStoreResource() {
const projectName = this.getLogProjectName();
const storeName = this.getLogStoreName();
const service = this.getServiceName();
return {
'Type': 'ALIYUN::SLS::Store',
'Properties': {
'projectName': projectName,
'storeName': storeName,
'description': 'Log store for Function Compute service ' + service + ', generated by the Serverless framework',
'ttl': 30, // days
'shardCount': 2
}
};
}
getLogIndexResource() {
const projectName = this.getLogProjectName();
const storeName = this.getLogStoreName();
return {
'Type': 'ALIYUN::SLS::Index',
'Properties': {
'projectName': projectName,
'storeName': storeName,
'ttl': 30,
'keys': {
'functionName': {
'caseSensitive': false,
'token': ['\n', '\t', ';', ',', '=', ':'],
'type': 'text'
}
},
}
};
}
getFunctionResource(funcObject) {
// Function-wise setting > service-wise setting > default
const service = this.serverless.service;
const memorySize = funcObject.memorySize ||
service.provider.memorySize ||
128;
const timeout = funcObject.timeout ||
service.provider.timeout ||
30;
// TODO(joyeecheung): description
return {
'Type': 'ALIYUN::FC::Function',
'Properties': {
'name': funcObject.name,
'service': this.getServiceName(),
'handler': funcObject.handler,
'memorySize': memorySize,
'timeout': timeout,
'runtime': 'nodejs6',
'code': {
'ossBucketName': this.getDeploymentBucketName(),
'ossObjectName': service.package.artifactFilePath
}
}
};
}
getRequestConfig(eventType, event) {
const path = event.RequestPath || event.path;
const requestPath = path.startsWith('/') ? path : `/${path}`;
return {
'RequestProtocol': eventType.toUpperCase(),
'RequestHttpMethod': (event.RequestHttpMethod || event.method || 'GET').toUpperCase(),
'RequestPath': requestPath,
'BodyFormat': (event.BodyFormat || event.bodyFormat || '').toUpperCase(),
'PostBodyDescription': ''
};
}
getServiceConfig(event, funcObject) {
return {
'ServiceProtocol': 'FunctionCompute',
'Mock': 'FALSE',
'ServiceTimeout': 30000, // TODO(joyeecheung): use config?
'FunctionComputeConfig': {
'FcRegionId': this.options.region,
'ServiceName': this.getServiceName(),
'FunctionName': funcObject.name,
'RoleArn': undefined
},
'ContentTypeValue': event.ContentTypeValue || 'application/json; charset=UTF-8'
};
}
getType(type) {
const dict = {
string: 'String',
number: 'Number',
boolean: 'Boolean'
};
return dict[type.toLowerCase()];
}
getLocation(loc) {
const dict = {
head: 'Head',
query: 'Query',
path: 'Path',
body: 'Body',
};
return dict[loc.toLowerCase()];
}
getCatalog(catalog) {
if (!catalog || catalog === undefined) {
return "REQUEST";
}
const dict = {
request: 'REQUEST',
constant: 'CONSTANT',
system: 'SYSTEM'
};
return dict[catalog.toLowerCase()];
}
getRequestParameters(event) {
let result = [];
if (event.parameters) {
result = event.parameters
.map((p) => {
var apiParameterName = p.name;
if (this.getCatalog(p.catalog) === "SYSTEM") {
apiParameterName = p.requestParameterName;
}
return {
ApiParameterName: apiParameterName,
ParameterType: this.getType(p.type),
Location: this.getLocation(p.location),
Required: p.optional ? 'OPTIONAL' : 'REQUIRED',
isHide: false, // do not support hidden params
DefaultValue: p.default,
DemoValue: p.demo,
Description: p.description || '',
ParameterCatalog: this.getCatalog(p.catalog) || "REQUEST"
};
});
}
if (event.RequestParameters) {
result = result.concat(event.RequestParameters);
}
return result;
}
getServiceParameters(event) {
let result = [];
if (event.parameters) {
result = event.parameters.map((p) => ({
ServiceParameterName: p.name,
Type: this.getType(p.type),
Location: this.getLocation(p.location),
ParameterCatalog: this.getCatalog(p.catalog) || "REQUEST"
}));
}
if (event.ServiceParameters) {
result = result.concat(event.ServiceParameters);
}
return result;
}
getServiceParametersMap(event) {
let result = [];
if (event.parameters) {
result = event.parameters.map((p) => ({
ServiceParameterName: p.name,
RequestParameterName: p.requestParameterName || p.name
}));
}
if (event.ServiceParametersMap) {
result = result.concat(event.ServiceParametersMap);
}
return result;
}
getVisibility(event) {
if (!event.visibility) {
return
}
const dict = {
public: 'PUBLIC',
private: 'PRIVATE'
};
return dict[event.visibility.toLowerCase()];
}
getAuthType(event) {
if (!event.authType) {
return
}
const dict = {
app: 'APP',
anonymous: 'ANONYMOUS',
appopenid: 'APPOPENID',
openid: 'OPENID'
};
return dict[event.authType.toLowerCase()];
}
getOpenIdApiType(type) {
const dict = {
business: 'BUSINESS',
idtoken: 'IDTOKEN'
};
return dict[type.toLowerCase()];
}
getOpenIdConnectConfig(event) {
if (event.openIdConnect) {
return JSON.stringify({
OpenIdApiType: this.getOpenIdApiType(event.openIdConnect.openIdApiType),
IdTokenParamName: event.openIdConnect.idTokenParamName,
PublicKeyId: event.openIdConnect.publicKeyId,
PublicKey: event.openIdConnect.publicKey
});
}
}
getHttpApiResource(event, funcObject) {
const eventType = 'http';
return {
'Type': 'ALIYUN::API::HTTP',
'Properties': {
'GroupName': this.getApiGroupName(),
'GroupId': undefined,
'ApiName': this.getEventName(eventType, funcObject.name),
'Visibility': this.getVisibility(event) || 'PRIVATE',
'Description': this.getApiDesc(eventType, funcObject.name),
'AuthType': this.getAuthType(event) || 'OPENID',
'OpenIdConnectConfig': this.getOpenIdConnectConfig(event) || '{}',
'RequestConfig': this.getRequestConfig(eventType, event),
'ServiceConfig': this.getServiceConfig(event, funcObject),
'RequestParameters': this.getRequestParameters(event),
'ServiceParameters': this.getServiceParameters(event),
'ServiceParametersMap': this.getServiceParametersMap(event),
'ResultType': event.ResultType || 'JSON',
'ResultSample': event.ResultSample || '{}'
}
};
}
getApiGroupResource() {
return {
'Type': 'ALIYUN::API::APIGroup',
'Properties': {
'GroupName': this.getApiGroupName(),
'Description': this.getApiGroupDesc(),
'Region': this.options.region,
'GroupId': undefined,
'SubDomain': undefined
}
};
}
getOSSTriggerResource(event, funcObject) {
const eventType = 'oss';
return {
'Type': 'ALIYUN::FC::Trigger',
'Properties': {
'invocationRole': event.invocationRole, // Arn of invoke-role
'sourceArn': event.sourceArn,
'triggerConfig': event.triggerConfig,
'triggerName': this.getEventName(eventType, funcObject.name),
'triggerType': eventType,
'functionName': funcObject.name,
'serviceName': this.getServiceName()
}
};
}
getServiceResource() {
// TODO(joyeecheung): description
return {
'Type': 'ALIYUN::FC::Service',
'Properties': {
'name': this.getServiceName(),
'region': this.options.region,
'logConfig': {
'logstore': this.getLogStoreName(),
'project': this.getLogProjectName()
},
// Arn of the created exec role
'role': undefined
}
};
}
getObjectResource(objectName, localPath) {
return {
'Type': 'ALIYUN::OSS:Object',
'Properties': {
'BucketName': this.getDeploymentBucketName(),
'ObjectName': objectName,
'LocalPath': localPath
}
};
}
getInvokeRoleName() {
const service = this.getServiceName();
const roleName = `sls-${service}-invoke-role`.replace(/_/g, '-');
return roleName;
}
getExecRoleName() {
const service = this.getServiceName();
const roleName = `sls-${service}-exec-role`.replace(/_/g, '-');
return roleName;
}
getInvokeRoleResource() {
const service = this.getServiceName();
const roleName = this.getInvokeRoleName();
return {
'Type': 'ALIYUN::RAM::Role',
'Properties': {
'RoleName': roleName,
'Description': 'Allow Function Compute service ' + service +
' to be triggered, generated by the Serverless framework',
'AssumeRolePolicyDocument': {
'Version': '1',
'Statement': [{
'Action': 'sts:AssumeRole',
'Effect': 'Allow',
'Principal': {
'Service': [
// to be added
]
}
}]
},
// This role has the predefined fc invocation policy attached
'Policies': [{
'PolicyType': 'System',
'PolicyName': 'AliyunFCInvocationAccess', // predefined
'RoleName': roleName
}]
}
};
}
getAGService() {
return 'apigateway.aliyuncs.com';
}
getOSSService() {
return 'oss.aliyuncs.com';
}
makeRoleAccessibleFromService(resource, newService) {
const statement = resource.Properties.AssumeRolePolicyDocument.Statement.find(
(stmt) => stmt.Action === 'sts:AssumeRole');
const services = statement.Principal.Service;
const foundServices = services.find((service) => service === newService);
if (!foundServices) {
services.push(newService);
}
}
makeRoleAccessibleFromAG(resource) {
this.makeRoleAccessibleFromService(resource, this.getAGService());
}
makeRoleAccessibleFromOSS(resource) {
this.makeRoleAccessibleFromService(resource, this.getOSSService());
}
getExecRolePolicyName() {
const service = this.getServiceName();
return `fc-${service}-access`;
}
getExecRoleResource() {
const service = this.getServiceName();
const roleName = this.getExecRoleName();
return {
'Type': 'ALIYUN::RAM::Role',
'Properties': {
'RoleName': roleName,
'Description': `Allow Function Compute service ${service} to access other services, generated by the Serverless framework`,
// This role can be accessed by FC
'AssumeRolePolicyDocument': {
'Version': '1',
'Statement': [{
'Action': 'sts:AssumeRole',
'Effect': 'Allow',
'Principal': {
'Service': [
'fc.aliyuncs.com'
]
}
}]
},
'Policies': [{
'PolicyName': this.getExecRolePolicyName(),
'Description': `Allow Function Compute service ${service} to access other services, generated by the Serverless framework`,
'PolicyDocument': {
'Version': '1',
'Statement': []
}
}]
}
};
}
letExecRoleAccessLog(resource) {
this.addRamRoleStatementsToExecRole(resource, this.getLogWritePolicyStatment());
}
// https://help.aliyun.com/document_detail/29049.html
getLogWritePolicyStatment() {
const project = this.getLogProjectName();
const store = this.getLogStoreName();
const account = this.key.aliyun_account_id;
return {
'Action': [
'log:PostLogStoreLogs'
],
'Resource': [
`acs:log:*:${account}:project/${project}/logstore/${store}`
],
'Effect': 'Allow'
};
}
addRamRoleStatementsToExecRole(resource, statement) {
const service = this.getServiceName();
const execRolePolicy = this.getExecRolePolicyName();
const policy = resource.Properties.Policies.find(
(policy) => policy.PolicyName === execRolePolicy);
const statements = policy.PolicyDocument.Statement;
const foundStatement = statements.find((item) => _.isEqual(item, statement));
if (!foundStatement) {
statements.push(statement);
}
}
getLogProject(projectName) {
return this.slsClient.getProject(projectName).catch((err) => {
console.log("err:" + JSON.stringify(err));
if (err.code === 'ProjectNotExist') {
return undefined;
}
throw err;
});
}
sleep(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), timeout);
});
}
createLogProject(projectName, project) {
return this.slsClient.createProject(projectName, {
description: project.description
})
.then(this.sleep(PROJECT_DELAY))
.then(() => this.getLogProject(projectName));
}
/**
* @param {string} projectName
* @return {logstores: [], total: number, count: number}
*/
getLogStoresForProject(projectName) {
return this.slsClient.listLogstore(projectName)
.then((res) => res.logstores);
}
getLogStore(projectName, storeName) {
return this.slsClient.getLogStore(projectName, storeName)
.catch((err) => {
if (err.code === 'LogStoreNotExist') {
return undefined;
}
throw err;
});
}
createLogStore(projectName, storeName, store) {
return this.slsClient.createLogStore(projectName, storeName, store)
.then(this.getLogStore(projectName, storeName));
}
getLogIndex(projectName, storeName) {
return this.slsClient.getIndexConfig(projectName, storeName)
.catch((err) => {
if (err.code === 'IndexConfigNotExist') {
return undefined;
}
throw err;
});
}
createLogIndex(projectName, storeName, index) {
return this.slsClient.createIndex(projectName, storeName, {
ttl: index.ttl,
keys: index.keys,
line: index.line
}).then(() => this.getLogIndex(projectName, storeName));
}
getLogsIfAvailable(projectName, storeName, days, query, count) {
const from = new Date();
const to = new Date(from);
from.setDate(from.getDate() - days);
const fullQuery = Object.keys(query)
.map((key) => `${key}:${query[key]}`).join(' or ');
return this.slsClient.getLogs(projectName, storeName, from, to, {
query: fullQuery,
line: count
}).catch((err) => {
if (err.code === 'IndexConfigNotExist' ||
err.code === 'LogStoreNotExist' ||
err.code === 'ProjectNotExist') {
return [];
}
throw err;
});
}
/**
* @param {string} bucketName
* @returns {{name: string, region: string, creationDate: string}}
*/
getBucket(bucketName) {
// TODO(joyeecheung): handle buckets with the same name
// in a different region
const ossClient = this.ossClient;
return co(function* getBucket() {
const res = yield ossClient.listBuckets({ prefix: bucketName });
if (!res.buckets) {
return undefined;
}
const bucket = res.buckets.find((b) => b.name === bucketName);
return bucket;
});
}
/**
* @param {string} bucketName
*/
createBucket(bucketName) {
const ossClient = this.ossClient;
const region = this.getOssRegion();
return co(function* createBucket() {
return yield ossClient.putBucket(bucketName, region);
});
}
/**
* @param {string} bucketName
*/
deleteBucket(bucketName) {
const ossClient = this.ossClient;
const region = this.getOssRegion();
return co(function* deleteBucket() {
return yield ossClient.deleteBucket(bucketName, region);
});
}
uploadObject(objectName, filePath) {
const ossClient = this.ossClient;
return co(function* uploadObject() {
return yield ossClient.put(objectName, filePath);
});
}
deleteObjects(objectNames) {
const ossClient = this.ossClient;
return co(function* deleteObjects() {
// TODO(joyeecheung): handle partial failures
return yield ossClient.deleteMulti(objectNames);
});
}
/**
*
* @param {object} props
*/
getObjects(props) {
// TODO(joyeecheung): handle >= 1000 objects
const ossClient = this.ossClient;
const query = Object.assign({ 'max-keys': 999 }, props);
return co(function* listObjects() {
const res = yield ossClient.list(query);
return res.objects || [];
});
}
// http://doxmate.cool/aliyun/fc-nodejs-sdk/api.html
/**
* @param {string} serviceName
* @return {ServiceResponse}
*/
getService(serviceName) {
return this.fcClient.getService(serviceName)
.catch((err) => {
if (err.code === 'ServiceNotFound') {
return undefined;
}
throw err;
});
}
/**
* @param {string} serviceName
* @param {{role: string, logConfig: {logstore: string, project: string}}} spec
* @return {ServiceResponse}
* https://help.aliyun.com/document_detail/52877.html#service
* https://help.aliyun.com/document_detail/52877.html#serviceresponse
*/
createService(serviceName, spec) {
return this.fcClient.createService(serviceName, spec);
}
deleteService(serviceName, options) {
return this.fcClient.deleteService(serviceName, options);
}
/**
* @param {string} serviceName
* @param {Service} options
* @return {ServiceResponse}
*/
updateService(serviceName, options) {
return this.fcClient.updateService(serviceName, options);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @return {FunctionResponse}
*/
getFunction(serviceName, functionName) {
return this.fcClient.getFunction(serviceName, functionName)
.catch((err) => {
if (err.code === 'FunctionNotFound') {
return undefined;
}
throw err;
});
}
/**
* @param {string} serviceName
* @param {string} funcName
* @param {Function} options
* @return {FunctionResponse}
* https://help.aliyun.com/document_detail/52877.html#function
* https://help.aliyun.com/document_detail/52877.html#functionresponse
*/
createFunction(serviceName, functionName, options) {
const config = Object.assign({ functionName }, options);
return this.fcClient.createFunction(serviceName, config);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @param {Function} options
* @return {FunctionResponse}
*/
updateFunction(serviceName, functionName, options) {
const config = Object.assign({ functionName }, options);
return this.fcClient.updateFunction(serviceName, functionName, config);
}
/**
* @param {string} serviceName
* @return {{functionName: string, functionId: string}}
* TODO(joyeecheung): paging
*/
getFunctions(serviceName) {
return this.fcClient.listFunctions(serviceName).then((res) => {
const functions = res.functions;
if (!functions) {
return [];
}
return functions;
});
}
/**
* @param {string} serviceName
* @param {string} functionName
* @return {object}
*/
deleteFunction(serviceName, functionName) {
return this.fcClient.deleteFunction(serviceName, functionName);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @param {Event} event
* @return {InvokeResponse}
* https://help.aliyun.com/document_detail/52877.html#invokeresponse
*/
invokeFunction(serviceName, functionName, event) {
return this.fcClient.invokeFunction(serviceName, functionName, event);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @param {Trigger} trigger
* @return {TriggerResponse}https://help.aliyun.com/document_detail/52877.html#trigger
* https://help.aliyun.com/document_detail/52877.html#triggerresponse
*/
createTrigger(serviceName, functionName, trigger, role) {
const triggerProps = Object.assign({}, trigger, {
invocationRole: role.Arn
});
return this.fcClient.createTrigger(serviceName, functionName, triggerProps);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @param {string} triggerName
* @return {TriggerResponse}
*/
getTrigger(serviceName, functionName, triggerName) {
return this.fcClient.getTrigger(serviceName, functionName, triggerName)
.catch((err) => {
if (['ServiceNotFound', 'FunctionNotFound', 'TriggerNotFound'].indexOf(err.code)) {
return undefined;
}
throw err;
});
}
listTriggers(serviceName, functionName) {
return this.fcClient.listTriggers(serviceName, functionName).then((res) => res.triggers || []);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @param {string} triggerName
* @param {Trigger} trigger
* @return {TriggerResponse}
*/
updateTrigger(serviceName, functionName, triggerName, trigger, role) {
const triggerProps = Object.assign({}, trigger, {
invocationRole: role.Arn
});
return this.fcClient.updateTrigger(
serviceName, functionName, triggerName, triggerProps);
}
/**
* @param {string} serviceName
* @param {string} functionName
* @param {string} triggerName
* @return {}
*/
deleteTrigger(serviceName, functionName, triggerName) {
return this.fcClient.deleteTrigger(serviceName, functionName, triggerName);
}
// https://help.aliyun.com/document_detail/43595.html
/**
* @param {string} groupName
* @return {{GroupId: string, GroupName: string, SubDomain: string}}
* https://help.aliyun.com/document_detail/43616.html
*/
getApiGroup(groupName) {
// TODO(joyeecheung): pagination
return this.agClient.describeApiGroups({
GroupName: groupName
}).then((res) => {
if (res.TotalCount === 0) {
return undefined;
}
const groups = res.ApiGroupAttributes.ApiGroupAttribute;
const group = groups.find(
(item) => item.GroupName === groupName);
return group;
});
}
/**
* @param {{GroupId: string}} group
* https://help.aliyun.com/document_detail/43617.html
*/
deleteApiGroup(group) {
return this.agClient.deleteApiGroup(group);
}
/**
* @param {{GroupName: string, Description: string}} props
* @return {{GroupId: string, GroupName: string, SubDomain: string}}
* https://help.aliyun.com/document_detail/43611.html
*/
createApiGroup(props) {
return this.agClient.createApiGroup(props);
}
/**
* @param {string} roleName
* @return {{RoleId: string, RoleName: string, Arn: string}}
* https://help.aliyun.com/document_detail/28711.html
*/
getRole(roleName) {
return this.ramClient.getRole({
RoleName: roleName
}).then(
(res) => res.Role,
(err) => {
if (err.name === 'EntityNotExist.RoleError') {
return undefined;
}
throw err;
}
);
}
deleteRole(roleName) {
return this.ramClient.deleteRole({
RoleName: roleName
});
}
/**
* @param {object} role
* @return {{RoleId: string, RoleName: string, Arn: string}}
* https://help.aliyun.com/document_detail/28710.html
*/
createRole(role) {
return this.ramClient.createRole({
RoleName: role.RoleName,
Description: role.Description,
AssumeRolePolicyDocument: JSON.stringify(role.AssumeRolePolicyDocument)
}).then((res) => res.Role);
}
/**
* @param {string} roleName
* @return {{PolicyName: string}[]}
*/
getPoliciesForRole(roleName) {
return this.ramClient.listPoliciesForRole({
RoleName: roleName
}).then((res) => res.Policies.Policy);
}
/**
*
* @param {string} policyName
* @param {'Custom' | 'System' } policyType
*/
getPolicy(policyName, policyType) {
return this.ramClient.getPolicy({
PolicyName: policyName,
PolicyType: policyType
}).then(
(res) => res.Policy,
(err) => {
if (err.name === 'EntityNotExist.PolicyError') {
return undefined;
}
throw err;
}
);
}
getPolicyVersion(policyName, policyType, versionId) {
return this.ramClient.getPolicyVersion({
PolicyName: policyName,
PolicyType: policyType,
VersionId: versionId
}).then(
(res) => {
return res.PolicyVersion;
},
(err) => {
if (err.name === 'EntityNotExist.Policy.Version') {
return undefined;
}
throw err;
}
);
}
/**
*
* @param {{PolicyName: string, PolicyDocument: object, Description: string}} policy
*/
createPolicy(policy) {
return this.ramClient.createPolicy({
PolicyName: policy.PolicyName,
Description: policy.Description,
PolicyDocument: JSON.stringify(policy.PolicyDocument)
});
}
createPolicyVersion(policy) {
return this.ramClient.createPolicyVersion({
PolicyName: policy.PolicyName,
Description: policy.Description,
PolicyDocument: JSON.stringify(policy.PolicyDocument),
SetAsDefault: "true"
})
}
deletePolicyVersion(policyName, versionId) {
return this.ramClient.deletePolicyVersion({
PolicyName: policyName,
VersionId: versionId
})
}
/**
* @param {{RoleName: string}} role
* @param {{PolicyName: string, PolicyType: string}} policy
* @return {{PolicyName: string}[]}
*/
attachPolicyToRole(role, policy) {
return this.ramClient.attachPolicyToRole({
RoleName: role.RoleName,
PolicyName: policy.PolicyName,
PolicyType: policy.PolicyType
});
}
/**
* @param {{RoleName: string}} role
* @param {{PolicyName: string, PolicyType: string}} policy
*/
detachPolicyFromRole(role, policy) {
return this.ramClient.detachPolicyFromRole({
RoleName: role.RoleName,
PolicyName: policy.PolicyName,
PolicyType: policy.PolicyType
});
}
/**
* @param {{GroupId: string}} props
* @returns {{GroupId: string, ApiName: string, ApiId: string}[]}
* https://help.aliyun.com/document_detail/43626.html
*/
getApis(props) {
const query = Object.assign({}, props, { PageSize: 50 });
return this.agClient.describeApis(query)
.then((res) => {
if (!res.ApiSummarys) {
return [];
}
const apis = res.ApiSummarys.ApiSummary;
if (res.TotalCount > apis.length) {
// TODO(joyeecheung): pagination
}
return apis;
});
}
/**
* https://help.aliyun.com/document_detail/43625.html
*/
getApi(group, api) {
const query = {
GroupId: group.GroupId,
ApiId: api.ApiId
};
return this.agClient.describeApi(query);
}
getApiProps(group, role, api) {
const toStringify = ['RequestConfig', 'ServiceConfig',
'RequestParameters', 'ServiceParameters', 'ServiceParametersMap'
];
const props = _.cloneDeep(api);
props.ServiceConfig.FunctionComputeConfig.RoleArn = role.Arn;
props.GroupId = group.GroupId;
toStringify.forEach((key) => {
props[key] = JSON.stringify(props[key]);
});
return props;
}
/**
* @param {object} props
* https://help.aliyun.com/document_detail/43623.html
*/
createApi(group, role, api) {
const props = this.getApiProps(group, role, api);
return this.agClient.createApi(props);
}
/**
*
* @param {{GroupId: string}} group
* @param {{ApiId: string}} api
* https://help.aliyun.com/document_detail/43639.html
*/
deleteApi(group, api) {
const props = {
GroupId: group.GroupId,
ApiId: api.ApiId
};
return this.agClient.deleteApi(props);
}
/**
* @param {object} props
* https://help.aliyun.com/document_detail/43623.html
*/
updateApi(group, role, api) {
const props = this.getApiProps(group, role, api);
return this.agClient.modifyApi(props);
}
deployApi(group, api) {
const props = {
GroupId: group.GroupId,
ApiId: api.ApiId,
StageName: 'RELEASE', // TODO(joyeecheung): should be based on this.options.stage?
Description: 'Release by the Serverless framework'
};
return this.agClient.deployApi(props);
}
/**
* @param {{GroupId: string}} props
* @returns {{GroupId: string, ApiName: string, ApiId: string}[]}
*/
getDeployedApis(props) {
const query = {
GroupId: props.GroupId,
StageName: 'RELEASE', // TODO(joyeecheung): should be based on this.options.stage?
PageSize: 50 // TODO(joyeecheung): pagination
};
return this.agClient.describeDeployedApis(query)
.then((res) => {
if (!res.DeployedApis) {
return [];
}
const apis = res.DeployedApis.DeployedApiItem;
if (res.TotalCount > apis.length) {
// TODO(joyeecheung): pagination
}
return apis.filter((item) => item.RegionId === this.options.region);
});
}
abolishApi(group, api) {
const props = {
GroupId: group.GroupId,
ApiId: api.ApiId,
StageName: 'RELEASE', // TODO(joyeecheung): should be based on this.options.stage?
};
return this.agClient.abolishApi(props);
}
createTable(tableResource) {
const tsClient = this.getTsClient(tableResource.instanceName);
tsClient.describeTable({ tableName: tableResource.params.tableMeta.tableName }, function(err, data) {
if (err) {
console.log("table %s from instance %s do not exists", tableResource.params.tableMeta.tableName, tableResource.instanceName);
tsClient.createTable(tableResource.params, function(err, data) {
if (err) {
console.log("Error: %s", JSON.stringify(err));
throw new Error(`Create table ${tableResource.params.tableMeta.tableName} instance %s failed`, tableResource.instanceName);
}
else {
console.log("Table %s instance %s created", tableResource.params.tableMeta.tableName, tableResource.instanceName);
}
});
}
else {
console.log("Table %s instance %s already created, ignore!", tableResource.params.tableMeta.tableName, tableResource.instanceName);
}
});
}
deleteTable(tableResource) {
const tsClient = this.getTsClient(tableResource.instanceName);
tsClient.describeTable({ tableName: tableResource.params.tableMeta.tableName }, function(err, data) {
if (data) {
tsClient.deleteTable({ tableName: tableResource.params.tableMeta.tableName }, function(err, data) {
if (err) {
console.log("Error: %s", JSON.stringify(err));
throw new Error(`Delete table ${tableResource.params.tableMeta.tableName} instance %s failed`, tableResource.instanceName);
}
else {
console.log("Table %s instance %s Deleted", tableResource.params.tableMeta.tableName, tableResource.instanceName);
}
});
}
if (err) {
if (err.message.indexOf("OTSObjectNotExist") >= 0) {
return;
}
console.log("error :%s", JSON.stringify(err));
throw new Error(`Delete table ${tableResource.params.tableMeta.tableName} instance %s failed`, tableResource.instanceName);
}
});
}
}
module.exports = AliyunProvider;