2020-07-01 22:56:53 +02:00
|
|
|
const fs = require("fs")
|
|
|
|
const AWS = require("aws-sdk")
|
|
|
|
const fetch = require("node-fetch")
|
2020-07-07 22:29:20 +02:00
|
|
|
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
2020-09-16 13:18:47 +02:00
|
|
|
const PouchDB = require("../../../db")
|
2020-07-01 22:56:53 +02:00
|
|
|
|
2020-07-07 18:47:18 +02:00
|
|
|
async function invalidateCDN(cfDistribution, appId) {
|
2020-07-06 20:43:40 +02:00
|
|
|
const cf = new AWS.CloudFront({})
|
|
|
|
|
2020-07-07 22:29:20 +02:00
|
|
|
return cf
|
|
|
|
.createInvalidation({
|
|
|
|
DistributionId: cfDistribution,
|
|
|
|
InvalidationBatch: {
|
|
|
|
CallerReference: appId,
|
|
|
|
Paths: {
|
|
|
|
Quantity: 1,
|
|
|
|
Items: [`/assets/${appId}/*`],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.promise()
|
2020-07-06 20:43:40 +02:00
|
|
|
}
|
2020-07-03 00:22:20 +02:00
|
|
|
|
2020-07-08 17:31:26 +02:00
|
|
|
exports.fetchTemporaryCredentials = async function() {
|
2020-07-06 20:43:40 +02:00
|
|
|
const response = await fetch(process.env.DEPLOYMENT_CREDENTIALS_URL, {
|
2020-07-01 22:56:53 +02:00
|
|
|
method: "POST",
|
|
|
|
body: JSON.stringify({
|
2020-07-07 22:29:20 +02:00
|
|
|
apiKey: process.env.BUDIBASE_API_KEY,
|
|
|
|
}),
|
2020-07-01 22:56:53 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
if (response.status !== 200) {
|
2020-07-07 22:29:20 +02:00
|
|
|
throw new Error(
|
|
|
|
`Error fetching temporary credentials for api key: ${process.env.BUDIBASE_API_KEY}`
|
|
|
|
)
|
2020-07-01 22:56:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const json = await response.json()
|
|
|
|
|
|
|
|
return json
|
2020-07-07 22:29:20 +02:00
|
|
|
}
|
2020-07-01 22:56:53 +02:00
|
|
|
|
2020-07-03 00:22:20 +02:00
|
|
|
const CONTENT_TYPE_MAP = {
|
|
|
|
html: "text/html",
|
|
|
|
css: "text/css",
|
2020-07-07 22:29:20 +02:00
|
|
|
js: "application/javascript",
|
|
|
|
}
|
2020-07-03 00:22:20 +02:00
|
|
|
|
2020-07-06 20:43:40 +02:00
|
|
|
/**
|
|
|
|
* Recursively walk a directory tree and execute a callback on all files.
|
2020-07-07 18:47:18 +02:00
|
|
|
* @param {String} dirPath - Directory to traverse
|
|
|
|
* @param {Function} callback - callback to execute on files
|
2020-07-06 20:43:40 +02:00
|
|
|
*/
|
|
|
|
function walkDir(dirPath, callback) {
|
|
|
|
for (let filename of fs.readdirSync(dirPath)) {
|
2020-07-07 22:29:20 +02:00
|
|
|
const filePath = `${dirPath}/${filename}`
|
2020-07-06 20:43:40 +02:00
|
|
|
const stat = fs.lstatSync(filePath)
|
2020-07-07 22:29:20 +02:00
|
|
|
|
2020-07-06 20:43:40 +02:00
|
|
|
if (stat.isFile()) {
|
2020-07-07 18:47:18 +02:00
|
|
|
callback(filePath)
|
2020-07-06 20:43:40 +02:00
|
|
|
} else {
|
|
|
|
walkDir(filePath, callback)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 17:22:13 +02:00
|
|
|
|
2020-09-17 13:45:28 +02:00
|
|
|
function prepareUploadForS3({ filePath, s3Key, metadata, s3 }) {
|
2020-09-16 13:18:47 +02:00
|
|
|
const fileExtension = [...filePath.split(".")].pop()
|
|
|
|
const fileBytes = fs.readFileSync(filePath)
|
|
|
|
return s3
|
|
|
|
.upload({
|
|
|
|
Key: s3Key,
|
|
|
|
Body: fileBytes,
|
|
|
|
ContentType: CONTENT_TYPE_MAP[fileExtension],
|
|
|
|
Metadata: metadata,
|
|
|
|
})
|
|
|
|
.promise()
|
|
|
|
}
|
|
|
|
|
2020-07-08 17:31:26 +02:00
|
|
|
exports.uploadAppAssets = async function({
|
|
|
|
appId,
|
2020-09-16 13:18:47 +02:00
|
|
|
instanceId,
|
2020-07-08 17:31:26 +02:00
|
|
|
credentials,
|
|
|
|
bucket,
|
|
|
|
cfDistribution,
|
|
|
|
accountId,
|
|
|
|
}) {
|
2020-07-01 22:56:53 +02:00
|
|
|
AWS.config.update({
|
2020-07-07 22:29:20 +02:00
|
|
|
accessKeyId: credentials.AccessKeyId,
|
2020-07-01 22:56:53 +02:00
|
|
|
secretAccessKey: credentials.SecretAccessKey,
|
2020-07-07 22:29:20 +02:00
|
|
|
sessionToken: credentials.SessionToken,
|
|
|
|
})
|
2020-07-01 22:56:53 +02:00
|
|
|
|
|
|
|
const s3 = new AWS.S3({
|
|
|
|
params: {
|
2020-07-07 22:29:20 +02:00
|
|
|
Bucket: bucket,
|
|
|
|
},
|
2020-07-01 22:56:53 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const appAssetsPath = `${budibaseAppsDir()}/${appId}/public`
|
|
|
|
|
|
|
|
const appPages = fs.readdirSync(appAssetsPath)
|
|
|
|
|
2020-09-15 17:22:13 +02:00
|
|
|
let uploads = []
|
2020-07-01 22:56:53 +02:00
|
|
|
|
|
|
|
for (let page of appPages) {
|
2020-09-15 17:22:13 +02:00
|
|
|
// Upload HTML, CSS and JS for each page of the web app
|
2020-09-17 13:45:28 +02:00
|
|
|
walkDir(`${appAssetsPath}/${page}`, function(filePath) {
|
2020-09-16 13:18:47 +02:00
|
|
|
const appAssetUpload = prepareUploadForS3({
|
|
|
|
filePath,
|
2020-09-17 13:45:28 +02:00
|
|
|
s3Key: filePath.replace(appAssetsPath, `assets/${appId}`),
|
2020-09-16 13:18:47 +02:00
|
|
|
s3,
|
|
|
|
metadata: { accountId }
|
|
|
|
})
|
|
|
|
uploads.push(appAssetUpload)
|
|
|
|
})
|
2020-07-01 22:56:53 +02:00
|
|
|
}
|
|
|
|
|
2020-09-15 17:22:13 +02:00
|
|
|
// Upload file attachments
|
2020-09-16 13:18:47 +02:00
|
|
|
const db = new PouchDB(instanceId)
|
|
|
|
const fileUploads = await db.get("_local/fileuploads")
|
|
|
|
if (fileUploads) {
|
|
|
|
fileUploads.awaitingUpload.forEach((file, idx) => {
|
|
|
|
|
|
|
|
const attachmentUpload = prepareUploadForS3({
|
|
|
|
filePath: file.path,
|
|
|
|
s3Key: `assets/${appId}/${file.name}`,
|
|
|
|
s3,
|
|
|
|
metadata: { accountId }
|
|
|
|
})
|
|
|
|
|
|
|
|
uploads.push(attachmentUpload)
|
2020-09-15 17:22:13 +02:00
|
|
|
|
2020-09-16 13:18:47 +02:00
|
|
|
// move the pending upload to the uploaded array
|
|
|
|
fileUploads.awaitingUpload.splice(idx, 1);
|
|
|
|
fileUploads.uploaded.push(awaitingUpload);
|
|
|
|
})
|
|
|
|
db.put(fileUploads);
|
|
|
|
}
|
2020-09-15 17:22:13 +02:00
|
|
|
|
2020-07-01 22:56:53 +02:00
|
|
|
try {
|
2020-07-07 18:47:18 +02:00
|
|
|
await Promise.all(uploads)
|
2020-09-15 17:22:13 +02:00
|
|
|
// TODO: update dynamoDB with a synopsis of the app deployment for historical purposes
|
2020-07-07 22:29:20 +02:00
|
|
|
await invalidateCDN(cfDistribution, appId)
|
2020-07-01 22:56:53 +02:00
|
|
|
} catch (err) {
|
|
|
|
console.error("Error uploading budibase app assets to s3", err)
|
|
|
|
throw err
|
|
|
|
}
|
2020-07-07 22:29:20 +02:00
|
|
|
}
|