diff --git a/bin/fun-nas-rm.js b/bin/fun-nas-rm.js new file mode 100755 index 000000000..e76879761 --- /dev/null +++ b/bin/fun-nas-rm.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node + +/* eslint-disable quotes */ + +'use strict'; + +const program = require('commander'); +const getVisitor = require('../lib/visitor').getVisitor; +const notifier = require('../lib/update-notifier'); + +program + .name('fun nas rm') + .description('Remove remote NAS file.') + .usage('[options] ') + .option('-r, --recursive', 'remove folders recursively') + .option('-f, --force', 'remove files without prompting for confirmation') + .parse(process.argv); + + +if (!program.args.length) { + console.error(); + console.error(" error: missing argument [nasDir]"); + program.help(); +} + +notifier.notify(); + +getVisitor(true).then((visitor) => { + visitor.pageview('/fun/nas/rm').send(); + + require('../lib/commands/nas/rm')(program.args[0], program) + .then(() => { + visitor.event({ + ec: 'rm', + ea: `rm`, + el: 'success', + dp: '/fun/nas/rm' + }).send(); + }) + .catch(error => { + visitor.event({ + ec: 'rm', + ea: `rm`, + el: 'error', + dp: '/fun/nas/rm' + }).send(); + + require('../lib/exception-handler')(error); + }); + +}); diff --git a/bin/fun-nas.js b/bin/fun-nas.js old mode 100644 new mode 100755 index ce0fedee8..4f6a29145 --- a/bin/fun-nas.js +++ b/bin/fun-nas.js @@ -12,7 +12,8 @@ program .command('info', 'Print nas config information, such as local temp directory of NAS.') .command('init', 'For each service with NAS config, create local NAS folder and deploy fun nas server service.') .command('sync', 'Synchronize the local NAS directory with the remote NAS file system.') - .command('ls', 'List contents of remote NAS directory'); + .command('ls', 'List contents of remote NAS directory') + .command('rm', 'Remove remote NAS file.'); // Print help information if commands are unknown. program.on('command:*', (cmds) => { diff --git a/lib/commands/nas/rm.js b/lib/commands/nas/rm.js new file mode 100644 index 000000000..99b1b4ce4 --- /dev/null +++ b/lib/commands/nas/rm.js @@ -0,0 +1,30 @@ +'use strict'; + +const rmNasFile = require('../../nas/rm'); +const { parseNasPath } = require('../../nas/path'); +const { detectTplPath } = require('../../tpl'); +const path = require('path'); +const validate = require('../../validate/validate'); +const { red } = require('colors'); + +async function rm(nasDir, options) { + + const tplPath = await detectTplPath(); + + if (!tplPath) { + throw new Error(red('Current folder not a fun project\nThe folder must contains template.[yml|yaml] or faas.[yml|yaml] .')); + } else if (path.basename(tplPath).startsWith('template')) { + await validate(tplPath); + + const isRecursive = options.recursive; + const isForce = options.force; + + const { nasPath, serviceName } = await parseNasPath(nasDir); + + await rmNasFile(serviceName, nasPath, isRecursive, isForce); + } else { + throw new Error(red('The template file name must be template.[yml|yaml].')); + } +} + +module.exports = rm; diff --git a/lib/docker-opts.js b/lib/docker-opts.js index f515b53de..dbf8d10f5 100644 --- a/lib/docker-opts.js +++ b/lib/docker-opts.js @@ -65,9 +65,9 @@ async function resolveRuntimeToDockerImage(runtime, isBuild) { const name = runtimeImageMap[runtime]; var imageName; if (isBuild) { - imageName = `aliyunfc/runtime-${name}:build-1.5.5`; + imageName = `aliyunfc/runtime-${name}:build-1.5.6`; } else { - imageName = `aliyunfc/runtime-${name}:1.5.5`; + imageName = `aliyunfc/runtime-${name}:1.5.6`; } const dockerImageRepo = await resolveDockerImageRepo(); diff --git a/lib/fc-utils/fc-fun-nas-server/package-lock.json b/lib/fc-utils/fc-fun-nas-server/package-lock.json index c38b7d587..aaed297c5 100644 --- a/lib/fc-utils/fc-fun-nas-server/package-lock.json +++ b/lib/fc-utils/fc-fun-nas-server/package-lock.json @@ -2018,9 +2018,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==" } } }, diff --git a/lib/nas/cp/upload.js b/lib/nas/cp/upload.js index d29fbe574..ae454ad93 100644 --- a/lib/nas/cp/upload.js +++ b/lib/nas/cp/upload.js @@ -82,7 +82,9 @@ async function upload(srcPath, dstPath, nasHttpTriggerPath) { await uploadSplitFiles(needToUploadfiles, nasTmpDir, nasHttpTriggerPath); + console.log('Merging split files and unzipping'); await sendMergeRequest(nasHttpTriggerPath, nasTmpDir, dstDir, fileName, zipHash); + console.log(`${green('✔')} merge and unzip done`); } rimraf.sync(tmpDir); diff --git a/lib/nas/init.js b/lib/nas/init.js index ef2563298..fbe53438f 100644 --- a/lib/nas/init.js +++ b/lib/nas/init.js @@ -10,7 +10,8 @@ const definition = require('../definition'); const constants = require('./constants'); const { green } = require('colors'); const tips = require('./tips'); - +const nas = require('../nas'); +const _ = require('lodash'); async function deployNasService(baseDir, tpl) { const profile = await getProfile(); @@ -73,6 +74,12 @@ async function deployNasService(baseDir, tpl) { console.log(`Waiting for service ${nasServiceName} to be deployed...`); await deployService(baseDir, nasServiceName, nasServiceRes); console.log(green(`service ${nasServiceName} deploy success\n`)); + + const nasMappings = await nas.convertNasConfigToNasMappings(baseDir, nasConfig, serviceName); + console.log(green(`Create local NAS directory of service ${serviceName}:`)); + _.forEach(nasMappings, (mappings) => { + console.log(`\t${mappings.localNasDir}`); + }); } console.log(); diff --git a/lib/nas/rm.js b/lib/nas/rm.js new file mode 100644 index 000000000..93b384950 --- /dev/null +++ b/lib/nas/rm.js @@ -0,0 +1,23 @@ +'use strict'; +const { sendCmdReqequest, getNasHttpTriggerPath } = require('./request'); +const { green } = require('colors'); + +function generateRmCmd(nasPath, isRecursiveOpt, isForceOpt) { + let cmd = 'rm ' + (isRecursiveOpt ? '-R ' : '') + (isForceOpt ? '-f ' : '') + nasPath; + return cmd; +} + +async function rm(serviceName, nasPath, isRecursiveOpt, isForceOpt) { + console.log('Removing...'); + const nasHttpTriggerPath = await getNasHttpTriggerPath(serviceName); + + const rmCmd = generateRmCmd(nasPath, isRecursiveOpt, isForceOpt); + + const rmResponse = await sendCmdReqequest(nasHttpTriggerPath, rmCmd); + + console.log(rmResponse.data.stdout); + console.log(rmResponse.data.stderr); + console.log(`${green('✔')} remove ${nasPath} done`); +} + +module.exports = rm; \ No newline at end of file diff --git a/lib/validate/schema/function.js b/lib/validate/schema/function.js index b7a96b4b2..87711f921 100644 --- a/lib/validate/schema/function.js +++ b/lib/validate/schema/function.js @@ -20,7 +20,7 @@ const functionSchema = { }, 'Runtime': { 'type': 'string', - 'enum': ['nodejs6', 'nodejs8', 'python2.7', 'python3', 'java8', 'php7.2', 'nodejs10', 'dotnetcore2.1'] + 'enum': ['nodejs6', 'nodejs8', 'python2.7', 'python3', 'java8', 'php7.2', 'nodejs10', 'dotnetcore2.1', 'custom'] }, 'CodeUri': { 'type': 'string' diff --git a/test/commands/nas/mock-data.js b/test/commands/nas/mock-data.js index 2666e865c..5d0f22f82 100644 --- a/test/commands/nas/mock-data.js +++ b/test/commands/nas/mock-data.js @@ -35,5 +35,6 @@ const tpl = { module.exports = { nasConfig, - tpl + tpl, + vpcConfig }; \ No newline at end of file diff --git a/test/commands/nas/rm.test.js b/test/commands/nas/rm.test.js new file mode 100644 index 000000000..2c00f4a93 --- /dev/null +++ b/test/commands/nas/rm.test.js @@ -0,0 +1,57 @@ +'use strict'; + + +const expect = require('expect.js'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); +const sandbox = sinon.createSandbox(); +const assert = sinon.assert; +const path = require('path'); + +const rmNasFile = sandbox.stub(); +const validate = sandbox.stub(); + +const tpl = { + detectTplPath: sandbox.stub().returns('/template.yml') +}; + +const rmStub = proxyquire('../../../lib/commands/nas/rm', { + '../../validate/validate': validate, + '../../nas/rm': rmNasFile, + '../../tpl': tpl +}); + +describe('command rm test', () => { + const options = + { + recursive: true, + force: true + }; + + afterEach(() => { + sandbox.reset(); + }); + + it('valid nas path', async () => { + const nasPath = 'nas://demo:/mnt/nas'; + + await rmStub(nasPath, options); + const mntDir = path.join('/', 'mnt', 'nas'); + assert.calledWith(rmNasFile, 'demo', mntDir, options.recursive, options.force); + }); + + it('invalid nas path', async () => { + const nasPath = '://demo://mnt/nas'; + let err; + + try { + await rmStub(nasPath, options); + assert.notCalled(rmNasFile); + } catch (error) { + err = error; + } + expect(err).to.eql(new Error('nas path err: ' + nasPath)); + + }); +}); + diff --git a/test/docker-opts.test.js b/test/docker-opts.test.js index 6fb61a77a..42667906d 100644 --- a/test/docker-opts.test.js +++ b/test/docker-opts.test.js @@ -153,7 +153,7 @@ describe('test generateLocalInvokeOpts', () => { 'OpenStdin': true, 'StdinOnce': true, 'Tty': false, - 'Image': 'aliyunfc/runtime-nodejs8:1.5.5', + 'Image': 'aliyunfc/runtime-nodejs8:1.5.6', 'HostConfig': { 'AutoRemove': true, 'Mounts': [ @@ -199,7 +199,7 @@ describe('test generateLocalInvokeOpts', () => { 'StdinOnce': true, 'Tty': false, 'User': null, - 'Image': 'aliyunfc/runtime-nodejs8:1.5.5', + 'Image': 'aliyunfc/runtime-nodejs8:1.5.6', 'Env': [ 'LD_LIBRARY_PATH=/code/.fun/root/usr/lib:/code/.fun/root/usr/lib/x86_64-linux-gnu:/code:/code/lib:/usr/local/lib', 'PATH=/code/.fun/root/usr/local/bin:/code/.fun/root/usr/local/sbin:/code/.fun/root/usr/bin:/code/.fun/root/usr/sbin:/code/.fun/root/sbin:/code/.fun/root/bin:/code/.fun/python/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin', diff --git a/test/local/invoke.test.js b/test/local/invoke.test.js index 1560c83e5..6b8626e6a 100644 --- a/test/local/invoke.test.js +++ b/test/local/invoke.test.js @@ -77,9 +77,9 @@ describe('test invoke construct and init', async () => { expect(invoke.codeMount).to.eql(codeMount); expect(invoke.mounts).to.eql([codeMount]); expect(invoke.containerName).to.contain('fun_local_'); - expect(invoke.imageName).to.contain('aliyunfc/runtime-python3.6:1.5.5'); + expect(invoke.imageName).to.contain('aliyunfc/runtime-python3.6:1.5.6'); - assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.5'); + assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.6'); }); it('test init with nas config', async () => { @@ -109,9 +109,9 @@ describe('test invoke construct and init', async () => { expect(invoke.codeMount).to.eql(codeMount); expect(invoke.mounts).to.eql([codeMount, ...nasMounts]); expect(invoke.containerName).to.contain('fun_local_'); - expect(invoke.imageName).to.eql('aliyunfc/runtime-python3.6:1.5.5'); + expect(invoke.imageName).to.eql('aliyunfc/runtime-python3.6:1.5.6'); - assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.5'); + assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.6'); }); }); diff --git a/test/nas/cp.test.js b/test/nas/cp.test.js index 9bd8ebe9f..5ec6ac092 100644 --- a/test/nas/cp.test.js +++ b/test/nas/cp.test.js @@ -34,6 +34,7 @@ describe('nas cp test', () => { const mntDir = path.join('/', 'mnt', 'nas'); const nasHttpTriggerPath = `/proxy/${constants.FUN_NAS_SERVICE_PREFIX}fun-nas-test/fun-nas-function/`; assert.calledWith(upload, srcPath, mntDir, nasHttpTriggerPath, false); + }); it('src path undefined test', async () => { diff --git a/test/nas/init.test.js b/test/nas/init.test.js index 35d9d9cc8..c091f66d4 100644 --- a/test/nas/init.test.js +++ b/test/nas/init.test.js @@ -1,87 +1,68 @@ 'use strict'; -const fs = require('fs-extra'); const path = require('path'); const sinon = require('sinon'); const proxyquire = require('proxyquire'); +const nasMockData = require('../commands/nas/mock-data'); const constants = require('../../lib/nas/constants'); +const { setProcess } = require('../test-utils'); const sandbox = sinon.createSandbox(); const assert = sinon.assert; +const baseDir = path.join('/', 'test-dir'); -const baseDir = '/test-dir'; -const proflieRes = { - defaultRegion: 'cn-hangzhou', - accountId: '12345', - accessKeyId: '123', - timeout: 60 -}; describe('test fun nas init', () => { - let fsPathExists; - let profile; let deploy; let nasInitStub; + let nas; + let fs; + let restoreProcess; beforeEach(() => { - profile = { - getProfile: sandbox.stub() - }; + + restoreProcess = setProcess({ + ACCOUNT_ID: 'ACCOUNT_ID', + DEFAULT_REGION: 'cn-shanghai', + ACCESS_KEY_ID: 'ACCESS_KEY_ID', + ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET' + }); + deploy = { deployService: sandbox.stub() }; - + nas = { + convertNasConfigToNasMappings: sandbox.stub() + }; + fs = { + pathExists: sandbox.stub() + }; nasInitStub = proxyquire('../../lib/nas/init', { - '../profile': profile, - '../deploy/deploy-by-tpl': deploy + '../deploy/deploy-by-tpl': deploy, + '../nas': nas, + 'fs-extra': fs }); - fsPathExists = sandbox.stub(fs, 'pathExists'); + }); + afterEach(() => { + restoreProcess(); sandbox.restore(); }); it('function deployNasService', async () => { - const nasConfig = { - UserId: 10003, - GroupId: 10003, - MountPoints: [{ - ServerAddr: '359414a1be-lwl67.cn-shanghai.nas.aliyuncs.com:/', - MountDir: '/mnt/nas' - }] - }; - const vpcConfig = { - VpcId: 'vpc-uf6p2abfodpmpmzu6onhy', - VSwitchIds: [ - 'vsw-uf6074gzypzsc95idtxk8' - ], - SecurityGroupId: 'sg-uf6h3g45f2fo5lr04akb' - }; - const policies = ['AliyunECSNetworkInterfaceManagementAccess', 'AliyunOSSFullAccess']; - const tpl = { - ROSTemplateFormatVersion: '2015-09-01', - Transform: 'Aliyun::Serverless-2018-04-03', - Resources: { - 'fun-nas-test': { - Type: 'Aliyun::Serverless::Service', - Properties: { - Policies: policies, - VpcConfig: vpcConfig, - NasConfig: nasConfig - } - } - } - }; const serviceName = 'fun-nas-test'; const nasServiceName = constants.FUN_NAS_SERVICE_PREFIX + serviceName; - const nasFunctionName = 'fun-nas-function'; + const nasFunctionName = constants.FUN_NAS_FUNCTION; + fs.pathExists.returns(true); const zipCodePath = path.resolve(__dirname, '../../lib/fc-utils/fc-fun-nas-server/dist/fun-nas-server.zip'); + const nasServiceRes = { 'Type': 'Aliyun::Serverless::Service', 'Properties': { 'Description': `service for fc nas used for service ${serviceName}`, - 'VpcConfig': vpcConfig, - 'NasConfig': nasConfig + 'VpcConfig': nasMockData.vpcConfig, + 'NasConfig': nasMockData.nasConfig }, [nasFunctionName]: { Type: 'Aliyun::Serverless::Function', @@ -105,14 +86,13 @@ describe('test fun nas init', () => { } } }; - profile.getProfile.returns(proflieRes); - fsPathExists.resolves(true); - - await nasInitStub.deployNasService(baseDir, tpl); + + nas.convertNasConfigToNasMappings.returns([{localNasDir: baseDir, remoteNasDir: baseDir}]); + await nasInitStub.deployNasService(baseDir, nasMockData.tpl); - assert.calledWith(fsPathExists, zipCodePath); + assert.calledWith(fs.pathExists, zipCodePath); assert.calledWith(deploy.deployService, baseDir, nasServiceName, nasServiceRes); - + }); }); \ No newline at end of file diff --git a/test/nas/ls.test.js b/test/nas/ls.test.js index fdb0f61f4..7366891e0 100644 --- a/test/nas/ls.test.js +++ b/test/nas/ls.test.js @@ -2,6 +2,7 @@ const sinon = require('sinon'); +const path = require('path'); const getNasHttpTriggerPath = require('../../lib/nas/request').getNasHttpTriggerPath; const sandbox = sinon.createSandbox(); const assert = sinon.assert; @@ -10,7 +11,7 @@ const proxyquire = require('proxyquire'); describe('ls test', () => { const serviceName = 'demo'; - const nasPath = '/mnt/nas'; + const nasPath = path.join('/', 'mnt', 'nas'); const isAllOpt = true; const isLongOpt = true; @@ -37,7 +38,7 @@ describe('ls test', () => { it('ls function test', async () => { await ls(serviceName, nasPath, isAllOpt, isLongOpt); const nasHttpTriggerPath = await getNasHttpTriggerPath(serviceName); - const cmd = 'ls -a -l /mnt/nas'; + const cmd = `ls -a -l ${nasPath}`; assert.calledWith(request.sendCmdReqequest, nasHttpTriggerPath, cmd); }); diff --git a/test/nas/rm.test.js b/test/nas/rm.test.js new file mode 100644 index 000000000..e08ff33d5 --- /dev/null +++ b/test/nas/rm.test.js @@ -0,0 +1,45 @@ +'use strict'; + + +const sinon = require('sinon'); +const path = require('path'); +const getNasHttpTriggerPath = require('../../lib/nas/request').getNasHttpTriggerPath; +const sandbox = sinon.createSandbox(); +const assert = sinon.assert; +const proxyquire = require('proxyquire'); + + +describe('ls test', () => { + const serviceName = 'demo'; + const nasPath = path.join('/', 'mnt', 'nas'); + const isRecursiveOpt = true; + const isForceOpt = true; + + let rm; + + let request; + beforeEach(() => { + + request = { + sendCmdReqequest: sandbox.stub().returns({ + data: '123', + stderr: '' + }) + }; + rm = proxyquire('../../lib/nas/rm', { + './request': request + }); + + }); + afterEach(() => { + sandbox.restore(); + }); + + it('rm function test', async () => { + await rm(serviceName, nasPath, isRecursiveOpt, isForceOpt); + const nasHttpTriggerPath = await getNasHttpTriggerPath(serviceName); + const cmd = `rm -R -f ${nasPath}`; + assert.calledWith(request.sendCmdReqequest, nasHttpTriggerPath, cmd); + + }); +}); \ No newline at end of file