mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-25 13:29:58 +00:00
Merge pull request #97 from resin-io/32-endpoint-for-device-state
Add endpoint to get device state
This commit is contained in:
commit
a2fa396b9f
@ -1,3 +1,4 @@
|
|||||||
|
* Add endpoint to get device state [Pablo]
|
||||||
* Check for valid strings or ints in all config values [Pablo]
|
* Check for valid strings or ints in all config values [Pablo]
|
||||||
* Remove quotes in OS version [Pablo]
|
* Remove quotes in OS version [Pablo]
|
||||||
|
|
||||||
|
39
docs/API.md
39
docs/API.md
@ -405,3 +405,42 @@ $ curl -X POST --header "Content-Type:application/json" \
|
|||||||
--data '{"deviceId": <deviceId>, "appId": <appId>}' \
|
--data '{"deviceId": <deviceId>, "appId": <appId>}' \
|
||||||
"https://api.resin.io/supervisor/v1/regenerate-api-key"
|
"https://api.resin.io/supervisor/v1/regenerate-api-key"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### GET /v1/device
|
||||||
|
|
||||||
|
Introduced in supervisor v1.6.
|
||||||
|
Returns the current device state, as reported to the Resin API and with some extra fields added to allow control over pending/locked updates.
|
||||||
|
The state is a JSON object that contains some or all of the following:
|
||||||
|
* `api_port`: Port on which the supervisor is listening.
|
||||||
|
* `commit`: Hash of the current commit of the application that is running.
|
||||||
|
* `ip_address`: Space-separated list of IP addresses of the device.
|
||||||
|
* `status`: Status of the device regarding the app, as a string, i.e. "Stopping", "Starting", "Downloading", "Installing", "Idle".
|
||||||
|
* `download_progress`: Amount of the application image that has been downloaded, expressed as a percentage. If the update has already been downloaded, this will be `null`.
|
||||||
|
* `os_version`: Version of the host OS running on the device.
|
||||||
|
* `supervisor_version`: Version of the supervisor running on the device.
|
||||||
|
* `update_pending`: This one is not reported to the Resin API. It's a boolean that will be true if the supervisor has detected there is a pending update.
|
||||||
|
* `update_downloaded`: Not reported to the Resin API either. Boolean that will be true if a pending update has already been downloaded.
|
||||||
|
* `update_failed`: Not reported to the Resin API. Boolean that will be true if the supervisor has tried to apply a pending update but failed (i.e. if the app was locked, there was a network failure or anything else went wrong).
|
||||||
|
|
||||||
|
Other attributes may be added in the future, and some may be missing or null if they haven't been set yet.
|
||||||
|
|
||||||
|
#### Examples:
|
||||||
|
From the app on the device:
|
||||||
|
```bash
|
||||||
|
$ curl -X GET --header "Content-Type:application/json" \
|
||||||
|
"$RESIN_SUPERVISOR_ADDRESS/v1/device?apikey=$RESIN_SUPERVISOR_API_KEY"
|
||||||
|
```
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","commit":"414e65cd378a69a96f403b75f14b40b55856f860","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0","update_pending":true,"update_downloaded":false,"update_failed":false}
|
||||||
|
```
|
||||||
|
|
||||||
|
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/device"
|
||||||
|
```
|
||||||
|
@ -136,4 +136,7 @@ module.exports = (application) ->
|
|||||||
.catch (err) ->
|
.catch (err) ->
|
||||||
res.status(503).send(err?.message or err or 'Unknown error')
|
res.status(503).send(err?.message or err or 'Unknown error')
|
||||||
|
|
||||||
|
api.get '/v1/device', (req, res) ->
|
||||||
|
res.json(device.getState())
|
||||||
|
|
||||||
return api
|
return api
|
||||||
|
@ -144,6 +144,7 @@ fetch = (app) ->
|
|||||||
.then ->
|
.then ->
|
||||||
logSystemEvent(logTypes.downloadAppSuccess, app)
|
logSystemEvent(logTypes.downloadAppSuccess, app)
|
||||||
device.updateState(status: 'Idle', download_progress: null)
|
device.updateState(status: 'Idle', download_progress: null)
|
||||||
|
device.setUpdateState(update_downloaded: true)
|
||||||
docker.getImage(app.imageId).inspectAsync()
|
docker.getImage(app.imageId).inspectAsync()
|
||||||
.catch (err) ->
|
.catch (err) ->
|
||||||
logSystemEvent(logTypes.downloadAppError, app, err)
|
logSystemEvent(logTypes.downloadAppError, app, err)
|
||||||
@ -516,6 +517,8 @@ application.update = update = (force) ->
|
|||||||
resourcesForUpdate = compareForUpdate(localApps, remoteApps, localAppEnvs, remoteAppEnvs)
|
resourcesForUpdate = compareForUpdate(localApps, remoteApps, localAppEnvs, remoteAppEnvs)
|
||||||
{ toBeRemoved, toBeDownloaded, toBeInstalled, toBeUpdated, appsWithChangedEnvs, allAppIds } = resourcesForUpdate
|
{ toBeRemoved, toBeDownloaded, toBeInstalled, toBeUpdated, appsWithChangedEnvs, allAppIds } = resourcesForUpdate
|
||||||
|
|
||||||
|
if !_.isEmpty(toBeRemoved) or !_.isEmpty(toBeInstalled) or !_.isEmpty(toBeUpdated)
|
||||||
|
device.setUpdateState(update_pending: true)
|
||||||
# Run special functions against variables if remoteAppEnvs has the corresponding variable function mapping.
|
# Run special functions against variables if remoteAppEnvs has the corresponding variable function mapping.
|
||||||
Promise.map appsWithChangedEnvs, (appId) ->
|
Promise.map appsWithChangedEnvs, (appId) ->
|
||||||
Promise.using lockUpdates(remoteApps[appId], force), ->
|
Promise.using lockUpdates(remoteApps[appId], force), ->
|
||||||
@ -578,11 +581,13 @@ application.update = update = (force) ->
|
|||||||
throw new Error(joinErrorMessages(failures)) if failures.length > 0
|
throw new Error(joinErrorMessages(failures)) if failures.length > 0
|
||||||
.then ->
|
.then ->
|
||||||
updateStatus.failed = 0
|
updateStatus.failed = 0
|
||||||
|
device.setUpdateState(update_pending: false, update_downloaded: false, update_failed: false)
|
||||||
# We cleanup here as we want a point when we have a consistent apps/images state, rather than potentially at a
|
# We cleanup here as we want a point when we have a consistent apps/images state, rather than potentially at a
|
||||||
# point where we might clean up an image we still want.
|
# point where we might clean up an image we still want.
|
||||||
dockerUtils.cleanupContainersAndImages()
|
dockerUtils.cleanupContainersAndImages()
|
||||||
.catch (err) ->
|
.catch (err) ->
|
||||||
updateStatus.failed++
|
updateStatus.failed++
|
||||||
|
device.setUpdateState(update_failed: true)
|
||||||
if updateStatus.state is UPDATE_REQUIRED
|
if updateStatus.state is UPDATE_REQUIRED
|
||||||
console.log('Updating failed, but there is already another update scheduled immediately: ', err)
|
console.log('Updating failed, but there is already another update scheduled immediately: ', err)
|
||||||
return
|
return
|
||||||
|
@ -128,13 +128,11 @@ exports.getDeviceType = do ->
|
|||||||
throw new Error('Device type not specified in config file')
|
throw new Error('Device type not specified in config file')
|
||||||
return configFromFile.deviceType
|
return configFromFile.deviceType
|
||||||
|
|
||||||
# Calling this function updates the local device state, which is then used to synchronise
|
do ->
|
||||||
# the remote device state, repeating any failed updates until successfully synchronised.
|
|
||||||
# This function will also optimise updates by merging multiple updates and only sending the latest state.
|
|
||||||
exports.updateState = do ->
|
|
||||||
applyPromise = Promise.resolve()
|
applyPromise = Promise.resolve()
|
||||||
targetState = {}
|
targetState = {}
|
||||||
actualState = {}
|
actualState = {}
|
||||||
|
updateState = { update_pending: false, update_failed: false, update_downloaded: false }
|
||||||
|
|
||||||
getStateDiff = ->
|
getStateDiff = ->
|
||||||
_.omit targetState, (value, key) ->
|
_.omit targetState, (value, key) ->
|
||||||
@ -169,7 +167,19 @@ exports.updateState = do ->
|
|||||||
# Check if any more state diffs have appeared whilst we've been processing this update.
|
# Check if any more state diffs have appeared whilst we've been processing this update.
|
||||||
applyState()
|
applyState()
|
||||||
|
|
||||||
return (updatedState = {}, retry = false) ->
|
exports.setUpdateState = (value) ->
|
||||||
|
_.merge(updateState, value)
|
||||||
|
|
||||||
|
exports.getState = ->
|
||||||
|
fieldsToOmit = ['api_secret', 'logs_channel', 'provisioning_progress', 'provisioning_state']
|
||||||
|
state = _.omit(targetState, fieldsToOmit)
|
||||||
|
_.merge(state, updateState)
|
||||||
|
return state
|
||||||
|
|
||||||
|
# Calling this function updates the local device state, which is then used to synchronise
|
||||||
|
# the remote device state, repeating any failed updates until successfully synchronised.
|
||||||
|
# This function will also optimise updates by merging multiple updates and only sending the latest state.
|
||||||
|
exports.updateState = (updatedState = {}, retry = false) ->
|
||||||
# Remove any updates that match the last we successfully sent.
|
# Remove any updates that match the last we successfully sent.
|
||||||
_.merge(targetState, updatedState)
|
_.merge(targetState, updatedState)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user