1 | 'use strict';
|
2 |
|
3 | const httpx = require('httpx');
|
4 | const cheerio = require('cheerio');
|
5 | const _ = require('lodash');
|
6 | const { red } = require('colors');
|
7 | const { Transform } = require('stream');
|
8 | const unrefTimeout = require('./unref-timeout');
|
9 | const detectMocha = require('detect-mocha');
|
10 |
|
11 | class 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 |
|
36 | class 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 |
|
62 | class 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 |
|
77 | class 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 |
|
91 | class 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 |
|
107 | class 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.
|
115 | Type 'fun install -h' for more help.`));
|
116 | });
|
117 | }
|
118 | }
|
119 |
|
120 | class 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.
|
128 | if You are using Docker for Windows/Mac, you can select the Docker icon and then Preferences > Resources > Advanced and increase docker image size.
|
129 | Please refer to https://docs.docker.com/docker-for-mac/space/ for more help.
|
130 | `));
|
131 | });
|
132 | }
|
133 | }
|
134 |
|
135 | class 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 |
|
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 |
|
194 | Also 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 |
|
207 | Also 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 |
|
217 | class 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.
|
225 | Please reinstall it like this:
|
226 |
|
227 | 1. fun install sbox -f ${this.serviceName}/${this.functionName} -i
|
228 | 2. fun-install npm install puppeteer
|
229 | 3. type 'exit' to exit container and then reRun your function
|
230 |
|
231 | Also you can install puppeteer through one command:
|
232 | fun install sbox -f puppeteer/html2png --cmd 'fun-install npm install puppeteer'`));
|
233 |
|
234 | this._autoExist();
|
235 | });
|
236 | }
|
237 | }
|
238 |
|
239 | class 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 |
|
265 | class 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 |
|
279 | function 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 |
|
296 | module.exports = {
|
297 | processorTransformFactory,
|
298 | FilterChain
|
299 | };
|