mirror of https://github.com/Budibase/budibase.git
19 changed files with 1538 additions and 61 deletions
@ -0,0 +1 @@ |
|||||
|
../packages/deployment/ |
||||
@ -0,0 +1,2 @@ |
|||||
|
node_modules/ |
||||
|
.env |
||||
@ -0,0 +1,15 @@ |
|||||
|
FROM node:12-alpine |
||||
|
|
||||
|
WORKDIR /app |
||||
|
|
||||
|
# copy files and install dependencies |
||||
|
COPY . ./ |
||||
|
RUN yarn |
||||
|
|
||||
|
EXPOSE 4001 |
||||
|
|
||||
|
# have to add node environment production after install |
||||
|
# due to this causing yarn to stop installing dev dependencies |
||||
|
# which are actually needed to get this environment up and running |
||||
|
ENV NODE_ENV=production |
||||
|
CMD ["yarn", "run:docker"] |
||||
@ -0,0 +1,34 @@ |
|||||
|
{ |
||||
|
"name": "@budibase/deployment", |
||||
|
"email": "hi@budibase.com", |
||||
|
"version": "0.3.8", |
||||
|
"description": "Budibase Deployment Server", |
||||
|
"main": "src/index.js", |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "https://github.com/Budibase/budibase.git" |
||||
|
}, |
||||
|
"keywords": [ |
||||
|
"budibase" |
||||
|
], |
||||
|
"scripts": { |
||||
|
"run:docker": "node src/index.js" |
||||
|
}, |
||||
|
"author": "Budibase", |
||||
|
"license": "AGPL-3.0-or-later", |
||||
|
"dependencies": { |
||||
|
"@koa/router": "^8.0.0", |
||||
|
"aws-sdk": "^2.811.0", |
||||
|
"got": "^11.8.1", |
||||
|
"joi": "^17.2.1", |
||||
|
"koa": "^2.7.0", |
||||
|
"koa-body": "^4.2.0", |
||||
|
"koa-compress": "^4.0.1", |
||||
|
"koa-pino-logger": "^3.0.0", |
||||
|
"koa-send": "^5.0.0", |
||||
|
"koa-session": "^5.12.0", |
||||
|
"koa-static": "^5.0.0", |
||||
|
"pino-pretty": "^4.0.0", |
||||
|
"server-destroy": "^1.0.1" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
const env = require("../../environment") |
||||
|
const got = require("got") |
||||
|
const AWS = require("aws-sdk") |
||||
|
|
||||
|
const APP_BUCKET = "app-assets" |
||||
|
// this doesn't matter in self host
|
||||
|
const REGION = "eu-west-1" |
||||
|
|
||||
|
async function getCouchSession() { |
||||
|
// fetch session token for the api user
|
||||
|
const session = await got.post(`${env.RAW_COUCH_DB_URL}/_session`, { |
||||
|
responseType: "json", |
||||
|
json: { |
||||
|
username: env.COUCH_DB_USERNAME, |
||||
|
password: env.COUCH_DB_PASSWORD, |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const cookie = session.headers["set-cookie"][0] |
||||
|
// Get the session cookie value only
|
||||
|
return cookie.split(";")[0] |
||||
|
} |
||||
|
|
||||
|
async function getMinioSession() { |
||||
|
AWS.config.update({ |
||||
|
accessKeyId: env.MINIO_ACCESS_KEY, |
||||
|
secretAccessKey: env.MINIO_SECRET_KEY, |
||||
|
}) |
||||
|
|
||||
|
// make sure the bucket exists
|
||||
|
const objClient = new AWS.S3({ |
||||
|
endpoint: env.RAW_MINIO_URL, |
||||
|
region: REGION, |
||||
|
s3ForcePathStyle: true, // needed with minio?
|
||||
|
params: { |
||||
|
Bucket: APP_BUCKET, |
||||
|
}, |
||||
|
}) |
||||
|
// make sure the bucket exists
|
||||
|
try { |
||||
|
await objClient.headBucket({ Bucket: APP_BUCKET }).promise() |
||||
|
} catch (err) { |
||||
|
// bucket doesn't exist create it
|
||||
|
if (err.statusCode === 404) { |
||||
|
await objClient.createBucket({ Bucket: APP_BUCKET }).promise() |
||||
|
} else { |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
// TODO: this doesn't seem to work get an error
|
||||
|
// TODO: Generating temporary credentials not allowed for this request.
|
||||
|
// TODO: this should work based on minio documentation
|
||||
|
// const sts = new AWS.STS({
|
||||
|
// endpoint: env.RAW_MINIO_URL,
|
||||
|
// region: REGION,
|
||||
|
// s3ForcePathStyle: true,
|
||||
|
// })
|
||||
|
// // NOTE: In the following commands RoleArn and RoleSessionName are not meaningful for MinIO
|
||||
|
// const params = {
|
||||
|
// DurationSeconds: 3600,
|
||||
|
// ExternalId: "123ABC",
|
||||
|
// Policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}',
|
||||
|
// RoleArn: 'arn:xxx:xxx:xxx:xxxx',
|
||||
|
// RoleSessionName: 'anything',
|
||||
|
// };
|
||||
|
// const assumedRole = await sts.assumeRole(params).promise();
|
||||
|
// if (!assumedRole) {
|
||||
|
// throw "Unable to get access to object store."
|
||||
|
// }
|
||||
|
// return assumedRole.Credentials
|
||||
|
// TODO: need to do something better than this
|
||||
|
return { |
||||
|
accessKeyId: env.MINIO_ACCESS_KEY, |
||||
|
secretAccessKey: env.MINIO_SECRET_KEY, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
exports.deploy = async ctx => { |
||||
|
ctx.body = { |
||||
|
couchDbSession: await getCouchSession(), |
||||
|
bucket: APP_BUCKET, |
||||
|
objectStoreSession: await getMinioSession(), |
||||
|
couchDbUrl: env.RAW_COUCH_DB_URL, |
||||
|
objectStoreUrl: env.RAW_MINIO_URL, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
const Router = require("@koa/router") |
||||
|
const compress = require("koa-compress") |
||||
|
const zlib = require("zlib") |
||||
|
const { routes } = require("./routes") |
||||
|
|
||||
|
const router = new Router() |
||||
|
|
||||
|
router |
||||
|
.use( |
||||
|
compress({ |
||||
|
threshold: 2048, |
||||
|
gzip: { |
||||
|
flush: zlib.Z_SYNC_FLUSH, |
||||
|
}, |
||||
|
deflate: { |
||||
|
flush: zlib.Z_SYNC_FLUSH, |
||||
|
}, |
||||
|
br: false, |
||||
|
}) |
||||
|
) |
||||
|
.use("/health", ctx => (ctx.status = 200)) |
||||
|
|
||||
|
// error handling middleware
|
||||
|
router.use(async (ctx, next) => { |
||||
|
try { |
||||
|
await next() |
||||
|
} catch (err) { |
||||
|
ctx.log.error(err) |
||||
|
ctx.status = err.status || err.statusCode || 500 |
||||
|
ctx.body = { |
||||
|
message: err.message, |
||||
|
status: ctx.status, |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
router.get("/health", ctx => (ctx.status = 200)) |
||||
|
|
||||
|
// authenticated routes
|
||||
|
for (let route of routes) { |
||||
|
router.use(route.routes()) |
||||
|
router.use(route.allowedMethods()) |
||||
|
} |
||||
|
|
||||
|
module.exports = router |
||||
@ -0,0 +1,10 @@ |
|||||
|
const Router = require("@koa/router") |
||||
|
const controller = require("../controllers/deploy") |
||||
|
const checkKey = require("../../middleware/check-key") |
||||
|
|
||||
|
const router = Router() |
||||
|
|
||||
|
router |
||||
|
.post("/api/deploy", checkKey, controller.deploy) |
||||
|
|
||||
|
module.exports = router |
||||
@ -0,0 +1,5 @@ |
|||||
|
const deployRoutes = require("./deploy") |
||||
|
|
||||
|
exports.routes = [ |
||||
|
deployRoutes, |
||||
|
] |
||||
@ -0,0 +1,15 @@ |
|||||
|
module.exports = { |
||||
|
SELF_HOSTED: process.env.SELF_HOSTED, |
||||
|
DEPLOYMENT_API_KEY: process.env.DEPLOYMENT_API_KEY, |
||||
|
PORT: process.env.PORT, |
||||
|
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, |
||||
|
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, |
||||
|
RAW_MINIO_URL: process.env.RAW_MINIO_URL, |
||||
|
COUCH_DB_USERNAME: process.env.COUCH_DB_USERNAME, |
||||
|
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, |
||||
|
RAW_COUCH_DB_URL: process.env.RAW_COUCH_DB_URL, |
||||
|
_set(key, value) { |
||||
|
process.env[key] = value |
||||
|
module.exports[key] = value |
||||
|
}, |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
const Koa = require("koa") |
||||
|
const destroyable = require("server-destroy") |
||||
|
const koaBody = require("koa-body") |
||||
|
const logger = require("koa-pino-logger") |
||||
|
const http = require("http") |
||||
|
const api = require("./api") |
||||
|
const env = require("./environment") |
||||
|
|
||||
|
const app = new Koa() |
||||
|
|
||||
|
if (!env.SELF_HOSTED) { |
||||
|
throw "Currently this service only supports use in self hosting" |
||||
|
} |
||||
|
|
||||
|
// set up top level koa middleware
|
||||
|
app.use(koaBody({ multipart: true })) |
||||
|
|
||||
|
app.use( |
||||
|
logger({ |
||||
|
prettyPrint: { |
||||
|
levelFirst: true, |
||||
|
}, |
||||
|
level: env.LOG_LEVEL || "error", |
||||
|
}) |
||||
|
) |
||||
|
|
||||
|
// api routes
|
||||
|
app.use(api.routes()) |
||||
|
|
||||
|
const server = http.createServer(app.callback()) |
||||
|
destroyable(server) |
||||
|
|
||||
|
server.on("close", () => console.log("Server Closed")) |
||||
|
|
||||
|
module.exports = server.listen(env.PORT || 4002, async () => { |
||||
|
console.log(`Deployment running on ${JSON.stringify(server.address())}`) |
||||
|
}) |
||||
|
|
||||
|
process.on("uncaughtException", err => { |
||||
|
console.error(err) |
||||
|
server.close() |
||||
|
server.destroy() |
||||
|
}) |
||||
|
|
||||
|
process.on("SIGTERM", () => { |
||||
|
server.close() |
||||
|
server.destroy() |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
module.exports = async (ctx, next) => { |
||||
|
// TODO: need to check the API key provided in the header
|
||||
|
await next() |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue