UNPKG

@flowfuse/flowfuse

Version:

An open source low-code development platform

328 lines (314 loc) • 12.8 kB
/** * Project Action api routes * * request.project will be defined for any route defined in here * * - /api/v1/projects/:instanceId/actions/ * * @namespace project * @memberof forge.routes.api */ module.exports = async function (app) { app.post('/start', { preHandler: app.needsPermission('project:change-status'), schema: { summary: 'Start an instance', tags: ['Instance Actions'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' }, 500: { $ref: 'APIError' } } } }, async (request, reply) => { try { if (request.project.state === 'suspended') { try { // This will perform all checks needed to ensure this instance // can be unsuspended await request.project.Team.checkInstanceStartAllowed(request.project) } catch (err) { reply.code(err.statusCode || 400).send({ code: err.code || 'unexpected_error', error: err.error || err.message }) return } // Restart the container request.project.state = 'running' await request.project.save() app.db.controllers.Project.setInflightState(request.project, 'starting') const startResult = await app.containers.start(request.project) startResult.started.then(async () => { await app.auditLog.Project.project.started(request.session.User, null, request.project) app.db.controllers.Project.clearInflightState(request.project) return true }).catch(err => { app.log.info(`failed to start project ${request.project.id}`) throw err }) } else { app.db.controllers.Project.setInflightState(request.project, 'starting') request.project.state = 'running' await request.project.save() await app.containers.startFlows(request.project) await app.auditLog.Project.project.started(request.session.User, null, request.project) app.db.controllers.Project.clearInflightState(request.project) } reply.send({ status: 'okay' }) } catch (err) { app.db.controllers.Project.clearInflightState(request.project) const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Project.project.started(request.session.User, resp, request.project) reply.code(500).send(resp) } }) app.post('/stop', { preHandler: app.needsPermission('project:change-status'), schema: { summary: 'Stop an instance', tags: ['Instance Actions'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' }, 500: { $ref: 'APIError' } } } }, async (request, reply) => { try { if (request.project.state === 'suspended') { reply.code(400).send({ code: 'project_suspended', error: 'Project suspended' }) return } app.db.controllers.Project.setInflightState(request.project, 'stopping') request.project.state = 'stopped' await request.project.save() await app.containers.stopFlows(request.project) await app.auditLog.Project.project.stopped(request.session.User, null, request.project) app.db.controllers.Project.clearInflightState(request.project) reply.send({ status: 'okay' }) } catch (err) { app.db.controllers.Project.clearInflightState(request.project) const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Project.project.stopped(request.session.User, resp, request.project) reply.code(500).send(resp) } }) app.post('/restart', { preHandler: app.needsPermission('project:change-status'), schema: { summary: 'Restart an instance', tags: ['Instance Actions'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' }, 500: { $ref: 'APIError' } } } }, async (request, reply) => { try { if (request.project.state === 'suspended') { reply.code(400).send({ code: 'project_suspended', error: 'Project suspended' }) return } app.db.controllers.Project.setInflightState(request.project, 'restarting') request.project.state = 'running' await request.project.save() await app.containers.restartFlows(request.project) await app.auditLog.Project.project.restarted(request.session.User, null, request.project) app.db.controllers.Project.clearInflightState(request.project) reply.send({ status: 'okay' }) } catch (err) { app.db.controllers.Project.clearInflightState(request.project) const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Project.project.restarted(request.session.User, resp, request.project) reply.code(500).send(resp) } }) app.post('/suspend', { preHandler: app.needsPermission('project:change-status'), schema: { summary: 'Suspend an instance', tags: ['Instance Actions'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' }, 500: { $ref: 'APIError' } } } }, async (request, reply) => { try { if (request.project.state === 'suspended') { reply.code(400).send({ code: 'project_suspended', error: 'Project suspended' }) return } app.db.controllers.Project.setInflightState(request.project, 'suspending') await app.containers.stop(request.project) app.db.controllers.Project.clearInflightState(request.project) await app.auditLog.Project.project.suspended(request.session.User, null, request.project) reply.send({ status: 'okay' }) } catch (err) { app.db.controllers.Project.clearInflightState(request.project) const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Project.project.suspended(request.session.User, resp, request.project) reply.code(500).send(resp) } }) app.post('/rollback', { preHandler: app.needsPermission('project:snapshot:rollback'), schema: { summary: 'Rollback an instance to a snapshot', tags: ['Instance Actions'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, body: { type: 'object', properties: { snapshot: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' }, 500: { $ref: 'APIError' } } } }, async (request, reply) => { let restartProject = false try { // get (and check) snapshot is valid / owned by project before any actions const snapshot = await app.db.models.ProjectSnapshot.byId(request.body.snapshot) if (!snapshot) { reply.code(400).send({ code: 'invalid_snapshot', error: `snapshot '${request.body.snapshot}' not found for project '${request.project.id}'` }) return } if (snapshot.ProjectId !== request.project.id) { reply.code(400).send({ code: 'invalid_snapshot', error: `snapshot '${request.body.snapshot}' not found for project '${request.project.id}'` }) return } if (request.project.state === 'running') { restartProject = true } app.db.controllers.Project.setInflightState(request.project, 'rollback') await app.db.controllers.Project.importProjectSnapshot(request.project, snapshot) app.db.controllers.Project.clearInflightState(request.project) await app.auditLog.Project.project.snapshot.rolledBack(request.session.User, null, request.project, snapshot) if (restartProject) { await app.containers.restartFlows(request.project) } reply.send({ status: 'okay' }) } catch (err) { app.db.controllers.Project.clearInflightState(request.project) const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Project.project.snapshot.rolledBack(request.session.User, resp, request.project) reply.code(500).send(resp) } }) app.post('/restartStack', { preHandler: app.needsPermission('project:change-status'), schema: { summary: 'Restart an instance stack', tags: ['Instance Actions'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' }, 500: { $ref: 'APIError' } } } }, async (request, reply) => { try { await app.auditLog.Project.project.stack.restart(request.session.User, null, request.project) if (request.project.state !== 'suspended') { app.db.controllers.Project.setInflightState(request.project, 'suspending') await app.containers.stop(request.project) app.db.controllers.Project.clearInflightState(request.project) await app.auditLog.Project.project.suspended(request.session.User, null, request.project) } request.project.state = 'running' await request.project.save() app.db.controllers.Project.setInflightState(request.project, 'starting') const startResult = await app.containers.start(request.project) startResult.started.then(async () => { await app.auditLog.Project.project.started(request.session.User, null, request.project) app.db.controllers.Project.clearInflightState(request.project) return true }).catch(err => { app.log.info(`failed to restartStack for ${request.project.id}`) throw err }) reply.send() } catch (err) { app.db.controllers.Project.clearInflightState(request.project) const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Project.project.stack.restart(request.session.User, resp, request.project) reply.code(500).send(resp) } }) }