UNPKG

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