Skip to content
This repository was archived by the owner on Oct 11, 2022. It is now read-only.

Commit 7c818ff

Browse files
authored
Merge pull request #5150 from withspectrum/off-site-backups
Implement offsite backups
2 parents 8a0f907 + 30578fc commit 7c818ff

File tree

8 files changed

+102
-18
lines changed

8 files changed

+102
-18
lines changed

chronos/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import processDailyCoreMetrics from 'chronos/queues/coreMetrics';
99
import processActiveCommunityAdminReport from 'chronos/queues/coreMetrics/activeCommunityAdminReport';
1010
import processRemoveSeenUsersNotifications from 'chronos/queues/remove-seen-usersNotifications';
1111
import processDatabaseBackup from 'chronos/queues/database-backup';
12+
import processOffsiteBackup from 'chronos/queues/offsite-backup';
1213
import {
1314
PROCESS_WEEKLY_DIGEST_EMAIL,
1415
PROCESS_DAILY_DIGEST_EMAIL,
@@ -17,6 +18,7 @@ import {
1718
PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT,
1819
PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS,
1920
PROCESS_DATABASE_BACKUP,
21+
PROCESS_OFFSITE_BACKUP,
2022
} from 'chronos/queues/constants';
2123
import { startJobs } from 'chronos/jobs';
2224

@@ -34,6 +36,7 @@ const server = createWorker(
3436
[PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT]: processActiveCommunityAdminReport,
3537
[PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS]: processRemoveSeenUsersNotifications,
3638
[PROCESS_DATABASE_BACKUP]: processDatabaseBackup,
39+
[PROCESS_OFFSITE_BACKUP]: processOffsiteBackup,
3740
},
3841
{
3942
settings: {

chronos/jobs/index.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
activeCommunityReportQueue,
99
removeSeenUsersNotificationsQueue,
1010
databaseBackupQueue,
11+
offsiteBackupQueue,
1112
} from 'shared/bull/queues';
1213

1314
/*
@@ -38,9 +39,14 @@ export const activeCommunityReport = () => {
3839
);
3940
};
4041

41-
export const dailyBackups = () => {
42-
// at 9am every day (~12 hours away from the automatic daily backup)
43-
return databaseBackupQueue.add(undefined, defaultJobOptions('0 9 * * *'));
42+
export const hourlyBackups = () => {
43+
// Every hour
44+
return databaseBackupQueue.add(undefined, defaultJobOptions('30 * * * *'));
45+
};
46+
47+
export const hourlyOffsiteBackup = () => {
48+
// Every hour offset by 30m from hourly backups, which should be enough time for the backups to finish
49+
return offsiteBackupQueue.add(undefined, defaultJobOptions('0 * * * *'));
4450
};
4551

4652
export const removeSeenUsersNotifications = () => {
@@ -57,5 +63,6 @@ export const startJobs = () => {
5763
dailyCoreMetrics();
5864
activeCommunityReport();
5965
removeSeenUsersNotifications();
60-
dailyBackups();
66+
hourlyBackups();
67+
hourlyOffsiteBackup();
6168
};

chronos/queues/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ export const PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT =
2727
export const PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS =
2828
'process remove seen usersNotifications';
2929
export const PROCESS_DATABASE_BACKUP = 'process database backup';
30+
export const PROCESS_OFFSITE_BACKUP = 'process offsite backup';

chronos/queues/database-backup.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,13 @@
11
// @flow
22
const debug = require('debug')('chronos:queue:database-backup');
3-
const fetch = require('node-fetch');
3+
const { compose, COMPOSE_DEPLOYMENT_ID } = require('../utils/compose');
44

5-
const COMPOSE_API_TOKEN = process.env.COMPOSE_API_TOKEN;
65
const processJob = async () => {
7-
if (!COMPOSE_API_TOKEN) {
8-
console.warn(
9-
'Cannot start hourly backup, COMPOSE_API_TOKEN env variable is missing.'
10-
);
11-
return;
12-
}
136
debug('pinging compose to start on-demand db backup');
14-
const result = await fetch(
15-
'https://api.compose.io/2016-07/deployments/5cd38bcf9c5cab000b617356/backups',
7+
const result = await compose(
8+
`2016-07/deployments/${COMPOSE_DEPLOYMENT_ID}/backups`,
169
{
1710
method: 'POST',
18-
headers: {
19-
'Content-Type': 'application/json',
20-
Authorization: `Bearer ${COMPOSE_API_TOKEN}`,
21-
},
2211
}
2312
);
2413
const json = await result.json();

chronos/queues/offsite-backup.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// @flow
2+
import AWS from 'aws-sdk';
3+
const https = require('https');
4+
const { compose, COMPOSE_DEPLOYMENT_ID } = require('../utils/compose');
5+
6+
AWS.config.update({
7+
accessKeyId: process.env.S3_TOKEN || 'asdf123',
8+
secretAccessKey: process.env.S3_SECRET || 'asdf123',
9+
apiVersions: {
10+
s3: 'latest',
11+
},
12+
});
13+
const s3 = new AWS.S3();
14+
15+
export default async () => {
16+
const backupListResult = await compose(
17+
`2016-07/deployments/${COMPOSE_DEPLOYMENT_ID}/backups`
18+
);
19+
20+
const backupListJson = await backupListResult.json();
21+
22+
if (!backupListJson._embedded || !backupListJson._embedded.backups) {
23+
console.error(
24+
`Failed to load list of backups of deployment ${COMPOSE_DEPLOYMENT_ID}.`
25+
);
26+
return;
27+
}
28+
29+
const newestBackup = backupListJson._embedded.backups
30+
.filter(backup => backup.is_downloadable)
31+
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0];
32+
33+
if (!newestBackup) {
34+
console.error('Failed to find latest backup.');
35+
return;
36+
}
37+
38+
const backupResult = await compose(
39+
`2016-07/deployments/${COMPOSE_DEPLOYMENT_ID}/backups/${newestBackup.id}`
40+
);
41+
const backupJson = await backupResult.json();
42+
43+
await new Promise((resolve, reject) => {
44+
https.get(backupJson.download_link, response => {
45+
s3.upload(
46+
{
47+
Body: response,
48+
Bucket: `spectrum-chat/backups`,
49+
Key: backupJson.name,
50+
},
51+
function(err) {
52+
if (err) {
53+
console.error(err);
54+
return reject(err);
55+
}
56+
return resolve();
57+
}
58+
);
59+
});
60+
});
61+
};

chronos/utils/compose.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @flow
2+
import fetch from 'node-fetch';
3+
4+
const COMPOSE_API_TOKEN = process.env.COMPOSE_API_TOKEN;
5+
6+
export const COMPOSE_DEPLOYMENT_ID = '5cd38bcf9c5cab000b617356';
7+
8+
export const compose = (path: string, fetchOptions?: Object = {}) => {
9+
if (!COMPOSE_API_TOKEN) {
10+
throw new Error('Please specify the COMPOSE_API_TOKEN env var.');
11+
}
12+
return fetch(`https://api.compose.io/${path}`, {
13+
...fetchOptions,
14+
headers: {
15+
'Content-Type': 'application/json',
16+
...(fetchOptions.headers || {}),
17+
Authorization: `Bearer ${COMPOSE_API_TOKEN}`,
18+
},
19+
});
20+
};

shared/bull/queues.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import {
7171
PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT,
7272
PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS,
7373
PROCESS_DATABASE_BACKUP,
74+
PROCESS_OFFSITE_BACKUP,
7475
} from 'chronos/queues/constants';
7576

7677
// Normalize our (inconsistent) queue names to a set of JS compatible names
@@ -143,6 +144,7 @@ exports.QUEUE_NAMES = {
143144
activeCommunityReportQueue: PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT,
144145
removeSeenUsersNotificationsQueue: PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS,
145146
databaseBackupQueue: PROCESS_DATABASE_BACKUP,
147+
offsiteBackupQueue: PROCESS_OFFSITE_BACKUP,
146148
};
147149

148150
// We add one error listener per queue, so we have to set the max listeners

shared/bull/types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,4 +523,5 @@ export type Queues = {
523523
activeCommunityReportQueue: BullQueue<void>,
524524
removeSeenUsersNotificationsQueue: BullQueue<void>,
525525
databaseBackupQueue: BullQueue<void>,
526+
offsiteBackupQueue: BullQueue<void>,
526527
};

0 commit comments

Comments
 (0)