mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-24 07:46:41 +00:00
Merge pull request #126 from resin-io/add-stop-start-endpoints
WIP: Add stop, start and get app endpoints
This commit is contained in:
commit
8577280dc5
@ -1,3 +1,5 @@
|
||||
# Add endpoints to start, stop and get app [Pablo]
|
||||
|
||||
# v1.7.0
|
||||
|
||||
* Add RESIN_HOST_LOG_TO_DISPLAY variable [Pablo]
|
||||
|
110
docs/API.md
110
docs/API.md
@ -228,9 +228,7 @@ $ curl -X POST --header "Content-Type:application/json" \
|
||||
|
||||
Restarts a user application container
|
||||
|
||||
When successful, responds with 200 and a
|
||||
```
|
||||
(This is implemented in Go)
|
||||
When successful, responds with 200 and an "OK"
|
||||
|
||||
#### Request body
|
||||
Has to be a JSON object with an `appId` property, corresponding to the ID of the application the device is running.
|
||||
@ -380,3 +378,109 @@ $ curl -X POST --header "Content-Type:application/json" \
|
||||
--data '{"deviceId": <deviceId>, "appId": <appId>, "method": "GET"}' \
|
||||
"https://api.resin.io/supervisor/v1/device"
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### POST /v1/apps/:appId/stop
|
||||
|
||||
Introduced in supervisor v1.8.
|
||||
Temporarily stops a user application container. A reboot or supervisor restart will cause the container to start again.
|
||||
The container is not removed with this endpoint.
|
||||
|
||||
When successful, responds with 200 and the Id of the stopped container.
|
||||
|
||||
The appId must be specified in the URL.
|
||||
|
||||
#### Request body
|
||||
Can contain a `force` property, which if set to `true` will cause the update lock to be overridden.
|
||||
|
||||
#### Examples:
|
||||
From the app on the device:
|
||||
|
||||
```bash
|
||||
$ curl -X POST --header "Content-Type:application/json" \
|
||||
"$RESIN_SUPERVISOR_ADDRESS/v1/apps/<appId>/stop?apikey=$RESIN_SUPERVISOR_API_KEY"
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{"containerId":"5f4d4a857742e9ecac505ba5710834d3852ad7d71e10389fc6f61d8655a21806"}
|
||||
```
|
||||
|
||||
Remotely via the API proxy:
|
||||
|
||||
```bash
|
||||
$ curl -X POST --header "Content-Type:application/json" \
|
||||
--header "Authorization: Bearer <auth token>" \
|
||||
--data '{"deviceId": <deviceId>, "appId": <appId>}' \
|
||||
"https://api.resin.io/supervisor/v1/apps/<appId>/stop"
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### POST /v1/apps/:appId/start
|
||||
|
||||
Introduced in supervisor v1.8.
|
||||
Starts a user application container, usually after it has been stopped with `/v1/stop`.
|
||||
|
||||
When successful, responds with 200 and the Id of the started container.
|
||||
|
||||
The appId must be specified in the URL.
|
||||
|
||||
#### Examples:
|
||||
From the app on the device:
|
||||
|
||||
```bash
|
||||
$ curl -X POST --header "Content-Type:application/json" \
|
||||
"$RESIN_SUPERVISOR_ADDRESS/v1/apps/<appId>/start?apikey=$RESIN_SUPERVISOR_API_KEY"
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{"containerId":"6d9e1efdb9aad90fdb2df911f785b6aa00270e9448e75226a9a7361c8a9500cf"}
|
||||
```
|
||||
|
||||
Remotely via the API proxy:
|
||||
|
||||
```bash
|
||||
$ curl -X POST --header "Content-Type:application/json" \
|
||||
--header "Authorization: Bearer <auth token>" \
|
||||
--data '{"deviceId": <deviceId>, "appId": <appId>}' \
|
||||
"https://api.resin.io/supervisor/v1/apps/<appId>/start"
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### GET /v1/apps/:appId
|
||||
|
||||
Introduced in supervisor v1.8.
|
||||
Returns the application running on the device
|
||||
The app is a JSON object that contains the following:
|
||||
* `appId`: The id of the app as per the Resin API.
|
||||
* `commit`: Application commit that is running.
|
||||
* `imageId`: The docker image of the current application build.
|
||||
* `containerId`: ID of the docker container of the running app.
|
||||
* `env`: A key-value store of the app's environment variables.
|
||||
|
||||
The appId must be specified in the URL.
|
||||
|
||||
#### Examples:
|
||||
From the app on the device:
|
||||
```bash
|
||||
$ curl -X GET --header "Content-Type:application/json" \
|
||||
"$RESIN_SUPERVISOR_ADDRESS/v1/apps/<appId>?apikey=$RESIN_SUPERVISOR_API_KEY"
|
||||
```
|
||||
Response:
|
||||
```json
|
||||
{"appId": 3134,"commit":"414e65cd378a69a96f403b75f14b40b55856f860","imageId":"registry.resin.io/superapp/414e65cd378a69a96f403b75f14b40b55856f860","containerId":"e5c1eace8b4e","env":{"FOO":"bar"}}
|
||||
```
|
||||
|
||||
Remotely via the API proxy:
|
||||
```bash
|
||||
$ curl -X POST --header "Content-Type:application/json" \
|
||||
--header "Authorization: Bearer <auth token>" \
|
||||
--data '{"deviceId": <deviceId>, "appId": <appId>, "method": "GET"}' \
|
||||
"https://api.resin.io/supervisor/v1/apps/<appId>"
|
||||
```
|
||||
|
@ -7,6 +7,12 @@ bodyParser = require 'body-parser'
|
||||
request = require 'request'
|
||||
config = require './config'
|
||||
device = require './device'
|
||||
_ = require 'lodash'
|
||||
|
||||
privateAppEnvVars = [
|
||||
'RESIN_SUPERVISOR_API_KEY'
|
||||
'RESIN_API_KEY'
|
||||
]
|
||||
|
||||
module.exports = (application) ->
|
||||
api = express()
|
||||
@ -95,6 +101,58 @@ module.exports = (application) ->
|
||||
.catch (err) ->
|
||||
res.status(503).send(err?.message or err or 'Unknown error')
|
||||
|
||||
api.post '/v1/apps/:appId/stop', (req, res) ->
|
||||
{ appId } = req.params
|
||||
{ force } = req.body
|
||||
utils.mixpanelTrack('Stop container', appId)
|
||||
if !appId?
|
||||
return res.status(400).send('Missing app id')
|
||||
Promise.using application.lockUpdates(appId, force), ->
|
||||
knex('app').select().where({ appId })
|
||||
.then ([ app ]) ->
|
||||
if !app?
|
||||
throw new Error('App not found')
|
||||
application.kill(app, true, false)
|
||||
.then ->
|
||||
res.json(_.pick(app, 'containerId'))
|
||||
.catch (err) ->
|
||||
res.status(503).send(err?.message or err or 'Unknown error')
|
||||
|
||||
api.post '/v1/apps/:appId/start', (req, res) ->
|
||||
{ appId } = req.params
|
||||
utils.mixpanelTrack('Start container', appId)
|
||||
if !appId?
|
||||
return res.status(400).send('Missing app id')
|
||||
Promise.using application.lockUpdates(appId), ->
|
||||
knex('app').select().where({ appId })
|
||||
.then ([ app ]) ->
|
||||
if !app?
|
||||
throw new Error('App not found')
|
||||
application.start(app)
|
||||
.then ->
|
||||
res.json(_.pick(app, 'containerId'))
|
||||
.catch (err) ->
|
||||
res.status(503).send(err?.message or err or 'Unknown error')
|
||||
|
||||
api.get '/v1/apps/:appId', (req, res) ->
|
||||
{ appId } = req.params
|
||||
utils.mixpanelTrack('GET app', appId)
|
||||
if !appId?
|
||||
return res.status(400).send('Missing app id')
|
||||
Promise.using application.lockUpdates(appId, true), ->
|
||||
columns = [ 'appId', 'containerId', 'commit', 'imageId', 'env' ]
|
||||
knex('app').select(columns).where({ appId })
|
||||
.then ([ app ]) ->
|
||||
if !app?
|
||||
throw new Error('App not found')
|
||||
# Don't return keys on the endpoint
|
||||
app.env = _.omit(JSON.parse(app.env), privateAppEnvVars)
|
||||
# Don't return data that will be of no use to the user
|
||||
res.json(app)
|
||||
.catch (err) ->
|
||||
res.status(503).send(err?.message or err or 'Unknown error')
|
||||
|
||||
|
||||
# Expires the supervisor's API key and generates a new one.
|
||||
# It also communicates the new key to the Resin API.
|
||||
api.post '/v1/regenerate-api-key', (req, res) ->
|
||||
|
@ -95,13 +95,14 @@ logSpecialAction = (action, value, success) ->
|
||||
|
||||
application = {}
|
||||
|
||||
application.kill = kill = (app, updateDB = true) ->
|
||||
application.kill = kill = (app, updateDB = true, removeContainer = true) ->
|
||||
logSystemEvent(logTypes.stopApp, app)
|
||||
device.updateState(status: 'Stopping')
|
||||
container = docker.getContainer(app.containerId)
|
||||
container.stopAsync(t: 10)
|
||||
.then ->
|
||||
container.removeAsync(v: true)
|
||||
container.removeAsync(v: true) if removeContainer
|
||||
return
|
||||
# Bluebird throws OperationalError for errors resulting in the normal execution of a promisified function.
|
||||
.catch Promise.OperationalError, (err) ->
|
||||
# Get the statusCode from the original cause and make sure statusCode its definitely a string for comparison
|
||||
@ -109,7 +110,8 @@ application.kill = kill = (app, updateDB = true) ->
|
||||
statusCode = '' + err.statusCode
|
||||
# 304 means the container was already stopped - so we can just remove it
|
||||
if statusCode is '304'
|
||||
return container.removeAsync(v: true)
|
||||
container.removeAsync(v: true) if removeContainer
|
||||
return
|
||||
# 404 means the container doesn't exist, precisely what we want! :D
|
||||
if statusCode is '404'
|
||||
return
|
||||
@ -118,7 +120,7 @@ application.kill = kill = (app, updateDB = true) ->
|
||||
lockFile.unlockAsync(lockPath(app))
|
||||
.tap ->
|
||||
logSystemEvent(logTypes.stopAppSuccess, app)
|
||||
if updateDB == true
|
||||
if removeContainer && updateDB
|
||||
app.containerId = null
|
||||
knex('app').update(app).where(appId: app.appId)
|
||||
.catch (err) ->
|
||||
@ -239,6 +241,9 @@ application.start = start = (app) ->
|
||||
return
|
||||
# If starting the container failed, we remove it so that it doesn't litter
|
||||
container.removeAsync(v: true)
|
||||
.then ->
|
||||
app.containerId = null
|
||||
knex('app').update(app).where(appId: app.appId)
|
||||
.finally ->
|
||||
logSystemEvent(logTypes.startAppError, app, err)
|
||||
throw err
|
||||
|
Loading…
Reference in New Issue
Block a user