Repository: aliyun/fun
Pull request #251 (2019-06-17 13:50:33)

Sunfeiyu/add containing signature library warning for single file
#245 


Patch:
diff --git a/lib/commands/local/http-support.js b/lib/commands/local/http-support.js
index 7975f7c..b0b63d3 100644
--- a/lib/commands/local/http-support.js
+++ b/lib/commands/local/http-support.js
@@ -1,11 +1,13 @@
 'use strict';
 
 const debug = require('debug')('fun:local');
+const path = require('path');
 
 const { green, yellow } = require('colors');
 
 const HttpInvoke = require('../../local/http-invoke');
 const ApiInvoke = require('../../local/api-invoke');
+const deploySupport = require('../../deploy/deploy-support');
 
 function logsHttpTrigger(serverPort, serviceName, functionName, triggerName, endpoint, httpMethods, authType) {
   console.log(green(`http trigger ${triggerName} of ${serviceName}/${functionName} was registered`));
@@ -34,6 +36,10 @@ async function registerHttpTriggers(app, serverPort, httpTriggers, debugPort, de
     debug('debug port: %d', debugPort);
 
     const httpInvoke = new HttpInvoke(serviceName, serviceRes, functionName, functionRes, debugPort, debugIde, tplPath, authType, endpointPrefix);
+      
+    const codeUri = functionRes.Properties.CodeUri;
+    const runtime = functionRes.Properties.Runtime;
+    await deploySupport.detectLibrary(codeUri, runtime, path.dirname(tplPath));
 
     for (let method of httpMethods) {
 
diff --git a/lib/commands/local/invoke.js b/lib/commands/local/invoke.js
index fc61915..c31ff77 100644
--- a/lib/commands/local/invoke.js
+++ b/lib/commands/local/invoke.js
@@ -8,6 +8,7 @@ const validate = require('../../validate/validate');
 const readline = require('readline');
 const getStdin = require('get-stdin');
 const definition = require('../../definition');
+const deploySupport = require('../../deploy/deploy-support');
 const { getDebugPort, getDebugIde } = require('../../debug');
 const getVisitor = require('../../visitor').getVisitor;
 
@@ -58,6 +59,9 @@ async function localInvoke(invokeName, tpl, debugPort, event, debugIde, tplPath)
   if (!functionRes) {
     throw new Error(red(`invokeName ${invokeName} is invalid`));
   }
+  const codeUri = functionRes.Properties.CodeUri;
+  const runtime = functionRes.Properties.Runtime;
+  await deploySupport.detectLibrary(codeUri, runtime, path.dirname(tplPath));
 
   debug(`found serviceName: ${serviceName}, functionName: ${functionName}, functionRes: ${functionRes}`);
 
diff --git a/lib/commands/local/start.js b/lib/commands/local/start.js
index d6bd14e..d2d7072 100644
--- a/lib/commands/local/start.js
+++ b/lib/commands/local/start.js
@@ -13,6 +13,7 @@ const { detectTplPath, getTpl } = require('../../tpl');
 const validate = require('../../validate/validate');
 
 const definition = require('../../definition');
+const deploySupport = require('../../deploy/deploy-support');
 
 const { getDebugPort, getDebugIde } = require('../../debug');
 
@@ -107,6 +108,12 @@ async function start(options) {
       return true;
     });
 
+    for (let { functionRes } of functions) {
+      const codeUri = functionRes.Properties.CodeUri;
+      const runtime = functionRes.Properties.Runtime;
+      await deploySupport.detectLibrary(codeUri, runtime, path.dirname(tplPath));
+    }    
+
     httpSupport.registerApis(app, serverPort, functions, debugPort, debugIde, tplPath);
 
     startExpress(app);
diff --git a/lib/deploy/deploy-support.js b/lib/deploy/deploy-support.js
index c377c18..48c32d5 100644
--- a/lib/deploy/deploy-support.js
+++ b/lib/deploy/deploy-support.js
@@ -27,6 +27,8 @@ const funignore = require('../package/ignore');
 const ram = require('../ram');
 
 const readFile = util.promisify(fs.readFile);
+const readdir = util.promisify(fs.readdir);
+const lstat = util.promisify(fs.lstat);
 
 const { green, red, yellow } = require('colors');
 const { addEnv } = require('../install/env');
@@ -380,10 +382,10 @@ function generateFunIngore(baseDir, codeUri) {
   return funignore(baseDir);
 }
 
-async function getFunCodeAsBase64(baseDir, codeUri) {
+async function getFunCodeAsBase64(baseDir, codeUri, runtime, functionName) {
   if (codeUri) {
     if (codeUri.endsWith('.zip') || codeUri.endsWith('.jar')) {
-      return Buffer.from(await readFile(codeUri)).toString('base64');
+      return {base64: Buffer.from(await readFile(codeUri)).toString('base64')};
     }
   } else {
     codeUri = './';
@@ -391,6 +393,8 @@ async function getFunCodeAsBase64(baseDir, codeUri) {
 
   const ignore = generateFunIngore(baseDir, codeUri);
 
+  await detectLibrary(codeUri, runtime, baseDir, '\t\t', functionName);
+  
   return await zip.pack(codeUri, ignore);
 }
 
@@ -435,8 +439,9 @@ async function makeFunction(baseDir, {
     code = extractOssCodeUri(codeUri);
   } else {
     console.log(`\t\tWaiting for packaging function ${functionName} code...`);
-    const {base64, count} = await getFunCodeAsBase64(baseDir, codeUri, runtime);
-    console.log(green(`\t\tpackage function ${functionName} code done, the number of files you have packaged is：` + yellow(`${count}`)));
+    const {base64, count} = await getFunCodeAsBase64(baseDir, codeUri, runtime, functionName);
+    const log = count ? ', the number of files you have packaged is：' + yellow(`${count}`) : '';
+    console.log(green(`\t\tpackage function ${functionName} code done` + log));
     code = {
       zipFile: base64
     };
@@ -795,6 +800,63 @@ async function makeInvocationRole(serviceName, functionName, triggerType) {
   return;
 }
 
+const runtimeTypeMapping = {
+  'nodejs6': 'node_modules',
+  'nodejs8': 'node_modules',
+  'python2.7': ['.egg-info', '.dist-info', '.fun'],
+  'python3': ['.egg-info', '.dist-info', '.fun'],
+  'php7.2': ['extension', 'vendor']
+};
+
+async function detectLibraryFolders(dirName, libraryFolders, childDir, wrap, functionName) {
+  if (Array.isArray(libraryFolders)) {
+    for (const iterator of libraryFolders) {
+      for (const name of childDir) {
+        if (_.endsWith(name, iterator)) {
+          console.warn(red(`${wrap}Fun detected that the library directory '${name}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
+          return;
+        }
+      }
+    }
+  } else {
+    if (childDir.includes(libraryFolders)) {
+      console.warn(red(`${wrap}Fun detected that the library directory '${libraryFolders}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
+    } else {
+
+      const funDir = childDir.filter(p => p === '.fun');
+      if (Array.isArray(funDir) && funDir.length > 0) {
+
+        const childFun = await readdir(path.join(dirName, '.fun'));
+
+        if (childFun.includes('root')) {
+
+          console.warn(red(`${wrap}Fun detected that the library directory '.fun/root' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
+
+        }
+      }
+    }
+  } 
+}
+
+async function detectLibrary(codeUri, runtime, baseDir, wrap = '', functionName) {
+  const stats = await lstat(codeUri);
+  if (stats.isFile()) {
+    let libraryFolders = runtimeTypeMapping[runtime];
+    let absoluteCodePath;
+
+    if (path.isAbsolute(codeUri)) {
+      absoluteCodePath = codeUri;
+    } else {
+      absoluteCodePath = path.join(baseDir, codeUri);
+    }
+
+    const dirName = path.dirname(absoluteCodePath);
+    const childDir = await readdir(dirName);
+    
+    await detectLibraryFolders(dirName, libraryFolders, childDir, wrap, functionName);
+  }
+}
+
 async function getTriggerNameList ({
   serviceName,
   functionName
@@ -1250,5 +1312,6 @@ module.exports = {
   makeInvocationRole,
   getFunCodeAsBase64,
   getTriggerNameList,
-  getTriggerConfig
+  getTriggerConfig,
+  detectLibrary
 };
\ No newline at end of file


Test patch:
diff --git a/test/deploy/deploy-support.test.js b/test/deploy/deploy-support.test.js
index 0d049a9..694c300 100644
--- a/test/deploy/deploy-support.test.js
+++ b/test/deploy/deploy-support.test.js
@@ -14,6 +14,8 @@ const assert = sinon.assert;
 const zip = require('../../lib/package/zip');
 const expect = require('expect.js');
 const path = require('path');
+const util = require('util');
+
 
 describe('make', () => {
 
@@ -632,13 +634,23 @@ describe('make invocation role', () => {
 describe('test getFunCodeAsBase64', () => {
 
   let restoreProcess;
+  let lstat;
 
   beforeEach(async () => {
 
     sandbox.stub(zip, 'pack').resolves({});
 
+    lstat = sandbox.stub();
+
+    lstat.resolves({
+      isFile: function () { return false; }
+    });
+
+    sandbox.stub(util, 'promisify').returns(lstat);
+
     deploySupport = await proxyquire('../../lib/deploy/deploy-support', {
-      '../package/zip': zip
+      '../package/zip': zip,
+      'util': util
     });
 
     restoreProcess = setProcess({
@@ -655,18 +667,34 @@ describe('test getFunCodeAsBase64', () => {
 
   it('test getFunCodeAsBase64: codeUri outside baseDir', async () => {
     await deploySupport.getFunCodeAsBase64('/a/b', '/a');
+    assert.calledWith(lstat, '/a');
     assert.calledWith(zip.pack, '/a', null);
   });
 
 
   it('test getFunCodeAsBase64: codeUri outside baseDir2', async () => {
     await deploySupport.getFunCodeAsBase64('/a/b', '../');
+    assert.calledWith(lstat, '../');
     assert.calledWith(zip.pack, '../', null);
   });
 
   it('test getFunCodeAsBase64: codeUri within baseDir', async () => {
     await deploySupport.getFunCodeAsBase64('/a/b', '/a/b/c');
+    assert.calledWith(lstat, '/a/b/c');
     assert.calledWith(zip.pack, '/a/b/c', sinon.match.func);
   });
 
+
+  it('test getFunCodeAsBase64: absolute codeUri path', async () => {
+    await deploySupport.getFunCodeAsBase64('/a/b', process.cwd() + '/a/b/c/index.js');
+    assert.calledWith(lstat, process.cwd() + '/a/b/c/index.js');
+    assert.calledWith(zip.pack, process.cwd() + '/a/b/c/index.js', null);
+  });
+
+  it('test getFunCodeAsBase64: relative codeUri path', async () => {
+    await deploySupport.getFunCodeAsBase64('/a/b', './index.js');
+    assert.calledWith(lstat, './index.js');
+    assert.calledWith(zip.pack, './index.js', null);
+  });
+  
 });
\ No newline at end of file