Repository: aliyun/fun
Pull request #90 (2018-11-18 15:03:49)

add config option, credentials and pull progress support


Patch:
diff --git a/bin/fun-local.js b/bin/fun-local.js
index cf95969..58d3bab 100644
--- a/bin/fun-local.js
+++ b/bin/fun-local.js
@@ -13,10 +13,11 @@ program.description('local run your serverless application');
 program
   .command('invoke <invokeName>')
   .usage('invoke [options] <[service/]function>')
-  .option('-d, --debug-port <port>', 'used for local debugging')
-  // todo: generate、detect、autoconfig vscode debug config options
+  .option('-d, --debug-port <port>', 'Used for local debugging')
   .description('Run your serverless application locally for quick development & testing.') 
-  .option('-e, --event <path>', 'event file containing event data passed to the function')
+  // todo: add auto option to auto config vscode
+  .option('-c, --config <ide/debugger>', 'Print out ide debug configuration. Options are vscode')
+  .option('-e, --event <path>', 'Event file containing event data passed to the function')
   .action(function(invokeName, options) {
     require('../lib/commands/local')(invokeName, options).catch(handler);
   });
diff --git a/lib/commands/local.js b/lib/commands/local.js
index 0e790c1..e88e7a9 100644
--- a/lib/commands/local.js
+++ b/lib/commands/local.js
@@ -33,7 +33,7 @@ function parseInvokeName(invokeName) {
   return [serviceName, functionName];
 }
 
-async function localInvoke(invokeName, tpl, debugPort, event) {
+async function localInvoke(invokeName, tpl, debugPort, event, debugIde) {
   debug(`invokeName: ${invokeName}`);
 
   const [parsedServiceName, parsedFunctionName] = parseInvokeName(invokeName);
@@ -49,7 +49,7 @@ async function localInvoke(invokeName, tpl, debugPort, event) {
     process.exit(-1);
   }
 
-  await docker.invokeFunction(serviceName, functionName, functionDefinition, debugPort, event);
+  await docker.invokeFunction(serviceName, functionName, functionDefinition, debugPort, event, debugIde);
 }
 
 async function getEventContent(options) {
@@ -75,6 +75,22 @@ async function getEventContent(options) {
   return event;
 }
 
+const allowedDebugIdes = ['vscode'];
+
+function getDebugIde(options) {
+  if (options.config) {
+    const debugIde = options.config;
+
+    if (allowedDebugIdes.indexOf(debugIde.toLowerCase()) > -1) {
+      return debugIde;
+    } 
+    console.error(red(`Error parsing debug config. Option is one of: ${JSON.stringify(allowedDebugIdes)}`));
+    process.exit(-1);
+  }
+
+  return null;
+}
+
 async function local(invokeName, options) {
 
   const tplPath = await detectTplPath();
@@ -109,8 +125,10 @@ async function local(invokeName, options) {
     }
 
     debug(`debugPort: ${debugPort}`);
+    
+    const debugIde = getDebugIde(options);
 
-    await localInvoke(invokeName, tpl, debugPort, event);
+    await localInvoke(invokeName, tpl, debugPort, event, debugIde);
   } else {
     console.error(red('The template file name must be template.[yml|yaml].'));
     process.exit(-1);
diff --git a/lib/debug.js b/lib/debug.js
index 91b5e5c..e0863ff 100644
--- a/lib/debug.js
+++ b/lib/debug.js
@@ -49,7 +49,7 @@ async function generateVscodeDebugConfig(serviceName, functionName, runtime, cod
           'port': debugPort,
           'localRoot': `${codePath}`,
           'remoteRoot': '/code',
-          'protocol': 'inspect',
+          'protocol': 'inspector',
           'stopOnEntry': false
         }
       ]
@@ -127,6 +127,7 @@ function generateDebugEnv(runtime, debugPort) {
   case 'java8':
     return `DEBUG_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=${debugPort}`;
   case 'php7.2':
+    console.log(`using remote_ip ${remoteIp}`);
     return `XDEBUG_CONFIG=remote_enable=1 remote_autostart=1 remote_port=${debugPort} remote_host=${remoteIp}`;
   default:
     console.error(red('could not found runtime.'));
diff --git a/lib/docker.js b/lib/docker.js
index 8dbd533..0e89e33 100644
--- a/lib/docker.js
+++ b/lib/docker.js
@@ -2,6 +2,8 @@
 
 const nestedObjectAssign = require('nested-object-assign');
 
+const getProfile = require('./profile').getProfile;
+
 const fs = require('fs');
 const path = require('path');
 
@@ -88,7 +90,7 @@ function generateDockerCmd(functionProps, event) {
 function findDockerImage(runtime) {
   if (runtimeImageMap[runtime]) {
     const name = runtimeImageMap[runtime];
-    const imageName = `aliyunfc/runtime-${name}`;
+    const imageName = `aliyunfc/runtime-${name}:latest`;
 
     debug('imageName: ' + imageName);
 
@@ -98,7 +100,23 @@ function findDockerImage(runtime) {
 }
 
 async function pullImage(imageName) {
-  await docker.pull(imageName);
+
+  // see https://github.com/apocas/dockerode/blob/master/README.md#helper-functions
+  await docker.pull(imageName, null, function(err, stream) {
+
+    process.stdout.write(`begin pullling image ${imageName}`);
+
+    function onFinished(err, output) {
+      console.log('\npull image finished');
+    }
+
+    function onProgress(event) {
+      debug(event);
+      process.stdout.write('.');
+    }
+
+    docker.modem.followProgress(stream, onFinished, onProgress);
+  });
 }
 
 function generateFunctionEnvs(functionProps) {
@@ -117,11 +135,13 @@ function generateFunctionEnvs(functionProps) {
   return envs;
 }
 
-function generateDockerOpts(functionProps, runtime, mount, debugPort) {
+async function generateDockerOpts(functionProps, runtime, mount, debugPort) {
 
   let envs = generateFunctionEnvs(functionProps);
 
-  envs.push('local=true');
+  const profile = await getProfile();
+
+  envs.push('local=true', `FC_ACCESS_KEY_ID=${profile.accessKeyId}`, `FC_ACCESS_KEY_SECRET=${profile.accessKeySecret}`);
 
   const dockerOpts = {
     HostConfig: {
@@ -157,7 +177,8 @@ function generateDockerOpts(functionProps, runtime, mount, debugPort) {
   return opts;
 }
 
-async function invokeFunction(serviceName, functionName, functionDefinition, debugPort, event) {
+async function invokeFunction(serviceName, functionName, functionDefinition, debugPort, event, debugIde) {
+
   // todo: exit container, when use ctrl + c
   
   const functionProps = functionDefinition.Properties;
@@ -191,7 +212,7 @@ async function invokeFunction(serviceName, functionName, functionDefinition, deb
   
   debug('debug port: ' + debugPort);
   
-  if (debugPort) {
+  if (debugPort && debugIde) {
     const vscodeDebugConfig = await generateVscodeDebugConfig(serviceName, functionName, runtime, mount.Source, debugPort);
   
     // todo: auto detect .vscode/launch.json in codeuri path.
@@ -201,7 +222,7 @@ async function invokeFunction(serviceName, functionName, functionDefinition, deb
     console.log('///////////////// config end /////////////////');
   }
   
-  const opts = generateDockerOpts(functionProps, runtime, mount, debugPort);
+  const opts = await generateDockerOpts(functionProps, runtime, mount, debugPort);
   
   debug('docker opts: %j' + opts);
   


Test patch:
diff --git a/test/debug.test.js b/test/debug.test.js
index 3415d1b..b6f7d01 100644
--- a/test/debug.test.js
+++ b/test/debug.test.js
@@ -95,7 +95,7 @@ describe('test generateVscodeDebugConfig', () => {
           'port': 9000,
           'localRoot': '.',
           'remoteRoot': '/code',
-          'protocol': 'inspect',
+          'protocol': 'inspector',
           'stopOnEntry': false
         }
       ]
diff --git a/test/docker.test.js b/test/docker.test.js
index 5a423b8..5e225a8 100644
--- a/test/docker.test.js
+++ b/test/docker.test.js
@@ -13,6 +13,8 @@ const sinon = require('sinon');
 const sandbox = sinon.createSandbox();
 const assert = sinon.assert;
 
+var prevHome;
+
 describe('test generateDockerCmd', () => {
   it('test generate docker cmd', () => {
     const functionProps = {
@@ -37,16 +39,16 @@ describe('test generateDockerCmd', () => {
 });
 
 describe('test findDockerImage', () => {
-  it('test find not python 3 image', () => {
+  it('test find not python image', () => {
     for (let runtime of ['nodejs6', 'nodejs8', 'python2.7', 'java8', 'php7.2']) {
       const imageName = docker.findDockerImage(runtime);
-      expect(imageName).to.be(`aliyunfc/runtime-${runtime}`);
+      expect(imageName).to.be(`aliyunfc/runtime-${runtime}:latest`);
     }
   });
 
   it('test find python 3 image', () => {
     const imageName = docker.findDockerImage('python3');
-    expect(imageName).to.be(`aliyunfc/runtime-python3.6`);
+    expect(imageName).to.be(`aliyunfc/runtime-python3.6:latest`);
   });
 });
 
@@ -99,8 +101,25 @@ describe('test generateDockerOpts', () => {
     'Runtime': 'python3'
   };
 
-  it('testf generate docker opts', () => {
-    const opts = docker.generateDockerOpts(functionProps, 'nodejs8', {
+  beforeEach(() => {
+    prevHome = os.homedir();
+    process.env.HOME = os.tmpdir();
+    process.env.ACCESS_KEY_ID = 'testKeyId';
+    process.env.ACCESS_KEY_SECRET = 'testKeySecret';
+  });
+
+  afterEach(() => {
+    process.env.HOME = prevHome;
+    delete process.env.ACCOUNT_ID;
+    delete process.env.ACCESS_KEY_ID;
+    delete process.env.ACCESS_KEY_SECRET;
+    delete process.env.DEFAULT_REGION;
+    delete process.env.TIMEOUT;
+    delete process.env.RETRIES;
+  });
+
+  it('test generate docker opts', async () => {
+    const opts = await docker.generateDockerOpts(functionProps, 'nodejs8', {
       Type: 'bind',
       Source: '/test',
       Target: '/code'
@@ -109,6 +128,8 @@ describe('test generateDockerOpts', () => {
     expect(opts).to.eql({
       'Env': [
         'local=true',
+        'FC_ACCESS_KEY_ID=testKeyId',
+        'FC_ACCESS_KEY_SECRET=testKeySecret',
         'DEBUG_OPTIONS=--inspect-brk=0.0.0.0:9000'
       ],
       'HostConfig': {
@@ -135,8 +156,8 @@ describe('test generateDockerOpts', () => {
     });
   });
 
-  it('testf generate docker opts without debug port', () => {
-    const opts = docker.generateDockerOpts(functionProps, 'nodejs8', {
+  it('test generate docker opts without debug port', async () => {
+    const opts = await docker.generateDockerOpts(functionProps, 'nodejs8', {
       Type: 'bind',
       Source: '/test',
       Target: '/code'
@@ -144,7 +165,9 @@ describe('test generateDockerOpts', () => {
 
     expect(opts).to.eql({
       'Env': [
-        'local=true'
+        'local=true',
+        'FC_ACCESS_KEY_ID=testKeyId',
+        'FC_ACCESS_KEY_SECRET=testKeySecret'
       ],
       'HostConfig': {
         'AutoRemove': true,
@@ -184,10 +207,23 @@ describe('test invokeFunction', async () => {
     docker = proxyquire('../lib/docker', {
       'dockerode': DockerCli
     });
+
+    prevHome = os.homedir();
+    process.env.HOME = os.tmpdir();
+    process.env.ACCESS_KEY_ID = 'testKeyId';
+    process.env.ACCESS_KEY_SECRET = 'testKeySecret';
   });
 
   afterEach(() => {
     sandbox.restore();
+
+    process.env.HOME = prevHome;
+    delete process.env.ACCOUNT_ID;
+    delete process.env.ACCESS_KEY_ID;
+    delete process.env.ACCESS_KEY_SECRET;
+    delete process.env.DEFAULT_REGION;
+    delete process.env.TIMEOUT;
+    delete process.env.RETRIES;
   });
 
   it('test invoke function without debug and event', async () => {
@@ -197,11 +233,11 @@ describe('test invokeFunction', async () => {
     assert.calledOnce(DockerCli.prototype.listImages);
 
     assert.calledWith(DockerCli.prototype.run,
-      'aliyunfc/runtime-python3.6',
+      'aliyunfc/runtime-python3.6:latest',
       ['-h', 'index.handler', '-i', 'index.initializer'],
       process.stdout,
       {
-        Env: ['local=true'],
+        Env: ['local=true', 'FC_ACCESS_KEY_ID=testKeyId', 'FC_ACCESS_KEY_SECRET=testKeySecret'],
         HostConfig: {
           AutoRemove: true,
           Mounts: [{ Source: codeDir, Target: '/code', Type: 'bind' }]
@@ -216,11 +252,11 @@ describe('test invokeFunction', async () => {
     assert.calledOnce(DockerCli.prototype.listImages);
 
     assert.calledWith(DockerCli.prototype.run,
-      'aliyunfc/runtime-python3.6',
+      'aliyunfc/runtime-python3.6:latest',
       ['-h', 'index.handler', '-i', 'index.initializer'],
       process.stdout,
       {
-        Env: ['local=true', 'DEBUG_OPTIONS=-m ptvsd --host 0.0.0.0 --port 9000 --wait'],
+        Env: ['local=true', 'FC_ACCESS_KEY_ID=testKeyId', 'FC_ACCESS_KEY_SECRET=testKeySecret', 'DEBUG_OPTIONS=-m ptvsd --host 0.0.0.0 --port 9000 --wait'],
         ExposedPorts: { '9000/tcp': {} },
         HostConfig: {
           AutoRemove: true,
@@ -237,11 +273,11 @@ describe('test invokeFunction', async () => {
     assert.calledOnce(DockerCli.prototype.listImages);
 
     assert.calledWith(DockerCli.prototype.run,
-      'aliyunfc/runtime-python3.6',
+      'aliyunfc/runtime-python3.6:latest',
       ['-h', 'index.handler', '--event', '{"testKey": "testValue"}', '-i', 'index.initializer'],
       process.stdout,
       {
-        Env: ['local=true', 'DEBUG_OPTIONS=-m ptvsd --host 0.0.0.0 --port 9000 --wait'],
+        Env: ['local=true', 'FC_ACCESS_KEY_ID=testKeyId', 'FC_ACCESS_KEY_SECRET=testKeySecret', 'DEBUG_OPTIONS=-m ptvsd --host 0.0.0.0 --port 9000 --wait'],
         ExposedPorts: { '9000/tcp': {} },
         HostConfig: {
           AutoRemove: true,