device-api: Add v2/device/tags api endpoint

This endpoint will fetch the device tags from the balena api

Change-type: minor
Closes: #890
Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
Cameron Diver 2019-03-12 14:43:01 +00:00
parent 3f231e8ff3
commit b922789dee
No known key found for this signature in database
GPG Key ID: 49690ED87032539F
5 changed files with 87 additions and 0 deletions

View File

@ -1126,3 +1126,29 @@ Response:
"deviceName": "holy-wildflower"
}
```
#### Device tags
Added in supervisor version v9.11.0
Retrieve any device tags from the balena API. Note that this endpoint will not work when
the device does not have an available connection to the balena API.
From an application container:
```
$ curl "$BALENA_SUPERVISOR_ADDRESS/v2/device/tags?apikey=$BALENA_SUPERVISOR_API_KEY"
```
Response:
```json
{
"status": "success",
"tags": [
{
"id": 188303,
"name": "DeviceLocation",
"value": "warehouse #3"
}
]
}
```

View File

@ -1,6 +1,8 @@
import * as Bluebird from 'bluebird';
import * as bodyParser from 'body-parser';
import * as express from 'express';
import { isLeft } from 'fp-ts/lib/Either';
import * as t from 'io-ts';
import * as _ from 'lodash';
import * as Path from 'path';
import { PinejsClientRequest, StatusError } from 'pinejs-client-request';
@ -57,6 +59,12 @@ interface DevicePinInfo {
commit: string;
}
interface DeviceTag {
id: number;
name: string;
value: string;
}
type KeyExchangeOpts = ConfigSchemaType<'provisioningOptions'>;
export class APIBinder {
@ -376,6 +384,39 @@ export class APIBinder {
return this.reportCurrentState();
}
public async fetchDeviceTags(): Promise<DeviceTag[]> {
if (this.balenaApi == null) {
throw new Error(
'Attempt to communicate with API, without initialized client',
);
}
const tags = (await this.balenaApi.get({
resource: 'device_tag',
options: {
$select: ['id', 'tag_key', 'value'],
},
})) as Array<Dictionary<unknown>>;
return tags.map(tag => {
// Do some type safe decoding and throw if we get an unexpected value
const id = t.number.decode(tag.id);
const name = t.string.decode(tag.tag_key);
const value = t.string.decode(tag.value);
if (isLeft(id) || isLeft(name) || isLeft(value)) {
throw new Error(
`There was an error parsing device tags from the api. Device tag: ${JSON.stringify(
tag,
)}`,
);
}
return {
id: id.value,
name: name.value,
value: value.value,
};
});
}
private getStateDiff(): DeviceApplicationState {
const lastReportedLocal = this.lastReportedState.local;
const lastReportedDependent = this.lastReportedState.dependent;

View File

@ -12,6 +12,7 @@ import DB from './db';
import { Service } from './compose/service';
import Config from './config';
import { APIBinder } from './api-binder';
declare interface Options {
force?: boolean;
@ -37,6 +38,7 @@ export class ApplicationManager extends EventEmitter {
public logger: Logger;
public deviceState: any;
public eventTracker: EventTracker;
public apiBinder: APIBinder;
public services: ServiceManager;
public config: Config;

View File

@ -451,4 +451,19 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
});
}
});
router.get('/v2/device/tags', async (_req, res) => {
try {
const tags = await applications.apiBinder.fetchDeviceTags();
return res.json({
status: 'success',
tags,
});
} catch (e) {
res.status(503).json({
status: 'failed',
message: messageFromError(e),
});
}
});
}

View File

@ -37,6 +37,9 @@ module.exports = class Supervisor extends EventEmitter
# FIXME: rearchitect proxyvisor to avoid this circular dependency
# by storing current state and having the APIBinder query and report it / provision devices
@deviceState.applications.proxyvisor.bindToAPI(@apiBinder)
# We could also do without the below dependency, but it's part of a much larger refactor
@deviceState.applications.apiBinder = @apiBinder
@api = new SupervisorAPI({
@config,
@eventTracker,