diff --git a/README.md b/README.md index 801409a..d4207ea 100755 --- a/README.md +++ b/README.md @@ -401,4 +401,29 @@ ret = await Serverless.getComponentVersion('name', 'version'); console.log(ret) ``` +### Scf 扩展API +```javascript +const { Others } = require('serverless-tencent-tools') +const ScfEx = Others.Scf; + +const scfEx = new ScfEx({ + appid: appid, + secret_id: secret_id, + secret_key: secret_key, + options: { + region: 'ap-guangzhou', + token: 'token' + } +}) + +const ret = await scfEx.publishLayerVersion({ + layerName: 'name', + compatibleRuntimes: ['Nodejs8.9'], + description: 'test node layer', // optional + zipFilePath: 'path/to/layer.zip' // the file size limit 10MB +}); + +console.log(ret) +``` + (* 该接口目前为1.0版本,后期会增加其复杂度,但是接口规范不会变。) diff --git a/library/common/http/http_connection.js b/library/common/http/http_connection.js index 56846e1..4df1fae 100644 --- a/library/common/http/http_connection.js +++ b/library/common/http/http_connection.js @@ -32,12 +32,12 @@ class HttpConnection { // node http[s] raw module send request let httpClient - const httpBody = QueryString.stringify(data) + const httpBody = JSON.stringify(data) opt = opt || {} opt.method = method if (method === 'GET') { - reqUrl += '?' + httpBody + reqUrl += '?' + QueryString.stringify(data) } else { opt['headers'] = opt['headers'] || {} opt['headers']['Content-Type'] = 'application/json' diff --git a/library/common/sign.js b/library/common/sign.js index 7fa32e8..5a8ae59 100644 --- a/library/common/sign.js +++ b/library/common/sign.js @@ -1,6 +1,6 @@ const TencentCloudSDKHttpException = require('./exception/tencent_cloud_sdk_exception') const crypto = require('crypto') - +const util = require('util') /** * @inner */ @@ -19,5 +19,89 @@ class Sign { const hmac = crypto.createHmac(signMethodMap[signMethod], secretKey || '') return hmac.update(Buffer.from(signStr, 'utf8')).digest('base64') } + + static signv3(credential, endpoint, time, signStr) { + + const ApiTc3Request = 'tc3_request' + const ApiSignedHeaders = 'content-type;host' + + const PrefixInteger = function(num, length) { + return (Array(length).join('0') + num).slice(-length) + } + + const sign = function(key, msg, hex) { + if (hex) { + return crypto + .createHmac('sha256', key) + .update(msg, 'utf8') + .digest('hex') + } + return crypto.createHmac('sha256', key).update(msg, 'utf8') + } + const newDate = time; + const timestamp = Math.ceil(newDate.getTime() / 1000) + const ctype = 'application/json' + const algorithm = 'TC3-HMAC-SHA256' + const payload = signStr + const canonical_headers = util.format('content-type:%s\nhost:%s\n', ctype, endpoint) + const http_request_method = 'POST' + const canonical_uri = '/' + const canonical_querystring = '' + const date = util.format( + '%s-%s-%s', + newDate.getFullYear(), + PrefixInteger(newDate.getMonth() + 1, 2), + PrefixInteger(newDate.getUTCDate(), 2) + ) + + const hashed_request_payload = crypto + .createHash('sha256') + .update(payload, 'utf8') + .digest() + const canonical_request = + http_request_method + + '\n' + + canonical_uri + + '\n' + + canonical_querystring + + '\n' + + canonical_headers + + '\n' + + ApiSignedHeaders + + '\n' + + hashed_request_payload.toString('hex') + console.log(canonical_request) + const service = endpoint.split('.')[0] + const credential_scope = date + '/' + service + '/' + ApiTc3Request + const hashed_canonical_request = crypto + .createHash('sha256') + .update(canonical_request, 'utf8') + .digest() + const string_to_sign = + algorithm + + '\n' + + timestamp + + '\n' + + credential_scope + + '\n' + + hashed_canonical_request.toString('hex') + console.log(string_to_sign) + const secret_date = sign('TC3' + credential.secretKey, date, false) + const secret_service = sign(new Buffer.from(secret_date.digest('hex'), 'hex'), service, false) + const secret_signing = sign( + new Buffer.from(secret_service.digest('hex'), 'hex'), + ApiTc3Request, + false + ) + const signature = sign(new Buffer.from(secret_signing.digest('hex'), 'hex'), string_to_sign, true) + return util.format( + '%s Credential=%s/%s, SignedHeaders=%s, Signature=%s', + algorithm, + credential.secretId, + credential_scope, + ApiSignedHeaders, + signature + ) + } } module.exports = Sign diff --git a/sdk/others/index.js b/sdk/others/index.js index aef9a21..c0c5810 100644 --- a/sdk/others/index.js +++ b/sdk/others/index.js @@ -1,4 +1,5 @@ module.exports = { IsInChina: require('./isInChina'), - DataReport: require('./dataReport') + DataReport: require('./dataReport'), + Scf: require('./scfExtension') } diff --git a/sdk/others/scfExtension.js b/sdk/others/scfExtension.js new file mode 100644 index 0000000..6088895 --- /dev/null +++ b/sdk/others/scfExtension.js @@ -0,0 +1,126 @@ +'use strict' +const util = require('util') +const assert = require('assert') +const fs = require('fs') +const { scf, common } = require('../../library') +const HttpConnection = common.HttpConnection +const ScfClient = scf.v20180416.Client +const ScfModels = scf.v20180416.Models +const Sign = require('../../library/common/sign') +const Credential = common.Credential +const HttpProfile = common.HttpProfile +const ClientProfile = common.ClientProfile +const TencentCloudSDKHttpException = require('../../library/common/exception/tencent_cloud_sdk_exception') + + +class ScfEx { + constructor({ appid, secret_id, secret_key, options }) { + this.appid = appid + this.secretKey = secret_key + this.secretId = secret_id + this.options = options + assert(options, 'Options should not is empty') + this._scfClient = ScfEx.createClient(secret_id, secret_key, options) + } + + static getCredential(secret_id, secret_key, options) { + const cred = options.token + ? new Credential(secret_id, secret_key, options.token) + : new Credential(secret_id, secret_key) + const httpProfile = new HttpProfile() + httpProfile.reqTimeout = 30 + const clientProfile = new ClientProfile('HmacSHA256', httpProfile) + assert(options.region, 'Region should not is empty') + return { + cred: cred, + region: options.region, + clientProfile: clientProfile + } + } + + static createClient(secret_id, secret_key, options) { + const info = ScfEx.getCredential(secret_id, secret_key, options) + const scfCli = new ScfClient(info.cred, info.region, info.clientProfile) + scfCli.sdkVersion = 'ServerlessFramework' + return scfCli + } + + async publishLayerVersion({layerName, compatibleRuntimes, zipFilePath, description, licenseInfo}) { + const req = {} + + assert(layerName, 'The request is missing a required parameter layerName') + assert(compatibleRuntimes, 'The request is missing a required parameter compatibleRuntimes') + assert(zipFilePath, 'The request is missing a required parameter zipFilePath') + + try { + fs.existsSync(zipFilePath) + } catch(err) { + assert(false, err) + } + const stat = fs.statSync(zipFilePath) + assert(stat.size <= (10 * 1024 * 1024), 'The request is layer file size limit exceed, max size 10MB') + + req.Content = { + ZipFile: fs.readFileSync(zipFilePath).toString('base64') + }; + req.LayerName = layerName; + if (description) + req.Description = description; + req.CompatibleRuntimes = compatibleRuntimes; + if (licenseInfo) + req.LicenseInfo = licenseInfo + + const newDate = new Date(); + const sign = Sign.signv3( + {secretKey: this.secretKey, secretId: this.secretId}, + this._scfClient.getEndpoint(), + newDate, JSON.stringify(req)) + + const headers = { + 'X-TC-Action': 'PublishLayerVersion', + 'X-TC-RequestClient': 'ServerlessFramework', + 'X-TC-Timestamp': Math.ceil(newDate.getTime() / 1000), + 'X-TC-Version': '2018-04-16', + 'X-TC-Region': this.options.region, + 'Host': this._scfClient.getEndpoint(), + 'Authorization': sign + } + + const optional = { + timeout: 30 * 1000, + headers: headers + } + + return new Promise((resolve, reject) => { + HttpConnection.doRequest( + 'POST', + 'https://' + this._scfClient.getEndpoint() + '/', + req, + (error, response, data) => { + if (error) { + reject(new TencentCloudSDKHttpException(error.message)) + } else if (response.statusCode !== 200) { + const tcError = new TencentCloudSDKHttpException(response.statusMessage) + tcError.httpCode = response.statusCode + reject(tcError) + } else { + data = JSON.parse(data) + if (data.Response && data.Response.Error) { + const tcError = new TencentCloudSDKHttpException( + data.Response.Error.Message, + data.Response.RequestId + ) + tcError.code = data.Response.Error.Code + reject(tcError) + } else { + resolve(data.Response) + } + } + }, + optional + ) + }) + } +} + +module.exports = ScfEx \ No newline at end of file diff --git a/sdk/serverless/index.js b/sdk/serverless/index.js index daf4bba..2080abb 100755 --- a/sdk/serverless/index.js +++ b/sdk/serverless/index.js @@ -56,8 +56,7 @@ class Serverless { } static async doRequest(action, params) { - const proxyOrigin = - 'https://service-m98cluso-1253970226.gz.apigw.tencentcs.com/release/listcompversion' + const proxyOrigin = 'https://service-cqwfbiyw-1300862921.gz.apigw.tencentcs.com/release/listcompversion' const optional = { timeout: 30 * 1000