UNPKG

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