UNPKG

9.04 kBJavaScriptView Raw
1'use strict';
2
3const httpx = require('httpx');
4const cheerio = require('cheerio');
5const _ = require('lodash');
6const { red } = require('colors');
7const { Transform } = require('stream');
8const unrefTimeout = require('./unref-timeout');
9const detectMocha = require('detect-mocha');
10
11class FilterChain {
12 constructor(options = {}) {
13 this.processors = [
14 new PuppeteerInvalidPlatformProcessor(options),
15 new DynamicLinkLibraryMissingProcessor(options),
16 new NoSpaceLeftOnDeviceProcessor(options),
17 new MissingAptGetProcessor(options),
18 new DockerNotStartedOrInstalledErrorProcessor(options),
19 new FcServiceNotEnabledProcessor(options),
20 new RamInactiveErrorProcessor(options)];
21 }
22
23 async process(message, err) {
24 for (const processor of this.processors) {
25 if (!message) { message = ''; }
26
27 if (processor.match(message, err)) {
28 await processor.process(message, err);
29 await processor.postProcess();
30 return true;
31 }
32 }
33 }
34}
35
36class ErrorProcessor {
37 constructor(options = {}) {
38 this.serviceName = options.serviceName;
39 this.functionName = options.functionName;
40 }
41
42 match(message, err) { }
43 async process(message, err) { }
44
45 _autoExist() {
46 process.nextTick(() => {
47 console.log(red('\nFun will auto exit after 3 seconds.\n'));
48
49 if (!detectMocha()) {
50 unrefTimeout(() => {
51 process.emit('SIGINT');
52 }, 3000);
53 }
54 });
55 }
56
57 async postProcess() {
58 console.log();
59 }
60}
61
62class DockerNotStartedOrInstalledErrorProcessor extends ErrorProcessor {
63 match(message, err) {
64 if (_.includes(message, 'connect ECONNREFUSED /var/run/docker.sock')
65 || _.includes(message, 'Error: connect ENOENT //./pipe/docker_engine')) {
66 return true;
67 }
68
69 return false;
70 }
71
72 async process(message) {
73 console.log(red('Fun detected that Docker is not installed on your host or not started. Please run \'docker ps\' command to check docker status.'));
74 }
75}
76
77class FcServiceNotEnabledProcessor extends ErrorProcessor {
78 match(message, err) {
79 if (_.includes(message, 'FC service is not enabled for current user')) {
80 return true;
81 }
82
83 return false;
84 }
85
86 async process(message) {
87 console.log(red('FC 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 .'));
88 }
89}
90
91class RamInactiveErrorProcessor extends ErrorProcessor {
92 match(message, err) {
93 if (_.includes(message, 'Account is inactive to this service')
94 && _.includes(message, 'ram.aliyuncs.com')) {
95 return true;
96 }
97
98 return false;
99 }
100
101 async process(message) {
102 console.log(red('Ram service is not enabled for current user. Please enable Ram service before using fun.\nYou can enable Ram service on this page https://www.aliyun.com/product/ram .'));
103 }
104}
105
106// 发生在 fun install 安装依赖,但是依赖包含解决方案,比如 puppeteer,需要使用 apt-get 安装,如果宿主机没有,那就提示使用 fun install -d
107class MissingAptGetProcessor extends ErrorProcessor {
108 match(message) {
109 return _.includes(message, 'touch: /var/cache/apt/pkgcache.bin: No such file or directory');
110 }
111
112 async process(message) {
113 process.nextTick(() => {
114 console.log(red(`Tips: Fun has detected that there is no apt-get installed on the machine, you need use 'fun install --use-docker' to reinstall.
115Type 'fun install -h' for more help.`));
116 });
117 }
118}
119
120class NoSpaceLeftOnDeviceProcessor extends ErrorProcessor {
121 match(message) {
122 return _.includes(message, 'no space left on device');
123 }
124
125 async process(message) {
126 process.nextTick(() => {
127 console.log(red(`Tips: Fun has detected that docker is no space left.
128if You are using Docker for Windows/Mac, you can select the Docker icon and then Preferences > Resources > Advanced and increase docker image size.
129Please refer to https://docs.docker.com/docker-for-mac/space/ for more help.
130`));
131 });
132 }
133}
134
135class DynamicLinkLibraryMissingProcessor extends ErrorProcessor {
136
137 constructor(options) {
138 super(options);
139
140 this.prefix = 'error while loading shared libraries: ';
141 this.suffix = ': cannot open shared object file: No such file or directory';
142 this.debianPakcageUrlPrefix = 'https://packages.debian.org/search?lang=en&suite=jessie&arch=amd64&mode=path&searchon=contents&keywords=';
143 this.libPrefixWhiteList = ['/usr/lib/x86_64-linux-gnu', '/lib/x86_64-linux-gnu', '/usr/local/lib'];
144 }
145
146 match(message) {
147 return _.includes(message, this.prefix)
148 && _.includes(message, this.suffix);
149 }
150
151 async _findPackageByDlName(lib) {
152 const response = await httpx.request(`${this.debianPakcageUrlPrefix}${lib}`, { timeout: 10000 });
153
154 const body = await httpx.read(response, 'utf8');
155
156 const $ = cheerio.load(body);
157
158 const packagesTable = $('#pcontentsres table tbody tr').map((i, element) => ({
159 path: $(element).find('td:nth-of-type(1)').text().trim(),
160 name: $(element).find('td:nth-of-type(2)').text().trim()
161 })).get();
162
163 const packageInfo = _.find(packagesTable, info => _.some(this.libPrefixWhiteList, (prefix) => info.path.startsWith(prefix)));
164
165 if (packageInfo) {
166 return packageInfo.name;
167 }
168
169 return null;
170 }
171
172 async _fetchDlName(message) {
173 // error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory
174
175 const prefixIdx = message.indexOf(this.prefix);
176 const suffixIdx = message.indexOf(this.suffix);
177
178 return message.substring(prefixIdx + this.prefix.length, suffixIdx);
179 }
180
181 async process(message) {
182 const lib = await this._fetchDlName(message);
183
184 const packageName = await this._findPackageByDlName(lib);
185
186 if (packageName) {
187 process.nextTick(() => {
188 console.log(red(`Tips: Fun has detected that you are missing ${lib} library, you can try to install it like this:
189
190 step1: fun install sbox -f ${this.serviceName}/${this.functionName} -i
191 step2: fun-install apt-get install ${packageName}
192 step3: type 'exit' to exit container and then reRun your function
193
194Also you can install dependencies through one command:
195
196 fun install sbox -f ${this.serviceName}/${this.functionName} --cmd 'fun-install apt-get install ${packageName}'
197`));
198 });
199 } else {
200 console.log(red(`Tips: Fun has detected that you are missing ${lib} library, you can try to install it like this:
201
202 step1: open this page ${this.debianPakcageUrlPrefix}${lib} to find your missing dependency
203 step2: fun install sbox -f ${this.serviceName}/${this.functionName} -i
204 step3: fun-install apt-get install YourPackageName
205 step4: type 'exit' to exit container and then reRun your function
206
207Also you can install dependencies through one command:
208
209 fun install sbox -f ${this.serviceName}/${this.functionName} --cmd 'fun-install apt-get install YourPackageName'
210`));
211 }
212
213 this._autoExist();
214 }
215}
216
217class PuppeteerInvalidPlatformProcessor extends ErrorProcessor {
218 match(message) {
219 return _.includes(message, 'Error: Chromium revision is not downloaded. Run "npm install" or "yarn install"');
220 }
221
222 async process(message) {
223 process.nextTick(() => {
224 console.log(red(`Tips: Fun has detected that your puppeteer installation platform is incorrect.
225Please reinstall it like this:
226
2271. fun install sbox -f ${this.serviceName}/${this.functionName} -i
2282. fun-install npm install puppeteer
2293. type 'exit' to exit container and then reRun your function
230
231Also you can install puppeteer through one command:
232fun install sbox -f puppeteer/html2png --cmd 'fun-install npm install puppeteer'`));
233
234 this._autoExist();
235 });
236 }
237}
238
239class ChunkSplitTransform extends Transform {
240 constructor(options) {
241 super(options);
242 this._buffer = '';
243 this._separator = options.separator || '\n';
244 }
245
246 _transform(chunk, encoding, done) {
247 let sepPos;
248 this._buffer += chunk.toString();
249
250 while ((sepPos = this._buffer.indexOf(this._separator)) !== -1) {
251 const portion = this._buffer.substr(0, sepPos);
252 this.push(portion + this._separator);
253 this._buffer = this._buffer.substr(sepPos + this._separator.length);
254 }
255
256 done();
257 }
258
259 _flush(done) {
260 this.push(this._buffer);
261 done();
262 }
263}
264
265class FcErrorTransform extends Transform {
266 constructor(options) {
267 super(options);
268 this.filterChain = new FilterChain(options);
269 }
270 _transform(chunk, encoding, done) {
271 const message = chunk.toString();
272 this.filterChain.process(message).then(() => {
273 this.push(message);
274 done();
275 });
276 }
277}
278
279function processorTransformFactory({
280 serviceName,
281 functionName,
282 errorStream
283}) {
284 const transform = new ChunkSplitTransform({
285 separator: '\n'
286 });
287
288 transform.pipe(new FcErrorTransform({
289 serviceName: serviceName,
290 functionName: functionName
291 })).pipe(errorStream);
292
293 return transform;
294}
295
296module.exports = {
297 processorTransformFactory,
298 FilterChain
299};