1 | 'use strict';
|
2 | var __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 | };
|
10 | const util = require('./import/utils');
|
11 | const bytes = require('bytes');
|
12 | const funignore = require('./package/ignore').isIgnored;
|
13 | const definition = require('./definition');
|
14 | const promiseRetry = require('./retry');
|
15 | const getProfile = require('./profile').getProfile;
|
16 | const securityGroup = require('./security-group');
|
17 | const uuid = require('uuid');
|
18 | const tmpDir = require('temp-dir');
|
19 | const fs = require('fs-extra');
|
20 | const path = require('path');
|
21 | const debug = require('debug')('fun:fc');
|
22 | const yaml = require('js-yaml');
|
23 | const zip = require('./package/zip');
|
24 | const vpc = require('./vpc');
|
25 | const nas = require('./nas');
|
26 | const nasCp = require('./nas/cp');
|
27 | const getUuid = require('uuid-by-string');
|
28 | const { sleep } = require('./time');
|
29 | const { makeTrigger } = require('./trigger');
|
30 | const { makeSlsAuto } = require('./deploy/deploy-support');
|
31 | const { isNotEmptyDir } = require('./nas/cp/file');
|
32 | const barUtil = require('./import/utils');
|
33 | const { isSpringBootJar } = require('./frameworks/common/java');
|
34 | const { updateTimestamps } = require('./utils/file');
|
35 | const { green, red, yellow } = require('colors');
|
36 | const { getFcClient, getEcsPopClient, getNasPopClient } = require('./client');
|
37 | const { makeDestination } = require('./function-async-config');
|
38 | const { getTpl, getBaseDir, getNasYmlPath, getRootTplPath, getProjectTpl } = require('./tpl');
|
39 | const { addEnv, mergeEnvs, resolveLibPathsFromLdConf, generateDefaultLibPath } = require('./install/env');
|
40 | const { readFileFromNasYml, mergeNasMappingsInNasYml, getNasMappingsFromNasYml, extractNasMappingsFromNasYml } = require('./nas/support');
|
41 | const { isBinary } = require('istextorbinary');
|
42 | const _ = require('lodash');
|
43 | const { promptForConfirmContinue, promptForMountTargets, promptForMountPoints, promptForFileSystems, promptForSecurityGroup, promptForInputContinue } = require('./init/prompt');
|
44 | const FUN_GENERATED_SERVICE = 'fun-generated-default-service';
|
45 | const SYSTEM_DEPENDENCY_PATH = path.join('.fun', 'root');
|
46 | const SUPPORT_RUNTIMES = ['nodejs6', 'nodejs8', 'nodejs10', 'nodejs12', 'python2.7', 'python3', 'java8', 'custom'];
|
47 | const defaultVpcConfig = {
|
48 | securityGroupId: '',
|
49 | vSwitchIds: [],
|
50 | vpcId: ''
|
51 | };
|
52 | const defaultNasConfig = {
|
53 | UserId: -1,
|
54 | GroupId: -1,
|
55 | MountPoints: []
|
56 | };
|
57 |
|
58 | const BOOTSTRAP_SPRING_BOOT_JAR_REGEX = new RegExp('(java .*?)-jar (.*?) ([0-9a-zA-Z./_-]+\\.jar)', 'm');
|
59 | function 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 |
|
72 | const 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 | };
|
81 | function 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 | }
|
95 | function 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 | }
|
105 | function 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 | }
|
113 | function 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 | }
|
134 | const NODE_RUNTIME_MAPPING = {
|
135 | 'localDir': 'node_modules',
|
136 | 'remoteDir': 'node_modules',
|
137 | 'env': 'NODE_PATH',
|
138 | 'defaultEnv': '/usr/local/lib/node_modules'
|
139 | };
|
140 | const PYTHON_RUNTIME_MAPPING = {
|
141 | 'localDir': path.join('.fun', 'python'),
|
142 | 'remoteDir': 'python',
|
143 | 'env': 'PYTHONUSERBASE'
|
144 | };
|
145 | const JAVA_RUNTIME_MAPPING = {
|
146 | 'localDir': path.join('.fun', 'build', 'artifacts'),
|
147 | 'remoteDir': 'java',
|
148 | 'env': 'CLASSPATH'
|
149 | };
|
150 | const CUSTOM_RUNTIME_JAVA_MAPPING = {
|
151 | 'localDir': path.join('target', 'lib'),
|
152 | 'remoteDir': 'java',
|
153 | 'env': 'CLASSPATH',
|
154 | 'pathSuffix': '*'
|
155 | };
|
156 | const FONTS_MAPPING = {
|
157 | 'localDir': 'fonts',
|
158 | 'remoteDir': 'fonts'
|
159 | };
|
160 | const 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 | };
|
170 | function 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 | }
|
182 | function 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 | }
|
200 | function 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 | }
|
206 | function 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 | }
|
221 | function 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 | }
|
228 | function 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 | }
|
242 | function 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 | }
|
261 | function 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 | }
|
268 | function 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 |
|
275 | const nasMappingPath = path.resolve(baseDir, '.nas.yml');
|
276 | const localSystemDependency = path.resolve(codeUri, SYSTEM_DEPENDENCY_PATH);
|
277 | if (yield fs.pathExists(localSystemDependency)) {
|
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)) {
|
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 | }
|
309 | function 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 | }
|
317 | function 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 | }
|
327 | function 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 |
|
340 | function outputNasMappingLog(baseDir, nasMappingPath, localNasDir) {
|
341 | console.log(green(`Fun add ${path.relative(baseDir, localNasDir)} to ${nasMappingPath}`));
|
342 | }
|
343 | function generateSystemNasEnvs(rootEnvPrefix) {
|
344 | return {
|
345 | 'LD_LIBRARY_PATH': `${generateDefaultLibPath(rootEnvPrefix)}`
|
346 | };
|
347 | }
|
348 | function 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 | }
|
359 | function 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 | }
|
391 | function 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 |
|
417 |
|
418 |
|
419 | function 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 | }
|
435 | function 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 |
|
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 | }
|
461 | function detectWarfilePathfromBootstrap(bootstrapContent) {
|
462 | return __awaiter(this, void 0, void 0, function* () {
|
463 |
|
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 | }
|
471 | function 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 | }
|
477 | function 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 | }
|
488 | function 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 |
|
498 |
|
499 |
|
500 |
|
501 |
|
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
|
520 | export JETTY_RUNNER=/mnt/auto/root/usr/local/java/jetty-runner.jar
|
521 | export PORT=9000
|
522 | java -jar $JETTY_RUNNER --port $PORT ${ctxDescriptorPath}
|
523 | `;
|
524 | yield generateRepackagedBootstrap(bootstrapPath, newBootstrapContent);
|
525 | }
|
526 | }
|
527 | });
|
528 | }
|
529 | function 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 | }
|
545 | function 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 |
|
558 |
|
559 |
|
560 | const idx = absJarfilePath.indexOf('target/');
|
561 | if (idx < 0) {
|
562 | throw new Error('could not found target directory');
|
563 | }
|
564 |
|
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 | }
|
575 | function 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 |
|
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 | }
|
599 | function 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 |
|
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 |
|
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 | }
|
637 | function 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 | }
|
648 | function 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 | }
|
667 | function 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(`
|
685 | Fun has detected that there is a model folder. It is recommend to synchronize your model folder to NAS.
|
686 | You can add the following configuration to ` + yellow(`'nasMapping.${serviceName}'`) + ` in ` + yellow(`${nasYmlPath}
|
687 | `)
|
688 | + yellow(`
|
689 | - localNasDir: ${absModelPath}
|
690 | remoteNasDir: ${remoteNasDir}
|
691 | `)
|
692 | + `
|
693 | After adding, fun is going to automatically synchronize the ` + yellow(`local`) + ` directory ${absModelPath} to ` + yellow(`remote`) + ` ${remoteNasDir}.
|
694 | If 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 | }
|
700 | function 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 | }
|
709 | function 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 | }
|
714 | function 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 | }
|
719 | function 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 | }
|
724 | function ensureOnlyOneMountPoinExists(mountPoints) {
|
725 | if (mountPoints.length > 1) {
|
726 | throw new Error(red(`More than one 'NasConfig' configuration in template.yml.`));
|
727 | }
|
728 | }
|
729 | function 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 | }
|
736 | function 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 | }
|
758 | function replaceNasConfig(nasConfig, mountDir) {
|
759 | const cloneNasConfig = _.cloneDeep(nasConfig);
|
760 | cloneNasConfig.MountPoints = cloneNasConfig.MountPoints.filter(f => f.MountDir === mountDir);
|
761 | return cloneNasConfig;
|
762 | }
|
763 | function 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(`
|
767 | You 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 | }
|
774 | function 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) {
|
785 | const content = yield fs.readFile(bootstrapPath, 'utf8');
|
786 |
|
787 |
|
788 |
|
789 |
|
790 | return _.includes(content, 'org.springframework.boot.loader.PropertiesLauncher');
|
791 | }
|
792 | return false;
|
793 | });
|
794 | }
|
795 | function 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);
|
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);
|
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 |
|
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 |
|
903 | const { mountTarget, securityGroupId } = yield processNasSelection();
|
904 | yield backupTemplateFile(tplPath);
|
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 | }
|
922 | function usingProjectTemplate(tplPath) {
|
923 | const baseDir = getBaseDir(tplPath);
|
924 | return path.dirname(tplPath) === path.resolve(baseDir);
|
925 | }
|
926 | function 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 | }
|
947 | function 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 | }
|
954 | const DEFAULT_FONTS_CONFIG_ENV = {
|
955 | 'FONTCONFIG_FILE': '/code/.fonts.conf'
|
956 | };
|
957 | function 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 |
|
968 |
|
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 | }
|
977 | function 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) {
|
995 | if (codeUri && codeUri.startsWith('oss://')) {
|
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 |
|
1056 | yield fc.createFunction(serviceName, streamPipe);
|
1057 | }
|
1058 | else {
|
1059 |
|
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 | }
|
1077 | function 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 | }
|
1092 | function generateSlsProjectName(accountId, region) {
|
1093 | const uuidHash = getUuid(accountId);
|
1094 | return `aliyun-fc-${region}-${uuidHash}`;
|
1095 | }
|
1096 | function 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 | }
|
1105 | function 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 | }
|
1121 | function isSlsNotExistException(e) {
|
1122 | return e.code === 'InvalidArgument'
|
1123 | && _.includes(e.message, 'not exist')
|
1124 | && (_.includes(e.message, 'logstore') || _.includes(e.message, 'project'));
|
1125 | }
|
1126 |
|
1127 | function 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 | }
|
1159 | function 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 |
|
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 |
|
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 | }
|
1243 | function 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 | }
|
1258 | const EXTREME_PATH_PREFIX = '/share';
|
1259 | function 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 | }
|
1266 | function 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 |
|
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 |
|
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 | }
|
1322 | function 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 | }
|
1333 | function 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 |
|
1360 | params['functionName'] = functionName;
|
1361 | fn = yield fc.createFunction(serviceName, params);
|
1362 | }
|
1363 | else {
|
1364 |
|
1365 | fn = yield fc.updateFunction(serviceName, functionName, params);
|
1366 | }
|
1367 | return fn;
|
1368 | });
|
1369 | }
|
1370 | function 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 \
|
1382 | permission, 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 | }
|
1389 | function 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 | }
|
1433 | function getFcUtilsFunctionCode(filename) {
|
1434 | return __awaiter(this, void 0, void 0, function* () {
|
1435 | return yield fs.readFile(path.join(__dirname, 'utils', filename));
|
1436 | });
|
1437 | }
|
1438 | function 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 | }
|
1456 | function 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 | }
|
1484 | function 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 | }
|
1493 | module.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 | };
|