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