UNPKG

44.9 kBJavaScriptView Raw
1'use strict';
2
3const util = require('./import/utils');
4const bytes = require('bytes');
5const funignore = require('./package/ignore');
6const definition = require('./definition');
7const promiseRetry = require('./retry');
8const getProfile = require('./profile').getProfile;
9const securityGroup = require('./security-group');
10
11const fs = require('fs-extra');
12const path = require('path');
13const debug = require('debug')('fun:fc');
14const yaml = require('js-yaml');
15const zip = require('./package/zip');
16const vpc = require('./vpc');
17const nas = require('./nas');
18const nasCp = require('./nas/cp');
19
20const { sleep } = require('./time');
21const { buildFunction } = require('./build/build');
22const { readJsonFromFile } = require('./utils/file');
23const { getTpl, getBaseDir, getNasYmlPath, DEFAULT_BUILD_ARTIFACTS_PATH_SUFFIX } = require('./tpl');
24const { green, red, yellow } = require('colors');
25const { getFcClient, getEcsPopClient, getNasPopClient } = require('./client');
26const { addEnv, mergeEnvs, resolveLibPathsFromLdConf, generateDefaultLibPath } = require('./install/env');
27const { readFileFromNasYml, mergeNasMappingsInNasYml, getNasMappingsFromNasYml, extractNasMappingsFromNasYml } = require('./nas/support');
28
29const _ = require('lodash');
30const _util = require('util');
31
32const {
33 promptForConfirmContinue,
34 promptForMountTargets,
35 promptForMountPoints,
36 promptForFileSystems,
37 promptForSecurityGroup,
38 promptForInputContinue
39} = require('./init/prompt');
40
41const FUN_GENERATED_SERVICE = 'fun-generated-default-service';
42
43const SYSTEM_DEPENDENCY_PATH = path.join('.fun', 'root');
44
45const SUPPORT_RUNTIMES = ['nodejs6', 'nodejs8', 'nodejs10', 'python2.7', 'python3', 'java8', 'custom'];
46
47const defaultVpcConfig = {
48 securityGroupId: '',
49 vSwitchIds: [],
50 vpcId: ''
51};
52
53const defaultNasConfig = {
54 UserId: -1,
55 GroupId: -1,
56 MountPoints: []
57};
58
59async function generateFunIngore(baseDir, codeUri, runtime) {
60 const absCodeUri = path.resolve(baseDir, codeUri);
61 const absBaseDir = path.resolve(baseDir);
62
63 const relative = path.relative(absBaseDir, absCodeUri);
64
65 if (codeUri.startsWith('..') || relative.startsWith('..')) {
66 console.warn(red(`\t\twarning: funignore is not supported for your CodeUri: ${codeUri}`));
67 return null;
68 }
69
70 return await funignore(baseDir, runtime);
71}
72
73// TODO: python runtime .egg-info and .dist-info
74const runtimeTypeMapping = {
75 'nodejs6': ['node_modules', '.fun/root'],
76 'nodejs8': ['node_modules', '.fun/root'],
77 'nodejs10': ['node_modules', '.fun/root'],
78 'python2.7': ['.fun/python', '.fun/root'],
79 'python3': ['.fun/python', '.fun/root'],
80 'php7.2': ['extension', 'vendor', '.fun/root']
81};
82
83async function detectLibraryFolders(dirName, libraryFolders, wrap, functionName) {
84 if (_.isEmpty(libraryFolders)) { return; }
85
86 for (const libraryFolder of libraryFolders) {
87 const libraryPath = path.join(dirName, libraryFolder);
88 if (await fs.pathExists(libraryPath)) {
89 console.warn(red(`${wrap}Fun detected that the library directory '${libraryFolder}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
90 return;
91 }
92 }
93}
94
95async function detectLibrary(codeUri, runtime, baseDir, functionName, wrap = '') {
96 const absoluteCodePath = path.resolve(baseDir, codeUri);
97
98 const stats = await fs.lstat(absoluteCodePath);
99 if (stats.isFile()) {
100 let libraryFolders = runtimeTypeMapping[runtime];
101
102 await detectLibraryFolders(path.dirname(absoluteCodePath), libraryFolders, wrap, functionName);
103 }
104}
105
106function extractOssCodeUri(ossUri) {
107 const prefixLength = 'oss://'.length;
108
109 const index = ossUri.indexOf('/', prefixLength);
110
111 return {
112 ossBucketName: ossUri.substring(prefixLength, index),
113 ossObjectName: ossUri.substring(index + 1)
114 };
115}
116
117async function zipCode(baseDir, codeUri, runtime, functionName) {
118 let codeAbsPath;
119
120 if (codeUri) {
121 codeAbsPath = path.resolve(baseDir, codeUri);
122
123 if (codeUri.endsWith('.zip') || codeUri.endsWith('.jar') || codeUri.endsWith('.war')) {
124
125 const lstat = await fs.stat(codeAbsPath);
126 return {
127 base64: Buffer.from(await fs.readFile(codeAbsPath)).toString('base64'),
128 compressedSize: lstat.size
129 };
130 }
131 } else {
132 codeAbsPath = path.resolve(baseDir, './');
133 }
134
135 const ignore = await generateFunIngore(baseDir, codeAbsPath, runtime);
136
137 await detectLibrary(codeAbsPath, runtime, baseDir, functionName, '\t\t');
138
139 return await zip.pack(codeAbsPath, ignore);
140}
141
142const NODE_RUNTIME_MAPPING = {
143 'localDir': 'node_modules',
144 'remoteDir': 'node_modules',
145 'env': 'NODE_PATH',
146 'defaultEnv': '/usr/local/lib/node_modules'
147};
148
149const PYTHON_RUNTIME_MAPPING = {
150 'localDir': '.fun/python',
151 'remoteDir': 'python',
152 'env': 'PYTHONUSERBASE'
153};
154
155const JAVA_RUNTIME_MAPPING = {
156 'localDir': '.fun/build/artifacts',
157 'remoteDir': 'java',
158 'env': 'JAVA_PATH'
159};
160
161const runtimeDependencyMappings = {
162 'nodejs6': [ NODE_RUNTIME_MAPPING ],
163 'nodejs8': [ NODE_RUNTIME_MAPPING ],
164 'nodejs10': [ NODE_RUNTIME_MAPPING ],
165 'python2.7': [ PYTHON_RUNTIME_MAPPING ],
166 'python3': [ PYTHON_RUNTIME_MAPPING ],
167 'java8': [ JAVA_RUNTIME_MAPPING ],
168 'custom': [ NODE_RUNTIME_MAPPING, PYTHON_RUNTIME_MAPPING ]
169};
170
171async function saveNasMappings(nasYmlPath, nasMappings) {
172
173 if (_.isEmpty(nasMappings)) { return {}; }
174
175 const contentObj = await readFileFromNasYml(nasYmlPath);
176
177 const mergedNasMappings = await mergeNasMappingsInNasYml(nasYmlPath, nasMappings);
178
179 contentObj.nasMappings = mergedNasMappings;
180
181 await fs.writeFile(nasYmlPath, yaml.dump(contentObj));
182
183 return mergedNasMappings;
184}
185
186async function copyEntryPoint(tplPath, codeUri, packageName) {
187 const packagePaths = packageName.split('.');
188
189 const asbCodeUri = path.resolve(path.dirname(tplPath), codeUri);
190 const entryPointPath = path.join(asbCodeUri, 'src', 'main', 'java', ...packagePaths, 'Entrypoint.java');
191
192 const sourcePath = path.resolve(__dirname, './utils/classLoader/Entrypoint.java');
193 const content = await fs.readFile(sourcePath, {
194 encoding: 'utf8'
195 });
196
197 const updatedContent = _util.format(content, packageName);
198
199 await fs.writeFile(entryPointPath, updatedContent, {
200 encoding: 'utf8'
201 });
202}
203
204// example.App::handleRequest -> example.App
205function extractJavaHandlerPackage(handler) {
206 const splitArray = handler.split('::');
207 const packageArray = splitArray[0].split('.').filter((ele, idx, arr) => {
208 return arr.length - 1 !== idx;
209 });
210 return packageArray.join('.');
211}
212
213async function buildOnMeta(baseDir, tpl, tplPath) {
214
215 const metaPath = path.resolve(baseDir, DEFAULT_BUILD_ARTIFACTS_PATH_SUFFIX, 'meta.json');
216
217 if (!await fs.pathExists(metaPath)) {
218 console.warn(red(`\nwarning: ${metaPath} does not exist.`));
219 return;
220 }
221
222 const metaObj = await readJsonFromFile(metaPath);
223
224 const { buildName, useDocker, verbose } = metaObj.buildOps || {};
225
226 console.log(yellow(`\nFun will execute 'fun build' to build functions.`));
227
228 // fun build
229 await buildFunction(buildName, tpl, baseDir, useDocker, ['install', 'build'], verbose, tplPath);
230}
231
232
233async function updateInitializerAndEnvs({tplPath, tpl,
234 serviceName,
235 functionName
236}) {
237 const updatedTplContent = _.cloneDeep(tpl);
238
239 const { functionRes } = definition.findFunctionByServiceAndFunctionName(updatedTplContent.Resources, serviceName, functionName);
240
241 const packageName = extractJavaHandlerPackage(functionRes.Properties.Handler);
242
243 const originHandler = functionRes.Properties.Handler;
244 const originInitializer = functionRes.Properties.Initializer;
245
246 functionRes.Properties.Handler = `${packageName}.Entrypoint::handleRequest`;
247
248 console.log(green(`Fun update Handler variables to ${tplPath}`));
249
250 const envs = {
251 'FUN_HANDLER': originHandler
252 };
253
254 console.log(green(`Fun add environment variables 'FUN_HANDLER' to ${tplPath}`));
255
256 if (originInitializer) {
257 functionRes.Properties.Initializer = `${packageName}.Entrypoint::initialize`;
258 console.log(green(`Fun update Initializer variables to ${tplPath}`));
259
260 Object.assign(envs, {
261 'FUN_INITIALIZER': originInitializer
262 });
263
264 console.log(green(`Fun add environment variables 'FUN_INITIALIZER' to ${tplPath}`));
265 }
266
267 functionRes.Properties.EnvironmentVariables = mergeEnvs(functionRes, envs);
268
269 util.outputTemplateFile(tplPath, updatedTplContent);
270
271 return {
272 packageName,
273 codeUri: functionRes.Properties.CodeUri,
274 updatedTplContent
275 };
276}
277
278async function processJavaIfNecessary({
279 runtime, baseDir,
280 tplPath, tpl,
281 serviceName,
282 functionName
283}) {
284
285 if (runtime !== 'java8' || usingProjectTemplate(tplPath)) { return tpl; }
286
287 const { projectTpl, projectTplPath } = await getProjectTpl(tplPath);
288
289 const { packageName, codeUri, updatedTplContent } = await updateInitializerAndEnvs({
290 tplPath: projectTplPath,
291 tpl: projectTpl,
292 serviceName: serviceName,
293 functionName: functionName
294 });
295
296 await copyEntryPoint(projectTplPath, codeUri, packageName);
297
298 // fun build
299 await buildOnMeta(baseDir, updatedTplContent, projectTplPath);
300
301 return await getTpl(tplPath);
302}
303
304async function updateEnvironmentsInTpl({ tplPath, tpl, envs,
305 serviceName,
306 functionName
307}) {
308 const updatedTplContent = _.cloneDeep(tpl);
309
310 const { functionRes } = definition.findFunctionByServiceAndFunctionName(updatedTplContent.Resources, serviceName, functionName);
311
312 const mergedEnvs = mergeEnvs(functionRes, envs);
313
314 if (_.isEmpty(functionRes['Properties'])) {
315 functionRes.Properties = {
316 'EnvironmentVariables': mergedEnvs
317 };
318 } else {
319 functionRes.Properties.EnvironmentVariables = mergedEnvs;
320 }
321
322 util.outputTemplateFile(tplPath, updatedTplContent);
323
324 console.log(green(`Fun add environment variables to '${serviceName}/${functionName}' with path ${tplPath}`));
325
326 return updatedTplContent;
327}
328
329function generateBackupTplPath(tplPath) {
330 const tplDir = path.dirname(tplPath);
331 const tplName = path.basename(tplPath);
332 const newTplName = `.${tplName}.backup`;
333 return path.join(tplDir, newTplName);
334}
335
336async function getProjectTpl(tplPath) {
337 const projectBaseDir = getBaseDir(tplPath);
338 const projectTplPath = path.resolve(projectBaseDir, path.basename(tplPath));
339 const projectTpl = await getTpl(projectTplPath);
340 return {
341 projectTpl,
342 projectTplPath
343 };
344}
345
346function updateNasAutoConfigureInTpl(tplPath, tpl, serviceName) {
347 const updatedTplContent = _.cloneDeep(tpl);
348
349 const { serviceRes } = definition.findServiceByServiceName(updatedTplContent.Resources, serviceName);
350
351 if (_.isEmpty(serviceRes['Properties'])) {
352 serviceRes.Properties = {
353 'NasConfig': 'Auto'
354 };
355 } else {
356 serviceRes.Properties.NasConfig = 'Auto';
357 }
358
359 util.outputTemplateFile(tplPath, updatedTplContent);
360
361 console.log(green(`Fun add 'NasConfig: Auto' configuration to ${tplPath}`));
362
363 return updatedTplContent;
364}
365
366async function updateNasAutoConfigure(tplPath, tpl, serviceName) {
367 const { projectTpl, projectTplPath } = await getTplInfo(tpl, tplPath);
368
369 const updatedTplContent = await updateNasAutoConfigureInTpl(projectTplPath, projectTpl, serviceName);
370 return updatedTplContent;
371}
372
373function updateNasAndVpcInTpl(tplPath, tpl, serviceName, nasAndVpcConfig) {
374 const updatedTplContent = _.cloneDeep(tpl);
375
376 const { serviceRes } = definition.findServiceByServiceName(updatedTplContent.Resources, serviceName);
377
378 if (_.isEmpty(serviceRes['Properties'])) {
379 serviceRes.Properties = nasAndVpcConfig;
380 } else {
381 serviceRes.Properties.VpcConfig = nasAndVpcConfig.VpcConfig;
382 serviceRes.Properties.NasConfig = nasAndVpcConfig.NasConfig;
383 }
384
385 console.log(green(`Fun add 'NasConfig' and 'VpcConfig' configuration to your template.yml.`));
386
387 util.outputTemplateFile(tplPath, updatedTplContent);
388 return updatedTplContent;
389}
390
391async function getTplInfo(tpl, tplPath) {
392 let projectTpl;
393 let projectTplPath;
394
395 if (usingProjectTemplate(tplPath)) {
396
397 projectTpl = tpl;
398 projectTplPath = tplPath;
399 } else {
400 const obj = await getProjectTpl(tplPath);
401 projectTpl = obj.projectTpl;
402 projectTplPath = obj.projectTplPath;
403 }
404
405 return {
406 projectTpl,
407 projectTplPath
408 };
409}
410
411async function updateNasAndVpc(tplPath, tpl, serviceName, nasAndVpcConfig) {
412 const { projectTpl, projectTplPath } = await getTplInfo(tpl, tplPath);
413
414 const updatedTplContent = updateNasAndVpcInTpl(projectTplPath, projectTpl, serviceName, nasAndVpcConfig);
415
416 return updatedTplContent;
417}
418
419async function generateNasMappingsAndEnvs({
420 baseDir,
421 serviceName,
422 functionName,
423 runtime,
424 codeUri,
425 nasConfig
426}) {
427 const envs = {};
428
429 const nasMappings = {};
430 const nasMapping = [];
431
432 const prefix = parseMountDirPrefix(nasConfig);
433 // used for log
434 const nasMappingPath = path.resolve(baseDir, '.nas.yml');
435 const localSystemDependency = path.resolve(codeUri, SYSTEM_DEPENDENCY_PATH);
436
437 if (await fs.pathExists(localSystemDependency)) { // system dependence
438 const remoteNasDir = `${prefix}root`;
439
440 nasMapping.push({
441 localNasDir: path.relative(baseDir, localSystemDependency),
442 remoteNasDir
443 });
444
445 nasMappings[serviceName] = nasMapping;
446
447 Object.assign(envs, generateSystemNasEnvs(remoteNasDir));
448
449 outputNasMappingLog(baseDir, nasMappingPath, localSystemDependency);
450 }
451
452 const dependencyMappings = runtimeDependencyMappings[runtime];
453
454 for (const mapping of dependencyMappings) {
455
456 const localDir = resolveLocalNasDir(runtime, baseDir, codeUri, mapping.localDir, serviceName, functionName);
457
458 if (await fs.pathExists(localDir)) { // language local dependencies dir exist
459
460 const remoteDir = `${prefix}${mapping.remoteDir}`;
461
462 nasMapping.push({
463 localNasDir: localDir,
464 remoteNasDir: remoteDir
465 });
466
467 Object.assign(envs, {
468 [mapping.env]: generateNasEnv(mapping.defaultEnv, remoteDir)
469 });
470
471 outputNasMappingLog(baseDir, nasMappingPath, localDir);
472 }
473 }
474
475 nasMappings[serviceName] = nasMapping;
476
477 return {
478 envs,
479 nasMappings,
480 remoteNasDirPrefix: prefix
481 };
482}
483
484function generateNasEnv(defaultEnv, remoteNasDir) {
485 let nasEnv;
486
487 if (defaultEnv) {
488 nasEnv = `${remoteNasDir}:${defaultEnv}`;
489 } else {
490 nasEnv = remoteNasDir;
491 }
492 return nasEnv;
493}
494
495function resolveLocalNasDir(runtime, baseDir, codeUri, localDirInNasMappings, serviceName, functionName) {
496 let localDir;
497 if (runtime === 'java8') {
498 localDir = path.relative(baseDir, path.join(localDirInNasMappings, serviceName, functionName, 'lib'));
499 } else {
500 localDir = path.relative(baseDir, path.join(codeUri, localDirInNasMappings));
501 }
502 return localDir;
503}
504
505function parseMountDirPrefix(nasConfig) {
506 if (definition.isNasAutoConfig(nasConfig)) {
507 return '/mnt/auto/';
508 }
509 const mountPoints = nasConfig.MountPoints;
510 ensureOnlyOneMountPoinExists(mountPoints);
511
512 const mountPoint = _.head(mountPoints).MountDir;
513 if (_.endsWith(mountPoint, '/')) {
514 return mountPoint;
515 }
516 return mountPoint + '/';
517}
518
519// Fun add .fun/build/artifacts/nas-example3/oversize-java-example/lib to /Users/ellison/fun/examples/ellison/oversize-java/.nas.yml
520function outputNasMappingLog(baseDir, nasMappingPath, localNasDir) {
521 console.log(green(`Fun add ${path.relative(baseDir, localNasDir)} to ${nasMappingPath}`));
522}
523
524function generateSystemNasEnvs(rootEnvPrefix) {
525 return {
526 'LD_LIBRARY_PATH': `${generateDefaultLibPath(rootEnvPrefix)}`
527 };
528}
529
530async function nasCpFromlocalNasDirToRemoteNasDir(tpl, tplPath, baseDir, nasServiceName, nasMappings) {
531 const localNasTmpDir = path.join(baseDir, '.fun', 'tmp', 'nas', 'cp');
532
533 for (const { localNasDir, remoteNasDir } of nasMappings) {
534 const srcPath = path.resolve(baseDir, localNasDir);
535 const dstPath = `nas://${nasServiceName}${remoteNasDir}/`;
536
537 console.log(yellow(`\nstarting upload ${srcPath} to ${dstPath}`));
538
539 await nasCp(srcPath, dstPath, true, false, localNasTmpDir, tpl, tplPath, baseDir, false, true);
540 }
541}
542
543async function processOtherFunctionsUnderServiceIfNecessary({
544 baseDir, codeUri, runtime, envs, tpl, tplPath,
545 originServiceName, originFunctionName
546}) {
547
548 let tplChanged = false;
549
550 const otherFunctions = definition.findFunctionsInTpl(tpl, (functionName, functionRes) => {
551 return originFunctionName !== functionName;
552 });
553
554 if (_.isEmpty(otherFunctions)) { return { updatedEnvsTpl: tpl, tplChanged }; }
555
556 const pendingFuntions = otherFunctions.filter(m => {
557 const functionProp = m.functionRes.Properties;
558
559 const otherCodeUri = (functionProp || {}).CodeUri;
560 const otherAbsCodeUri = path.resolve(baseDir, otherCodeUri);
561 const otherRuntime = (functionProp || {}).Runtime;
562
563 return (_.isEqual(runtimeDependencyMappings[runtime], runtimeDependencyMappings[otherRuntime]) && codeUri === otherAbsCodeUri);
564 });
565
566 if (_.isEmpty(pendingFuntions)) { return { updatedEnvsTpl: tpl, tplChanged }; }
567
568 for (const pendingFuntion of pendingFuntions) {
569
570 tpl = await updateEnvironmentsInTpl({ tplPath, tpl, envs,
571 serviceName: originServiceName,
572 functionName: pendingFuntion.functionName
573 });
574 }
575
576 return {
577 updatedEnvsTpl: tpl,
578 tplChanged: true
579 };
580}
581
582async function processNasMappingsAndEnvs({ tpl, tplPath, runtime, codeUri, baseDir,
583 serviceName,
584 functionName,
585 convertedNasConfig
586}) {
587
588 const { serviceRes } = definition.findFunctionByServiceAndFunctionName(tpl.Resources, serviceName, functionName);
589
590 const { envs, nasMappings, remoteNasDirPrefix } = await generateNasMappingsAndEnvs({
591 baseDir,
592 serviceName: serviceName,
593 functionName: functionName,
594 runtime,
595 codeUri,
596 nasConfig: convertedNasConfig || (serviceRes.Properties || {}).NasConfig
597 });
598
599 const localDirs = _.map(runtimeDependencyMappings[runtime], mapping => path.join(codeUri, mapping.localDir));
600
601 if (_.isEmpty(nasMappings)) {
602 throw new Error(red(`\nFun detects that your dependencies are not included in path ${localDirs} or ${path.resolve(codeUri, SYSTEM_DEPENDENCY_PATH)}`));
603 }
604
605 const nasMappingsObj = await saveNasMappings(getNasYmlPath(tplPath), nasMappings);
606
607 const { updatedEnvsTpl, tplChanged} = await updateEnvironments({
608 tplPath, tpl, envs, baseDir, codeUri, runtime,
609 serviceName,
610 functionName
611 });
612
613 return {
614 tplChanged,
615 remoteNasDirPrefix,
616 updatedTpl: updatedEnvsTpl,
617 serviceNasMappings: nasMappingsObj
618 };
619}
620
621async function processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, convertedNasConfig, stage,
622 serviceName,
623 functionName
624}) {
625
626 const baseDir = getBaseDir(tplPath);
627
628 const rs = await processNasMappingsAndEnvs({ tpl, tplPath, runtime, codeUri, baseDir,
629 serviceName,
630 functionName,
631 convertedNasConfig
632 });
633
634 if (stage === 'package') { return rs.tplChanged; }
635
636 const updatedTplContent = await processJavaIfNecessary({ runtime, baseDir, tplPath,
637 tpl: rs.updatedTpl,
638 serviceName: serviceName,
639 functionName: functionName
640 });
641
642 const serviceNasMappings = await processPythonModelIfNecessary({ codeUri, runtime, baseDir,
643 serviceName: serviceName,
644 nasYmlPath: getNasYmlPath(tplPath),
645 remoteNasDirPrefix: rs.remoteNasDirPrefix,
646 serviceNasMappings: rs.serviceNasMappings
647 });
648
649 // fun nas cp
650 await nasCpFromlocalNasDirToRemoteNasDir(tpl, tplPath, baseDir, serviceName, serviceNasMappings[serviceName]);
651
652 console.log(yellow(`\nFun has automatically uploaded your code dependency to NAS, then fun will use 'fun deploy ${serviceName}/${functionName}' to redeploy.`));
653
654 console.log(`Waiting for service ${serviceName} to be deployed...`);
655
656 const partialDeploy = await require('./deploy/deploy-by-tpl').partialDeployment(`${serviceName}/${functionName}`, updatedTplContent);
657
658 if (partialDeploy.resourceName) {
659 // can not use baseDir, should use tpl dirname
660 await require('./deploy/deploy-by-tpl').deployService(path.dirname(tplPath), partialDeploy.resourceName, partialDeploy.resourceRes, false, tplPath, true);
661 }
662
663 return rs.tplChanged;
664}
665
666async function updateEnvironments({
667 tplPath, tpl, envs, baseDir, codeUri, runtime,
668 serviceName, functionName
669}) {
670
671 const { projectTpl, projectTplPath } = await getTplInfo(tpl, tplPath);
672
673 const updatedTplContent = await updateEnvironmentsInTpl({
674 envs,
675 tpl: projectTpl,
676 tplPath: projectTplPath,
677 serviceName, functionName
678 });
679
680 return await processOtherFunctionsUnderServiceIfNecessary({
681 tpl: updatedTplContent, tplPath,
682 baseDir, codeUri, runtime, envs,
683 originServiceName: serviceName,
684 originFunctionName: functionName
685 });
686}
687
688async function processPythonModelIfNecessary({ nasYmlPath, codeUri, runtime, baseDir,
689 serviceName,
690 remoteNasDirPrefix,
691 serviceNasMappings
692}) {
693
694 if (!_.includes(['python2.7', 'python3'], runtime)) { return serviceNasMappings; }
695
696 const absModelPath = path.resolve(codeUri, 'model');
697
698 if (!await fs.pathExists(absModelPath)) { return serviceNasMappings; }
699
700 const nasMappings = await extractNasMappingsFromNasYml(baseDir, serviceName);
701
702 const modelMapping = nasMappings.find(arr => {
703 return path.resolve(baseDir, arr.localNasDir) === absModelPath;
704 });
705
706 if (!_.isEmpty(modelMapping)) { return serviceNasMappings; }
707
708 const remoteNasDir = `${remoteNasDirPrefix}model`;
709
710 console.log(`
711Fun has detected that there is a model folder. It is recommend to synchronize your model folder to NAS.
712You can add the following configuration to ` + yellow(`'nasMapping.${serviceName}'`) + ` in ` + yellow(`${nasYmlPath}
713`)
714
715+ yellow(`
716 - localNasDir: ${absModelPath}
717 remoteNasDir: ${remoteNasDir}
718 `)
719+ `
720After adding, fun is going to automatically synchronize the ` + yellow(`local`) + ` directory ${absModelPath} to ` + yellow(`remote`) + ` ${remoteNasDir}.
721If these files ` + yellow('under') + ` model directory are used on your function code, you need to ${remoteNasDir} update these files path manully.
722`);
723
724 await promptForInputContinue('Please input enter to continue.');
725
726 return await getNasMappingsFromNasYml(nasYmlPath);
727}
728
729async function backupTemplateFile(tplPath) {
730 const baseDir = getBaseDir(tplPath);
731 const originTplPath = path.resolve(baseDir, path.basename(tplPath));
732 const newPath = generateBackupTplPath(originTplPath);
733 await fs.copy(originTplPath, newPath);
734 console.log(green(`\nFun automatically backups the original ${path.basename(tplPath)} file to ${newPath}`));
735}
736
737function ensureMountTargetsExist(mountTargets) {
738 if (_.isEmpty(mountTargets)) {
739 throw new Error(red('Nas has not configured the mountTarget yet, please go to the console https://nas.console.aliyun.com/ to manually create the mountTarget.'));
740 }
741}
742
743function ensureSecurityGroupsExist(securityGroups) {
744 if (_.isEmpty(securityGroups)) {
745 throw new Error(red(`\nThere is no SecurityGroup available. You need to login to the vpc console https://ecs.console.aliyun.com/ to create one and then use 'fun deploy' to deploy your resources again.`));
746 }
747}
748
749function ensureNasFileSystemsExist(nasFileSystems) {
750 if (_.isEmpty(nasFileSystems)) {
751 throw new Error(red(`\nThere is no NAS file system available. You need to login to the nas console http://nas.console.aliyun.com to create one and then use 'fun deploy' to deploy your resources again.`));
752 }
753}
754
755function ensureOnlyOneMountPoinExists(mountPoints) {
756 if (mountPoints.length > 1) {
757 throw new Error(red(`More than one 'NasConfig' configuration in template.yml.`));
758 }
759}
760
761async function getSecurityGroups(vpcId) {
762 const ecsClient = await getEcsPopClient();
763 const profile = await getProfile();
764 return await securityGroup.describeSecurityGroups(ecsClient, profile.defaultRegion, vpcId, undefined);
765}
766
767async function processNasSelection() {
768 const nasClient = await getNasPopClient();
769 const nasFileSystems = await nas.getAvailableNasFileSystems(nasClient);
770 ensureNasFileSystemsExist(nasFileSystems);
771
772 const nasAnswer = await promptForFileSystems(nasFileSystems);
773 const nasSelected = nasFileSystems.filter(f => f.fileSystemId === nasAnswer.fileSystemId);
774 const mountTargets = _.head(nasSelected).mountTargets;
775 ensureMountTargetsExist(mountTargets);
776
777 const mountTargetAnswer = await promptForMountTargets(mountTargets);
778 const mountTargetSelected = mountTargets.filter(f => f.MountTargetDomain === mountTargetAnswer.mountTargetDomain);
779 const mountTarget = _.head(mountTargetSelected);
780
781 const securityGroups = await getSecurityGroups(mountTarget.VpcId);
782 ensureSecurityGroupsExist(securityGroups);
783
784 const securityGroupAnswer = await promptForSecurityGroup(securityGroups);
785 const securityGroupId = securityGroupAnswer.securityGroupId;
786
787 return {
788 mountTarget,
789 securityGroupId
790 };
791}
792
793function replaceNasConfig(nasConfig, mountDir) {
794 const cloneNasConfig = _.cloneDeep(nasConfig);
795 cloneNasConfig.MountPoints = cloneNasConfig.MountPoints.filter(f => f.MountDir === mountDir);
796 return cloneNasConfig;
797}
798
799async function ensureCodeUriForJava(codeUri, serviceName, functionName) {
800
801 if (codeUri.endsWith('.zip') || codeUri.endsWith('.jar') || codeUri.endsWith('.war')) {
802 throw new Error(`
803You can follow these steps:
804 1. Modify ${serviceName}/${functionName}'s 'CodeUri' property to the directory where 'pom.xml' is located.
805 2. Execute 'fun build' to build your functions.
806 3. Execute 'fun deploy' to deploy resources.`);
807 }
808}
809
810async function nasAutoConfigurationIfNecessary({ stage, tplPath, runtime, codeUri, nasConfig, vpcConfig,
811 compressedSize,
812 nasFunctionName,
813 nasServiceName
814}) {
815
816 let stop = false;
817 let tplChanged = false;
818 if (compressedSize > 52428800 && _.includes(SUPPORT_RUNTIMES, runtime)) { // 50M
819
820 console.log(red(`\nFun detected that your function ${nasServiceName}/${nasFunctionName} sizes exceed 50M. It is recommended that using the nas service to manage your function dependencies.`));
821 await ensureCodeUriForJava(codeUri, nasServiceName, nasFunctionName);
822
823 if (await promptForConfirmContinue(`Do you want to let fun to help you automate the configuration?`)) {
824
825 const packageStage = (stage === 'package');
826 const tpl = await getTpl(tplPath);
827
828 if (definition.isNasAutoConfig(nasConfig)) {
829 const yes = await promptForConfirmContinue(`You have already configured 'NasConfig: Auto’. We want to use this configuration to store your function dependencies.`);
830 if (yes) {
831
832 if (packageStage && !_.isEmpty(vpcConfig)) {
833 throw new Error(`When 'NasConfig: Auto' is specified, 'VpcConfig' is not supported.`);
834 }
835 await backupTemplateFile(tplPath); // backup tpl
836
837 tplChanged = await processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, stage,
838 serviceName: nasServiceName,
839 functionName: nasFunctionName
840 });
841
842 stop = true;
843 } else {
844 throw new Error(red(`\nIf 'NasConfig: Auto' is configured, only the configuration store function dependency is currently supported.`));
845 }
846 } else if (!_.isEmpty(vpcConfig) && _.isEmpty(nasConfig)) {
847
848 throw new Error(red(`\nFun has detected that you only have VPC configuration. This scenario is not supported at this time. You also need to manually configure the NAS service. You can refer to: https://github.com/alibaba/funcraft/blob/master/docs/specs/2018-04-03-zh-cn.md#nas-%E9%85%8D%E7%BD%AE%E5%AF%B9%E8%B1%A1 and https://nas.console.aliyun.com/`));
849 } else if (!_.isEmpty(vpcConfig) && !_.isEmpty(nasConfig)) {
850
851 if (packageStage) {
852 throw new Error(`When 'NasConfig' is specified, 'VpcConfig' is not supported.`);
853 }
854 if (definition.onlyOneNASExists(nasConfig)) {
855 const yes = await promptForConfirmContinue(`We have detected that you already have a NAS configuration. Do you directly use this NAS storage function dependencies.`);
856 if (yes) {
857 await backupTemplateFile(tplPath);
858
859 tplChanged = await processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, stage,
860 serviceName: nasServiceName,
861 functionName: nasFunctionName
862 });
863 } else {
864 throw new Error(red(`If your yml has been already configured with 'NasConfig', fun only supports to use this 'NasConfig' to process your function dependencies. Otherwise you need to handle the dependencies by yourself.\n\nRefer to https://yq.aliyun.com/articles/712700 for more help.`));
865 }
866 } else {
867 const answer = await promptForMountPoints(nasConfig.MountPoints);
868 const convertedNasConfig = replaceNasConfig(nasConfig, answer.mountDir);
869 await backupTemplateFile(tplPath);
870
871 tplChanged = await processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, stage,
872 convertedNasConfig,
873 serviceName: nasServiceName,
874 functionName: nasFunctionName
875 });
876 }
877
878 stop = true;
879 } else if (_.isEmpty(vpcConfig) && _.isEmpty(nasConfig)) {
880 const yes = await promptForConfirmContinue(`We recommend using the 'NasConfig: Auto' configuration to manage your function dependencies.`);
881 if (yes) {
882 await backupTemplateFile(tplPath);
883 // write back to yml
884 const updatedTpl = await updateNasAutoConfigure(tplPath, tpl, nasServiceName);
885
886 tplChanged = await processNasAutoConfiguration({ tpl: updatedTpl, tplPath, runtime, codeUri, stage,
887 serviceName: nasServiceName,
888 functionName: nasFunctionName
889 });
890 } else {
891 // list available NAS
892 const { mountTarget, securityGroupId } = await processNasSelection();
893
894 await backupTemplateFile(tplPath); // backup tpl
895
896 const nasAndVpcConfig = generateNasAndVpcConfig(mountTarget, securityGroupId, nasServiceName);
897 const updatedTpl = await updateNasAndVpc(tplPath, tpl, nasServiceName, nasAndVpcConfig);
898
899 tplChanged = await processNasAutoConfiguration({ tpl: updatedTpl, tplPath, runtime, codeUri, stage,
900 serviceName: nasServiceName,
901 functionName: nasFunctionName
902 });
903 }
904 stop = true;
905 }
906 }
907 }
908 return {
909 stop,
910 tplChanged
911 };
912}
913
914function usingProjectTemplate(tplPath) {
915 const baseDir = getBaseDir(tplPath);
916 return path.dirname(tplPath) === path.resolve(baseDir);
917}
918
919function generateNasAndVpcConfig(mountTarget, securityGroupId, serviceName) {
920 const nasConfig = {
921 'UserId': 10003,
922 'GroupId': 10003,
923 'MountPoints': [
924 {
925 'ServerAddr': `${mountTarget.MountTargetDomain}:/${serviceName}`,
926 'MountDir': '/mnt/nas'
927 }
928 ]
929 };
930
931 const vpcConfig = {
932 'VpcId': mountTarget.VpcId,
933 'VSwitchIds': [mountTarget.VswId],
934 'SecurityGroupId': securityGroupId
935 };
936
937 return {
938 'VpcConfig': vpcConfig,
939 'NasConfig': nasConfig
940 };
941}
942
943async function makeFunction(baseDir, {
944 serviceName,
945 functionName,
946 description = '',
947 handler,
948 initializer = '',
949 timeout = 3,
950 initializationTimeout = 3,
951 memorySize = 128,
952 runtime = 'nodejs6',
953 codeUri,
954 environmentVariables = {},
955 instanceConcurrency,
956 nasConfig,
957 vpcConfig
958}, onlyConfig, tplPath) {
959 const fc = await getFcClient();
960
961 var fn;
962 try {
963 fn = await fc.getFunction(serviceName, functionName);
964 } catch (ex) {
965 if (ex.code !== 'FunctionNotFound') {
966 throw ex;
967 }
968 }
969
970 if (!fn && onlyConfig) {
971 throw new Error(`\nFunction '` + `${serviceName}` + '/' + `${functionName}` + `' was detected as the first deployment, and the code package had to be uploaded when creating the function. You can ` + yellow(`either`) + ` re-execute the command to remove the -u(--update-config)` + ` option ` + yellow(`or`) + ` execute 'fun deploy ${serviceName}/${functionName}' before doing so.`);
972 }
973
974 let code;
975
976 if (!onlyConfig) { // ignore code
977
978 if (codeUri && codeUri.startsWith('oss://')) { // oss://my-bucket/function.zip
979 code = extractOssCodeUri(codeUri);
980 } else {
981 console.log(`\t\tWaiting for packaging function ${functionName} code...`);
982 const { base64, count, compressedSize } = await zipCode(baseDir, codeUri, runtime, functionName);
983
984 const rs = await nasAutoConfigurationIfNecessary({
985 compressedSize, tplPath, runtime, nasConfig, vpcConfig,
986 nasFunctionName: functionName,
987 nasServiceName: serviceName,
988 codeUri: path.resolve(baseDir, codeUri)
989 });
990
991 if (rs.stop) { return { tplChanged: rs.tplChanged }; }
992
993 const convertedSize = bytes(compressedSize, {
994 unitSeparator: ' '
995 });
996
997 if (!count || !compressedSize) {
998 console.log(green(`\t\tThe function ${functionName} has been packaged.`));
999 } else {
1000 console.log(green(`\t\tThe function ${functionName} has been packaged. A total of ` + yellow(`${count}`) + `${count === 1 ? ' file' : ' files'}` + ` files were compressed and the final size was` + yellow(` ${convertedSize}`)));
1001 }
1002
1003 code = {
1004 zipFile: base64
1005 };
1006 }
1007 }
1008
1009 const confEnv = await resolveLibPathsFromLdConf(baseDir, codeUri);
1010
1011 Object.assign(environmentVariables, confEnv);
1012
1013 const params = {
1014 description,
1015 handler,
1016 initializer,
1017 timeout,
1018 initializationTimeout,
1019 memorySize,
1020 runtime,
1021 code,
1022 environmentVariables: addEnv(environmentVariables, nasConfig),
1023 instanceConcurrency
1024 };
1025
1026 for (let i in params.environmentVariables) {
1027 if (!isNaN(params.environmentVariables[i])) {
1028 debug(`the value in environmentVariables:${params.environmentVariables[i]} cast String Done`);
1029 params.environmentVariables[i] = params.environmentVariables[i] + '';
1030 }
1031 }
1032
1033 try {
1034 if (!fn) {
1035 // create
1036 params['functionName'] = functionName;
1037 await fc.createFunction(serviceName, params);
1038 } else {
1039 // update
1040 await fc.updateFunction(serviceName, functionName, params);
1041 }
1042 } catch (ex) {
1043
1044 if (ex.message.indexOf('timeout') !== -1) {
1045 throw new Error(`\nError message: ${ex.message}.\n\n` + red(`This error may be caused by network latency. You can set the client timeout to a larger value through 'fun config' and try again.`));
1046 }
1047 throw ex;
1048 }
1049 return {
1050 tplChanged: false
1051 };
1052}
1053
1054async function makeService({
1055 serviceName,
1056 role,
1057 description,
1058 internetAccess = true,
1059 logConfig = {},
1060 vpcConfig,
1061 nasConfig
1062}) {
1063 const fc = await getFcClient();
1064
1065 var service;
1066 await promiseRetry(async (retry, times) => {
1067 try {
1068 service = await fc.getService(serviceName);
1069 } catch (ex) {
1070
1071 if (ex.code === 'AccessDenied' || !ex.code || ex.code === 'ENOTFOUND') {
1072
1073 if (ex.message.indexOf('FC service is not enabled for current user') !== -1) {
1074
1075 console.error(red(`\nFC service is not enabled for current user. Please enable FC service before using fun.\nYou can enable FC service on this page https://www.aliyun.com/product/fc .\n`));
1076
1077 } else {
1078 console.error(red(`\nThe accountId you entered is incorrect. You can only use the primary account id, whether or not you use a sub-account or a primary account ak. You can get primary account ID on this page https://account.console.aliyun.com/#/secure .\n`));
1079 }
1080
1081 throw ex;
1082 } else if (ex.code !== 'ServiceNotFound') {
1083 debug('error when getService, serviceName is %s, error is: \n%O', serviceName, ex);
1084
1085 console.log(red(`\tretry ${times} times`));
1086 retry(ex);
1087 }
1088 }
1089 });
1090
1091 const options = {
1092 description,
1093 role,
1094 logConfig: {
1095 project: logConfig.Project || '',
1096 logstore: logConfig.Logstore || ''
1097 }
1098 };
1099
1100 if (internetAccess !== null) {
1101 // vpc feature is not supported in some region
1102 Object.assign(options, {
1103 internetAccess
1104 });
1105 }
1106
1107 const isNasAuto = definition.isNasAutoConfig(nasConfig);
1108 const isVpcAuto = definition.isVpcAutoConfig(vpcConfig);
1109
1110 if (!_.isEmpty(vpcConfig) || isNasAuto) {
1111
1112 if (isVpcAuto || (_.isEmpty(vpcConfig) && isNasAuto)) {
1113 console.log('\tusing \'VpcConfig: Auto\', Fun will try to generate related vpc resources automatically');
1114 vpcConfig = await vpc.createDefaultVpcIfNotExist();
1115 console.log(green('\tgenerated auto VpcConfig done: ', JSON.stringify(vpcConfig)));
1116
1117 debug('generated vpcConfig: %j', vpcConfig);
1118 }
1119 }
1120
1121 Object.assign(options, {
1122 vpcConfig: vpcConfig || defaultVpcConfig
1123 });
1124
1125 if (isNasAuto) {
1126 const vpcId = vpcConfig.vpcId || vpcConfig.VpcId;
1127 const vswitchIds = vpcConfig.vswitchIds || vpcConfig.VSwitchIds;
1128
1129 console.log('\tusing \'NasConfig: Auto\', Fun will try to generate related nas file system automatically');
1130 nasConfig = await nas.generateAutoNasConfig(serviceName, vpcId, vswitchIds, nasConfig.UserId, nasConfig.GroupId);
1131 console.log(green('\tgenerated auto NasConfig done: ', JSON.stringify(nasConfig)));
1132 }
1133
1134 Object.assign(options, {
1135 nasConfig: nasConfig || defaultNasConfig
1136 });
1137
1138 await promiseRetry(async (retry, times) => {
1139 try {
1140 if (!service) {
1141 debug('create service %s, options is %j', serviceName, options);
1142 service = await fc.createService(serviceName, options);
1143 } else {
1144 debug('update service %s, options is %j', serviceName, options);
1145 service = await fc.updateService(serviceName, options);
1146 }
1147 } catch (ex) {
1148 if (ex.code === 'AccessDenied') {
1149 throw ex;
1150 }
1151 debug('error when createService or updateService, serviceName is %s, options is %j, error is: \n%O', serviceName, options, ex);
1152
1153 console.log(red(`\tretry ${times} times`));
1154 retry(ex);
1155 }
1156 });
1157
1158 // make sure nas dir exist
1159 if (serviceName !== FUN_GENERATED_SERVICE
1160 && !_.isEmpty(nasConfig)
1161 && !_.isEmpty(nasConfig.MountPoints)) {
1162
1163 await ensureNasDirExist({
1164 role, vpcConfig, nasConfig
1165 });
1166 }
1167
1168 return service;
1169}
1170
1171function mapMountPointDir(mountPoints, func) {
1172 let resolvedMountPoints = _.map(mountPoints, (mountPoint) => {
1173 const serverAddr = mountPoint.ServerAddr;
1174
1175 const index = _.lastIndexOf(serverAddr, ':');
1176 if (index >= 0) {
1177 const mountPointDomain = serverAddr.substring(0, index);
1178 const remoteDir = serverAddr.substring(index + 1);
1179 const mountDir = mountPoint.MountDir;
1180
1181 debug('remoteDir is: %s', remoteDir);
1182
1183 return func(mountPointDomain, remoteDir, mountDir);
1184 }
1185 });
1186
1187 resolvedMountPoints = _.compact(resolvedMountPoints);
1188
1189 return resolvedMountPoints;
1190}
1191
1192const EXTREME_PATH_PREFIX = '/share';
1193
1194function checkMountPointDomainIsExtremeNas(mountPointDomain, remoteDir) {
1195 const isExtremeNAS = mountPointDomain.indexOf('.extreme.nas.aliyuncs.com') !== -1;
1196
1197 if (isExtremeNAS && (remoteDir !== EXTREME_PATH_PREFIX && !remoteDir.startsWith(EXTREME_PATH_PREFIX + '/'))) {
1198 throw new Error('Extreme nas mount point must start with /share. Please refer to https://nas.console.aliyun.com/#/extreme for more help.');
1199 }
1200
1201 return isExtremeNAS;
1202}
1203
1204async function ensureNasDirExist({
1205 role,
1206 vpcConfig,
1207 nasConfig
1208}) {
1209 const mountPoints = nasConfig.MountPoints;
1210 const modifiedNasConfig = _.cloneDeep(nasConfig);
1211
1212 modifiedNasConfig.MountPoints = mapMountPointDir(mountPoints, (mountPointDomain, remoteDir, mountDir) => {
1213
1214 if (checkMountPointDomainIsExtremeNas(mountPointDomain, remoteDir)) {
1215 // 极速 nas
1216 return {
1217 ServerAddr: `${mountPointDomain}:${EXTREME_PATH_PREFIX}`,
1218 MountDir: `${mountDir}`
1219 };
1220 } else if (remoteDir !== '/') {
1221 return {
1222 ServerAddr: `${mountPointDomain}:/`,
1223 MountDir: `${mountDir}`
1224 };
1225 } return null;
1226 });
1227
1228 const nasMountDirs = mapMountPointDir(mountPoints, (mountPointDomain, remoteDir, mountDir) => {
1229 if (checkMountPointDomainIsExtremeNas(mountPointDomain, remoteDir)) {
1230 if (remoteDir !== EXTREME_PATH_PREFIX) {
1231 return { mountDir, remoteDir, isExtreme: true };
1232 }
1233 } else if (remoteDir !== '/') {
1234 return { mountDir, remoteDir, isExtreme: false };
1235 }
1236 return null;
1237 });
1238
1239 debug('dirs need to check: %s', nasMountDirs);
1240
1241 if (!_.isEmpty(nasMountDirs)) {
1242 let nasRemoteDirs = [];
1243 let nasDirsNeedToCheck = [];
1244 for (let nasMountDir of nasMountDirs) {
1245 nasRemoteDirs.push(nasMountDir.remoteDir);
1246 if (nasMountDir.isExtreme) {
1247 // 002aab55-fbdt.cn-hangzhou.extreme.nas.aliyuncs.com:/share
1248 nasDirsNeedToCheck.push(path.posix.join(nasMountDir.mountDir, nasMountDir.remoteDir.substring(EXTREME_PATH_PREFIX.length)));
1249 } else {
1250 nasDirsNeedToCheck.push(path.posix.join(nasMountDir.mountDir, nasMountDir.remoteDir));
1251 }
1252 }
1253
1254 console.log(`\tChecking if nas directories ${nasRemoteDirs} exists, if not, it will be created automatically`);
1255
1256 const utilFunctionName = await makeFcUtilsFunctionNasDirChecker(role, vpcConfig, modifiedNasConfig);
1257 await sleep(1000);
1258 await invokeFcUtilsFunction({
1259 functionName: utilFunctionName,
1260 event: JSON.stringify(nasDirsNeedToCheck)
1261 });
1262
1263 console.log(green('\tChecking nas directories done', JSON.stringify(nasRemoteDirs)));
1264 }
1265}
1266
1267async function makeFcUtilsService(role, vpcConfig, nasConfig) {
1268 return await makeService({
1269 serviceName: FUN_GENERATED_SERVICE,
1270 role,
1271 description: 'generated by Funcraft',
1272 vpcConfig,
1273 nasConfig
1274 });
1275}
1276
1277async function makeFcUtilsFunction({
1278 serviceName,
1279 functionName,
1280 codes,
1281 description = '',
1282 handler,
1283 timeout = 60,
1284 memorySize = 128,
1285 runtime = 'nodejs8'
1286}) {
1287 const fc = await getFcClient();
1288
1289 var fn;
1290 try {
1291 fn = await fc.getFunction(serviceName, functionName);
1292 } catch (ex) {
1293 if (ex.code !== 'FunctionNotFound') {
1294 throw ex;
1295 }
1296 }
1297
1298 const base64 = await zip.packFromJson(codes);
1299
1300 let code = {
1301 zipFile: base64
1302 };
1303
1304 const params = {
1305 description,
1306 handler,
1307 initializer: '',
1308 timeout,
1309 memorySize,
1310 runtime,
1311 code
1312 };
1313
1314 if (!fn) {
1315 // create
1316 params['functionName'] = functionName;
1317 fn = await fc.createFunction(serviceName, params);
1318 } else {
1319 // update
1320 fn = await fc.updateFunction(serviceName, functionName, params);
1321 }
1322
1323 return fn;
1324}
1325
1326async function invokeFcUtilsFunction({
1327 functionName,
1328 event
1329}) {
1330 const fc = await getFcClient();
1331 const rs = await fc.invokeFunction(FUN_GENERATED_SERVICE, functionName, event, {
1332 'X-Fc-Log-Type': 'Tail'
1333 });
1334
1335 if (rs.data !== 'OK') {
1336 const log = rs.headers['x-fc-log-result'];
1337
1338 if (log) {
1339 const decodedLog = Buffer.from(log, 'base64');
1340 if ((decodedLog.toString().toLowerCase()).includes('permission denied')) {
1341 throw new Error(`fc utils function ${functionName} invoke error, error message is: ${decodedLog}\n${red('May be UserId and GroupId in NasConfig don\'t have enough \
1342permission, more information please refer to https://github.com/alibaba/funcraft/blob/master/docs/usage/faq-zh.md')}`);
1343 }
1344 throw new Error(`fc utils function ${functionName} invoke error, error message is: ${decodedLog}`);
1345 }
1346 }
1347}
1348
1349async function getFcUtilsFunctionCode(filename) {
1350 return await fs.readFile(path.join(__dirname, 'utils', filename));
1351}
1352
1353async function makeFcUtilsFunctionNasDirChecker(role, vpcConfig, nasConfig) {
1354 await makeFcUtilsService(role, vpcConfig, nasConfig);
1355
1356 const functionName = 'nas_dir_checker';
1357
1358 const functionCode = await getFcUtilsFunctionCode('nas-dir-check.js');
1359
1360 const codes = {
1361 'index.js': functionCode
1362 };
1363
1364 await makeFcUtilsFunction({
1365 serviceName: FUN_GENERATED_SERVICE,
1366 functionName: 'nas_dir_checker',
1367 codes,
1368 description: 'used for fun to ensure nas remote dir exist',
1369 handler: 'index.handler'
1370 });
1371
1372 return functionName;
1373}
1374
1375
1376async function invokeFunction({
1377 serviceName,
1378 functionName,
1379 event,
1380 invocationType
1381}) {
1382
1383 var rs;
1384 const fc = await getFcClient();
1385
1386 if (invocationType === 'Sync') {
1387
1388 rs = await fc.invokeFunction(serviceName, functionName, event, {
1389 'X-Fc-Log-Type': 'Tail',
1390 'X-Fc-Invocation-Type': invocationType
1391 });
1392
1393 const log = rs.headers['x-fc-log-result'];
1394
1395 if (log) {
1396
1397 console.log(yellow('========= FC invoke Logs begin ========='));
1398 const decodedLog = Buffer.from(log, 'base64');
1399 console.log(decodedLog.toString());
1400 console.log(yellow('========= FC invoke Logs end ========='));
1401
1402 console.log(green('\nFC Invoke Result:'));
1403 console.log(rs.data);
1404 }
1405 } else {
1406
1407 rs = await fc.invokeFunction(serviceName, functionName, event, {
1408 'X-Fc-Invocation-Type': invocationType
1409 });
1410
1411 console.log(green('✔ ') + `${serviceName}/${functionName} async invoke success.`);
1412 }
1413
1414 return rs;
1415}
1416
1417module.exports = {
1418 zipCode, makeService, makeFunction, invokeFunction,
1419 detectLibrary, generateFunIngore, parseMountDirPrefix,
1420 FUN_GENERATED_SERVICE, invokeFcUtilsFunction, getFcUtilsFunctionCode,
1421 processNasMappingsAndEnvs, processNasAutoConfiguration, nasAutoConfigurationIfNecessary,
1422 makeFcUtilsFunctionNasDirChecker
1423};
\No newline at end of file