Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/allow backup cronjobs to be run by a k8s cronjob ams #310

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
680d99d
add github workflows with gitops flow
stijnvandervegt Jan 24, 2021
b076584
Update .github/workflows/gitops.yml
stijnvandervegt Jan 25, 2021
8e62242
Update .github/workflows/gitops.yml
stijnvandervegt Jan 25, 2021
56e48d6
Update .github/workflows/gitops.yml
stijnvandervegt Jan 25, 2021
1d8fba7
Run gitops actions on release-* branches
rudivanhierden May 12, 2021
d382164
Merge branch 'feature/gitops-workflow' of github.com:draadnu/openstad…
stijnvandervegt Jul 23, 2021
5c94461
Add formId to submissions
rudivanhierden Oct 8, 2021
a4fad31
Make submissions listable and viewable
rudivanhierden Oct 8, 2021
e045f06
Update changelog
rudivanhierden Oct 8, 2021
19f8909
Refactor duplicate code for the sequelize filters to utility method
rudivanhierden Oct 11, 2021
c9f3e08
Refactor to reduce code complexity
rudivanhierden Oct 11, 2021
c02c07e
Merge pull request #27 from draadnl/feature/make-submissions-listable…
stijnvandervegt Oct 20, 2021
d1d46c1
Merge remote-tracking branch 'draad/master' into feature/submission-e…
stijnvandervegt Oct 20, 2021
c3abedc
Merge pull request #28 from draadnl/feature/submission-events-draad
stijnvandervegt Oct 20, 2021
b4c3972
Merge pull request #21 from draadnl/feature/gitops-workflow
stijnvandervegt Oct 20, 2021
8510702
Merge remote-tracking branch 'origin/master'
stijnvandervegt Nov 19, 2021
ee8e88d
Merge remote-tracking branch 'amsterdam/master'
rudivanhierden Jan 27, 2022
6f2e172
Use multipart upload & chunking for mongodb s3 backups
rudivanhierden Jan 26, 2022
7c195c9
Refactor MongoDB S3 backup
rudivanhierden Jan 26, 2022
1e4dd83
Update changelog
rudivanhierden Jan 27, 2022
a8739d7
Add backup.js to run MySQL / MongoDB S3 backup
rudivanhierden Jan 27, 2022
5420817
Refactor to reduce complexity & duplication
rudivanhierden Jan 27, 2022
7e7133e
Merge branch 'feature/mongodb-s3-backup-memory-fix' into feature/allo…
rudivanhierden Jan 27, 2022
c54f22e
Fix conflicts, remove workflow file
LorenzoJokhan Jul 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Update version number
* Do not wait for cms when updating site config
* Move docker builds from travis to github actions

## UNRELEASED
* Change mongoDB S3 backup to read the database dump in chunks and upload it to the S3 bucket using a MultiPart upload setup.
* Allow s3 backups to be disabled from the cronjobs, but instead be run through a different entrypoint (backup.js) to allow kubernetes cronjobs to be used.

Comment on lines +29 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you push this to the top of the file?

## v0.20.0 (2021-12-20)
* Fix responding to not existing routes with to much information
* Make submissions listable & viewable, and allow them to be filtered by formId
Expand Down
27 changes: 27 additions & 0 deletions backup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const backupMapping = {
'mongo': 'mongodb_s3_backups',
'mysql': 'mysql_s3_backups'
};

if (!!process.env.BACKUP_TYPE === false) {
console.error ('No backup type given in ENV variables');
process.exit(1);
}

if (Object.keys(backupMapping).indexOf(process.env.BACKUP_TYPE) === -1) {
console.error ('Backup type not supported');
process.exit(1);
}

const backup = require(`./src/cron/${backupMapping[process.env.BACKUP_TYPE]}`);

async function run() {
try {
console.log(`${backupMapping[process.env.BACKUP_TYPE]}`);
await backup.onTick();
} catch (err) {
console.error('Backup went wrong', err);
}
}

run();
7,863 changes: 4,446 additions & 3,417 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/cron/mongodb_backups.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ module.exports = {
cronTime: '0 0 1 * * *',
runOnInit: true,
onTick: function() {
if (!!process.env.PREVENT_BACKUP_CRONJOBS === true || process.env.S3_MONGO_BACKUPS === 'ON') {
return;
}

const objectStoreUrl = process.env.OBJECT_STORE_URL;
const objectStoreUser = process.env.OBJECT_STORE_USER;
const objectStorePass = process.env.OBJECT_STORE_PASS;
Expand Down
124 changes: 54 additions & 70 deletions src/cron/mongodb_s3_backups.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,60 @@
const AWS = require('aws-sdk');
const fs = require('fs'); // Needed for example below
const fs = require('fs');
const moment = require('moment')
const os = require('os');
//const BACKUP_PATH = (ZIP_NAME) => path.resolve(os.tmpdir(), ZIP_NAME);
const { exec } = require('child_process');
const log = require('debug')('app:cron');
const db = require('../db');

// Purpose
// -------
// Auto-close ideas that passed the deadline.
// accessKeyId: process.env.S3_KEY,
secretAccessKey: process.env.S3_SECRET
// Runs every night at 1:00.
//
function currentTime(timezoneOffset) {
if (timezoneOffset) {
return moment(moment(moment.now()).utcOffset(timezoneOffset, true).toDate()).format("YYYY-MM-DDTHH-mm-ss");
} else {
return moment
.utc()
.format('YYYY-MM-DDTHH-mm-ss');
}
}
const {exec} = require('child_process');
const s3 = require('../services/awsS3');
const util = require('util');

const backupMongoDBToS3 = async () => {
if (process.env.S3_MONGO_BACKUPS === 'ON') {
const host = process.env.MONGO_DB_HOST || 'localhost';
const port = process.env.MONGO_DB_PORT || 27017;
const tmpDbFile = 'db_mongo'

// let DB_BACKUP_NAME = `mongodb_${currentTime()}.gz`;

// Default command, does not considers username or password
let command = `mongodump -h ${host} --port=${port} --archive=${tmpDbFile}`;

// When Username and password is provided
// /
//if (username && password) {
// command = `mongodump -h ${host} --port=${port} -d ${database} -p ${password} -u ${username} --quiet --gzip --archive=${BACKUP_PATH(DB_BACKUP_NAME)}`;
//}

exec(command, (err, stdout, stderr) => {
if (err) {
// Most likely, mongodump isn't installed or isn't accessible
console.log('errere', err);
} else {
const spacesEndpoint = new AWS.Endpoint(process.env.S3_ENDPOINT);

const created = moment().format('YYYY-MM-DD hh:mm:ss')
const fileContent = fs.readFileSync(tmpDbFile);

const s3 = new AWS.S3({
endpoint: spacesEndpoint,
accessKeyId: process.env.S3_KEY,
secretAccessKey: process.env.S3_SECRET
});

var params = {
Bucket: process.env.S3_BUCKET,
Key: "mongodb/mongo_" + created,
Body: fileContent,
ACL: "private"
};
if (!!process.env.PREVENT_BACKUP_CRONJOBS === true) {
return;
}

console.log('backing up to mongodb', process.env.S3_MONGO_BACKUPS);

if (process.env.S3_MONGO_BACKUPS === 'ON') {
const host = process.env.MONGO_DB_HOST || 'localhost';
const port = process.env.MONGO_DB_PORT || 27017;
const tempFile = 'db_mongo';
const isOnK8s = !!process.env.KUBERNETES_NAMESPACE;
const namespace = process.env.KUBERNETES_NAMESPACE;
const created = moment().format('YYYY-MM-DD hh:mm:ss')
const fileNameInS3 = isOnK8s ? `mongodb/${namespace}/mongo_${created}` : `mongodb/mongo_${created}`;
const deleteTempFile = () => {
try {
console.log ('removing temp file', tempFile);
fs.unlinkSync(tempFile);
} catch (e) {
console.error('error removing file', tempFile, e);
}
};
// Default command, does not considers username or password
let command = `mongodump -h ${host} --port=${port} --archive=${tempFile}`;
const promiseExec = util.promisify(exec);

return promiseExec(command, async (err, stdout, stderr) => {
if (err) {
// Most likely, mongodump isn't installed or isn't accessible
console.error(`mongodump command error: ${err}`);
deleteTempFile();
return;
}

const statsFile = fs.statSync(tempFile);
console.info(`file size: ${Math.round(statsFile.size / 1024 / 1024)}MB`);

s3.putObject(params, function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
});
}
});
}
try {
await s3.uploadFile(tempFile, fileNameInS3);
deleteTempFile();
console.log('successfully uploaded to s3');
} catch (e) {
deleteTempFile();
throw e;
}

});
}
}


Expand All @@ -89,6 +73,6 @@ module.exports = {
cronTime: '0 0 1 * * *',
runOnInit: true,
onTick: async function() {
backupMongoDBToS3();
return backupMongoDBToS3();
}
};
5 changes: 5 additions & 0 deletions src/cron/mysql_backups.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ module.exports = {
cronTime: '0 0 1 * * *',
runOnInit: true,
onTick: function() {
// Do not run this cronjob if we have PREVENT_BACKUP_CRONJOBS set to true, or if the S3_DBS_TO_BACKUP is not empty (which means we want to backup to S3).
if (!!process.env.PREVENT_BACKUP_CRONJOBS === true || !!process.env.S3_DBS_TO_BACKUP === false) {
return;
}

const mysqlRootPw = process.env.MYSQL_ROOT_PASS;
const objectStoreUrl = process.env.OBJECT_STORE_URL;
const objectStoreUser = process.env.OBJECT_STORE_USER;
Expand Down
29 changes: 14 additions & 15 deletions src/cron/mysql_s3_backups.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
const AWS = require('aws-sdk');
const fs = require('fs'); // Needed for example below
const mysqldump = require('mysqldump');
const moment = require('moment')
const log = require('debug')('app:cron');
const db = require('../db');
const s3 = require('../services/awsS3');

// Purpose
// -------
// Auto-close ideas that passed the deadline.
//
// Runs every night at 1:00.
const backupMysqlToS3 = async () => {
if (!!process.env.PREVENT_BACKUP_CRONJOBS === true) {
return;
}

const dbsToBackup = process.env.S3_DBS_TO_BACKUP ? process.env.S3_DBS_TO_BACKUP.split(',') : false;

const isOnK8s = !!process.env.KUBERNETES_NAMESPACE;
const namespace = process.env.KUBERNETES_NAMESPACE;

if (dbsToBackup) {
dbsToBackup.forEach(async function(dbName) {
// return the dump from the function and not to a file
Expand All @@ -32,24 +35,20 @@ const backupMysqlToS3 = async () => {
}
});

const spacesEndpoint = new AWS.Endpoint(process.env.S3_ENDPOINT);

const created = moment().format('YYYY-MM-DD hh:mm:ss')

const s3 = new AWS.S3({
endpoint: spacesEndpoint,
accessKeyId: process.env.S3_KEY,
secretAccessKey: process.env.S3_SECRET
});
const key = isOnK8s ? `mysql/${namespace}/${dbName}_${created}sql` : `mysql/${dbName}_${created}sql`;

var params = {
Bucket: process.env.S3_BUCKET,
Key: 'mysql/' + dbName + '_' + created + ".sql",
Key: key,
Body: result.dump.data,
ACL: "private"
};

const client = s3.getClient();

s3.putObject(params, function(err, data) {
client.putObject(params, function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
});
Expand All @@ -76,6 +75,6 @@ module.exports = {
cronTime: '0 0 1 * * *',
runOnInit: false,
onTick: async function() {
backupMysqlToS3();
return backupMysqlToS3();
}
};
46 changes: 25 additions & 21 deletions src/models/Submission.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this file don't belong in this PR

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = function (db, sequelize, DataTypes) {
type: DataTypes.INTEGER,
allowNull: true
},

formName: {
type: DataTypes.STRING,
allowNull: true
Expand Down Expand Up @@ -78,28 +79,31 @@ module.exports = function (db, sequelize, DataTypes) {
attributes : ['role', 'displayName', 'nickName', 'firstName', 'lastName', 'email']
}]
},
forSiteId: function (siteId) {
return {
where: {
siteId: siteId,
}
};
},
filter: function (filtersInclude, filtersExclude) {
const filterKeys = [
{
'key': 'id'
},
{
'key': 'status'
},
{
'key': 'formId'
},
];
forSiteId: function (siteId) {
return {
where: {
siteId: siteId,
}
};
},
filter: function (filtersInclude, filtersExclude) {
const filterKeys = [
{
'key': 'id'
},
{
'key': 'status'
},
{
'key': 'formName'
},
{
'key': 'formId'
},
];

return getSequelizeConditionsForFilters(filterKeys, filtersInclude, sequelize, filtersExclude);
},
return getSequelizeConditionsForFilters(filterKeys, filtersInclude, sequelize, filtersExclude);
},
};
}

Expand Down
Loading
Loading