budibase complete deployment

This commit is contained in:
Martin McKeaveney 2020-07-07 17:47:18 +01:00
parent f7d65deb5e
commit 27975057c7
9 changed files with 48 additions and 96 deletions

View File

@ -32,36 +32,4 @@ jobs:
- run: yarn test - run: yarn test
env: env:
CI: true CI: true
name: Budibase CI name: Budibase CI
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-ecr-repo
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: my-container
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: my-service
cluster: my-cluster
wait-for-service-stability: true

View File

@ -27,8 +27,7 @@
animation: false, animation: false,
}) })
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false)) $: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false))
$: noChildrenAllowed = false $: noChildrenAllowed = !component ||
!component ||
getComponentDefinition($store, component._component).children === false getComponentDefinition($store, component._component).children === false
$: noPaste = !$store.componentToPaste $: noPaste = !$store.componentToPaste

View File

@ -23,7 +23,6 @@ export const loadBudibase = async opts => {
componentLibraryModules[library] = await import( componentLibraryModules[library] = await import(
`/componentlibrary?library=${encodeURI(library)}` `/componentlibrary?library=${encodeURI(library)}`
) )
// componentLibraryModules[library] = await import(`/assets/componentlibrary/${library}/dist/index.js`)
} }
componentLibraryModules[builtinLibName] = builtins(_window) componentLibraryModules[builtinLibName] = builtins(_window)

View File

@ -14,6 +14,4 @@ PORT=4001
# error level for koa-pino # error level for koa-pino
LOG_LEVEL=error LOG_LEVEL=error
DEPLOYMENT_CF_DISTRIBUTION_ID=
DEPLOYMENT_APP_ASSETS_BUCKET=g
DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/" DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/"

View File

@ -1,17 +0,0 @@
# url of couch db, including username and password
# http://admin:password@localhost:5984
COUCH_DB_URL=
# identifies a client database - i.e. group of apps
CLIENT_ID=1
# used to create cookie hashes
JWT_SECRET=4715888e-144f-4802-b8d8-862a1b8365dd
# port to run http server on
PORT=4001
# error level for koa-pino
LOG_LEVEL=error
COUCH_DB_REMOTE=https://admin:afasdgafgF342G@couchdb.budi.live:5984
BUDIBASE_APP_ASSETS_BUCKET=prod-budi-app-assets
BUDIBASE_API_KEY=d498278c-4ab4-144b-c212-b8f9e6da5c2b

View File

@ -5,11 +5,11 @@ const {
budibaseAppsDir, budibaseAppsDir,
} = require("../../../utilities/budibaseDir") } = require("../../../utilities/budibaseDir")
async function invalidateCDN(appId) { async function invalidateCDN(cfDistribution, appId) {
const cf = new AWS.CloudFront({}) const cf = new AWS.CloudFront({})
return cf.createInvalidation({ return cf.createInvalidation({
DistributionId: process.env.DEPLOYMENT_CF_DISTRIBUTION_ID, DistributionId: cfDistribution,
InvalidationBatch: { InvalidationBatch: {
CallerReference: appId, CallerReference: appId,
Paths: { Paths: {
@ -47,8 +47,8 @@ const CONTENT_TYPE_MAP = {
/** /**
* Recursively walk a directory tree and execute a callback on all files. * Recursively walk a directory tree and execute a callback on all files.
* @param {Re} dirPath - Directory to traverse * @param {String} dirPath - Directory to traverse
* @param {*} callback - callback to execute on files * @param {Function} callback - callback to execute on files
*/ */
function walkDir(dirPath, callback) { function walkDir(dirPath, callback) {
for (let filename of fs.readdirSync(dirPath)) { for (let filename of fs.readdirSync(dirPath)) {
@ -56,10 +56,7 @@ function walkDir(dirPath, callback) {
const stat = fs.lstatSync(filePath) const stat = fs.lstatSync(filePath)
if (stat.isFile()) { if (stat.isFile()) {
callback({ callback(filePath)
bytes: fs.readFileSync(filePath),
filename
})
} else { } else {
walkDir(filePath, callback) walkDir(filePath, callback)
} }
@ -67,7 +64,12 @@ function walkDir(dirPath, callback) {
} }
exports.uploadAppAssets = async function ({ appId }) { exports.uploadAppAssets = async function ({ appId }) {
const { credentials, accountId } = await fetchTemporaryCredentials() const {
credentials,
accountId,
bucket,
cfDistribution,
} = await fetchTemporaryCredentials()
AWS.config.update({ AWS.config.update({
accessKeyId: credentials.AccessKeyId, accessKeyId: credentials.AccessKeyId,
@ -77,7 +79,7 @@ exports.uploadAppAssets = async function ({ appId }) {
const s3 = new AWS.S3({ const s3 = new AWS.S3({
params: { params: {
Bucket: process.env.DEPLOYMENT_APP_ASSETS_BUCKET Bucket: bucket
} }
}) })
@ -88,12 +90,13 @@ exports.uploadAppAssets = async function ({ appId }) {
const uploads = [] const uploads = []
for (let page of appPages) { for (let page of appPages) {
walkDir(`${appAssetsPath}/${page}`, function prepareUploadsForS3({ bytes, filename }) { walkDir(`${appAssetsPath}/${page}`, function prepareUploadsForS3(filePath) {
const fileExtension = [...filename.split(".")].pop() const fileExtension = [...filePath.split(".")].pop()
const fileBytes = fs.readFileSync(filePath)
const upload = s3.upload({ const upload = s3.upload({
Key: `assets/${appId}/${page}/${filename}`, Key: filePath.replace(appAssetsPath, `assets/${appId}`),
Body: bytes, Body: fileBytes,
ContentType: CONTENT_TYPE_MAP[fileExtension], ContentType: CONTENT_TYPE_MAP[fileExtension],
Metadata: { Metadata: {
accountId accountId
@ -105,10 +108,8 @@ exports.uploadAppAssets = async function ({ appId }) {
} }
try { try {
const uploadAllFiles = Promise.all(uploads) await Promise.all(uploads)
const invalidateCloudfront = invalidateCDN(appId) await invalidateCDN(cfDistribution, appId)
await uploadAllFiles
await invalidateCloudfront
} catch (err) { } catch (err) {
console.error("Error uploading budibase app assets to s3", err) console.error("Error uploading budibase app assets to s3", err)
throw err throw err

View File

@ -7,7 +7,7 @@ const {
function replicate(local, remote) { function replicate(local, remote) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const replication = local.replicate.to(remote); const replication = local.sync(remote);
replication.on("complete", () => resolve()) replication.on("complete", () => resolve())
replication.on("error", err => reject(err)) replication.on("error", err => reject(err))
@ -15,24 +15,24 @@ function replicate(local, remote) {
} }
async function replicateCouch(instanceId, clientId) { async function replicateCouch(instanceId, clientId) {
const clientDb = new PouchDB(`client_${clientId}`)
const remoteClientDb = new CouchDB(`${process.env.COUCH_DB_REMOTE}/client_${clientId}`)
const clientAppLookupDb = new PouchDB("client_app_lookup") const databases = [
const remoteClientAppLookupDb = new CouchDB(`${process.env.COUCH_DB_REMOTE}/client_app_lookup`) `client_${clientId}`,
"client_app_lookup",
instanceId
];
const localDb = new PouchDB(instanceId) const replications = databases.map(local => {
const remoteDb = new CouchDB(`${process.env.COUCH_DB_REMOTE}/${instanceId}`) const localDb = new PouchDB(local);
const remoteDb = new CouchDB(`${process.env.DEPLOYMENT_COUCH_DB_URL}/${local}`)
await Promise.all([ return replicate(localDb, remoteDb);
replicate(clientDb, remoteClientDb), });
replicate(clientAppLookupDb, remoteClientAppLookupDb),
replicate(localDb, remoteDb) await Promise.all(replications)
])
} }
exports.deployApp = async function(ctx) { exports.deployApp = async function(ctx) {
// TODO: This should probably be async - it could take a while
try { try {
const clientAppLookupDB = new PouchDB("client_app_lookup") const clientAppLookupDB = new PouchDB("client_app_lookup")
const { clientId } = await clientAppLookupDB.get(ctx.user.appId) const { clientId } = await clientAppLookupDB.get(ctx.user.appId)
@ -51,8 +51,7 @@ exports.deployApp = async function(ctx) {
status: "SUCCESS", status: "SUCCESS",
completed: Date.now() completed: Date.now()
} }
} catch (err) {
} catch (err) { ctx.throw(err.status || 500, `Deployment Failed: ${err.message}`);
ctx.throw(err.status || 500, `Deployment Failed: ${err.message}`); }
}
} }

View File

@ -8,7 +8,6 @@ const setBuilderToken = require("../../utilities/builder/setBuilderToken")
const { ANON_LEVEL_ID } = require("../../utilities/accessLevels") const { ANON_LEVEL_ID } = require("../../utilities/accessLevels")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const fetch = require("node-fetch") const fetch = require("node-fetch")
const { S3 } = require("aws-sdk")
exports.serveBuilder = async function(ctx) { exports.serveBuilder = async function(ctx) {
let builderPath = resolve(__dirname, "../../../builder") let builderPath = resolve(__dirname, "../../../builder")
@ -28,14 +27,20 @@ exports.serveApp = async function(ctx) {
"public", "public",
mainOrAuth mainOrAuth
) )
let appId = ctx.params.appId
if (process.env.CLOUD) {
appId = ctx.subdomains[1]
}
// only set the appId cookie for /appId .. we COULD check for valid appIds // only set the appId cookie for /appId .. we COULD check for valid appIds
// but would like to avoid that DB hit // but would like to avoid that DB hit
const looksLikeAppId = /^[0-9a-f]{32}$/.test(ctx.params.appId) const looksLikeAppId = /^[0-9a-f]{32}$/.test(appId)
if (looksLikeAppId && !ctx.isAuthenticated) { if (looksLikeAppId && !ctx.isAuthenticated) {
const anonUser = { const anonUser = {
userId: "ANON", userId: "ANON",
accessLevelId: ANON_LEVEL_ID, accessLevelId: ANON_LEVEL_ID,
appId: ctx.params.appId, appId,
} }
const anonToken = jwt.sign(anonUser, ctx.config.jwtSecret) const anonToken = jwt.sign(anonUser, ctx.config.jwtSecret)
ctx.cookies.set("budibase:token", anonToken, { ctx.cookies.set("budibase:token", anonToken, {
@ -45,7 +50,7 @@ exports.serveApp = async function(ctx) {
} }
if (process.env.CLOUD) { if (process.env.CLOUD) {
const S3_URL = `https://${ctx.params.appId}.app.budi.live/assets/${ctx.params.appId}/${mainOrAuth}/${ctx.file || "index.production.html"}` const S3_URL = `https://${appId}.app.budi.live/assets/${appId}/${mainOrAuth}/${ctx.file || "index.production.html"}`
const response = await fetch(S3_URL) const response = await fetch(S3_URL)
const body = await response.text() const body = await response.text()
ctx.body = body ctx.body = body

View File

@ -16,7 +16,7 @@ app.use(
prettyPrint: { prettyPrint: {
levelFirst: true, levelFirst: true,
}, },
level: "info" || "info", level: env.LOG_LEVEL || "error",
}) })
) )
@ -29,4 +29,4 @@ module.exports = async port => {
const serverPort = port || env.PORT const serverPort = port || env.PORT
const server = http.createServer(app.callback()) const server = http.createServer(app.callback())
return server.listen(serverPort || 4001) return server.listen(serverPort || 4001)
} }