-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathindex.js
More file actions
181 lines (162 loc) · 4.99 KB
/
Copy pathindex.js
File metadata and controls
181 lines (162 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
const Promise = require('bluebird');
const { Microfleet, ConnectorsTypes } = require('@microfleet/core');
const noop = require('lodash/noop');
const merge = require('lodash/merge');
const fsort = require('redis-filtered-sort');
const LockManager = require('dlock');
const RedisCluster = require('ioredis').Cluster;
// constants
const { HttpStatusError } = require('common-errors');
const { WEBHOOK_RESOURCE_ID } = require('./constant');
const StorageProviders = require('./providers');
const conf = require('./config');
/**
* @class Files
*/
class Files extends Microfleet {
/**
* class Constructor, initializes configuration
* and internal providers
*/
constructor(opts = {}) {
super(merge({}, Files.defaultOpts, opts));
const { config } = this;
/**
* Invoke this method to start post-processing of all pending files
* @return {Promise}
*/
this.postProcess = require('./post-process');
// extend with storage providers
StorageProviders(this);
// 2 different plugin types
let redisDuplicate;
if (config.plugins.includes('redisCluster')) {
this.redisType = 'redisCluster';
redisDuplicate = () => new RedisCluster(config.redis.hosts, config.redis.options);
} else if (config.plugins.includes('redisSentinel')) {
this.redisType = 'redisSentinel';
redisDuplicate = (redis) => redis.duplicate();
} else {
throw new Error('must include redis family plugins');
}
// init scripts
this.on(`plugin:connect:${this.redisType}`, (redis) => {
fsort.attach(redis, 'fsort');
this.log.debug('enabling lock manager');
this.dlock = new LockManager({
...config.lockManager,
// main connection
client: redis,
// second connection
pubsub: redisDuplicate(redis),
log: this.log,
});
});
// add migration connector
if (config.migrations.enabled === true) {
this.addConnector(ConnectorsTypes.migration, () => (
this.migrate('redis', `${__dirname}/migrations`)
));
}
}
/**
* Init's webhook
*/
initWebhook() {
this.log.debug('initializing webhook');
const { redis } = this;
return Promise
.map(this.providers, (provider, idx) => {
const hookId = `${WEBHOOK_RESOURCE_ID}_${idx}`;
return Promise
.bind(provider)
.then(() => process.env[hookId] || redis.get(hookId))
.then(provider.setupChannel)
.then((resourceId) => resourceId && redis.set(hookId, resourceId));
});
}
/**
* Terminate notifications
*/
stopWebhook() {
return Promise
.map(this.providers, (provider, idx) => {
const hookId = `${WEBHOOK_RESOURCE_ID}_${idx}`;
return provider
.stopChannel()
.tap((data) => this.log.info('stopped channel', data))
.then(() => this.redis.del(hookId))
.catch((e) => this.log.error({ err: e }, 'failed to stop channel'));
});
}
/**
* Overload close and make sure pubsub is stopped
* @return {Promise}
*/
close() {
return Promise.join(
super.close(),
this.dlock.pubsub.disconnect(),
process.env.WEBHOOK_TERMINATE ? this.stopWebhook() : noop
);
}
/**
* Handles upload notification
* https://github.com/GoogleCloudPlatform/google-cloud-node/blob/pubsub-0.9.0/packages/pubsub/src/subscription.js#L344
* @param {String} ackId
* @param {String} id
* @param {Mixed} data
* @param {Mixed} attributes
* @return {Promise}
*/
async handleUploadNotification(message) {
this.log.debug({ message }, 'upload notification');
const { prefix } = this.config.router.routes;
const route = `${prefix}.finish`;
try {
await this.router.dispatch(route, {
headers: {},
query: {},
// payload
params: {
filename: message.attributes.objectId,
resourceId: message.attributes.resource,
action: message.attributes.eventType,
},
transport: 'amqp',
method: 'amqp',
});
} catch (err) {
this.log.warn({ route, args: message.attributes, err }, 'failed notification');
if (!(err instanceof HttpStatusError)) {
message.nack(err);
return;
}
}
message.ack();
}
// log failed notification
logWarn(route, args, e) {
this.log.warn({ route, args, err: e }, 'failed notification');
}
/**
* Overload connect and make sure we have access to bucket
* @return {Promise}
*/
async connect() {
this.log.debug('started connecting');
await super.connect();
await this.initWebhook();
await Promise.mapSeries(this.providers, (provider) => {
if (!['aws', 'gce'].includes(provider.config.name)) return null;
if (!provider.config.bucket.channel.pubsub) return null;
return provider.subscribe(this.handleUploadNotification.bind(this));
});
}
}
/**
* Default options for the service
* @type {Object}
*/
Files.defaultOpts = conf.get('/', { env: process.env.NODE_ENV });
module.exports = Files;