mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-22 06:57:49 +00:00
Convert some coffeescript tests to typescript
Change-type: patch Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
f2b7543531
commit
b5a427f2b9
83
package-lock.json
generated
83
package-lock.json
generated
@ -254,6 +254,15 @@
|
||||
"integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/chai-as-promised": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz",
|
||||
"integrity": "sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/chai": "*"
|
||||
}
|
||||
},
|
||||
"@types/common-tags": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz",
|
||||
@ -517,6 +526,16 @@
|
||||
"integrity": "sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/sinon-chai": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.3.tgz",
|
||||
"integrity": "sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/chai": "*",
|
||||
"@types/sinon": "*"
|
||||
}
|
||||
},
|
||||
"@types/tmp": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz",
|
||||
@ -1295,7 +1314,7 @@
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -1323,7 +1342,7 @@
|
||||
},
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
},
|
||||
@ -2153,7 +2172,7 @@
|
||||
},
|
||||
"cacache": {
|
||||
"version": "10.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
|
||||
"integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -2462,7 +2481,7 @@
|
||||
"dependencies": {
|
||||
"coffee-script": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz",
|
||||
"integrity": "sha1-vxxHrWREOg2V0S3ysUfMCk2q1uk=",
|
||||
"dev": true
|
||||
},
|
||||
@ -3123,7 +3142,7 @@
|
||||
"dependencies": {
|
||||
"globby": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -3136,7 +3155,7 @@
|
||||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
@ -3481,7 +3500,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -3493,7 +3512,7 @@
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
}
|
||||
@ -3926,7 +3945,7 @@
|
||||
},
|
||||
"event-stream": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -4066,7 +4085,7 @@
|
||||
"dependencies": {
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
|
||||
"dev": true
|
||||
},
|
||||
@ -6095,13 +6114,13 @@
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
|
||||
"dev": true
|
||||
},
|
||||
"onetime": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||
"dev": true
|
||||
},
|
||||
@ -6156,13 +6175,13 @@
|
||||
"dependencies": {
|
||||
"bluebird": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
||||
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
|
||||
"dev": true
|
||||
}
|
||||
@ -6838,7 +6857,7 @@
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -7518,7 +7537,7 @@
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
@ -7857,7 +7876,7 @@
|
||||
},
|
||||
"map-stream": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
|
||||
"dev": true
|
||||
},
|
||||
@ -8142,7 +8161,7 @@
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
},
|
||||
@ -8250,7 +8269,7 @@
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
|
||||
"dev": true
|
||||
},
|
||||
@ -8792,7 +8811,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
|
||||
"dev": true
|
||||
}
|
||||
@ -9451,7 +9470,7 @@
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
},
|
||||
@ -9470,7 +9489,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@ -9652,7 +9671,7 @@
|
||||
"dependencies": {
|
||||
"bluebird": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
||||
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
|
||||
"dev": true
|
||||
}
|
||||
@ -9701,7 +9720,7 @@
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
|
||||
"dev": true
|
||||
},
|
||||
@ -9803,7 +9822,7 @@
|
||||
},
|
||||
"resin-register-device": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resin-register-device/-/resin-register-device-3.0.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/resin-register-device/-/resin-register-device-3.0.0.tgz",
|
||||
"integrity": "sha1-PmyWJfOc8jR5K4uQlI8J7AO+JgM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -10419,7 +10438,7 @@
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
|
||||
"integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
@ -10504,7 +10523,7 @@
|
||||
},
|
||||
"split": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
|
||||
"resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz",
|
||||
"integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -10740,7 +10759,7 @@
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
@ -12586,7 +12605,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
|
||||
"dev": true
|
||||
},
|
||||
@ -12697,7 +12716,7 @@
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -12722,7 +12741,7 @@
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -12733,7 +12752,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@balena/contrato": "^0.2.1",
|
||||
"@types/bluebird": "^3.5.25",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chai-as-promised": "^7.1.2",
|
||||
"@types/common-tags": "^1.8.0",
|
||||
"@types/dockerode": "^2.5.20",
|
||||
"@types/event-stream": "^3.3.34",
|
||||
@ -57,6 +58,7 @@
|
||||
"@types/rwlock": "^5.0.2",
|
||||
"@types/shell-quote": "^1.6.0",
|
||||
"@types/sinon": "^7.0.13",
|
||||
"@types/sinon-chai": "^3.2.3",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"balena-sync": "^10.0.0",
|
||||
"blinking": "~0.0.2",
|
||||
|
2
src/application-manager.d.ts
vendored
2
src/application-manager.d.ts
vendored
@ -19,6 +19,7 @@ import VolumeManager from './compose/volume-manager';
|
||||
import Network from './compose/network';
|
||||
import Service from './compose/service';
|
||||
import Volume from './compose/volume';
|
||||
import DockerUtils from './lib/docker-utils';
|
||||
|
||||
declare interface Options {
|
||||
force?: boolean;
|
||||
@ -45,6 +46,7 @@ export class ApplicationManager extends EventEmitter {
|
||||
public deviceState: any;
|
||||
public eventTracker: EventTracker;
|
||||
public apiBinder: APIBinder;
|
||||
public docker: DockerUtils;
|
||||
|
||||
public services: ServiceManager;
|
||||
public volumes: VolumeManager;
|
||||
|
5
src/device-state.d.ts
vendored
5
src/device-state.d.ts
vendored
@ -24,6 +24,11 @@ class DeviceState extends EventEmitter {
|
||||
|
||||
public healthcheck(): Promise<void>;
|
||||
public normaliseLegacy(client: PinejsClientRequest): Promise<void>;
|
||||
public loadTargetFromFile(filename: string): Promise<void>;
|
||||
public getTarget(): Promise<any>;
|
||||
public setTarget(target: any): Promise<any>;
|
||||
public triggerApplyTarget(opts: any): Promise<any>;
|
||||
public reportCurrentState(state: any);
|
||||
|
||||
public async init();
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ export interface CheckIntOptions {
|
||||
const ENV_VAR_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
const LABEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\.\-]*$/;
|
||||
|
||||
type NullableLiteral = Nullable<number | string>;
|
||||
|
||||
/**
|
||||
* checkInt
|
||||
*
|
||||
@ -21,7 +19,7 @@ type NullableLiteral = Nullable<number | string>;
|
||||
* to be positive
|
||||
*/
|
||||
export function checkInt(
|
||||
s: NullableLiteral,
|
||||
s: unknown,
|
||||
options: CheckIntOptions = {},
|
||||
): number | void {
|
||||
if (s == null) {
|
||||
@ -48,7 +46,7 @@ export function checkInt(
|
||||
*
|
||||
* Check that a string exists, and is not an empty string, 'null', or 'undefined'
|
||||
*/
|
||||
export function checkString(s: NullableLiteral): string | void {
|
||||
export function checkString(s: unknown): string | void {
|
||||
if (s == null || !_.isString(s) || _.includes(['null', 'undefined', ''], s)) {
|
||||
return;
|
||||
}
|
||||
@ -62,7 +60,7 @@ export function checkString(s: NullableLiteral): string | void {
|
||||
* Given a value which can be a string, boolean or number, return a boolean
|
||||
* which represents if the input was truthy
|
||||
*/
|
||||
export function checkTruthy(v: string | boolean | number): boolean | void {
|
||||
export function checkTruthy(v: unknown): boolean | void {
|
||||
if (_.isString(v)) {
|
||||
v = v.toLowerCase();
|
||||
}
|
||||
@ -90,7 +88,7 @@ export function checkTruthy(v: string | boolean | number): boolean | void {
|
||||
* Check that the input string is definitely a string,
|
||||
* and has a length which is less than 255
|
||||
*/
|
||||
export function isValidShortText(t: string): boolean {
|
||||
export function isValidShortText(t: unknown): boolean {
|
||||
return _.isString(t) && t.length <= 255;
|
||||
}
|
||||
|
||||
|
@ -1,17 +0,0 @@
|
||||
process.env.ROOT_MOUNTPOINT = './test/data'
|
||||
process.env.BOOT_MOUNTPOINT = '/mnt/boot'
|
||||
process.env.CONFIG_JSON_PATH = '/config.json'
|
||||
process.env.DATABASE_PATH = './test/data/database.sqlite'
|
||||
process.env.DATABASE_PATH_2 = './test/data/database2.sqlite'
|
||||
process.env.DATABASE_PATH_3 = './test/data/database3.sqlite'
|
||||
process.env.LED_FILE = './test/data/led_file'
|
||||
|
||||
{ stub } = require 'sinon'
|
||||
|
||||
dbus = require 'dbus-native'
|
||||
|
||||
stub(dbus, 'systemBus').returns({
|
||||
invoke: (obj, cb) ->
|
||||
console.log(obj)
|
||||
cb()
|
||||
})
|
18
test/00-init.ts
Normal file
18
test/00-init.ts
Normal file
@ -0,0 +1,18 @@
|
||||
process.env.ROOT_MOUNTPOINT = './test/data';
|
||||
process.env.BOOT_MOUNTPOINT = '/mnt/boot';
|
||||
process.env.CONFIG_JSON_PATH = '/config.json';
|
||||
process.env.DATABASE_PATH = './test/data/database.sqlite';
|
||||
process.env.DATABASE_PATH_2 = './test/data/database2.sqlite';
|
||||
process.env.DATABASE_PATH_3 = './test/data/database3.sqlite';
|
||||
process.env.LED_FILE = './test/data/led_file';
|
||||
|
||||
import { stub } from 'sinon';
|
||||
|
||||
import dbus = require('dbus-native');
|
||||
|
||||
stub(dbus, 'systemBus').returns(({
|
||||
invoke(obj: unknown, cb: () => void) {
|
||||
console.log(obj);
|
||||
return cb();
|
||||
},
|
||||
} as unknown) as dbus.MessageBus);
|
@ -1,11 +0,0 @@
|
||||
prepare = require './lib/prepare'
|
||||
{ expect } = require './lib/chai-config'
|
||||
constants = require '../src/lib/constants'
|
||||
|
||||
describe 'constants', ->
|
||||
before ->
|
||||
prepare()
|
||||
it 'has the correct configJsonPathOnHost', ->
|
||||
expect(constants.configJsonPathOnHost).to.equal('/config.json')
|
||||
it 'has the correct rootMountPoint', ->
|
||||
expect(constants.rootMountPoint).to.equal('./test/data')
|
14
test/01-constants.spec.ts
Normal file
14
test/01-constants.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import ChaiConfig = require('./lib/chai-config');
|
||||
import prepare = require('./lib/prepare');
|
||||
|
||||
const { expect } = ChaiConfig;
|
||||
|
||||
import constants = require('../src/lib/constants');
|
||||
|
||||
describe('constants', function() {
|
||||
before(() => prepare());
|
||||
it('has the correct configJsonPathOnHost', () =>
|
||||
expect(constants.configJsonPathOnHost).to.equal('/config.json'));
|
||||
it('has the correct rootMountPoint', () =>
|
||||
expect(constants.rootMountPoint).to.equal('./test/data'));
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
prepare = require './lib/prepare'
|
||||
Promise = require 'bluebird'
|
||||
{ expect } = require './lib/chai-config'
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
Knex = require('knex')
|
||||
{ DB } = require('../src/db')
|
||||
|
||||
createOldDatabase = (path) ->
|
||||
knex = new Knex(
|
||||
client: 'sqlite3'
|
||||
connection:
|
||||
filename: path
|
||||
useNullAsDefault: true
|
||||
)
|
||||
createEmptyTable = (name, fn) ->
|
||||
knex.schema.createTable name, (t) ->
|
||||
fn(t) if fn?
|
||||
createEmptyTable 'app', (t) ->
|
||||
t.increments('id').primary()
|
||||
t.boolean('privileged')
|
||||
t.string('containerId')
|
||||
.then ->
|
||||
createEmptyTable 'config', (t) ->
|
||||
t.string('key')
|
||||
t.string('value')
|
||||
.then ->
|
||||
createEmptyTable 'dependentApp', (t) ->
|
||||
t.increments('id').primary()
|
||||
.then ->
|
||||
createEmptyTable 'dependentDevice', (t) ->
|
||||
t.increments('id').primary()
|
||||
.then ->
|
||||
return knex
|
||||
|
||||
|
||||
describe 'DB', ->
|
||||
before ->
|
||||
prepare()
|
||||
@db = new DB()
|
||||
|
||||
it 'initializes correctly, running the migrations', ->
|
||||
expect(@db.init()).to.be.fulfilled
|
||||
|
||||
it 'creates a database at the path from an env var', ->
|
||||
promise = fs.statAsync(process.env.DATABASE_PATH)
|
||||
expect(promise).to.be.fulfilled
|
||||
|
||||
it 'creates a database at the path passed on creation', ->
|
||||
db2 = new DB({ databasePath: process.env.DATABASE_PATH_2 })
|
||||
promise = db2.init().then( -> fs.statAsync(process.env.DATABASE_PATH_2))
|
||||
expect(promise).to.be.fulfilled
|
||||
|
||||
it 'adds new fields and removes old ones in an old database', ->
|
||||
databasePath = process.env.DATABASE_PATH_3
|
||||
createOldDatabase(databasePath)
|
||||
.then (knexForDB) ->
|
||||
db = new DB({ databasePath })
|
||||
db.init()
|
||||
.then ->
|
||||
Promise.all([
|
||||
expect(knexForDB.schema.hasColumn('app', 'appId')).to.eventually.be.true
|
||||
expect(knexForDB.schema.hasColumn('app', 'releaseId')).to.eventually.be.true
|
||||
expect(knexForDB.schema.hasColumn('app', 'config')).to.eventually.be.false
|
||||
expect(knexForDB.schema.hasColumn('app', 'privileged')).to.eventually.be.false
|
||||
expect(knexForDB.schema.hasColumn('app', 'containerId')).to.eventually.be.false
|
||||
expect(knexForDB.schema.hasColumn('dependentApp', 'environment')).to.eventually.be.true
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'markedForDeletion')).to.eventually.be.true
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'localId')).to.eventually.be.true
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'is_managed_by')).to.eventually.be.true
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'lock_expiry_date')).to.eventually.be.true
|
||||
])
|
||||
|
||||
it 'creates a deviceConfig table with a single default value', ->
|
||||
promise = @db.models('deviceConfig').select()
|
||||
Promise.all([
|
||||
expect(promise).to.eventually.have.lengthOf(1)
|
||||
expect(promise).to.eventually.deep.equal([ { targetValues: '{}' } ])
|
||||
])
|
||||
|
||||
it 'allows performing transactions', ->
|
||||
@db.transaction (trx) ->
|
||||
expect(trx.commit()).to.be.fulfilled
|
108
test/02-db.spec.ts
Normal file
108
test/02-db.spec.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as Knex from 'knex';
|
||||
import { fs } from 'mz';
|
||||
|
||||
import ChaiConfig = require('./lib/chai-config');
|
||||
import prepare = require('./lib/prepare');
|
||||
|
||||
import { DB } from '../src/db';
|
||||
|
||||
const { expect } = ChaiConfig;
|
||||
|
||||
async function createOldDatabase(path: string) {
|
||||
const knex = Knex({
|
||||
client: 'sqlite3',
|
||||
connection: {
|
||||
filename: path,
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
const createEmptyTable = (
|
||||
name: string,
|
||||
fn: (trx: Knex.CreateTableBuilder) => void,
|
||||
) =>
|
||||
knex.schema.createTable(name, t => {
|
||||
if (fn != null) {
|
||||
return fn(t);
|
||||
}
|
||||
});
|
||||
|
||||
await createEmptyTable('app', t => {
|
||||
t.increments('id').primary();
|
||||
t.boolean('privileged');
|
||||
return t.string('containerId');
|
||||
});
|
||||
await createEmptyTable('config', t => {
|
||||
t.string('key');
|
||||
return t.string('value');
|
||||
});
|
||||
await createEmptyTable('dependentApp', t => t.increments('id').primary());
|
||||
await createEmptyTable('dependentDevice', t => t.increments('id').primary());
|
||||
return knex;
|
||||
}
|
||||
|
||||
describe('DB', () => {
|
||||
let db: DB;
|
||||
|
||||
before(() => {
|
||||
prepare();
|
||||
db = new DB();
|
||||
});
|
||||
|
||||
it('initializes correctly, running the migrations', () => {
|
||||
return expect(db.init()).to.be.fulfilled;
|
||||
});
|
||||
|
||||
it('creates a database at the path from an env var', () => {
|
||||
const promise = fs.stat(process.env.DATABASE_PATH!);
|
||||
return expect(promise).to.be.fulfilled;
|
||||
});
|
||||
|
||||
it('creates a database at the path passed on creation', () => {
|
||||
const db2 = new DB({ databasePath: process.env.DATABASE_PATH_2 });
|
||||
const promise = db2
|
||||
.init()
|
||||
.then(() => fs.stat(process.env.DATABASE_PATH_2!));
|
||||
return expect(promise).to.be.fulfilled;
|
||||
});
|
||||
|
||||
it('adds new fields and removes old ones in an old database', async () => {
|
||||
const databasePath = process.env.DATABASE_PATH_3!;
|
||||
|
||||
const knexForDB = await createOldDatabase(databasePath);
|
||||
const testDb = new DB({ databasePath });
|
||||
await testDb.init();
|
||||
await Bluebird.all([
|
||||
expect(knexForDB.schema.hasColumn('app', 'appId')).to.eventually.be.true,
|
||||
expect(knexForDB.schema.hasColumn('app', 'releaseId')).to.eventually.be
|
||||
.true,
|
||||
expect(knexForDB.schema.hasColumn('app', 'config')).to.eventually.be
|
||||
.false,
|
||||
expect(knexForDB.schema.hasColumn('app', 'privileged')).to.eventually.be
|
||||
.false,
|
||||
expect(knexForDB.schema.hasColumn('app', 'containerId')).to.eventually.be
|
||||
.false,
|
||||
expect(knexForDB.schema.hasColumn('dependentApp', 'environment')).to
|
||||
.eventually.be.true,
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'markedForDeletion'))
|
||||
.to.eventually.be.true,
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'localId')).to
|
||||
.eventually.be.true,
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'is_managed_by')).to
|
||||
.eventually.be.true,
|
||||
expect(knexForDB.schema.hasColumn('dependentDevice', 'lock_expiry_date'))
|
||||
.to.eventually.be.true,
|
||||
]);
|
||||
});
|
||||
|
||||
it('creates a deviceConfig table with a single default value', async () => {
|
||||
const deviceConfig = await db.models('deviceConfig').select();
|
||||
expect(deviceConfig).to.have.lengthOf(1);
|
||||
expect(deviceConfig).to.deep.equal([{ targetValues: '{}' }]);
|
||||
});
|
||||
|
||||
it('allows performing transactions', () => {
|
||||
return db.transaction(trx => expect(trx.commit()).to.be.fulfilled);
|
||||
});
|
||||
});
|
@ -1,126 +0,0 @@
|
||||
prepare = require './lib/prepare'
|
||||
Promise = require 'bluebird'
|
||||
chai = require './lib/chai-config'
|
||||
chai.use(require('chai-events'))
|
||||
{ expect } = chai
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
|
||||
{ DB } = require('../src/db')
|
||||
{ Config } = require('../src/config')
|
||||
constants = require('../src/lib/constants')
|
||||
|
||||
describe 'Config', ->
|
||||
before ->
|
||||
prepare()
|
||||
@db = new DB()
|
||||
@conf = new Config({ @db })
|
||||
@initialization = @db.init().then =>
|
||||
@conf.init()
|
||||
|
||||
it 'uses the correct config.json path', ->
|
||||
expect(@conf.configJsonBackend.path()).to.eventually.equal('test/data/config.json')
|
||||
|
||||
it 'uses the correct config.json path from the root mount when passed as argument to the constructor', ->
|
||||
conf2 = new Config({ @db, configPath: '/foo.json' })
|
||||
expect(conf2.configJsonBackend.path()).to.eventually.equal('test/data/foo.json')
|
||||
|
||||
it 'initializes correctly', ->
|
||||
expect(@initialization).to.be.fulfilled
|
||||
|
||||
it 'reads and exposes values from the config.json', ->
|
||||
promise = @conf.get('applicationId')
|
||||
expect(promise).to.eventually.equal(78373)
|
||||
|
||||
it 'allows reading several values in one getMany call', ->
|
||||
promise = @conf.getMany([ 'applicationId', 'apiEndpoint' ])
|
||||
expect(promise).to.eventually.deep.equal({ applicationId: 78373, apiEndpoint: 'https://api.resin.io' })
|
||||
|
||||
it 'generates a uuid and stores it in config.json', ->
|
||||
promise = @conf.get('uuid')
|
||||
promise2 = fs.readFileAsync('./test/data/config.json').then(JSON.parse).get('uuid')
|
||||
Promise.all([
|
||||
expect(promise).to.be.fulfilled
|
||||
expect(promise2).to.be.fulfilled
|
||||
]).then ([uuid1, uuid2]) ->
|
||||
expect(uuid1).to.be.a('string')
|
||||
expect(uuid1).to.have.lengthOf(62)
|
||||
expect(uuid1).to.equal(uuid2)
|
||||
|
||||
it 'does not allow setting an immutable field', ->
|
||||
promise = @conf.set({ username: 'somebody else' })
|
||||
# We catch it to avoid the unhandled error log
|
||||
promise.catch(->)
|
||||
expect(promise).to.be.rejected
|
||||
|
||||
it 'allows setting both config.json and database fields transparently', ->
|
||||
promise = @conf.set({ appUpdatePollInterval: 30000, name: 'a new device name' }).then =>
|
||||
@conf.getMany([ 'appUpdatePollInterval', 'name' ])
|
||||
expect(promise).to.eventually.deep.equal({ appUpdatePollInterval: 30000, name: 'a new device name' })
|
||||
|
||||
it 'allows removing a db key', ->
|
||||
promise = @conf.remove('apiSecret').then =>
|
||||
@conf.get('apiSecret')
|
||||
expect(promise).to.be.fulfilled
|
||||
expect(promise).to.eventually.be.undefined
|
||||
|
||||
it 'allows deleting a config.json key and returns a default value if none is set', ->
|
||||
promise = @conf.remove('appUpdatePollInterval').then =>
|
||||
@conf.get('appUpdatePollInterval')
|
||||
expect(promise).to.be.fulfilled
|
||||
expect(promise).to.eventually.equal(60000)
|
||||
|
||||
it 'allows deleting a config.json key if it is null', ->
|
||||
promise = @conf.set('apiKey': null).then =>
|
||||
@conf.get('apiKey')
|
||||
expect(promise).to.be.fulfilled
|
||||
expect(promise).to.eventually.be.undefined
|
||||
.then ->
|
||||
fs.readFileAsync('./test/data/config.json')
|
||||
.then(JSON.parse)
|
||||
.then (confFromFile) ->
|
||||
expect(confFromFile).to.not.have.property('apiKey')
|
||||
|
||||
it 'does not allow modifying or removing a function value', ->
|
||||
promise1 = @conf.remove('version')
|
||||
promise1.catch(->)
|
||||
promise2 = @conf.set(version: '2.0')
|
||||
promise2.catch(->)
|
||||
Promise.all([
|
||||
expect(promise1).to.be.rejected
|
||||
expect(promise2).to.be.rejected
|
||||
])
|
||||
|
||||
it 'throws when asked for an unknown key', ->
|
||||
promise = @conf.get('unknownInvalidValue')
|
||||
promise.catch(->)
|
||||
expect(promise).to.be.rejected
|
||||
|
||||
it 'emits a change event when values are set', (done) ->
|
||||
@conf.on 'change', (val) ->
|
||||
expect(val).to.deep.equal({ name: 'someValue' })
|
||||
done()
|
||||
@conf.set({ name: 'someValue' })
|
||||
expect(@conf).to.emit('change')
|
||||
return
|
||||
|
||||
it "returns an undefined OS variant if it doesn't exist", ->
|
||||
oldPath = constants.hostOSVersionPath
|
||||
constants.hostOSVersionPath = 'test/data/etc/os-release-novariant'
|
||||
@conf.get('osVariant')
|
||||
.then (osVariant) ->
|
||||
constants.hostOSVersionPath = oldPath
|
||||
expect(osVariant).to.be.undefined
|
||||
|
||||
describe 'Function config providers', ->
|
||||
before ->
|
||||
prepare()
|
||||
@db = new DB()
|
||||
@conf = new Config({ @db })
|
||||
@initialization = @db.init().then =>
|
||||
@conf.init()
|
||||
|
||||
it 'should throw if a non-mutable function provider is set', ->
|
||||
expect(@conf.set({ version: 'some-version' })).to.be.rejected
|
||||
|
||||
it 'should throw if a non-mutbale function provider is removed', ->
|
||||
expect(@conf.remove('version')).to.be.rejected
|
154
test/03-config.spec.ts
Normal file
154
test/03-config.spec.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import { fs } from 'mz';
|
||||
|
||||
import chai = require('./lib/chai-config');
|
||||
import prepare = require('./lib/prepare');
|
||||
|
||||
// tslint:disable-next-line
|
||||
chai.use(require('chai-events'));
|
||||
const { expect } = chai;
|
||||
|
||||
import Config from '../src/config';
|
||||
import DB from '../src/db';
|
||||
import constants = require('../src/lib/constants');
|
||||
|
||||
describe('Config', () => {
|
||||
let db: DB;
|
||||
let conf: Config;
|
||||
let initialization: Bluebird<unknown>;
|
||||
|
||||
before(() => {
|
||||
prepare();
|
||||
db = new DB();
|
||||
conf = new Config({ db });
|
||||
|
||||
initialization = db.init().then(() => conf.init());
|
||||
});
|
||||
|
||||
it('uses the correct config.json path', async () => {
|
||||
expect(await (conf as any).configJsonBackend.path()).to.equal(
|
||||
'test/data/config.json',
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the correct config.json path from the root mount when passed as argument to the constructor', async () => {
|
||||
const conf2 = new Config({ db, configPath: '/foo.json' });
|
||||
expect(await (conf2 as any).configJsonBackend.path()).to.equal(
|
||||
'test/data/foo.json',
|
||||
);
|
||||
});
|
||||
|
||||
it('initializes correctly', () => {
|
||||
return expect(initialization).to.be.fulfilled;
|
||||
});
|
||||
|
||||
it('reads and exposes values from the config.json', async () => {
|
||||
const id = await conf.get('applicationId');
|
||||
return expect(id).to.equal(78373);
|
||||
});
|
||||
|
||||
it('allows reading several values in one getMany call', async () => {
|
||||
return expect(
|
||||
await conf.getMany(['applicationId', 'apiEndpoint']),
|
||||
).to.deep.equal({
|
||||
applicationId: 78373,
|
||||
apiEndpoint: 'https://api.resin.io',
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a uuid and stores it in config.json', async () => {
|
||||
const uuid = await conf.get('uuid');
|
||||
const configJsonUuid = JSON.parse(
|
||||
await fs.readFile('./test/data/config.json', 'utf8'),
|
||||
).uuid;
|
||||
expect(uuid).to.be.a('string');
|
||||
expect(uuid).to.have.lengthOf(62);
|
||||
expect(uuid).to.equal(configJsonUuid);
|
||||
});
|
||||
|
||||
it('does not allow setting an immutable field', async () => {
|
||||
const promise = conf.set({ username: 'somebody else' });
|
||||
// We catch it to avoid the unhandled error log
|
||||
promise.catch(_.noop);
|
||||
return expect(promise).to.be.rejected;
|
||||
});
|
||||
|
||||
it('allows setting both config.json and database fields transparently', async () => {
|
||||
await conf.set({ appUpdatePollInterval: 30000, name: 'a new device name' });
|
||||
const config = await conf.getMany(['appUpdatePollInterval', 'name']);
|
||||
return expect(config).to.deep.equal({
|
||||
appUpdatePollInterval: 30000,
|
||||
name: 'a new device name',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows removing a db key', async () => {
|
||||
await conf.remove('apiSecret');
|
||||
const secret = await conf.get('apiSecret');
|
||||
return expect(secret).to.be.undefined;
|
||||
});
|
||||
|
||||
it('allows deleting a config.json key and returns a default value if none is set', async () => {
|
||||
await conf.remove('appUpdatePollInterval');
|
||||
const poll = await conf.get('appUpdatePollInterval');
|
||||
return expect(poll).to.equal(60000);
|
||||
});
|
||||
|
||||
it('allows deleting a config.json key if it is null', async () => {
|
||||
await conf.set({ apiKey: null });
|
||||
const key = await conf.get('apiKey');
|
||||
|
||||
expect(key).to.be.undefined;
|
||||
expect(
|
||||
JSON.parse(await fs.readFile('./test/data/config.json', 'utf8')),
|
||||
).to.not.have.property('apiKey');
|
||||
});
|
||||
|
||||
it('does not allow modifying or removing a function value', () => {
|
||||
// We have to cast to any below, as the type system will
|
||||
// not allow removing a function value
|
||||
expect(conf.remove('version' as any)).to.be.rejected;
|
||||
expect(conf.set({ version: '2.0' })).to.be.rejected;
|
||||
});
|
||||
|
||||
it('throws when asked for an unknown key', () => {
|
||||
expect(conf.get('unknownInvalidValue' as any)).to.be.rejected;
|
||||
});
|
||||
|
||||
it('emits a change event when values are set', done => {
|
||||
conf.on('change', val => {
|
||||
expect(val).to.deep.equal({ name: 'someValue' });
|
||||
return done();
|
||||
});
|
||||
conf.set({ name: 'someValue' });
|
||||
(expect(conf).to as any).emit('change');
|
||||
});
|
||||
|
||||
it("returns an undefined OS variant if it doesn't exist", async () => {
|
||||
const oldPath = constants.hostOSVersionPath;
|
||||
constants.hostOSVersionPath = 'test/data/etc/os-release-novariant';
|
||||
|
||||
const osVariant = await conf.get('osVariant');
|
||||
constants.hostOSVersionPath = oldPath;
|
||||
expect(osVariant).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('Function config providers', () => {
|
||||
before(async () => {
|
||||
prepare();
|
||||
db = new DB();
|
||||
conf = new Config({ db });
|
||||
await db.init();
|
||||
await conf.init();
|
||||
});
|
||||
|
||||
it('should throw if a non-mutable function provider is set', () => {
|
||||
expect(conf.set({ version: 'some-version' })).to.be.rejected;
|
||||
});
|
||||
|
||||
it('should throw if a non-mutable function provider is removed', () => {
|
||||
expect(conf.remove('version' as any)).to.be.rejected;
|
||||
});
|
||||
});
|
||||
});
|
@ -1,548 +0,0 @@
|
||||
{ assert, expect } = require './lib/chai-config'
|
||||
|
||||
_ = require 'lodash'
|
||||
|
||||
{ Service } = require '../src/compose/service'
|
||||
|
||||
configs = {
|
||||
simple: {
|
||||
compose: require('./data/docker-states/simple/compose.json')
|
||||
imageInfo: require('./data/docker-states/simple/imageInfo.json')
|
||||
inspect: require('./data/docker-states/simple/inspect.json')
|
||||
}
|
||||
entrypoint: {
|
||||
compose: require('./data/docker-states/entrypoint/compose.json')
|
||||
imageInfo: require('./data/docker-states/entrypoint/imageInfo.json')
|
||||
inspect: require('./data/docker-states/entrypoint/inspect.json')
|
||||
},
|
||||
networkModeService: {
|
||||
compose: require('./data/docker-states/network-mode-service/compose.json')
|
||||
imageInfo: require('./data/docker-states/network-mode-service/imageInfo.json')
|
||||
inspect: require('./data/docker-states/network-mode-service/inspect.json')
|
||||
}
|
||||
}
|
||||
|
||||
describe 'compose/service', ->
|
||||
|
||||
it 'extends environment variables properly', ->
|
||||
extendEnvVarsOpts = {
|
||||
uuid: '1234'
|
||||
appName: 'awesomeApp'
|
||||
commit: 'abcdef'
|
||||
name: 'awesomeDevice'
|
||||
version: 'v1.0.0'
|
||||
deviceType: 'raspberry-pi'
|
||||
osVersion: 'Resin OS 2.0.2'
|
||||
}
|
||||
service = {
|
||||
appId: '23'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
serviceName: 'serviceName'
|
||||
environment:
|
||||
FOO: 'bar'
|
||||
A_VARIABLE: 'ITS_VALUE'
|
||||
}
|
||||
s = Service.fromComposeObject(service, extendEnvVarsOpts)
|
||||
|
||||
expect(s.config.environment).to.deep.equal({
|
||||
FOO: 'bar'
|
||||
A_VARIABLE: 'ITS_VALUE'
|
||||
RESIN_APP_ID: '23'
|
||||
RESIN_APP_NAME: 'awesomeApp'
|
||||
RESIN_DEVICE_UUID: '1234'
|
||||
RESIN_DEVICE_TYPE: 'raspberry-pi'
|
||||
RESIN_HOST_OS_VERSION: 'Resin OS 2.0.2'
|
||||
RESIN_SERVICE_NAME: 'serviceName'
|
||||
RESIN_SUPERVISOR_VERSION: 'v1.0.0'
|
||||
RESIN_APP_LOCK_PATH: '/tmp/balena/updates.lock'
|
||||
RESIN_SERVICE_KILL_ME_PATH: '/tmp/balena/handover-complete'
|
||||
RESIN: '1'
|
||||
BALENA_APP_ID: '23'
|
||||
BALENA_APP_NAME: 'awesomeApp'
|
||||
BALENA_DEVICE_UUID: '1234'
|
||||
BALENA_DEVICE_TYPE: 'raspberry-pi'
|
||||
BALENA_HOST_OS_VERSION: 'Resin OS 2.0.2'
|
||||
BALENA_SERVICE_NAME: 'serviceName'
|
||||
BALENA_SUPERVISOR_VERSION: 'v1.0.0'
|
||||
BALENA_APP_LOCK_PATH: '/tmp/balena/updates.lock'
|
||||
BALENA_SERVICE_HANDOVER_COMPLETE_PATH: '/tmp/balena/handover-complete'
|
||||
BALENA: '1'
|
||||
USER: 'root'
|
||||
})
|
||||
|
||||
it 'returns the correct default bind mounts', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
}, { appName: 'foo' })
|
||||
binds = Service.defaultBinds(s.appId, s.serviceName)
|
||||
expect(binds).to.deep.equal([
|
||||
'/tmp/balena-supervisor/services/1234/foo:/tmp/resin'
|
||||
'/tmp/balena-supervisor/services/1234/foo:/tmp/balena'
|
||||
])
|
||||
|
||||
it 'produces the correct port bindings and exposed ports', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
expose: [
|
||||
1000,
|
||||
'243/udp'
|
||||
],
|
||||
ports: [
|
||||
'2344'
|
||||
'2345:2354'
|
||||
'2346:2367/udp'
|
||||
]
|
||||
}, {
|
||||
imageInfo: Config: {
|
||||
ExposedPorts: {
|
||||
'53/tcp': {}
|
||||
'53/udp': {}
|
||||
'2354/tcp': {}
|
||||
}
|
||||
}
|
||||
})
|
||||
ports = s.generateExposeAndPorts()
|
||||
expect(ports.portBindings).to.deep.equal({
|
||||
'2344/tcp': [{
|
||||
HostIp: '',
|
||||
HostPort: '2344'
|
||||
}],
|
||||
'2354/tcp': [{
|
||||
HostIp: '',
|
||||
HostPort: '2345'
|
||||
}],
|
||||
'2367/udp': [{
|
||||
HostIp: '',
|
||||
HostPort: '2346'
|
||||
}]
|
||||
})
|
||||
expect(ports.exposedPorts).to.deep.equal({
|
||||
'1000/tcp': {}
|
||||
'243/udp': {}
|
||||
'2344/tcp': {}
|
||||
'2354/tcp': {}
|
||||
'2367/udp': {}
|
||||
'53/tcp': {}
|
||||
'53/udp': {}
|
||||
})
|
||||
|
||||
it 'correctly handles port ranges', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
expose: [
|
||||
1000,
|
||||
'243/udp'
|
||||
],
|
||||
ports: [
|
||||
'1000-1003:2000-2003'
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
|
||||
ports = s.generateExposeAndPorts()
|
||||
expect(ports.portBindings).to.deep.equal({
|
||||
'2000/tcp': [
|
||||
HostIp: ''
|
||||
HostPort: '1000'
|
||||
],
|
||||
'2001/tcp': [
|
||||
HostIp: ''
|
||||
HostPort: '1001'
|
||||
],
|
||||
'2002/tcp': [
|
||||
HostIp: ''
|
||||
HostPort: '1002'
|
||||
],
|
||||
'2003/tcp': [
|
||||
HostIp: ''
|
||||
HostPort: '1003'
|
||||
]
|
||||
})
|
||||
|
||||
expect(ports.exposedPorts).to.deep.equal({
|
||||
'1000/tcp': {}
|
||||
'2000/tcp': {}
|
||||
'2001/tcp': {}
|
||||
'2002/tcp': {}
|
||||
'2003/tcp': {}
|
||||
'243/udp': {}
|
||||
})
|
||||
|
||||
it 'should correctly handle large port ranges', ->
|
||||
@timeout(60000)
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
ports: [
|
||||
'5-65536:5-65536/tcp'
|
||||
'5-65536:5-65536/udp'
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(s.generateExposeAndPorts()).to.not.throw
|
||||
|
||||
it 'should correctly report implied exposed ports from portMappings', ->
|
||||
service = Service.fromComposeObject({
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'test',
|
||||
ports: [
|
||||
'80:80'
|
||||
'100:100'
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(service.config).to.have.property('expose').that.deep.equals(['80/tcp', '100/tcp'])
|
||||
|
||||
describe 'Ordered array parameters', ->
|
||||
it 'Should correctly compare ordered array parameters', ->
|
||||
svc1 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
dns: [
|
||||
'8.8.8.8',
|
||||
'1.1.1.1',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
svc2 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
dns: [
|
||||
'8.8.8.8',
|
||||
'1.1.1.1',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
assert(svc1.isEqualConfig(svc2))
|
||||
|
||||
svc2 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
dns: [
|
||||
'1.1.1.1',
|
||||
'8.8.8.8',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
assert(!svc1.isEqualConfig(svc2))
|
||||
|
||||
it 'should correctly compare non-ordered array parameters', ->
|
||||
svc1 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: [
|
||||
'abcdef',
|
||||
'ghijk',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
svc2 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: [
|
||||
'abcdef',
|
||||
'ghijk',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
assert(svc1.isEqualConfig(svc2))
|
||||
|
||||
svc2 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: [
|
||||
'ghijk',
|
||||
'abcdef',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
assert(svc1.isEqualConfig(svc2))
|
||||
|
||||
it 'should correctly compare both ordered and non-ordered array parameters', ->
|
||||
svc1 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: [
|
||||
'abcdef',
|
||||
'ghijk',
|
||||
],
|
||||
dns: [
|
||||
'8.8.8.8',
|
||||
'1.1.1.1',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
svc2 = Service.fromComposeObject({
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: [
|
||||
'ghijk',
|
||||
'abcdef',
|
||||
],
|
||||
dns: [
|
||||
'8.8.8.8',
|
||||
'1.1.1.1',
|
||||
]
|
||||
}, { appName: 'test' })
|
||||
assert(svc1.isEqualConfig(svc2))
|
||||
|
||||
|
||||
describe 'parseMemoryNumber()', ->
|
||||
makeComposeServiceWithLimit = (memLimit) ->
|
||||
Service.fromComposeObject({
|
||||
appId: 123456
|
||||
serviceId: 123456
|
||||
serviceName: 'foobar'
|
||||
mem_limit: memLimit
|
||||
}, { appName: 'test' })
|
||||
|
||||
it 'should correctly parse memory number strings without a unit', ->
|
||||
expect(makeComposeServiceWithLimit('64').config.memLimit).to.equal(64)
|
||||
|
||||
it 'should correctly apply the default value', ->
|
||||
expect(makeComposeServiceWithLimit(undefined).config.memLimit).to.equal(0)
|
||||
|
||||
it 'should correctly support parsing numbers as memory limits', ->
|
||||
expect(makeComposeServiceWithLimit(64).config.memLimit).to.equal(64)
|
||||
|
||||
it 'should correctly parse memory number strings that use a byte unit', ->
|
||||
expect(makeComposeServiceWithLimit('64b').config.memLimit).to.equal(64)
|
||||
expect(makeComposeServiceWithLimit('64B').config.memLimit).to.equal(64)
|
||||
|
||||
it 'should correctly parse memory number strings that use a kilobyte unit', ->
|
||||
expect(makeComposeServiceWithLimit('64k').config.memLimit).to.equal(65536)
|
||||
expect(makeComposeServiceWithLimit('64K').config.memLimit).to.equal(65536)
|
||||
|
||||
expect(makeComposeServiceWithLimit('64kb').config.memLimit).to.equal(65536)
|
||||
expect(makeComposeServiceWithLimit('64Kb').config.memLimit).to.equal(65536)
|
||||
|
||||
it 'should correctly parse memory number strings that use a megabyte unit', ->
|
||||
expect(makeComposeServiceWithLimit('64m').config.memLimit).to.equal(67108864)
|
||||
expect(makeComposeServiceWithLimit('64M').config.memLimit).to.equal(67108864)
|
||||
|
||||
expect(makeComposeServiceWithLimit('64mb').config.memLimit).to.equal(67108864)
|
||||
expect(makeComposeServiceWithLimit('64Mb').config.memLimit).to.equal(67108864)
|
||||
|
||||
it 'should correctly parse memory number strings that use a gigabyte unit', ->
|
||||
expect(makeComposeServiceWithLimit('64g').config.memLimit).to.equal(68719476736)
|
||||
expect(makeComposeServiceWithLimit('64G').config.memLimit).to.equal(68719476736)
|
||||
|
||||
expect(makeComposeServiceWithLimit('64gb').config.memLimit).to.equal(68719476736)
|
||||
expect(makeComposeServiceWithLimit('64Gb').config.memLimit).to.equal(68719476736)
|
||||
|
||||
describe 'getWorkingDir', ->
|
||||
makeComposeServiceWithWorkdir = (workdir) ->
|
||||
Service.fromComposeObject({
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'foobar'
|
||||
workingDir: workdir
|
||||
}, { appName: 'test' })
|
||||
|
||||
it 'should remove a trailing slash', ->
|
||||
expect(makeComposeServiceWithWorkdir('/usr/src/app/').config.workingDir).to.equal('/usr/src/app')
|
||||
expect(makeComposeServiceWithWorkdir('/').config.workingDir).to.equal('/')
|
||||
expect(makeComposeServiceWithWorkdir('/usr/src/app').config.workingDir).to.equal('/usr/src/app')
|
||||
expect(makeComposeServiceWithWorkdir('').config.workingDir).to.equal('')
|
||||
|
||||
describe 'Docker <-> Compose config', ->
|
||||
|
||||
omitConfigForComparison = (config) ->
|
||||
return _.omit(config, ['running', 'networks'])
|
||||
|
||||
it 'should be identical when converting a simple service', ->
|
||||
composeSvc = Service.fromComposeObject(configs.simple.compose, configs.simple.imageInfo)
|
||||
dockerSvc = Service.fromDockerContainer(configs.simple.inspect)
|
||||
|
||||
composeConfig = omitConfigForComparison(composeSvc.config)
|
||||
dockerConfig = omitConfigForComparison(dockerSvc.config)
|
||||
expect(composeConfig).to.deep.equal(dockerConfig)
|
||||
|
||||
expect(dockerSvc.isEqualConfig(composeSvc)).to.be.true
|
||||
|
||||
|
||||
it 'should correct convert formats with a null entrypoint', ->
|
||||
composeSvc = Service.fromComposeObject(configs.entrypoint.compose, configs.entrypoint.imageInfo)
|
||||
dockerSvc = Service.fromDockerContainer(configs.entrypoint.inspect)
|
||||
|
||||
composeConfig = omitConfigForComparison(composeSvc.config)
|
||||
dockerConfig = omitConfigForComparison(dockerSvc.config)
|
||||
expect(composeConfig).to.deep.equal(dockerConfig)
|
||||
|
||||
expect(dockerSvc.isEqualConfig(composeSvc)).to.equals(true)
|
||||
|
||||
describe 'Networks', ->
|
||||
|
||||
it 'should correctly convert networks from compose to docker format', ->
|
||||
makeComposeServiceWithNetwork = (networks) ->
|
||||
Service.fromComposeObject({
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'test',
|
||||
networks
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(makeComposeServiceWithNetwork({
|
||||
'balena': {
|
||||
'ipv4Address': '1.2.3.4'
|
||||
}
|
||||
}).toDockerContainer({ deviceName: 'foo' }).NetworkingConfig).to.deep.equal({
|
||||
EndpointsConfig: {
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4'
|
||||
},
|
||||
Aliases: [
|
||||
'test'
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(makeComposeServiceWithNetwork({
|
||||
balena: {
|
||||
aliases: [ 'test', '1123']
|
||||
ipv4Address: '1.2.3.4'
|
||||
ipv6Address: '5.6.7.8'
|
||||
linkLocalIps: [ '123.123.123' ]
|
||||
}
|
||||
}).toDockerContainer({ deviceName: 'foo' }).NetworkingConfig).to.deep.equal({
|
||||
EndpointsConfig: {
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4'
|
||||
IPv6Address: '5.6.7.8'
|
||||
LinkLocalIPs: [ '123.123.123' ]
|
||||
}
|
||||
Aliases: [ 'test', '1123' ]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it 'should correctly convert Docker format to service format', ->
|
||||
dockerCfg = require('./data/docker-states/simple/inspect.json')
|
||||
makeServiceFromDockerWithNetwork = (networks) ->
|
||||
Service.fromDockerContainer(
|
||||
newConfig = _.cloneDeep(dockerCfg)
|
||||
newConfig.NetworkSettings = { Networks: networks }
|
||||
)
|
||||
|
||||
expect(makeServiceFromDockerWithNetwork({
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4'
|
||||
},
|
||||
Aliases: []
|
||||
}
|
||||
}).config.networks).to.deep.equal({
|
||||
'123456_balena': {
|
||||
'ipv4Address': '1.2.3.4'
|
||||
}
|
||||
})
|
||||
|
||||
expect(makeServiceFromDockerWithNetwork({
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4'
|
||||
IPv6Address: '5.6.7.8'
|
||||
LinkLocalIps: [ '123.123.123' ]
|
||||
}
|
||||
Aliases: [ 'test', '1123' ]
|
||||
}
|
||||
}).config.networks).to.deep.equal({
|
||||
'123456_balena': {
|
||||
ipv4Address: '1.2.3.4'
|
||||
ipv6Address: '5.6.7.8'
|
||||
linkLocalIps: [ '123.123.123' ]
|
||||
aliases: [ 'test', '1123' ]
|
||||
}
|
||||
})
|
||||
|
||||
describe 'Network mode=service:', ->
|
||||
it 'should correctly add a depends_on entry for the service', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
network_mode: 'service: test'
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(s.dependsOn).to.deep.equal(['test'])
|
||||
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
depends_on: [
|
||||
'another_service'
|
||||
]
|
||||
network_mode: 'service: test'
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(s.dependsOn).to.deep.equal([
|
||||
'another_service',
|
||||
'test'
|
||||
])
|
||||
|
||||
it 'should correctly convert a network_mode service: to a container:', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
network_mode: 'service: test'
|
||||
}, { appName: 'test' })
|
||||
expect(s.toDockerContainer({ deviceName: '', containerIds: { test: 'abcdef' } }))
|
||||
.to.have.property('HostConfig')
|
||||
.that.has.property('NetworkMode')
|
||||
.that.equals('container:abcdef')
|
||||
|
||||
it 'should not cause a container restart if a service: container has not changed', ->
|
||||
composeSvc = Service.fromComposeObject(configs.networkModeService.compose, configs.networkModeService.imageInfo)
|
||||
dockerSvc = Service.fromDockerContainer(configs.networkModeService.inspect)
|
||||
|
||||
composeConfig = omitConfigForComparison(composeSvc.config)
|
||||
dockerConfig = omitConfigForComparison(dockerSvc.config)
|
||||
expect(composeConfig).to.not.deep.equal(dockerConfig)
|
||||
|
||||
expect(dockerSvc.isEqualConfig(
|
||||
composeSvc,
|
||||
{ test: 'abcdef' }
|
||||
)).to.be.true
|
||||
|
||||
it 'should restart a container when its dependent network mode container changes', ->
|
||||
composeSvc = Service.fromComposeObject(configs.networkModeService.compose, configs.networkModeService.imageInfo)
|
||||
dockerSvc = Service.fromDockerContainer(configs.networkModeService.inspect)
|
||||
|
||||
composeConfig = omitConfigForComparison(composeSvc.config)
|
||||
dockerConfig = omitConfigForComparison(dockerSvc.config)
|
||||
expect(composeConfig).to.not.deep.equal(dockerConfig)
|
||||
|
||||
expect(dockerSvc.isEqualConfig(
|
||||
composeSvc,
|
||||
{ test: 'qwerty' }
|
||||
)).to.be.false
|
||||
|
662
test/04-service.spec.ts
Normal file
662
test/04-service.spec.ts
Normal file
@ -0,0 +1,662 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import ChaiConfig = require('./lib/chai-config');
|
||||
const { assert, expect } = ChaiConfig;
|
||||
|
||||
import Service from '../src/compose/service';
|
||||
import {
|
||||
ServiceComposeConfig,
|
||||
ServiceConfig,
|
||||
} from '../src/compose/types/service';
|
||||
|
||||
const configs = {
|
||||
simple: {
|
||||
compose: require('./data/docker-states/simple/compose.json'),
|
||||
imageInfo: require('./data/docker-states/simple/imageInfo.json'),
|
||||
inspect: require('./data/docker-states/simple/inspect.json'),
|
||||
},
|
||||
entrypoint: {
|
||||
compose: require('./data/docker-states/entrypoint/compose.json'),
|
||||
imageInfo: require('./data/docker-states/entrypoint/imageInfo.json'),
|
||||
inspect: require('./data/docker-states/entrypoint/inspect.json'),
|
||||
},
|
||||
networkModeService: {
|
||||
compose: require('./data/docker-states/network-mode-service/compose.json'),
|
||||
imageInfo: require('./data/docker-states/network-mode-service/imageInfo.json'),
|
||||
inspect: require('./data/docker-states/network-mode-service/inspect.json'),
|
||||
},
|
||||
};
|
||||
|
||||
describe('compose/service', () => {
|
||||
it('extends environment variables properly', () => {
|
||||
const extendEnvVarsOpts = {
|
||||
uuid: '1234',
|
||||
appName: 'awesomeApp',
|
||||
commit: 'abcdef',
|
||||
name: 'awesomeDevice',
|
||||
version: 'v1.0.0',
|
||||
deviceType: 'raspberry-pi',
|
||||
osVersion: 'Resin OS 2.0.2',
|
||||
};
|
||||
const service = {
|
||||
appId: '23',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
serviceName: 'serviceName',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
A_VARIABLE: 'ITS_VALUE',
|
||||
},
|
||||
};
|
||||
const s = Service.fromComposeObject(service, extendEnvVarsOpts as any);
|
||||
|
||||
expect(s.config.environment).to.deep.equal({
|
||||
FOO: 'bar',
|
||||
A_VARIABLE: 'ITS_VALUE',
|
||||
RESIN_APP_ID: '23',
|
||||
RESIN_APP_NAME: 'awesomeApp',
|
||||
RESIN_DEVICE_UUID: '1234',
|
||||
RESIN_DEVICE_TYPE: 'raspberry-pi',
|
||||
RESIN_HOST_OS_VERSION: 'Resin OS 2.0.2',
|
||||
RESIN_SERVICE_NAME: 'serviceName',
|
||||
RESIN_SUPERVISOR_VERSION: 'v1.0.0',
|
||||
RESIN_APP_LOCK_PATH: '/tmp/balena/updates.lock',
|
||||
RESIN_SERVICE_KILL_ME_PATH: '/tmp/balena/handover-complete',
|
||||
RESIN: '1',
|
||||
BALENA_APP_ID: '23',
|
||||
BALENA_APP_NAME: 'awesomeApp',
|
||||
BALENA_DEVICE_UUID: '1234',
|
||||
BALENA_DEVICE_TYPE: 'raspberry-pi',
|
||||
BALENA_HOST_OS_VERSION: 'Resin OS 2.0.2',
|
||||
BALENA_SERVICE_NAME: 'serviceName',
|
||||
BALENA_SUPERVISOR_VERSION: 'v1.0.0',
|
||||
BALENA_APP_LOCK_PATH: '/tmp/balena/updates.lock',
|
||||
BALENA_SERVICE_HANDOVER_COMPLETE_PATH: '/tmp/balena/handover-complete',
|
||||
BALENA: '1',
|
||||
USER: 'root',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the correct default bind mounts', () => {
|
||||
const s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
},
|
||||
{ appName: 'foo' } as any,
|
||||
);
|
||||
const binds = (Service as any).defaultBinds(s.appId, s.serviceName);
|
||||
expect(binds).to.deep.equal([
|
||||
'/tmp/balena-supervisor/services/1234/foo:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/foo:/tmp/balena',
|
||||
]);
|
||||
});
|
||||
|
||||
it('produces the correct port bindings and exposed ports', () => {
|
||||
const s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
expose: [1000, '243/udp'],
|
||||
ports: ['2344', '2345:2354', '2346:2367/udp'],
|
||||
},
|
||||
{
|
||||
imageInfo: {
|
||||
Config: {
|
||||
ExposedPorts: {
|
||||
'53/tcp': {},
|
||||
'53/udp': {},
|
||||
'2354/tcp': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
);
|
||||
|
||||
const ports = (s as any).generateExposeAndPorts();
|
||||
expect(ports.portBindings).to.deep.equal({
|
||||
'2344/tcp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '2344',
|
||||
},
|
||||
],
|
||||
'2354/tcp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '2345',
|
||||
},
|
||||
],
|
||||
'2367/udp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '2346',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(ports.exposedPorts).to.deep.equal({
|
||||
'1000/tcp': {},
|
||||
'243/udp': {},
|
||||
'2344/tcp': {},
|
||||
'2354/tcp': {},
|
||||
'2367/udp': {},
|
||||
'53/tcp': {},
|
||||
'53/udp': {},
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly handles port ranges', () => {
|
||||
const s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
expose: [1000, '243/udp'],
|
||||
ports: ['1000-1003:2000-2003'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
const ports = (s as any).generateExposeAndPorts();
|
||||
expect(ports.portBindings).to.deep.equal({
|
||||
'2000/tcp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '1000',
|
||||
},
|
||||
],
|
||||
'2001/tcp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '1001',
|
||||
},
|
||||
],
|
||||
'2002/tcp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '1002',
|
||||
},
|
||||
],
|
||||
'2003/tcp': [
|
||||
{
|
||||
HostIp: '',
|
||||
HostPort: '1003',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(ports.exposedPorts).to.deep.equal({
|
||||
'1000/tcp': {},
|
||||
'2000/tcp': {},
|
||||
'2001/tcp': {},
|
||||
'2002/tcp': {},
|
||||
'2003/tcp': {},
|
||||
'243/udp': {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly handle large port ranges', function() {
|
||||
this.timeout(60000);
|
||||
const s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
ports: ['5-65536:5-65536/tcp', '5-65536:5-65536/udp'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
expect((s as any).generateExposeAndPorts()).to.not.throw;
|
||||
});
|
||||
|
||||
it('should correctly report implied exposed ports from portMappings', () => {
|
||||
const service = Service.fromComposeObject(
|
||||
{
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'test',
|
||||
ports: ['80:80', '100:100'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
expect(service.config)
|
||||
.to.have.property('expose')
|
||||
.that.deep.equals(['80/tcp', '100/tcp']);
|
||||
});
|
||||
|
||||
describe('Ordered array parameters', () => {
|
||||
it('Should correctly compare ordered array parameters', () => {
|
||||
const svc1 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
dns: ['8.8.8.8', '1.1.1.1'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
let svc2 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
dns: ['8.8.8.8', '1.1.1.1'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
assert(svc1.isEqualConfig(svc2, {}));
|
||||
|
||||
svc2 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
dns: ['1.1.1.1', '8.8.8.8'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
assert(!svc1.isEqualConfig(svc2, {}));
|
||||
});
|
||||
|
||||
it('should correctly compare non-ordered array parameters', () => {
|
||||
const svc1 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: ['abcdef', 'ghijk'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
let svc2 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: ['abcdef', 'ghijk'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
assert(svc1.isEqualConfig(svc2, {}));
|
||||
|
||||
svc2 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: ['ghijk', 'abcdef'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
assert(svc1.isEqualConfig(svc2, {}));
|
||||
});
|
||||
|
||||
it('should correctly compare both ordered and non-ordered array parameters', () => {
|
||||
const svc1 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: ['abcdef', 'ghijk'],
|
||||
dns: ['8.8.8.8', '1.1.1.1'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
const svc2 = Service.fromComposeObject(
|
||||
{
|
||||
appId: 1,
|
||||
serviceId: 1,
|
||||
serviceName: 'test',
|
||||
volumes: ['ghijk', 'abcdef'],
|
||||
dns: ['8.8.8.8', '1.1.1.1'],
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
assert(svc1.isEqualConfig(svc2, {}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMemoryNumber()', () => {
|
||||
const makeComposeServiceWithLimit = (memLimit?: string | number) =>
|
||||
Service.fromComposeObject(
|
||||
{
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'foobar',
|
||||
mem_limit: memLimit,
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
it('should correctly parse memory number strings without a unit', () =>
|
||||
expect(makeComposeServiceWithLimit('64').config.memLimit).to.equal(64));
|
||||
|
||||
it('should correctly apply the default value', () =>
|
||||
expect(makeComposeServiceWithLimit(undefined).config.memLimit).to.equal(
|
||||
0,
|
||||
));
|
||||
|
||||
it('should correctly support parsing numbers as memory limits', () =>
|
||||
expect(makeComposeServiceWithLimit(64).config.memLimit).to.equal(64));
|
||||
|
||||
it('should correctly parse memory number strings that use a byte unit', () => {
|
||||
expect(makeComposeServiceWithLimit('64b').config.memLimit).to.equal(64);
|
||||
expect(makeComposeServiceWithLimit('64B').config.memLimit).to.equal(64);
|
||||
});
|
||||
|
||||
it('should correctly parse memory number strings that use a kilobyte unit', () => {
|
||||
expect(makeComposeServiceWithLimit('64k').config.memLimit).to.equal(
|
||||
65536,
|
||||
);
|
||||
expect(makeComposeServiceWithLimit('64K').config.memLimit).to.equal(
|
||||
65536,
|
||||
);
|
||||
|
||||
expect(makeComposeServiceWithLimit('64kb').config.memLimit).to.equal(
|
||||
65536,
|
||||
);
|
||||
expect(makeComposeServiceWithLimit('64Kb').config.memLimit).to.equal(
|
||||
65536,
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly parse memory number strings that use a megabyte unit', () => {
|
||||
expect(makeComposeServiceWithLimit('64m').config.memLimit).to.equal(
|
||||
67108864,
|
||||
);
|
||||
expect(makeComposeServiceWithLimit('64M').config.memLimit).to.equal(
|
||||
67108864,
|
||||
);
|
||||
|
||||
expect(makeComposeServiceWithLimit('64mb').config.memLimit).to.equal(
|
||||
67108864,
|
||||
);
|
||||
expect(makeComposeServiceWithLimit('64Mb').config.memLimit).to.equal(
|
||||
67108864,
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly parse memory number strings that use a gigabyte unit', () => {
|
||||
expect(makeComposeServiceWithLimit('64g').config.memLimit).to.equal(
|
||||
68719476736,
|
||||
);
|
||||
expect(makeComposeServiceWithLimit('64G').config.memLimit).to.equal(
|
||||
68719476736,
|
||||
);
|
||||
|
||||
expect(makeComposeServiceWithLimit('64gb').config.memLimit).to.equal(
|
||||
68719476736,
|
||||
);
|
||||
expect(makeComposeServiceWithLimit('64Gb').config.memLimit).to.equal(
|
||||
68719476736,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWorkingDir', () => {
|
||||
const makeComposeServiceWithWorkdir = (workdir?: string) =>
|
||||
Service.fromComposeObject(
|
||||
{
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'foobar',
|
||||
workingDir: workdir,
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
it('should remove a trailing slash', () => {
|
||||
expect(
|
||||
makeComposeServiceWithWorkdir('/usr/src/app/').config.workingDir,
|
||||
).to.equal('/usr/src/app');
|
||||
expect(makeComposeServiceWithWorkdir('/').config.workingDir).to.equal(
|
||||
'/',
|
||||
);
|
||||
expect(
|
||||
makeComposeServiceWithWorkdir('/usr/src/app').config.workingDir,
|
||||
).to.equal('/usr/src/app');
|
||||
expect(makeComposeServiceWithWorkdir('').config.workingDir).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Docker <-> Compose config', () => {
|
||||
const omitConfigForComparison = (config: ServiceConfig) =>
|
||||
_.omit(config, ['running', 'networks']);
|
||||
|
||||
it('should be identical when converting a simple service', () => {
|
||||
const composeSvc = Service.fromComposeObject(
|
||||
configs.simple.compose,
|
||||
configs.simple.imageInfo,
|
||||
);
|
||||
const dockerSvc = Service.fromDockerContainer(configs.simple.inspect);
|
||||
|
||||
const composeConfig = omitConfigForComparison(composeSvc.config);
|
||||
const dockerConfig = omitConfigForComparison(dockerSvc.config);
|
||||
expect(composeConfig).to.deep.equal(dockerConfig);
|
||||
|
||||
expect(dockerSvc.isEqualConfig(composeSvc, {})).to.be.true;
|
||||
});
|
||||
|
||||
it('should correct convert formats with a null entrypoint', () => {
|
||||
const composeSvc = Service.fromComposeObject(
|
||||
configs.entrypoint.compose,
|
||||
configs.entrypoint.imageInfo,
|
||||
);
|
||||
const dockerSvc = Service.fromDockerContainer(configs.entrypoint.inspect);
|
||||
|
||||
const composeConfig = omitConfigForComparison(composeSvc.config);
|
||||
const dockerConfig = omitConfigForComparison(dockerSvc.config);
|
||||
expect(composeConfig).to.deep.equal(dockerConfig);
|
||||
|
||||
expect(dockerSvc.isEqualConfig(composeSvc, {})).to.equals(true);
|
||||
});
|
||||
|
||||
describe('Networks', () => {
|
||||
it('should correctly convert networks from compose to docker format', () => {
|
||||
const makeComposeServiceWithNetwork = (
|
||||
networks: ServiceComposeConfig['networks'],
|
||||
) =>
|
||||
Service.fromComposeObject(
|
||||
{
|
||||
appId: 123456,
|
||||
serviceId: 123456,
|
||||
serviceName: 'test',
|
||||
networks,
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
expect(
|
||||
makeComposeServiceWithNetwork({
|
||||
balena: {
|
||||
ipv4Address: '1.2.3.4',
|
||||
},
|
||||
}).toDockerContainer({ deviceName: 'foo' } as any).NetworkingConfig,
|
||||
).to.deep.equal({
|
||||
EndpointsConfig: {
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4',
|
||||
},
|
||||
Aliases: ['test'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
makeComposeServiceWithNetwork({
|
||||
balena: {
|
||||
aliases: ['test', '1123'],
|
||||
ipv4Address: '1.2.3.4',
|
||||
ipv6Address: '5.6.7.8',
|
||||
linkLocalIps: ['123.123.123'],
|
||||
},
|
||||
}).toDockerContainer({ deviceName: 'foo' } as any).NetworkingConfig,
|
||||
).to.deep.equal({
|
||||
EndpointsConfig: {
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4',
|
||||
IPv6Address: '5.6.7.8',
|
||||
LinkLocalIPs: ['123.123.123'],
|
||||
},
|
||||
Aliases: ['test', '1123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly convert Docker format to service format', () => {
|
||||
const dockerCfg = require('./data/docker-states/simple/inspect.json');
|
||||
|
||||
const makeServiceFromDockerWithNetwork = (networks: {
|
||||
[name: string]: any;
|
||||
}) => {
|
||||
const newConfig = _.cloneDeep(dockerCfg);
|
||||
newConfig.NetworkSettings = {
|
||||
Networks: networks,
|
||||
};
|
||||
return Service.fromDockerContainer(newConfig);
|
||||
};
|
||||
|
||||
expect(
|
||||
makeServiceFromDockerWithNetwork({
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4',
|
||||
},
|
||||
Aliases: [],
|
||||
},
|
||||
}).config.networks,
|
||||
).to.deep.equal({
|
||||
'123456_balena': {
|
||||
ipv4Address: '1.2.3.4',
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
makeServiceFromDockerWithNetwork({
|
||||
'123456_balena': {
|
||||
IPAMConfig: {
|
||||
IPv4Address: '1.2.3.4',
|
||||
IPv6Address: '5.6.7.8',
|
||||
LinkLocalIps: ['123.123.123'],
|
||||
},
|
||||
Aliases: ['test', '1123'],
|
||||
},
|
||||
}).config.networks,
|
||||
).to.deep.equal({
|
||||
'123456_balena': {
|
||||
ipv4Address: '1.2.3.4',
|
||||
ipv6Address: '5.6.7.8',
|
||||
linkLocalIps: ['123.123.123'],
|
||||
aliases: ['test', '1123'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return describe('Network mode=service:', () => {
|
||||
it('should correctly add a depends_on entry for the service', () => {
|
||||
let s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
network_mode: 'service: test',
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
expect(s.dependsOn).to.deep.equal(['test']);
|
||||
|
||||
s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
depends_on: ['another_service'],
|
||||
network_mode: 'service: test',
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
|
||||
expect(s.dependsOn).to.deep.equal(['another_service', 'test']);
|
||||
});
|
||||
|
||||
it('should correctly convert a network_mode service: to a container:', () => {
|
||||
const s = Service.fromComposeObject(
|
||||
{
|
||||
appId: '1234',
|
||||
serviceName: 'foo',
|
||||
releaseId: 2,
|
||||
serviceId: 3,
|
||||
imageId: 4,
|
||||
network_mode: 'service: test',
|
||||
},
|
||||
{ appName: 'test' } as any,
|
||||
);
|
||||
return expect(
|
||||
s.toDockerContainer({
|
||||
deviceName: '',
|
||||
containerIds: { test: 'abcdef' },
|
||||
}),
|
||||
)
|
||||
.to.have.property('HostConfig')
|
||||
.that.has.property('NetworkMode')
|
||||
.that.equals('container:abcdef');
|
||||
});
|
||||
|
||||
it('should not cause a container restart if a service: container has not changed', () => {
|
||||
const composeSvc = Service.fromComposeObject(
|
||||
configs.networkModeService.compose,
|
||||
configs.networkModeService.imageInfo,
|
||||
);
|
||||
const dockerSvc = Service.fromDockerContainer(
|
||||
configs.networkModeService.inspect,
|
||||
);
|
||||
|
||||
const composeConfig = omitConfigForComparison(composeSvc.config);
|
||||
const dockerConfig = omitConfigForComparison(dockerSvc.config);
|
||||
expect(composeConfig).to.not.deep.equal(dockerConfig);
|
||||
|
||||
expect(dockerSvc.isEqualConfig(composeSvc, { test: 'abcdef' })).to.be
|
||||
.true;
|
||||
});
|
||||
|
||||
it('should restart a container when its dependent network mode container changes', () => {
|
||||
const composeSvc = Service.fromComposeObject(
|
||||
configs.networkModeService.compose,
|
||||
configs.networkModeService.imageInfo,
|
||||
);
|
||||
const dockerSvc = Service.fromDockerContainer(
|
||||
configs.networkModeService.inspect,
|
||||
);
|
||||
|
||||
const composeConfig = omitConfigForComparison(composeSvc.config);
|
||||
const dockerConfig = omitConfigForComparison(dockerSvc.config);
|
||||
expect(composeConfig).to.not.deep.equal(dockerConfig);
|
||||
|
||||
return expect(dockerSvc.isEqualConfig(composeSvc, { test: 'qwerty' }))
|
||||
.to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,311 +0,0 @@
|
||||
Promise = require 'bluebird'
|
||||
_ = require 'lodash'
|
||||
{ stub } = require 'sinon'
|
||||
chai = require './lib/chai-config'
|
||||
chai.use(require('chai-events'))
|
||||
{ expect } = chai
|
||||
|
||||
prepare = require './lib/prepare'
|
||||
DeviceState = require '../src/device-state'
|
||||
{ DB } = require('../src/db')
|
||||
{ Config } = require('../src/config')
|
||||
{ RPiConfigBackend } = require('../src/config/backend')
|
||||
|
||||
{ Service } = require '../src/compose/service'
|
||||
|
||||
mockedInitialConfig = {
|
||||
'RESIN_SUPERVISOR_CONNECTIVITY_CHECK': 'true'
|
||||
'RESIN_SUPERVISOR_DELTA': 'false'
|
||||
'RESIN_SUPERVISOR_DELTA_APPLY_TIMEOUT': '0'
|
||||
'RESIN_SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000'
|
||||
'RESIN_SUPERVISOR_DELTA_RETRY_COUNT': '30'
|
||||
'RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
|
||||
'RESIN_SUPERVISOR_DELTA_VERSION': '2'
|
||||
'RESIN_SUPERVISOR_INSTANT_UPDATE_TRIGGER': 'true'
|
||||
'RESIN_SUPERVISOR_LOCAL_MODE': 'false'
|
||||
'RESIN_SUPERVISOR_LOG_CONTROL': 'true'
|
||||
'RESIN_SUPERVISOR_OVERRIDE_LOCK': 'false'
|
||||
'RESIN_SUPERVISOR_POLL_INTERVAL': '60000'
|
||||
'RESIN_SUPERVISOR_VPN_CONTROL': 'true'
|
||||
}
|
||||
|
||||
testTarget1 = {
|
||||
local: {
|
||||
name: 'aDevice'
|
||||
config: {
|
||||
'HOST_CONFIG_gpu_mem': '256'
|
||||
'SUPERVISOR_CONNECTIVITY_CHECK': 'true'
|
||||
'SUPERVISOR_DELTA': 'false'
|
||||
'SUPERVISOR_DELTA_APPLY_TIMEOUT': '0'
|
||||
'SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000'
|
||||
'SUPERVISOR_DELTA_RETRY_COUNT': '30'
|
||||
'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
|
||||
'SUPERVISOR_DELTA_VERSION': '2'
|
||||
'SUPERVISOR_INSTANT_UPDATE_TRIGGER': 'true'
|
||||
'SUPERVISOR_LOCAL_MODE': 'false'
|
||||
'SUPERVISOR_LOG_CONTROL': 'true'
|
||||
'SUPERVISOR_OVERRIDE_LOCK': 'false'
|
||||
'SUPERVISOR_POLL_INTERVAL': '60000'
|
||||
'SUPERVISOR_VPN_CONTROL': 'true'
|
||||
'SUPERVISOR_PERSISTENT_LOGGING': 'false'
|
||||
}
|
||||
apps: {
|
||||
'1234': {
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'abcdef'
|
||||
releaseId: 1
|
||||
services: [
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
imageId: 12345
|
||||
serviceName: 'someservice'
|
||||
releaseId: 1
|
||||
image: 'registry2.resin.io/superapp/abcdef:latest'
|
||||
labels: {
|
||||
'io.resin.something': 'bar'
|
||||
}
|
||||
}
|
||||
]
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
testTarget2 = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
}
|
||||
apps: {
|
||||
'1234': {
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
}
|
||||
labels: {}
|
||||
},
|
||||
'24': {
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12346
|
||||
image: 'registry2.resin.io/superapp/afaff'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
}
|
||||
labels: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
testTargetWithDefaults2 = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'HOST_CONFIG_gpu_mem': '512'
|
||||
'SUPERVISOR_CONNECTIVITY_CHECK': 'true'
|
||||
'SUPERVISOR_DELTA': 'false'
|
||||
'SUPERVISOR_DELTA_APPLY_TIMEOUT': '0'
|
||||
'SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000'
|
||||
'SUPERVISOR_DELTA_RETRY_COUNT': '30'
|
||||
'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
|
||||
'SUPERVISOR_DELTA_VERSION': '2'
|
||||
'SUPERVISOR_INSTANT_UPDATE_TRIGGER': 'true'
|
||||
'SUPERVISOR_LOCAL_MODE': 'false'
|
||||
'SUPERVISOR_LOG_CONTROL': 'true'
|
||||
'SUPERVISOR_OVERRIDE_LOCK': 'false'
|
||||
'SUPERVISOR_POLL_INTERVAL': '60000'
|
||||
'SUPERVISOR_VPN_CONTROL': 'true'
|
||||
'SUPERVISOR_PERSISTENT_LOGGING': 'false'
|
||||
}
|
||||
apps: {
|
||||
'1234': {
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: [
|
||||
_.merge({ appId: 1234, serviceId: 23, releaseId: 2 }, _.clone(testTarget2.local.apps['1234'].services['23'])),
|
||||
_.merge({ appId: 1234, serviceId: 24, releaseId: 2 }, _.clone(testTarget2.local.apps['1234'].services['24']))
|
||||
]
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
testTargetInvalid = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: '1234'
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: '2'
|
||||
config: {}
|
||||
services: [
|
||||
{
|
||||
serviceId: '23'
|
||||
serviceName: 'aservice'
|
||||
imageId: '12345'
|
||||
image: 'registry2.resin.io/superapp/edfabc'
|
||||
config: {}
|
||||
environment: {
|
||||
' FOO': 'bar'
|
||||
}
|
||||
labels: {}
|
||||
},
|
||||
{
|
||||
serviceId: '24'
|
||||
serviceName: 'anotherService'
|
||||
imageId: '12346'
|
||||
image: 'registry2.resin.io/superapp/afaff'
|
||||
config: {}
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
}
|
||||
labels: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
describe 'deviceState', ->
|
||||
before ->
|
||||
prepare()
|
||||
@db = new DB()
|
||||
@config = new Config({ @db })
|
||||
@logger = {
|
||||
clearOutOfDateDBLogs: ->
|
||||
}
|
||||
eventTracker = {
|
||||
track: console.log
|
||||
}
|
||||
stub(Service, 'extendEnvVars').callsFake (env) ->
|
||||
env['ADDITIONAL_ENV_VAR'] = 'foo'
|
||||
return env
|
||||
@deviceState = new DeviceState({ @db, @config, eventTracker, @logger })
|
||||
stub(@deviceState.applications.docker, 'getNetworkGateway').returns(Promise.resolve('172.17.0.1'))
|
||||
stub(@deviceState.applications.images, 'inspectByName').callsFake ->
|
||||
Promise.try ->
|
||||
err = new Error()
|
||||
err.statusCode = 404
|
||||
throw err
|
||||
@deviceState.deviceConfig.configBackend = new RPiConfigBackend()
|
||||
@db.init()
|
||||
.then =>
|
||||
@config.init()
|
||||
|
||||
after ->
|
||||
Service.extendEnvVars.restore()
|
||||
@deviceState.applications.docker.getNetworkGateway.restore()
|
||||
@deviceState.applications.images.inspectByName.restore()
|
||||
|
||||
it 'loads a target state from an apps.json file and saves it as target state, then returns it', ->
|
||||
stub(@deviceState.applications.images, 'save').returns(Promise.resolve())
|
||||
stub(@deviceState.deviceConfig, 'getCurrent').returns(Promise.resolve(mockedInitialConfig))
|
||||
@deviceState.loadTargetFromFile(process.env.ROOT_MOUNTPOINT + '/apps.json')
|
||||
.then =>
|
||||
@deviceState.getTarget()
|
||||
.then (targetState) ->
|
||||
testTarget = _.cloneDeep(testTarget1)
|
||||
testTarget.local.apps['1234'].services = _.map testTarget.local.apps['1234'].services, (s) ->
|
||||
s.imageName = s.image
|
||||
return Service.fromComposeObject(s, { appName: 'superapp' })
|
||||
# We serialize and parse JSON to avoid checking fields that are functions or undefined
|
||||
expect(JSON.parse(JSON.stringify(targetState))).to.deep.equal(JSON.parse(JSON.stringify(testTarget)))
|
||||
.finally =>
|
||||
@deviceState.applications.images.save.restore()
|
||||
@deviceState.deviceConfig.getCurrent.restore()
|
||||
|
||||
it 'stores info for pinning a device after loading an apps.json with a pinDevice field', ->
|
||||
stub(@deviceState.applications.images, 'save').returns(Promise.resolve())
|
||||
stub(@deviceState.deviceConfig, 'getCurrent').returns(Promise.resolve(mockedInitialConfig))
|
||||
@deviceState.loadTargetFromFile(process.env.ROOT_MOUNTPOINT + '/apps-pin.json')
|
||||
.then =>
|
||||
@deviceState.applications.images.save.restore()
|
||||
@deviceState.deviceConfig.getCurrent.restore()
|
||||
|
||||
@config.get('pinDevice').then (pinned) ->
|
||||
expect(pinned).to.have.property('app').that.equals(1234)
|
||||
expect(pinned).to.have.property('commit').that.equals('abcdef')
|
||||
|
||||
it 'emits a change event when a new state is reported', ->
|
||||
@deviceState.reportCurrentState({ someStateDiff: 'someValue' })
|
||||
expect(@deviceState).to.emit('change')
|
||||
|
||||
it 'returns the current state'
|
||||
|
||||
it 'writes the target state to the db with some extra defaults', ->
|
||||
testTarget = _.cloneDeep(testTargetWithDefaults2)
|
||||
Promise.map testTarget.local.apps['1234'].services, (s) =>
|
||||
@deviceState.applications.images.normalise(s.image)
|
||||
.then (imageName) ->
|
||||
s.image = imageName
|
||||
s.imageName = imageName
|
||||
Service.fromComposeObject(s, { appName: 'supertest' })
|
||||
.then (services) =>
|
||||
testTarget.local.apps['1234'].services = services
|
||||
@deviceState.setTarget(testTarget2)
|
||||
.then =>
|
||||
@deviceState.getTarget()
|
||||
.then (target) ->
|
||||
expect(JSON.parse(JSON.stringify(target))).to.deep.equal(JSON.parse(JSON.stringify(testTarget)))
|
||||
|
||||
it 'does not allow setting an invalid target state', ->
|
||||
promise = @deviceState.setTarget(testTargetInvalid)
|
||||
promise.catch(->)
|
||||
expect(promise).to.be.rejected
|
||||
|
||||
it 'allows triggering applying the target state', (done) ->
|
||||
stub(@deviceState, 'applyTarget').returns(Promise.resolve())
|
||||
@deviceState.triggerApplyTarget({ force: true })
|
||||
expect(@deviceState.applyTarget).to.not.be.called
|
||||
setTimeout =>
|
||||
expect(@deviceState.applyTarget).to.be.calledWith({ force: true, initial: false })
|
||||
@deviceState.applyTarget.restore()
|
||||
done()
|
||||
, 5
|
||||
|
||||
it 'cancels current promise applying the target state', (done) ->
|
||||
@deviceState.scheduledApply = { force: false, delay: 100 }
|
||||
@deviceState.applyInProgress = true
|
||||
@deviceState.applyCancelled = false
|
||||
new Promise (resolve, reject) =>
|
||||
setTimeout(resolve, 100000)
|
||||
@deviceState.cancelDelay = reject
|
||||
.catch =>
|
||||
@deviceState.applyCancelled = true
|
||||
.finally =>
|
||||
expect(@deviceState.scheduledApply).to.deep.equal({ force: true, delay: 0 })
|
||||
expect(@deviceState.applyCancelled).to.be.true
|
||||
done()
|
||||
@deviceState.triggerApplyTarget({ force: true, isFromApi: true })
|
||||
|
||||
|
||||
it 'applies the target state for device config'
|
||||
|
||||
it 'applies the target state for applications'
|
384
test/05-device-state.spec.ts
Normal file
384
test/05-device-state.spec.ts
Normal file
@ -0,0 +1,384 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import { stub } from 'sinon';
|
||||
|
||||
import chai = require('./lib/chai-config');
|
||||
import prepare = require('./lib/prepare');
|
||||
// tslint:disable-next-line
|
||||
chai.use(require('chai-events'));
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
import Config from '../src/config';
|
||||
import { RPiConfigBackend } from '../src/config/backend';
|
||||
import DB from '../src/db';
|
||||
import DeviceState = require('../src/device-state');
|
||||
|
||||
import Service from '../src/compose/service';
|
||||
|
||||
const mockedInitialConfig = {
|
||||
RESIN_SUPERVISOR_CONNECTIVITY_CHECK: 'true',
|
||||
RESIN_SUPERVISOR_DELTA: 'false',
|
||||
RESIN_SUPERVISOR_DELTA_APPLY_TIMEOUT: '0',
|
||||
RESIN_SUPERVISOR_DELTA_REQUEST_TIMEOUT: '30000',
|
||||
RESIN_SUPERVISOR_DELTA_RETRY_COUNT: '30',
|
||||
RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL: '10000',
|
||||
RESIN_SUPERVISOR_DELTA_VERSION: '2',
|
||||
RESIN_SUPERVISOR_INSTANT_UPDATE_TRIGGER: 'true',
|
||||
RESIN_SUPERVISOR_LOCAL_MODE: 'false',
|
||||
RESIN_SUPERVISOR_LOG_CONTROL: 'true',
|
||||
RESIN_SUPERVISOR_OVERRIDE_LOCK: 'false',
|
||||
RESIN_SUPERVISOR_POLL_INTERVAL: '60000',
|
||||
RESIN_SUPERVISOR_VPN_CONTROL: 'true',
|
||||
};
|
||||
|
||||
const testTarget1 = {
|
||||
local: {
|
||||
name: 'aDevice',
|
||||
config: {
|
||||
HOST_CONFIG_gpu_mem: '256',
|
||||
SUPERVISOR_CONNECTIVITY_CHECK: 'true',
|
||||
SUPERVISOR_DELTA: 'false',
|
||||
SUPERVISOR_DELTA_APPLY_TIMEOUT: '0',
|
||||
SUPERVISOR_DELTA_REQUEST_TIMEOUT: '30000',
|
||||
SUPERVISOR_DELTA_RETRY_COUNT: '30',
|
||||
SUPERVISOR_DELTA_RETRY_INTERVAL: '10000',
|
||||
SUPERVISOR_DELTA_VERSION: '2',
|
||||
SUPERVISOR_INSTANT_UPDATE_TRIGGER: 'true',
|
||||
SUPERVISOR_LOCAL_MODE: 'false',
|
||||
SUPERVISOR_LOG_CONTROL: 'true',
|
||||
SUPERVISOR_OVERRIDE_LOCK: 'false',
|
||||
SUPERVISOR_POLL_INTERVAL: '60000',
|
||||
SUPERVISOR_VPN_CONTROL: 'true',
|
||||
SUPERVISOR_PERSISTENT_LOGGING: 'false',
|
||||
},
|
||||
apps: {
|
||||
'1234': {
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'abcdef',
|
||||
releaseId: 1,
|
||||
services: [
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
imageId: 12345,
|
||||
serviceName: 'someservice',
|
||||
releaseId: 1,
|
||||
image: 'registry2.resin.io/superapp/abcdef:latest',
|
||||
labels: {
|
||||
'io.resin.something': 'bar',
|
||||
},
|
||||
},
|
||||
],
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
const testTarget2 = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
},
|
||||
apps: {
|
||||
'1234': {
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
},
|
||||
labels: {},
|
||||
},
|
||||
'24': {
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12346,
|
||||
image: 'registry2.resin.io/superapp/afaff',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
},
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
const testTargetWithDefaults2 = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
HOST_CONFIG_gpu_mem: '512',
|
||||
SUPERVISOR_CONNECTIVITY_CHECK: 'true',
|
||||
SUPERVISOR_DELTA: 'false',
|
||||
SUPERVISOR_DELTA_APPLY_TIMEOUT: '0',
|
||||
SUPERVISOR_DELTA_REQUEST_TIMEOUT: '30000',
|
||||
SUPERVISOR_DELTA_RETRY_COUNT: '30',
|
||||
SUPERVISOR_DELTA_RETRY_INTERVAL: '10000',
|
||||
SUPERVISOR_DELTA_VERSION: '2',
|
||||
SUPERVISOR_INSTANT_UPDATE_TRIGGER: 'true',
|
||||
SUPERVISOR_LOCAL_MODE: 'false',
|
||||
SUPERVISOR_LOG_CONTROL: 'true',
|
||||
SUPERVISOR_OVERRIDE_LOCK: 'false',
|
||||
SUPERVISOR_POLL_INTERVAL: '60000',
|
||||
SUPERVISOR_VPN_CONTROL: 'true',
|
||||
SUPERVISOR_PERSISTENT_LOGGING: 'false',
|
||||
},
|
||||
apps: {
|
||||
'1234': {
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [
|
||||
_.merge(
|
||||
{ appId: 1234, serviceId: 23, releaseId: 2 },
|
||||
_.clone(testTarget2.local.apps['1234'].services['23']),
|
||||
),
|
||||
_.merge(
|
||||
{ appId: 1234, serviceId: 24, releaseId: 2 },
|
||||
_.clone(testTarget2.local.apps['1234'].services['24']),
|
||||
),
|
||||
],
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
const testTargetInvalid = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: '1234',
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: '2',
|
||||
config: {},
|
||||
services: [
|
||||
{
|
||||
serviceId: '23',
|
||||
serviceName: 'aservice',
|
||||
imageId: '12345',
|
||||
image: 'registry2.resin.io/superapp/edfabc',
|
||||
config: {},
|
||||
environment: {
|
||||
' FOO': 'bar',
|
||||
},
|
||||
labels: {},
|
||||
},
|
||||
{
|
||||
serviceId: '24',
|
||||
serviceName: 'anotherService',
|
||||
imageId: '12346',
|
||||
image: 'registry2.resin.io/superapp/afaff',
|
||||
config: {},
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
},
|
||||
labels: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
describe('deviceState', () => {
|
||||
const db = new DB();
|
||||
const config = new Config({ db });
|
||||
const logger = {
|
||||
clearOutOfDateDBLogs() {
|
||||
/* noop */
|
||||
},
|
||||
};
|
||||
let deviceState: DeviceState;
|
||||
before(async () => {
|
||||
prepare();
|
||||
const eventTracker = {
|
||||
track: console.log,
|
||||
};
|
||||
|
||||
stub(Service as any, 'extendEnvVars').callsFake(env => {
|
||||
env['ADDITIONAL_ENV_VAR'] = 'foo';
|
||||
return env;
|
||||
});
|
||||
|
||||
deviceState = new DeviceState({
|
||||
db,
|
||||
config,
|
||||
eventTracker: eventTracker as any,
|
||||
logger: logger as any,
|
||||
});
|
||||
|
||||
stub(deviceState.applications.docker, 'getNetworkGateway').returns(
|
||||
Promise.resolve('172.17.0.1'),
|
||||
);
|
||||
|
||||
stub(deviceState.applications.images, 'inspectByName').callsFake(() => {
|
||||
const err: any = new Error();
|
||||
err.statusCode = 404;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
(deviceState as any).deviceConfig.configBackend = new RPiConfigBackend();
|
||||
await db.init();
|
||||
await config.init();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
(Service as any).extendEnvVars.restore();
|
||||
(deviceState.applications.docker
|
||||
.getNetworkGateway as sinon.SinonStub).restore();
|
||||
(deviceState.applications.images
|
||||
.inspectByName as sinon.SinonStub).restore();
|
||||
});
|
||||
|
||||
it('loads a target state from an apps.json file and saves it as target state, then returns it', async () => {
|
||||
stub(deviceState.applications.images, 'save').returns(Promise.resolve());
|
||||
stub(deviceState.deviceConfig, 'getCurrent').returns(
|
||||
Promise.resolve(mockedInitialConfig),
|
||||
);
|
||||
|
||||
try {
|
||||
await deviceState.loadTargetFromFile(
|
||||
process.env.ROOT_MOUNTPOINT + '/apps.json',
|
||||
);
|
||||
const targetState = await deviceState.getTarget();
|
||||
|
||||
const testTarget = _.cloneDeep(testTarget1);
|
||||
testTarget.local.apps['1234'].services = _.map(
|
||||
testTarget.local.apps['1234'].services,
|
||||
(s: any) => {
|
||||
s.imageName = s.image;
|
||||
return Service.fromComposeObject(s, { appName: 'superapp' } as any);
|
||||
},
|
||||
) as any;
|
||||
|
||||
expect(JSON.parse(JSON.stringify(targetState))).to.deep.equal(
|
||||
JSON.parse(JSON.stringify(testTarget)),
|
||||
);
|
||||
} finally {
|
||||
(deviceState.applications.images.save as sinon.SinonStub).restore();
|
||||
(deviceState.deviceConfig.getCurrent as sinon.SinonStub).restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('stores info for pinning a device after loading an apps.json with a pinDevice field', () => {
|
||||
stub(deviceState.applications.images, 'save').returns(Promise.resolve());
|
||||
stub(deviceState.deviceConfig, 'getCurrent').returns(
|
||||
Promise.resolve(mockedInitialConfig),
|
||||
);
|
||||
deviceState
|
||||
.loadTargetFromFile(process.env.ROOT_MOUNTPOINT + '/apps-pin.json')
|
||||
.then(() => {
|
||||
(deviceState as any).applications.images.save.restore();
|
||||
(deviceState as any).deviceConfig.getCurrent.restore();
|
||||
|
||||
config.get('pinDevice').then(pinned => {
|
||||
expect(pinned)
|
||||
.to.have.property('app')
|
||||
.that.equals(1234);
|
||||
expect(pinned)
|
||||
.to.have.property('commit')
|
||||
.that.equals('abcdef');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('emits a change event when a new state is reported', () => {
|
||||
deviceState.reportCurrentState({ someStateDiff: 'someValue' });
|
||||
return (expect as any)(deviceState).to.emit('change');
|
||||
});
|
||||
|
||||
it('returns the current state');
|
||||
|
||||
it('writes the target state to the db with some extra defaults', async () => {
|
||||
const testTarget = _.cloneDeep(testTargetWithDefaults2);
|
||||
|
||||
const services: Service[] = [];
|
||||
for (const service of testTarget.local.apps['1234'].services) {
|
||||
const imageName = await (deviceState.applications
|
||||
.images as any).normalise(service.image);
|
||||
service.image = imageName;
|
||||
(service as any).imageName = imageName;
|
||||
services.push(
|
||||
Service.fromComposeObject(service, { appName: 'supertest' } as any),
|
||||
);
|
||||
}
|
||||
|
||||
(testTarget as any).local.apps['1234'].services = services;
|
||||
await deviceState.setTarget(testTarget2);
|
||||
const target = await deviceState.getTarget();
|
||||
expect(JSON.parse(JSON.stringify(target))).to.deep.equal(
|
||||
JSON.parse(JSON.stringify(testTarget)),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not allow setting an invalid target state', () => {
|
||||
expect(deviceState.setTarget(testTargetInvalid)).to.be.rejected;
|
||||
});
|
||||
|
||||
it('allows triggering applying the target state', done => {
|
||||
stub(deviceState as any, 'applyTarget').returns(Promise.resolve());
|
||||
|
||||
deviceState.triggerApplyTarget({ force: true });
|
||||
expect((deviceState as any).applyTarget).to.not.be.called;
|
||||
|
||||
setTimeout(() => {
|
||||
expect((deviceState as any).applyTarget).to.be.calledWith({
|
||||
force: true,
|
||||
initial: false,
|
||||
});
|
||||
(deviceState as any).applyTarget.restore();
|
||||
done();
|
||||
}, 5);
|
||||
});
|
||||
|
||||
it('cancels current promise applying the target state', done => {
|
||||
(deviceState as any).scheduledApply = { force: false, delay: 100 };
|
||||
(deviceState as any).applyInProgress = true;
|
||||
(deviceState as any).applyCancelled = false;
|
||||
|
||||
new Bluebird((resolve, reject) => {
|
||||
setTimeout(resolve, 100000);
|
||||
(deviceState as any).cancelDelay = reject;
|
||||
})
|
||||
.catch(() => {
|
||||
(deviceState as any).applyCancelled = true;
|
||||
})
|
||||
.finally(() => {
|
||||
expect((deviceState as any).scheduledApply).to.deep.equal({
|
||||
force: true,
|
||||
delay: 0,
|
||||
});
|
||||
expect((deviceState as any).applyCancelled).to.be.true;
|
||||
done();
|
||||
});
|
||||
|
||||
deviceState.triggerApplyTarget({ force: true, isFromApi: true });
|
||||
});
|
||||
|
||||
it('applies the target state for device config');
|
||||
|
||||
it('applies the target state for applications');
|
||||
});
|
@ -1,54 +0,0 @@
|
||||
Promise = require 'bluebird'
|
||||
iptables = require '../src/lib/iptables'
|
||||
|
||||
{ stub } = require 'sinon'
|
||||
{ expect } = require './lib/chai-config'
|
||||
|
||||
describe 'iptables', ->
|
||||
it 'calls iptables to delete and recreate rules to block a port', ->
|
||||
stub(iptables, 'execAsync').returns(Promise.resolve())
|
||||
iptables.rejectOnAllInterfacesExcept(['foo', 'bar'], 42)
|
||||
.then ->
|
||||
expect(iptables.execAsync.callCount).to.equal(12)
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -A INPUT -p tcp --dport 42 -j REJECT')
|
||||
.then ->
|
||||
iptables.execAsync.restore()
|
||||
|
||||
it "falls back to blocking the port with DROP if there's no REJECT support", ->
|
||||
stub(iptables, 'execAsync').callsFake (cmd) ->
|
||||
if /REJECT$/.test(cmd)
|
||||
Promise.reject(new Error())
|
||||
else
|
||||
Promise.resolve()
|
||||
iptables.rejectOnAllInterfacesExcept(['foo', 'bar'], 42)
|
||||
.then ->
|
||||
expect(iptables.execAsync.callCount).to.equal(16)
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j DROP')
|
||||
expect(iptables.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j DROP')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -A INPUT -p tcp --dport 42 -j REJECT')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -D INPUT -p tcp --dport 42 -j DROP')
|
||||
expect(iptables.execAsync).to.be.calledWith('ip6tables -A INPUT -p tcp --dport 42 -j DROP')
|
||||
.then ->
|
||||
iptables.execAsync.restore()
|
114
test/06-iptables.spec.ts
Normal file
114
test/06-iptables.spec.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { stub } from 'sinon';
|
||||
import { expect } from './lib/chai-config';
|
||||
|
||||
import * as iptables from '../src/lib/iptables';
|
||||
|
||||
describe('iptables', async () => {
|
||||
it('calls iptables to delete and recreate rules to block a port', async () => {
|
||||
stub(iptables, 'execAsync').returns(Bluebird.resolve(''));
|
||||
|
||||
await iptables.rejectOnAllInterfacesExcept(['foo', 'bar'], 42);
|
||||
expect((iptables.execAsync as sinon.SinonStub).callCount).to.equal(12);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -A INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -A INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
(iptables.execAsync as sinon.SinonStub).restore();
|
||||
});
|
||||
|
||||
it("falls back to blocking the port with DROP if there's no REJECT support", async () => {
|
||||
stub(iptables, 'execAsync').callsFake(cmd => {
|
||||
if (/REJECT$/.test(cmd)) {
|
||||
return Bluebird.reject(new Error());
|
||||
} else {
|
||||
return Bluebird.resolve('');
|
||||
}
|
||||
});
|
||||
|
||||
await iptables.rejectOnAllInterfacesExcept(['foo', 'bar'], 42);
|
||||
expect((iptables.execAsync as sinon.SinonStub).callCount).to.equal(16);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -A INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -D INPUT -p tcp --dport 42 -j DROP',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'iptables -A INPUT -p tcp --dport 42 -j DROP',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -A INPUT -p tcp --dport 42 -j REJECT',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -D INPUT -p tcp --dport 42 -j DROP',
|
||||
);
|
||||
expect(iptables.execAsync).to.be.calledWith(
|
||||
'ip6tables -A INPUT -p tcp --dport 42 -j DROP',
|
||||
);
|
||||
|
||||
(iptables.execAsync as sinon.SinonStub).restore();
|
||||
});
|
||||
});
|
@ -1,218 +0,0 @@
|
||||
_ = require 'lodash'
|
||||
{ expect } = require './lib/chai-config'
|
||||
|
||||
validation = require '../src/lib/validation'
|
||||
|
||||
almostTooLongText = _.map([0...255], -> 'a').join('')
|
||||
|
||||
describe 'validation', ->
|
||||
|
||||
describe 'checkTruthy', ->
|
||||
it 'returns true for a truthy value', ->
|
||||
expect(validation.checkTruthy(true)).to.equal(true)
|
||||
expect(validation.checkTruthy('true')).to.equal(true)
|
||||
expect(validation.checkTruthy('1')).to.equal(true)
|
||||
expect(validation.checkTruthy(1)).to.equal(true)
|
||||
expect(validation.checkTruthy('on')).to.equal(true)
|
||||
|
||||
it 'returns false for a falsy value', ->
|
||||
expect(validation.checkTruthy(false)).to.equal(false)
|
||||
expect(validation.checkTruthy('false')).to.equal(false)
|
||||
expect(validation.checkTruthy('0')).to.equal(false)
|
||||
expect(validation.checkTruthy(0)).to.equal(false)
|
||||
expect(validation.checkTruthy('off')).to.equal(false)
|
||||
|
||||
it 'returns undefined for invalid values', ->
|
||||
expect(validation.checkTruthy({})).to.be.undefined
|
||||
expect(validation.checkTruthy(10)).to.be.undefined
|
||||
expect(validation.checkTruthy('on1')).to.be.undefined
|
||||
expect(validation.checkTruthy('foo')).to.be.undefined
|
||||
expect(validation.checkTruthy(undefined)).to.be.undefined
|
||||
expect(validation.checkTruthy(null)).to.be.undefined
|
||||
expect(validation.checkTruthy('')).to.be.undefined
|
||||
|
||||
describe 'checkString', ->
|
||||
it 'validates a string', ->
|
||||
expect(validation.checkString('foo')).to.equal('foo')
|
||||
expect(validation.checkString('bar')).to.equal('bar')
|
||||
|
||||
it 'returns undefined for empty strings or strings that equal null or undefined', ->
|
||||
expect(validation.checkString('')).to.be.undefined
|
||||
expect(validation.checkString('null')).to.be.undefined
|
||||
expect(validation.checkString('undefined')).to.be.undefined
|
||||
|
||||
it 'returns undefined for things that are not strings', ->
|
||||
expect(validation.checkString({})).to.be.undefined
|
||||
expect(validation.checkString([])).to.be.undefined
|
||||
expect(validation.checkString(123)).to.be.undefined
|
||||
expect(validation.checkString(0)).to.be.undefined
|
||||
expect(validation.checkString(null)).to.be.undefined
|
||||
expect(validation.checkString(undefined)).to.be.undefined
|
||||
|
||||
describe 'checkInt', ->
|
||||
it 'returns an integer for a string that can be parsed as one', ->
|
||||
expect(validation.checkInt('200')).to.equal(200)
|
||||
expect(validation.checkInt('0')).to.equal(0)
|
||||
expect(validation.checkInt('-3')).to.equal(-3)
|
||||
it 'returns the same integer when passed an integer', ->
|
||||
expect(validation.checkInt(345)).to.equal(345)
|
||||
expect(validation.checkInt(-345)).to.equal(-345)
|
||||
it 'returns undefined when passed something that can\'t be parsed as int', ->
|
||||
expect(validation.checkInt({})).to.be.undefined
|
||||
expect(validation.checkInt([])).to.be.undefined
|
||||
expect(validation.checkInt('foo')).to.be.undefined
|
||||
expect(validation.checkInt(null)).to.be.undefined
|
||||
expect(validation.checkInt(undefined)).to.be.undefined
|
||||
it 'returns undefined when passed a negative or zero value and the positive option is set', ->
|
||||
expect(validation.checkInt('-3', positive: true)).to.be.undefined
|
||||
expect(validation.checkInt('0', positive: true)).to.be.undefined
|
||||
|
||||
describe 'isValidShortText', ->
|
||||
it 'returns true for a short text', ->
|
||||
expect(validation.isValidShortText('foo')).to.equal(true)
|
||||
expect(validation.isValidShortText('')).to.equal(true)
|
||||
expect(validation.isValidShortText(almostTooLongText)).to.equal(true)
|
||||
it 'returns false for a text longer than 255 characters', ->
|
||||
expect(validation.isValidShortText(almostTooLongText + 'a')).to.equal(false)
|
||||
it 'returns false when passed a non-string', ->
|
||||
expect(validation.isValidShortText({})).to.equal(false)
|
||||
expect(validation.isValidShortText(1)).to.equal(false)
|
||||
expect(validation.isValidShortText(null)).to.equal(false)
|
||||
expect(validation.isValidShortText(undefined)).to.equal(false)
|
||||
|
||||
describe 'isValidAppsObject', ->
|
||||
it 'returns true for a valid object', ->
|
||||
apps = {
|
||||
'1234': {
|
||||
name: 'something'
|
||||
releaseId: 123
|
||||
commit: 'bar'
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz'
|
||||
imageId: 34
|
||||
image: 'foo'
|
||||
environment: {}
|
||||
labels: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(true)
|
||||
it 'returns false with an invalid environment', ->
|
||||
apps = {
|
||||
'1234': {
|
||||
name: 'something'
|
||||
releaseId: 123
|
||||
commit: 'bar'
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz'
|
||||
imageId: 34
|
||||
image: 'foo'
|
||||
environment: { ' baz': 'bat' }
|
||||
labels: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(false)
|
||||
it 'returns false with an invalid appId', ->
|
||||
apps = {
|
||||
'boo': {
|
||||
name: 'something'
|
||||
releaseId: 123
|
||||
commit: 'bar'
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz'
|
||||
imageId: 34
|
||||
image: 'foo'
|
||||
environment: {}
|
||||
labels: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(false)
|
||||
|
||||
it 'returns true with a missing releaseId', ->
|
||||
apps = {
|
||||
'1234': {
|
||||
name: 'something'
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz'
|
||||
imageId: 34
|
||||
image: 'foo'
|
||||
environment: {}
|
||||
labels: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(true)
|
||||
|
||||
it 'returns false with an invalid releaseId', ->
|
||||
apps = {
|
||||
'1234': {
|
||||
name: 'something'
|
||||
releaseId: '123a'
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz'
|
||||
imageId: 34
|
||||
image: 'foo'
|
||||
environment: {}
|
||||
labels: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(true)
|
||||
|
||||
describe 'isValidDependentDevicesObject', ->
|
||||
it 'returns true for a valid object', ->
|
||||
devices = {}
|
||||
devices[almostTooLongText] = {
|
||||
name: 'foo'
|
||||
apps: {
|
||||
'234': {
|
||||
config: { bar: 'baz' }
|
||||
environment: { dead: 'beef' }
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(true)
|
||||
it 'returns false with a missing apps object', ->
|
||||
devices = {
|
||||
'abcd1234': {
|
||||
name: 'foo'
|
||||
}
|
||||
}
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(false)
|
||||
it 'returns false with an invalid environment', ->
|
||||
devices = {
|
||||
'abcd1234': {
|
||||
name: 'foo'
|
||||
apps: {
|
||||
'234': {
|
||||
config: { bar: 'baz' }
|
||||
environment: { dead: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(false)
|
||||
it 'returns false if the uuid is too long', ->
|
||||
devices = {}
|
||||
devices[almostTooLongText + 'a'] = {
|
||||
name: 'foo'
|
||||
apps: {
|
||||
'234': {
|
||||
config: { bar: 'baz' }
|
||||
environment: { dead: 'beef' }
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(false)
|
258
test/07-validation.spec.ts
Normal file
258
test/07-validation.spec.ts
Normal file
@ -0,0 +1,258 @@
|
||||
import * as _ from 'lodash';
|
||||
import { expect } from './lib/chai-config';
|
||||
|
||||
import * as validation from '../src/lib/validation';
|
||||
|
||||
const almostTooLongText = _.times(255, () => 'a').join('');
|
||||
|
||||
describe('validation', () => {
|
||||
describe('checkTruthy', () => {
|
||||
it('returns true for a truthy value', () => {
|
||||
expect(validation.checkTruthy(true)).to.equal(true);
|
||||
expect(validation.checkTruthy('true')).to.equal(true);
|
||||
expect(validation.checkTruthy('1')).to.equal(true);
|
||||
expect(validation.checkTruthy(1)).to.equal(true);
|
||||
expect(validation.checkTruthy('on')).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false for a falsy value', () => {
|
||||
expect(validation.checkTruthy(false)).to.equal(false);
|
||||
expect(validation.checkTruthy('false')).to.equal(false);
|
||||
expect(validation.checkTruthy('0')).to.equal(false);
|
||||
expect(validation.checkTruthy(0)).to.equal(false);
|
||||
expect(validation.checkTruthy('off')).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns undefined for invalid values', () => {
|
||||
expect(validation.checkTruthy({})).to.be.undefined;
|
||||
expect(validation.checkTruthy(10)).to.be.undefined;
|
||||
expect(validation.checkTruthy('on1')).to.be.undefined;
|
||||
expect(validation.checkTruthy('foo')).to.be.undefined;
|
||||
expect(validation.checkTruthy(undefined)).to.be.undefined;
|
||||
expect(validation.checkTruthy(null)).to.be.undefined;
|
||||
expect(validation.checkTruthy('')).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkString', () => {
|
||||
it('validates a string', () => {
|
||||
expect(validation.checkString('foo')).to.equal('foo');
|
||||
expect(validation.checkString('bar')).to.equal('bar');
|
||||
});
|
||||
|
||||
it('returns undefined for empty strings or strings that equal null or undefined', () => {
|
||||
expect(validation.checkString('')).to.be.undefined;
|
||||
expect(validation.checkString('null')).to.be.undefined;
|
||||
expect(validation.checkString('undefined')).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns undefined for things that are not strings', () => {
|
||||
expect(validation.checkString({})).to.be.undefined;
|
||||
expect(validation.checkString([])).to.be.undefined;
|
||||
expect(validation.checkString(123)).to.be.undefined;
|
||||
expect(validation.checkString(0)).to.be.undefined;
|
||||
expect(validation.checkString(null)).to.be.undefined;
|
||||
expect(validation.checkString(undefined)).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkInt', () => {
|
||||
it('returns an integer for a string that can be parsed as one', () => {
|
||||
expect(validation.checkInt('200')).to.equal(200);
|
||||
expect(validation.checkInt('0')).to.equal(0);
|
||||
expect(validation.checkInt('-3')).to.equal(-3);
|
||||
});
|
||||
|
||||
it('returns the same integer when passed an integer', () => {
|
||||
expect(validation.checkInt(345)).to.equal(345);
|
||||
return expect(validation.checkInt(-345)).to.equal(-345);
|
||||
});
|
||||
|
||||
it("returns undefined when passed something that can't be parsed as int", () => {
|
||||
expect(validation.checkInt({})).to.be.undefined;
|
||||
expect(validation.checkInt([])).to.be.undefined;
|
||||
expect(validation.checkInt('foo')).to.be.undefined;
|
||||
expect(validation.checkInt(null)).to.be.undefined;
|
||||
return expect(validation.checkInt(undefined)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns undefined when passed a negative or zero value and the positive option is set', () => {
|
||||
expect(validation.checkInt('-3', { positive: true })).to.be.undefined;
|
||||
expect(validation.checkInt('0', { positive: true })).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidShortText', () => {
|
||||
it('returns true for a short text', () => {
|
||||
expect(validation.isValidShortText('foo')).to.equal(true);
|
||||
expect(validation.isValidShortText('')).to.equal(true);
|
||||
expect(validation.isValidShortText(almostTooLongText)).to.equal(true);
|
||||
});
|
||||
it('returns false for a text longer than 255 characters', () =>
|
||||
expect(validation.isValidShortText(almostTooLongText + 'a')).to.equal(
|
||||
false,
|
||||
));
|
||||
|
||||
it('returns false when passed a non-string', () => {
|
||||
expect(validation.isValidShortText({})).to.equal(false);
|
||||
expect(validation.isValidShortText(1)).to.equal(false);
|
||||
expect(validation.isValidShortText(null)).to.equal(false);
|
||||
expect(validation.isValidShortText(undefined)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidAppsObject', () => {
|
||||
it('returns true for a valid object', () => {
|
||||
const apps = {
|
||||
'1234': {
|
||||
name: 'something',
|
||||
releaseId: 123,
|
||||
commit: 'bar',
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz',
|
||||
imageId: 34,
|
||||
image: 'foo',
|
||||
environment: {},
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with an invalid environment', () => {
|
||||
const apps = {
|
||||
'1234': {
|
||||
name: 'something',
|
||||
releaseId: 123,
|
||||
commit: 'bar',
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz',
|
||||
imageId: 34,
|
||||
image: 'foo',
|
||||
environment: { ' baz': 'bat' },
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false with an invalid appId', () => {
|
||||
const apps = {
|
||||
boo: {
|
||||
name: 'something',
|
||||
releaseId: 123,
|
||||
commit: 'bar',
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz',
|
||||
imageId: 34,
|
||||
image: 'foo',
|
||||
environment: {},
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true with a missing releaseId', () => {
|
||||
const apps = {
|
||||
'1234': {
|
||||
name: 'something',
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz',
|
||||
imageId: 34,
|
||||
image: 'foo',
|
||||
environment: {},
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with an invalid releaseId', () => {
|
||||
const apps = {
|
||||
'1234': {
|
||||
name: 'something',
|
||||
releaseId: '123a',
|
||||
services: {
|
||||
'45': {
|
||||
serviceName: 'bazbaz',
|
||||
imageId: 34,
|
||||
image: 'foo',
|
||||
environment: {},
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidAppsObject(apps)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidDependentDevicesObject', () => {
|
||||
it('returns true for a valid object', () => {
|
||||
const devices: Dictionary<any> = {};
|
||||
devices[almostTooLongText] = {
|
||||
name: 'foo',
|
||||
apps: {
|
||||
'234': {
|
||||
config: { bar: 'baz' },
|
||||
environment: { dead: 'beef' },
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with a missing apps object', () => {
|
||||
const devices = {
|
||||
abcd1234: {
|
||||
name: 'foo',
|
||||
},
|
||||
};
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false with an invalid environment', () => {
|
||||
const devices = {
|
||||
abcd1234: {
|
||||
name: 'foo',
|
||||
apps: {
|
||||
'234': {
|
||||
config: { bar: 'baz' },
|
||||
environment: { dead: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(validation.isValidDependentDevicesObject(devices)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false if the uuid is too long', () => {
|
||||
const devices: Dictionary<any> = {};
|
||||
devices[almostTooLongText + 'a'] = {
|
||||
name: 'foo',
|
||||
apps: {
|
||||
'234': {
|
||||
config: { bar: 'baz' },
|
||||
environment: { dead: 'beef' },
|
||||
},
|
||||
},
|
||||
};
|
||||
return expect(validation.isValidDependentDevicesObject(devices)).to.equal(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
Promise = require 'bluebird'
|
||||
constants = require '../src/lib/constants'
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
blink = require('../src/lib/blink')
|
||||
{ expect } = require './lib/chai-config'
|
||||
|
||||
describe 'blink', ->
|
||||
it 'is a blink function', ->
|
||||
expect(blink).to.be.a('function')
|
||||
|
||||
it 'has a pattern property with start and stop functions', ->
|
||||
expect(blink.pattern.start).to.be.a('function')
|
||||
expect(blink.pattern.stop).to.be.a('function')
|
||||
|
||||
it 'writes to a file that represents the LED, and writes a 0 at the end to turn the LED off', ->
|
||||
blink(1)
|
||||
.then ->
|
||||
fs.readFileAsync(constants.ledFile)
|
||||
.then (contents) ->
|
||||
expect(contents.toString()).to.equal('0')
|
22
test/08-blink.spec.ts
Normal file
22
test/08-blink.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { fs } from 'mz';
|
||||
import { expect } from './lib/chai-config';
|
||||
|
||||
import blink = require('../src/lib/blink');
|
||||
import constants = require('../src/lib/constants');
|
||||
|
||||
describe('blink', () => {
|
||||
it('is a blink function', () => expect(blink).to.be.a('function'));
|
||||
|
||||
it('has a pattern property with start and stop functions', () => {
|
||||
expect(blink.pattern.start).to.be.a('function');
|
||||
expect(blink.pattern.stop).to.be.a('function');
|
||||
});
|
||||
|
||||
it('writes to a file that represents the LED, and writes a 0 at the end to turn the LED off', async () => {
|
||||
// TODO: Fix the typings for blink
|
||||
await (blink as any)(1);
|
||||
const contents = await fs.readFile(constants.ledFile);
|
||||
|
||||
expect(contents.toString()).to.equal('0');
|
||||
});
|
||||
});
|
@ -1,128 +0,0 @@
|
||||
mixpanel = require 'mixpanel'
|
||||
|
||||
{ expect } = require './lib/chai-config'
|
||||
{ stub } = require 'sinon'
|
||||
|
||||
supervisorVersion = require '../src/lib/supervisor-version'
|
||||
|
||||
{ EventTracker } = require '../src/event-tracker'
|
||||
describe 'EventTracker', ->
|
||||
before ->
|
||||
stub(mixpanel, 'init').callsFake (token) ->
|
||||
return {
|
||||
token: token
|
||||
track: stub().returns()
|
||||
}
|
||||
|
||||
@eventTrackerOffline = new EventTracker()
|
||||
@eventTracker = new EventTracker()
|
||||
stub(EventTracker.prototype, 'logEvent')
|
||||
|
||||
after ->
|
||||
EventTracker.prototype.logEvent.restore()
|
||||
mixpanel.init.restore()
|
||||
|
||||
it 'initializes in unmanaged mode', ->
|
||||
promise = @eventTrackerOffline.init({
|
||||
unmanaged: true
|
||||
uuid: 'foobar'
|
||||
mixpanelHost: { host: '', path: '' }
|
||||
})
|
||||
expect(promise).to.be.fulfilled
|
||||
.then =>
|
||||
expect(@eventTrackerOffline.client).to.be.null
|
||||
|
||||
it 'logs events in unmanaged mode, with the correct properties', ->
|
||||
@eventTrackerOffline.track('Test event', { appId: 'someValue' })
|
||||
expect(@eventTrackerOffline.logEvent).to.be.calledWith('Event:', 'Test event', JSON.stringify({ appId: 'someValue' }))
|
||||
|
||||
it 'initializes a mixpanel client when not in unmanaged mode', ->
|
||||
promise = @eventTracker.init({
|
||||
mixpanelToken: 'someToken'
|
||||
uuid: 'barbaz'
|
||||
mixpanelHost: { host: '', path: '' }
|
||||
})
|
||||
expect(promise).to.be.fulfilled
|
||||
.then =>
|
||||
expect(mixpanel.init).to.have.been.calledWith('someToken')
|
||||
expect(@eventTracker.client.token).to.equal('someToken')
|
||||
expect(@eventTracker.client.track).to.be.a('function')
|
||||
|
||||
it 'calls the mixpanel client track function with the event, properties and uuid as distinct_id', ->
|
||||
@eventTracker.track('Test event 2', { appId: 'someOtherValue' })
|
||||
expect(@eventTracker.logEvent).to.be.calledWith('Event:', 'Test event 2', JSON.stringify({ appId: 'someOtherValue' }))
|
||||
expect(@eventTracker.client.track).to.be.calledWith('Test event 2', {
|
||||
appId: 'someOtherValue'
|
||||
uuid: 'barbaz'
|
||||
distinct_id: 'barbaz'
|
||||
supervisorVersion
|
||||
})
|
||||
|
||||
it 'can be passed an Error and it is added to the event properties', ->
|
||||
theError = new Error('something went wrong')
|
||||
@eventTracker.track('Error event', theError)
|
||||
expect(@eventTracker.client.track).to.be.calledWith('Error event', {
|
||||
error:
|
||||
message: theError.message
|
||||
stack: theError.stack
|
||||
uuid: 'barbaz'
|
||||
distinct_id: 'barbaz'
|
||||
supervisorVersion
|
||||
})
|
||||
|
||||
it 'hides service environment variables, to avoid logging keys or secrets', ->
|
||||
props = {
|
||||
service:
|
||||
appId: '1'
|
||||
environment: {
|
||||
RESIN_API_KEY: 'foo'
|
||||
RESIN_SUPERVISOR_API_KEY: 'bar'
|
||||
OTHER_VAR: 'hi'
|
||||
}
|
||||
}
|
||||
@eventTracker.track('Some app event', props)
|
||||
expect(@eventTracker.client.track).to.be.calledWith('Some app event', {
|
||||
service: { appId: '1' }
|
||||
uuid: 'barbaz'
|
||||
distinct_id: 'barbaz'
|
||||
supervisorVersion
|
||||
})
|
||||
|
||||
it 'should handle being passed no properties object', ->
|
||||
expect(@eventTracker.track('no-options')).to.not.throw
|
||||
|
||||
describe 'Rate limiting', ->
|
||||
|
||||
it 'should rate limit events of the same type', ->
|
||||
@eventTracker.client.track.reset()
|
||||
|
||||
@eventTracker.track('test', {})
|
||||
@eventTracker.track('test', {})
|
||||
@eventTracker.track('test', {})
|
||||
@eventTracker.track('test', {})
|
||||
@eventTracker.track('test', {})
|
||||
|
||||
expect(@eventTracker.client.track).to.have.callCount(1)
|
||||
|
||||
it 'should rate limit events of the same type with different arguments', ->
|
||||
@eventTracker.client.track.reset()
|
||||
|
||||
@eventTracker.track('test2', { a: 1 })
|
||||
@eventTracker.track('test2', { b: 2 })
|
||||
@eventTracker.track('test2', { c: 3 })
|
||||
@eventTracker.track('test2', { d: 4 })
|
||||
@eventTracker.track('test2', { e: 5 })
|
||||
|
||||
expect(@eventTracker.client.track).to.have.callCount(1)
|
||||
|
||||
it 'should not rate limit events of different types', ->
|
||||
@eventTracker.client.track.reset()
|
||||
|
||||
@eventTracker.track('test3', { a: 1 })
|
||||
@eventTracker.track('test4', { b: 2 })
|
||||
@eventTracker.track('test5', { c: 3 })
|
||||
@eventTracker.track('test6', { d: 4 })
|
||||
@eventTracker.track('test7', { e: 5 })
|
||||
|
||||
expect(@eventTracker.client.track).to.have.callCount(5)
|
||||
|
168
test/09-event-tracker.spec.ts
Normal file
168
test/09-event-tracker.spec.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import * as mixpanel from 'mixpanel';
|
||||
import { stub } from 'sinon';
|
||||
|
||||
import { expect } from './lib/chai-config';
|
||||
|
||||
import EventTracker from '../src/event-tracker';
|
||||
import supervisorVersion = require('../src/lib/supervisor-version');
|
||||
|
||||
describe('EventTracker', () => {
|
||||
let eventTrackerOffline: EventTracker;
|
||||
let eventTracker: EventTracker;
|
||||
|
||||
before(() => {
|
||||
stub(mixpanel, 'init').callsFake(token => ({
|
||||
token,
|
||||
track: stub().returns(undefined),
|
||||
}));
|
||||
|
||||
eventTrackerOffline = new EventTracker();
|
||||
eventTracker = new EventTracker();
|
||||
return stub(EventTracker.prototype as any, 'logEvent');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
(EventTracker.prototype as any).logEvent.restore();
|
||||
return mixpanel.init.restore();
|
||||
});
|
||||
|
||||
it('initializes in unmanaged mode', () => {
|
||||
const promise = eventTrackerOffline.init({
|
||||
unmanaged: true,
|
||||
uuid: 'foobar',
|
||||
mixpanelHost: { host: '', path: '' },
|
||||
mixpanelToken: '',
|
||||
});
|
||||
expect(promise).to.be.fulfilled.then(() => {
|
||||
// @ts-ignore
|
||||
expect(eventTrackerOffline.client).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
it('logs events in unmanaged mode, with the correct properties', () => {
|
||||
eventTrackerOffline.track('Test event', { appId: 'someValue' });
|
||||
// @ts-ignore
|
||||
expect(eventTrackerOffline.logEvent).to.be.calledWith(
|
||||
'Event:',
|
||||
'Test event',
|
||||
JSON.stringify({ appId: 'someValue' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('initializes a mixpanel client when not in unmanaged mode', () => {
|
||||
const promise = eventTracker.init({
|
||||
mixpanelToken: 'someToken',
|
||||
uuid: 'barbaz',
|
||||
mixpanelHost: { host: '', path: '' },
|
||||
unmanaged: false,
|
||||
});
|
||||
expect(promise).to.be.fulfilled.then(() => {
|
||||
expect(mixpanel.init).to.have.been.calledWith('someToken');
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.token).to.equal('someToken');
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', () => {
|
||||
eventTracker.track('Test event 2', { appId: 'someOtherValue' });
|
||||
// @ts-ignore
|
||||
expect(eventTracker.logEvent).to.be.calledWith(
|
||||
'Event:',
|
||||
'Test event 2',
|
||||
JSON.stringify({ appId: 'someOtherValue' }),
|
||||
);
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.be.calledWith('Test event 2', {
|
||||
appId: 'someOtherValue',
|
||||
uuid: 'barbaz',
|
||||
distinct_id: 'barbaz',
|
||||
supervisorVersion,
|
||||
});
|
||||
});
|
||||
|
||||
it('can be passed an Error and it is added to the event properties', () => {
|
||||
const theError = new Error('something went wrong');
|
||||
eventTracker.track('Error event', theError);
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.be.calledWith('Error event', {
|
||||
error: {
|
||||
message: theError.message,
|
||||
stack: theError.stack,
|
||||
},
|
||||
uuid: 'barbaz',
|
||||
distinct_id: 'barbaz',
|
||||
supervisorVersion,
|
||||
});
|
||||
});
|
||||
|
||||
it('hides service environment variables, to avoid logging keys or secrets', () => {
|
||||
const props = {
|
||||
service: {
|
||||
appId: '1',
|
||||
environment: {
|
||||
RESIN_API_KEY: 'foo',
|
||||
RESIN_SUPERVISOR_API_KEY: 'bar',
|
||||
OTHER_VAR: 'hi',
|
||||
},
|
||||
},
|
||||
};
|
||||
eventTracker.track('Some app event', props);
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.be.calledWith('Some app event', {
|
||||
service: { appId: '1' },
|
||||
uuid: 'barbaz',
|
||||
distinct_id: 'barbaz',
|
||||
supervisorVersion,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle being passed no properties object', () => {
|
||||
expect(eventTracker.track('no-options')).to.not.throw;
|
||||
});
|
||||
|
||||
return describe('Rate limiting', () => {
|
||||
it('should rate limit events of the same type', () => {
|
||||
// @ts-ignore
|
||||
eventTracker.client.track.reset();
|
||||
|
||||
eventTracker.track('test', {});
|
||||
eventTracker.track('test', {});
|
||||
eventTracker.track('test', {});
|
||||
eventTracker.track('test', {});
|
||||
eventTracker.track('test', {});
|
||||
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.have.callCount(1);
|
||||
});
|
||||
|
||||
it('should rate limit events of the same type with different arguments', () => {
|
||||
// @ts-ignore
|
||||
eventTracker.client.track.reset();
|
||||
|
||||
eventTracker.track('test2', { a: 1 });
|
||||
eventTracker.track('test2', { b: 2 });
|
||||
eventTracker.track('test2', { c: 3 });
|
||||
eventTracker.track('test2', { d: 4 });
|
||||
eventTracker.track('test2', { e: 5 });
|
||||
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.have.callCount(1);
|
||||
});
|
||||
|
||||
it('should not rate limit events of different types', () => {
|
||||
// @ts-ignore
|
||||
eventTracker.client.track.reset();
|
||||
|
||||
eventTracker.track('test3', { a: 1 });
|
||||
eventTracker.track('test4', { b: 2 });
|
||||
eventTracker.track('test5', { c: 3 });
|
||||
eventTracker.track('test6', { d: 4 });
|
||||
eventTracker.track('test7', { e: 5 });
|
||||
|
||||
// @ts-ignore
|
||||
expect(eventTracker.client.track).to.have.callCount(5);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,20 +1,21 @@
|
||||
os = require 'os'
|
||||
import * as os from 'os';
|
||||
import { stub } from 'sinon';
|
||||
|
||||
{ expect } = require './lib/chai-config'
|
||||
{ stub } = require 'sinon'
|
||||
import { expect } from './lib/chai-config';
|
||||
|
||||
network = require '../src/network'
|
||||
describe 'network', ->
|
||||
describe 'getIPAddresses', ->
|
||||
before ->
|
||||
import * as network from '../src/network';
|
||||
|
||||
describe('network', () => {
|
||||
describe('getIPAddresses', () => {
|
||||
before(() =>
|
||||
stub(os, 'networkInterfaces').returns({
|
||||
lo:
|
||||
[{
|
||||
lo: [
|
||||
{
|
||||
address: '127.0.0.1',
|
||||
netmask: '255.0.0.0',
|
||||
family: 'IPv4',
|
||||
mac: '00:00:00:00:00:00',
|
||||
internal: true
|
||||
internal: true,
|
||||
},
|
||||
{
|
||||
address: '::1',
|
||||
@ -22,15 +23,16 @@ describe 'network', ->
|
||||
family: 'IPv6',
|
||||
mac: '00:00:00:00:00:00',
|
||||
scopeid: 0,
|
||||
internal: true
|
||||
}]
|
||||
docker0:
|
||||
[{
|
||||
internal: true,
|
||||
},
|
||||
],
|
||||
docker0: [
|
||||
{
|
||||
address: '172.17.0.1',
|
||||
netmask: '255.255.0.0',
|
||||
family: 'IPv4',
|
||||
mac: '02:42:0f:33:06:ad',
|
||||
internal: false
|
||||
internal: false,
|
||||
},
|
||||
{
|
||||
address: 'fe80::42:fff:fe33:6ad',
|
||||
@ -38,15 +40,16 @@ describe 'network', ->
|
||||
family: 'IPv6',
|
||||
mac: '02:42:0f:33:06:ad',
|
||||
scopeid: 3,
|
||||
internal: false
|
||||
}]
|
||||
wlan0:
|
||||
[{
|
||||
internal: false,
|
||||
},
|
||||
],
|
||||
wlan0: [
|
||||
{
|
||||
address: '192.168.1.137',
|
||||
netmask: '255.255.255.0',
|
||||
family: 'IPv4',
|
||||
mac: '60:6d:c7:c6:44:3d',
|
||||
internal: false
|
||||
internal: false,
|
||||
},
|
||||
{
|
||||
address: '2605:9080:1103:3011:2dbe:35e3:1b5a:b99',
|
||||
@ -54,18 +57,25 @@ describe 'network', ->
|
||||
family: 'IPv6',
|
||||
mac: '60:6d:c7:c6:44:3d',
|
||||
scopeid: 0,
|
||||
internal: false
|
||||
}]
|
||||
'resin-vpn':
|
||||
[{
|
||||
internal: false,
|
||||
},
|
||||
],
|
||||
'resin-vpn': [
|
||||
{
|
||||
address: '10.10.2.14',
|
||||
netmask: '255.255.0.0',
|
||||
family: 'IPv4',
|
||||
mac: '01:43:1f:32:05:bd',
|
||||
internal: false
|
||||
}]
|
||||
})
|
||||
after ->
|
||||
os.networkInterfaces.restore()
|
||||
it 'returns only the relevant IP addresses', ->
|
||||
expect(network.getIPAddresses()).to.deep.equal([ '192.168.1.137' ])
|
||||
internal: false,
|
||||
},
|
||||
],
|
||||
} as any),
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
after(() => os.networkInterfaces.restore());
|
||||
|
||||
it('returns only the relevant IP addresses', () =>
|
||||
expect(network.getIPAddresses()).to.deep.equal(['192.168.1.137']));
|
||||
});
|
||||
});
|
@ -1,162 +0,0 @@
|
||||
prepare = require './lib/prepare'
|
||||
Promise = require 'bluebird'
|
||||
balenaAPI = require './lib/mocked-balena-api'
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
|
||||
{ expect } = require './lib/chai-config'
|
||||
{ stub, spy } = require 'sinon'
|
||||
|
||||
{ DB } = require('../src/db')
|
||||
{ Config } = require('../src/config')
|
||||
DeviceState = require('../src/device-state')
|
||||
{ APIBinder } = require('../src/api-binder')
|
||||
|
||||
initModels = (filename) ->
|
||||
prepare()
|
||||
@db = new DB()
|
||||
@config = new Config({ @db, configPath: filename })
|
||||
@eventTracker = {
|
||||
track: stub().callsFake (ev, props) ->
|
||||
console.log(ev, props)
|
||||
}
|
||||
@logger = {
|
||||
clearOutOfDateDBLogs: ->
|
||||
}
|
||||
@deviceState = new DeviceState({ @db, @config, @eventTracker, @logger })
|
||||
@apiBinder = new APIBinder({ @db, @config, @eventTracker, @deviceState })
|
||||
@db.init()
|
||||
.then =>
|
||||
@config.init()
|
||||
.then =>
|
||||
@apiBinder.initClient() # Initializes the clients but doesn't trigger provisioning
|
||||
|
||||
mockProvisioningOpts = {
|
||||
apiEndpoint: 'http://0.0.0.0:3000'
|
||||
uuid: 'abcd'
|
||||
deviceApiKey: 'averyvalidkey'
|
||||
provisioningApiKey: 'anotherveryvalidkey'
|
||||
apiTimeout: 30000
|
||||
}
|
||||
|
||||
describe 'APIBinder', ->
|
||||
before ->
|
||||
spy(balenaAPI.balenaBackend, 'registerHandler')
|
||||
@server = balenaAPI.listen(3000)
|
||||
after ->
|
||||
balenaAPI.balenaBackend.registerHandler.restore()
|
||||
try
|
||||
@server.close()
|
||||
|
||||
# We do not support older OS versions anymore, so we only test this case
|
||||
describe 'on an OS with deviceApiKey support', ->
|
||||
before ->
|
||||
initModels.call(this, '/config-apibinder.json')
|
||||
|
||||
it 'provisions a device', ->
|
||||
promise = @apiBinder.provisionDevice()
|
||||
expect(promise).to.be.fulfilled
|
||||
.then =>
|
||||
expect(balenaAPI.balenaBackend.registerHandler).to.be.calledOnce
|
||||
balenaAPI.balenaBackend.registerHandler.resetHistory()
|
||||
expect(@eventTracker.track).to.be.calledWith('Device bootstrap success')
|
||||
|
||||
it 'deletes the provisioning key', ->
|
||||
expect(@config.get('apiKey')).to.eventually.be.undefined
|
||||
|
||||
it 'sends the correct parameters when provisioning', ->
|
||||
fs.readFileAsync('./test/data/config-apibinder.json')
|
||||
.then(JSON.parse)
|
||||
.then (conf) ->
|
||||
expect(balenaAPI.balenaBackend.devices).to.deep.equal({
|
||||
'1': {
|
||||
id: 1
|
||||
user: conf.userId
|
||||
application: conf.applicationId
|
||||
uuid: conf.uuid
|
||||
device_type: conf.deviceType
|
||||
api_key: conf.deviceApiKey
|
||||
}
|
||||
})
|
||||
|
||||
describe 'fetchDevice', ->
|
||||
before ->
|
||||
initModels.call(this, '/config-apibinder.json')
|
||||
|
||||
it 'gets a device by its uuid from the balena API', ->
|
||||
# Manually add a device to the mocked API
|
||||
balenaAPI.balenaBackend.devices[3] = {
|
||||
id: 3
|
||||
user: 'foo'
|
||||
application: 1337
|
||||
uuid: 'abcd'
|
||||
device_type: 'intel-nuc'
|
||||
api_key: 'verysecure'
|
||||
}
|
||||
@apiBinder.fetchDevice('abcd', 'someApiKey', 30000)
|
||||
.then (theDevice) ->
|
||||
expect(theDevice).to.deep.equal(balenaAPI.balenaBackend.devices[3])
|
||||
|
||||
describe 'exchangeKeyAndGetDevice', ->
|
||||
before ->
|
||||
initModels.call(this, '/config-apibinder.json')
|
||||
|
||||
it 'returns the device if it can fetch it with the deviceApiKey', ->
|
||||
spy(balenaAPI.balenaBackend, 'deviceKeyHandler')
|
||||
fetchDeviceStub = stub(@apiBinder, 'fetchDevice')
|
||||
fetchDeviceStub.onCall(0).resolves({ id: 1 })
|
||||
@apiBinder.exchangeKeyAndGetDevice(mockProvisioningOpts)
|
||||
.then (device) =>
|
||||
expect(balenaAPI.balenaBackend.deviceKeyHandler).to.not.be.called
|
||||
expect(device).to.deep.equal({ id: 1 })
|
||||
expect(@apiBinder.fetchDevice).to.be.calledOnce
|
||||
@apiBinder.fetchDevice.restore()
|
||||
balenaAPI.balenaBackend.deviceKeyHandler.restore()
|
||||
|
||||
it 'throws if it cannot get the device with any of the keys', ->
|
||||
spy(balenaAPI.balenaBackend, 'deviceKeyHandler')
|
||||
stub(@apiBinder, 'fetchDevice').returns(Promise.resolve(null))
|
||||
promise = @apiBinder.exchangeKeyAndGetDevice(mockProvisioningOpts)
|
||||
promise.catch(->)
|
||||
expect(promise).to.be.rejected
|
||||
.then =>
|
||||
expect(balenaAPI.balenaBackend.deviceKeyHandler).to.not.be.called
|
||||
expect(@apiBinder.fetchDevice).to.be.calledTwice
|
||||
@apiBinder.fetchDevice.restore()
|
||||
balenaAPI.balenaBackend.deviceKeyHandler.restore()
|
||||
|
||||
it 'exchanges the key and returns the device if the provisioning key is valid', ->
|
||||
spy(balenaAPI.balenaBackend, 'deviceKeyHandler')
|
||||
fetchDeviceStub = stub(@apiBinder, 'fetchDevice')
|
||||
fetchDeviceStub.onCall(0).returns(Promise.resolve(null))
|
||||
fetchDeviceStub.onCall(1).returns(Promise.resolve({ id: 1 }))
|
||||
@apiBinder.exchangeKeyAndGetDevice(mockProvisioningOpts)
|
||||
.then (device) =>
|
||||
expect(balenaAPI.balenaBackend.deviceKeyHandler).to.be.calledOnce
|
||||
expect(device).to.deep.equal({ id: 1 })
|
||||
expect(@apiBinder.fetchDevice).to.be.calledTwice
|
||||
@apiBinder.fetchDevice.restore()
|
||||
balenaAPI.balenaBackend.deviceKeyHandler.restore()
|
||||
|
||||
describe 'unmanaged mode', ->
|
||||
before ->
|
||||
initModels.call(this, '/config-apibinder-offline.json')
|
||||
|
||||
it 'does not generate a key if the device is in unmanaged mode', ->
|
||||
@config.get('unmanaged').then (mode) =>
|
||||
# Ensure offline mode is set
|
||||
expect(mode).to.equal(true)
|
||||
# Check that there is no deviceApiKey
|
||||
@config.getMany([ 'deviceApiKey', 'uuid' ]).then (conf) ->
|
||||
expect(conf['deviceApiKey']).to.be.empty
|
||||
expect(conf['uuid']).to.not.be.undefined
|
||||
|
||||
describe 'Minimal config unmanaged mode', ->
|
||||
before ->
|
||||
initModels.call(this, '/config-apibinder-offline2.json')
|
||||
|
||||
it 'does not generate a key with the minimal config', ->
|
||||
@config.get('unmanaged').then (mode) =>
|
||||
expect(mode).to.equal(true)
|
||||
@config.getMany([ 'deviceApiKey', 'uuid' ]).then (conf) ->
|
||||
expect(conf['deviceApiKey']).to.be.empty
|
||||
expect(conf['uuid']).to.not.be.undefined
|
246
test/11-api-binder.spec.ts
Normal file
246
test/11-api-binder.spec.ts
Normal file
@ -0,0 +1,246 @@
|
||||
import { fs } from 'mz';
|
||||
import { Server } from 'net';
|
||||
import { spy, stub } from 'sinon';
|
||||
|
||||
import chai = require('./lib/chai-config');
|
||||
import balenaAPI = require('./lib/mocked-balena-api');
|
||||
import prepare = require('./lib/prepare');
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
import ApiBinder from '../src/api-binder';
|
||||
import Config from '../src/config';
|
||||
import DB from '../src/db';
|
||||
import DeviceState = require('../src/device-state');
|
||||
|
||||
const initModels = async (obj: Dictionary<any>, filename: string) => {
|
||||
prepare();
|
||||
|
||||
obj.db = new DB();
|
||||
obj.config = new Config({ db: obj.db, configPath: filename });
|
||||
|
||||
obj.eventTracker = {
|
||||
track: stub().callsFake((ev, props) => console.log(ev, props)),
|
||||
} as any;
|
||||
|
||||
obj.logger = {
|
||||
clearOutOfDateDBLogs: () => {
|
||||
/* noop */
|
||||
},
|
||||
} as any;
|
||||
|
||||
obj.deviceState = new DeviceState({
|
||||
db: obj.db,
|
||||
config: obj.config,
|
||||
eventTracker: obj.eventTracker,
|
||||
logger: obj.logger,
|
||||
});
|
||||
|
||||
obj.apiBinder = new ApiBinder({
|
||||
db: obj.db,
|
||||
config: obj.config,
|
||||
logger: obj.logger,
|
||||
eventTracker: obj.eventTracker,
|
||||
deviceState: obj.deviceState,
|
||||
});
|
||||
await obj.db.init();
|
||||
await obj.config.init();
|
||||
await obj.apiBinder.initClient(); // Initializes the clients but doesn't trigger provisioning
|
||||
};
|
||||
|
||||
const mockProvisioningOpts = {
|
||||
apiEndpoint: 'http://0.0.0.0:3000',
|
||||
uuid: 'abcd',
|
||||
deviceApiKey: 'averyvalidkey',
|
||||
provisioningApiKey: 'anotherveryvalidkey',
|
||||
apiTimeout: 30000,
|
||||
};
|
||||
|
||||
describe('ApiBinder', () => {
|
||||
let server: Server;
|
||||
|
||||
before(() => {
|
||||
spy(balenaAPI.balenaBackend!, 'registerHandler');
|
||||
server = balenaAPI.listen(3000);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// @ts-ignore
|
||||
balenaAPI.balenaBackend!.registerHandler.restore();
|
||||
try {
|
||||
server.close();
|
||||
} catch (error) {
|
||||
/* noop */
|
||||
}
|
||||
});
|
||||
|
||||
// We do not support older OS versions anymore, so we only test this case
|
||||
describe('on an OS with deviceApiKey support', () => {
|
||||
const components: Dictionary<any> = {};
|
||||
before(() => {
|
||||
return initModels(components, '/config-apibinder.json');
|
||||
});
|
||||
|
||||
it('provisions a device', () => {
|
||||
// @ts-ignore
|
||||
const promise = components.apiBinder.provisionDevice();
|
||||
|
||||
return expect(promise).to.be.fulfilled.then(() => {
|
||||
expect(balenaAPI.balenaBackend!.registerHandler).to.be.calledOnce;
|
||||
|
||||
// @ts-ignore
|
||||
balenaAPI.balenaBackend!.registerHandler.resetHistory();
|
||||
expect(components.eventTracker.track).to.be.calledWith(
|
||||
'Device bootstrap success',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the provisioning key', async () => {
|
||||
expect(await components.config.get('apiKey')).to.be.undefined;
|
||||
});
|
||||
|
||||
it('sends the correct parameters when provisioning', async () => {
|
||||
const conf = JSON.parse(
|
||||
await fs.readFile('./test/data/config-apibinder.json', 'utf8'),
|
||||
);
|
||||
expect(balenaAPI.balenaBackend!.devices).to.deep.equal({
|
||||
'1': {
|
||||
id: 1,
|
||||
user: conf.userId,
|
||||
application: conf.applicationId,
|
||||
uuid: conf.uuid,
|
||||
device_type: conf.deviceType,
|
||||
api_key: conf.deviceApiKey,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchDevice', () => {
|
||||
const components: Dictionary<any> = {};
|
||||
before(() => {
|
||||
return initModels(components, '/config-apibinder.json');
|
||||
});
|
||||
|
||||
it('gets a device by its uuid from the balena API', async () => {
|
||||
// Manually add a device to the mocked API
|
||||
balenaAPI.balenaBackend!.devices[3] = {
|
||||
id: 3,
|
||||
user: 'foo',
|
||||
application: 1337,
|
||||
uuid: 'abcd',
|
||||
device_type: 'intel-nuc',
|
||||
api_key: 'verysecure',
|
||||
};
|
||||
|
||||
const device = await components.apiBinder.fetchDevice(
|
||||
'abcd',
|
||||
'someApiKey',
|
||||
30000,
|
||||
);
|
||||
expect(device).to.deep.equal(balenaAPI.balenaBackend!.devices[3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exchangeKeyAndGetDevice', () => {
|
||||
const components: Dictionary<any> = {};
|
||||
before(() => {
|
||||
return initModels(components, '/config-apibinder.json');
|
||||
});
|
||||
|
||||
it('returns the device if it can fetch it with the deviceApiKey', async () => {
|
||||
spy(balenaAPI.balenaBackend!, 'deviceKeyHandler');
|
||||
|
||||
const fetchDeviceStub = stub(components.apiBinder, 'fetchDevice');
|
||||
fetchDeviceStub.onCall(0).resolves({ id: 1 });
|
||||
|
||||
// @ts-ignore
|
||||
const device = await components.apiBinder.exchangeKeyAndGetDevice(
|
||||
mockProvisioningOpts,
|
||||
);
|
||||
|
||||
expect(balenaAPI.balenaBackend!.deviceKeyHandler).to.not.be.called;
|
||||
expect(device).to.deep.equal({ id: 1 });
|
||||
expect(components.apiBinder.fetchDevice).to.be.calledOnce;
|
||||
|
||||
// @ts-ignore
|
||||
components.apiBinder.fetchDevice.restore();
|
||||
// @ts-ignore
|
||||
balenaAPI.balenaBackend.deviceKeyHandler.restore();
|
||||
});
|
||||
|
||||
it('throws if it cannot get the device with any of the keys', () => {
|
||||
spy(balenaAPI.balenaBackend!, 'deviceKeyHandler');
|
||||
stub(components.apiBinder, 'fetchDevice').returns(Promise.resolve(null));
|
||||
|
||||
// @ts-ignore
|
||||
const promise = components.apiBinder.exchangeKeyAndGetDevice(
|
||||
mockProvisioningOpts,
|
||||
);
|
||||
promise.catch(() => {
|
||||
/* noop */
|
||||
});
|
||||
|
||||
return expect(promise).to.be.rejected.then(() => {
|
||||
expect(balenaAPI.balenaBackend!.deviceKeyHandler).to.not.be.called;
|
||||
expect(components.apiBinder.fetchDevice).to.be.calledTwice;
|
||||
// @ts-ignore
|
||||
components.apiBinder.fetchDevice.restore();
|
||||
// @ts-ignore
|
||||
balenaAPI.balenaBackend.deviceKeyHandler.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('exchanges the key and returns the device if the provisioning key is valid', async () => {
|
||||
spy(balenaAPI.balenaBackend!, 'deviceKeyHandler');
|
||||
const fetchDeviceStub = stub(components.apiBinder, 'fetchDevice');
|
||||
fetchDeviceStub.onCall(0).returns(Promise.resolve(null));
|
||||
fetchDeviceStub.onCall(1).returns(Promise.resolve({ id: 1 }));
|
||||
|
||||
// @ts-ignore
|
||||
const device = await components.apiBinder.exchangeKeyAndGetDevice(
|
||||
mockProvisioningOpts as any,
|
||||
);
|
||||
expect(balenaAPI.balenaBackend!.deviceKeyHandler).to.be.calledOnce;
|
||||
expect(device).to.deep.equal({ id: 1 });
|
||||
expect(components.apiBinder.fetchDevice).to.be.calledTwice;
|
||||
// @ts-ignore
|
||||
components.apiBinder.fetchDevice.restore();
|
||||
// @ts-ignore
|
||||
balenaAPI.balenaBackend.deviceKeyHandler.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('unmanaged mode', () => {
|
||||
const components: Dictionary<any> = {};
|
||||
before(() => {
|
||||
return initModels(components, '/config-apibinder-offline.json');
|
||||
});
|
||||
|
||||
it('does not generate a key if the device is in unmanaged mode', async () => {
|
||||
const mode = await components.config.get('unmanaged');
|
||||
// Ensure offline mode is set
|
||||
expect(mode).to.equal(true);
|
||||
// Check that there is no deviceApiKey
|
||||
const conf = await components.config.getMany(['deviceApiKey', 'uuid']);
|
||||
expect(conf['deviceApiKey']).to.be.empty;
|
||||
expect(conf['uuid']).to.not.be.undefined;
|
||||
});
|
||||
|
||||
describe('Minimal config unmanaged mode', () => {
|
||||
const components2: Dictionary<any> = {};
|
||||
before(() => {
|
||||
return initModels(components2, '/config-apibinder-offline2.json');
|
||||
});
|
||||
|
||||
it('does not generate a key with the minimal config', async () => {
|
||||
const mode = await components2.config.get('unmanaged');
|
||||
expect(mode).to.equal(true);
|
||||
const conf = await components2.config.getMany(['deviceApiKey', 'uuid']);
|
||||
expect(conf['deviceApiKey']).to.be.empty;
|
||||
return expect(conf['uuid']).to.not.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,817 +0,0 @@
|
||||
|
||||
exports.targetState = targetState = []
|
||||
targetState[0] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234
|
||||
serviceName: 'aservice'
|
||||
commit: 'afafafa'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
}
|
||||
privileged: false
|
||||
volumes: []
|
||||
labels: {}
|
||||
running: true
|
||||
},
|
||||
'24': {
|
||||
appId: 1234
|
||||
serviceName: 'anotherService'
|
||||
commit: 'afafafa'
|
||||
imageId: 12346
|
||||
image: 'registry2.resin.io/superapp/afaff:latest'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
}
|
||||
volumes: []
|
||||
privileged: false
|
||||
labels: {}
|
||||
running: true
|
||||
}
|
||||
}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
targetState[1] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234
|
||||
serviceName: 'aservice'
|
||||
commit: 'afafafa'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
volumes: []
|
||||
labels: {}
|
||||
running: true
|
||||
}
|
||||
}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
targetState[2] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234
|
||||
serviceName: 'aservice'
|
||||
commit: 'afafafa'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
volumes: []
|
||||
labels: {}
|
||||
running: true
|
||||
},
|
||||
'24': {
|
||||
appId: 1234
|
||||
serviceName: 'anotherService'
|
||||
commit: 'afafafa'
|
||||
imageId: 12347
|
||||
image: 'registry2.resin.io/superapp/foooo:latest'
|
||||
depends_on: [ 'aservice' ]
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: []
|
||||
privileged: false
|
||||
labels: {}
|
||||
running: true
|
||||
}
|
||||
}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
targetState[3] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234
|
||||
serviceName: 'aservice'
|
||||
commit: 'afafafa'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
volumes: []
|
||||
labels: {}
|
||||
running: true
|
||||
},
|
||||
'24': {
|
||||
appId: 1234
|
||||
serviceName: 'anotherService'
|
||||
commit: 'afafafa'
|
||||
imageId: 12347
|
||||
image: 'registry2.resin.io/superapp/foooo:latest'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: []
|
||||
privileged: false
|
||||
labels: {
|
||||
'io.resin.update.strategy': 'kill-then-download'
|
||||
}
|
||||
running: true
|
||||
}
|
||||
}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
targetState[4] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234
|
||||
serviceName: 'aservice'
|
||||
commit: 'afafafa'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
environment: {
|
||||
'FOO': 'THIS VALUE CHANGED'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
volumes: []
|
||||
labels: {}
|
||||
running: true
|
||||
},
|
||||
'24': {
|
||||
appId: 1234
|
||||
serviceName: 'anotherService'
|
||||
commit: 'afafafa'
|
||||
imageId: 12347
|
||||
image: 'registry2.resin.io/superapp/foooo:latest'
|
||||
depends_on: [ 'aservice' ]
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: []
|
||||
privileged: false
|
||||
labels: {}
|
||||
running: true
|
||||
}
|
||||
}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
targetState[5] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234
|
||||
serviceName: 'aservice'
|
||||
commit: 'afafafa'
|
||||
imageId: 12345
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
environment: {
|
||||
'FOO': 'THIS VALUE CHANGED'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
volumes: []
|
||||
labels: {}
|
||||
running: true
|
||||
},
|
||||
'24': {
|
||||
appId: 1234
|
||||
serviceName: 'anotherService'
|
||||
commit: 'afafafa'
|
||||
imageId: 12347
|
||||
image: 'registry2.resin.io/superapp/foooo:latest'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: []
|
||||
privileged: false
|
||||
labels: {}
|
||||
running: true
|
||||
}
|
||||
}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
targetState[6] = {
|
||||
local: {
|
||||
name: 'volumeTest'
|
||||
config: {
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 12345
|
||||
name: 'volumeApp'
|
||||
commit: 'asd'
|
||||
releaseId: 3
|
||||
services: {}
|
||||
volumes: {}
|
||||
networks: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
exports.currentState = currentState = []
|
||||
currentState[0] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: [
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
image: 'id1'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
|
||||
}
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena'
|
||||
]
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '23'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'aservice'
|
||||
}
|
||||
running: true
|
||||
createdAt: new Date()
|
||||
containerId: '1'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'aservice' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
},
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 24
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12346
|
||||
image: 'id0'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/balena'
|
||||
]
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '24'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'anotherService'
|
||||
}
|
||||
running: false
|
||||
createdAt: new Date()
|
||||
containerId: '2'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'anotherService' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
}
|
||||
]
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
currentState[1] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: []
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
currentState[2] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: [
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
expose: []
|
||||
ports: []
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
image: 'id1'
|
||||
environment: {
|
||||
'FOO': 'THIS VALUE CHANGED'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena'
|
||||
]
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '23'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'aservice'
|
||||
}
|
||||
running: true
|
||||
createdAt: new Date()
|
||||
containerId: '1'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'aservice' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
}
|
||||
]
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
currentState[3] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: [
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
expose: []
|
||||
ports: []
|
||||
image: 'id1'
|
||||
environment: {
|
||||
'FOO': 'THIS VALUE CHANGED'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena'
|
||||
]
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '23'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'aservice'
|
||||
}
|
||||
running: true
|
||||
createdAt: new Date(0)
|
||||
containerId: '1'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'aservice' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
},
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
expose: []
|
||||
ports: []
|
||||
image: 'id1'
|
||||
environment: {
|
||||
'FOO': 'THIS VALUE CHANGED'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena'
|
||||
]
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '23'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'aservice'
|
||||
}
|
||||
running: true
|
||||
createdAt: new Date(1)
|
||||
containerId: '2'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'aservice' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
}
|
||||
]
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
currentState[4] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: [
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 24
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12346
|
||||
image: 'id0'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/balena'
|
||||
]
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '24'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'anotherService'
|
||||
}
|
||||
running: false
|
||||
createdAt: new Date()
|
||||
containerId: '2'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'aservice' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
}
|
||||
]
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
currentState[5] = {
|
||||
local: {
|
||||
name: 'volumeTest'
|
||||
config: {}
|
||||
apps: [
|
||||
{
|
||||
appId: 12345
|
||||
name: 'volumeApp'
|
||||
commit: 'asd'
|
||||
releaseId: 3
|
||||
services: []
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
},
|
||||
{
|
||||
appId: 12,
|
||||
name: 'previous-app',
|
||||
commit: '123',
|
||||
releaseId: 10
|
||||
services: [],
|
||||
networks: {},
|
||||
volumes: {
|
||||
my_volume: {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
currentState[6] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName'
|
||||
config: {
|
||||
'RESIN_HOST_CONFIG_gpu_mem': '512'
|
||||
'RESIN_HOST_LOG_TO_DISPLAY': '1'
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
appId: 1234
|
||||
name: 'superapp'
|
||||
commit: 'afafafa'
|
||||
releaseId: 2
|
||||
services: [
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
image: 'id1'
|
||||
environment: {
|
||||
'FOO': 'bar'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
|
||||
}
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena'
|
||||
]
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '23'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'aservice'
|
||||
}
|
||||
running: true
|
||||
createdAt: new Date()
|
||||
containerId: '1'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'aservice' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
},
|
||||
{
|
||||
appId: 1234
|
||||
serviceId: 24
|
||||
releaseId: 2
|
||||
commit: 'afafafa'
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12346
|
||||
image: 'id0'
|
||||
environment: {
|
||||
'FOO': 'bro'
|
||||
'ADDITIONAL_ENV_VAR': 'foo'
|
||||
}
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/balena'
|
||||
]
|
||||
privileged: false
|
||||
restart: 'always'
|
||||
labels: {
|
||||
'io.resin.app-id': '1234'
|
||||
'io.resin.service-id': '24'
|
||||
'io.resin.supervised': 'true'
|
||||
'io.resin.service-name': 'anotherService'
|
||||
}
|
||||
running: true
|
||||
createdAt: new Date()
|
||||
containerId: '2'
|
||||
networkMode: 'default'
|
||||
networks: { 'default': { aliases: [ 'anotherService' ] } }
|
||||
command: [ 'someCommand' ]
|
||||
entrypoint: [ 'theEntrypoint' ]
|
||||
}
|
||||
]
|
||||
volumes: {}
|
||||
networks: { default: {} }
|
||||
}
|
||||
]
|
||||
}
|
||||
dependent: { apps: [], devices: [] }
|
||||
}
|
||||
|
||||
exports.availableImages = availableImages = []
|
||||
availableImages[0] = [
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/afaff:latest'
|
||||
appId: 1234
|
||||
serviceId: 24
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12346
|
||||
releaseId: 2
|
||||
dependent: 0
|
||||
dockerImageId: 'id0'
|
||||
},
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
releaseId: 2
|
||||
dependent: 0
|
||||
dockerImageId: 'id1'
|
||||
}
|
||||
]
|
||||
availableImages[1] = [
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/foooo:latest'
|
||||
appId: 1234
|
||||
serviceId: 24
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12347
|
||||
releaseId: 2
|
||||
dependent: 0
|
||||
dockerImageId: 'id2'
|
||||
},
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/edfabc:latest'
|
||||
appId: 1234
|
||||
serviceId: 23
|
||||
serviceName: 'aservice'
|
||||
imageId: 12345
|
||||
releaseId: 2
|
||||
dependent: 0
|
||||
dockerImageId: 'id1'
|
||||
}
|
||||
]
|
||||
|
||||
availableImages[2] = [
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/foooo:latest'
|
||||
appId: 1234
|
||||
serviceId: 24
|
||||
serviceName: 'anotherService'
|
||||
imageId: 12347
|
||||
releaseId: 2
|
||||
dependent: 0
|
||||
dockerImageId: 'id2'
|
||||
}
|
||||
]
|
820
test/lib/application-manager-test-states.ts
Normal file
820
test/lib/application-manager-test-states.ts
Normal file
@ -0,0 +1,820 @@
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Sanity-check the conversion and remove this comment.
|
||||
|
||||
export let availableImages: any;
|
||||
export let currentState: any;
|
||||
export let targetState: any;
|
||||
|
||||
targetState = [];
|
||||
targetState[0] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234,
|
||||
serviceName: 'aservice',
|
||||
commit: 'afafafa',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
},
|
||||
privileged: false,
|
||||
volumes: [],
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
'24': {
|
||||
appId: 1234,
|
||||
serviceName: 'anotherService',
|
||||
commit: 'afafafa',
|
||||
imageId: 12346,
|
||||
image: 'registry2.resin.io/superapp/afaff:latest',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
},
|
||||
volumes: [],
|
||||
privileged: false,
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
targetState[1] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234,
|
||||
serviceName: 'aservice',
|
||||
commit: 'afafafa',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
volumes: [],
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
targetState[2] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234,
|
||||
serviceName: 'aservice',
|
||||
commit: 'afafafa',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
volumes: [],
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
'24': {
|
||||
appId: 1234,
|
||||
serviceName: 'anotherService',
|
||||
commit: 'afafafa',
|
||||
imageId: 12347,
|
||||
image: 'registry2.resin.io/superapp/foooo:latest',
|
||||
depends_on: ['aservice'],
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [],
|
||||
privileged: false,
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
targetState[3] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234,
|
||||
serviceName: 'aservice',
|
||||
commit: 'afafafa',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
volumes: [],
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
'24': {
|
||||
appId: 1234,
|
||||
serviceName: 'anotherService',
|
||||
commit: 'afafafa',
|
||||
imageId: 12347,
|
||||
image: 'registry2.resin.io/superapp/foooo:latest',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [],
|
||||
privileged: false,
|
||||
labels: {
|
||||
'io.resin.update.strategy': 'kill-then-download',
|
||||
},
|
||||
running: true,
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
targetState[4] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234,
|
||||
serviceName: 'aservice',
|
||||
commit: 'afafafa',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
environment: {
|
||||
FOO: 'THIS VALUE CHANGED',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
volumes: [],
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
'24': {
|
||||
appId: 1234,
|
||||
serviceName: 'anotherService',
|
||||
commit: 'afafafa',
|
||||
imageId: 12347,
|
||||
image: 'registry2.resin.io/superapp/foooo:latest',
|
||||
depends_on: ['aservice'],
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [],
|
||||
privileged: false,
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
targetState[5] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'23': {
|
||||
appId: 1234,
|
||||
serviceName: 'aservice',
|
||||
commit: 'afafafa',
|
||||
imageId: 12345,
|
||||
image: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
environment: {
|
||||
FOO: 'THIS VALUE CHANGED',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
volumes: [],
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
'24': {
|
||||
appId: 1234,
|
||||
serviceName: 'anotherService',
|
||||
commit: 'afafafa',
|
||||
imageId: 12347,
|
||||
image: 'registry2.resin.io/superapp/foooo:latest',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [],
|
||||
privileged: false,
|
||||
labels: {},
|
||||
running: true,
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
targetState[6] = {
|
||||
local: {
|
||||
name: 'volumeTest',
|
||||
config: {},
|
||||
apps: [
|
||||
{
|
||||
appId: 12345,
|
||||
name: 'volumeApp',
|
||||
commit: 'asd',
|
||||
releaseId: 3,
|
||||
services: {},
|
||||
volumes: {},
|
||||
networks: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState = [];
|
||||
currentState[0] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
image: 'id1',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena',
|
||||
],
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '23',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'aservice',
|
||||
},
|
||||
running: true,
|
||||
createdAt: new Date(),
|
||||
containerId: '1',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['aservice'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 24,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12346,
|
||||
image: 'id0',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/balena',
|
||||
],
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '24',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'anotherService',
|
||||
},
|
||||
running: false,
|
||||
createdAt: new Date(),
|
||||
containerId: '2',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['anotherService'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState[1] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState[2] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
expose: [],
|
||||
ports: [],
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
image: 'id1',
|
||||
environment: {
|
||||
FOO: 'THIS VALUE CHANGED',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena',
|
||||
],
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '23',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'aservice',
|
||||
},
|
||||
running: true,
|
||||
createdAt: new Date(),
|
||||
containerId: '1',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['aservice'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState[3] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
expose: [],
|
||||
ports: [],
|
||||
image: 'id1',
|
||||
environment: {
|
||||
FOO: 'THIS VALUE CHANGED',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena',
|
||||
],
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '23',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'aservice',
|
||||
},
|
||||
running: true,
|
||||
createdAt: new Date(0),
|
||||
containerId: '1',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['aservice'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
expose: [],
|
||||
ports: [],
|
||||
image: 'id1',
|
||||
environment: {
|
||||
FOO: 'THIS VALUE CHANGED',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena',
|
||||
],
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '23',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'aservice',
|
||||
},
|
||||
running: true,
|
||||
createdAt: new Date(1),
|
||||
containerId: '2',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['aservice'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState[4] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 24,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12346,
|
||||
image: 'id0',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/balena',
|
||||
],
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '24',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'anotherService',
|
||||
},
|
||||
running: false,
|
||||
createdAt: new Date(),
|
||||
containerId: '2',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['aservice'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState[5] = {
|
||||
local: {
|
||||
name: 'volumeTest',
|
||||
config: {},
|
||||
apps: [
|
||||
{
|
||||
appId: 12345,
|
||||
name: 'volumeApp',
|
||||
commit: 'asd',
|
||||
releaseId: 3,
|
||||
services: [],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
{
|
||||
appId: 12,
|
||||
name: 'previous-app',
|
||||
commit: '123',
|
||||
releaseId: 10,
|
||||
services: [],
|
||||
networks: {},
|
||||
volumes: {
|
||||
my_volume: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
currentState[6] = {
|
||||
local: {
|
||||
name: 'aDeviceWithDifferentName',
|
||||
config: {
|
||||
RESIN_HOST_CONFIG_gpu_mem: '512',
|
||||
RESIN_HOST_LOG_TO_DISPLAY: '1',
|
||||
},
|
||||
apps: [
|
||||
{
|
||||
appId: 1234,
|
||||
name: 'superapp',
|
||||
commit: 'afafafa',
|
||||
releaseId: 2,
|
||||
services: [
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
image: 'id1',
|
||||
environment: {
|
||||
FOO: 'bar',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/aservice:/tmp/balena',
|
||||
],
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '23',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'aservice',
|
||||
},
|
||||
running: true,
|
||||
createdAt: new Date(),
|
||||
containerId: '1',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['aservice'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
{
|
||||
appId: 1234,
|
||||
serviceId: 24,
|
||||
releaseId: 2,
|
||||
commit: 'afafafa',
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12346,
|
||||
image: 'id0',
|
||||
environment: {
|
||||
FOO: 'bro',
|
||||
ADDITIONAL_ENV_VAR: 'foo',
|
||||
},
|
||||
volumes: [
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/resin',
|
||||
'/tmp/balena-supervisor/services/1234/anotherService:/tmp/balena',
|
||||
],
|
||||
privileged: false,
|
||||
restart: 'always',
|
||||
labels: {
|
||||
'io.resin.app-id': '1234',
|
||||
'io.resin.service-id': '24',
|
||||
'io.resin.supervised': 'true',
|
||||
'io.resin.service-name': 'anotherService',
|
||||
},
|
||||
running: true,
|
||||
createdAt: new Date(),
|
||||
containerId: '2',
|
||||
networkMode: 'default',
|
||||
networks: { default: { aliases: ['anotherService'] } },
|
||||
command: ['someCommand'],
|
||||
entrypoint: ['theEntrypoint'],
|
||||
},
|
||||
],
|
||||
volumes: {},
|
||||
networks: { default: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
dependent: { apps: [], devices: [] },
|
||||
};
|
||||
|
||||
availableImages = [];
|
||||
availableImages[0] = [
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/afaff:latest',
|
||||
appId: 1234,
|
||||
serviceId: 24,
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12346,
|
||||
releaseId: 2,
|
||||
dependent: 0,
|
||||
dockerImageId: 'id0',
|
||||
},
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
releaseId: 2,
|
||||
dependent: 0,
|
||||
dockerImageId: 'id1',
|
||||
},
|
||||
];
|
||||
availableImages[1] = [
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/foooo:latest',
|
||||
appId: 1234,
|
||||
serviceId: 24,
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12347,
|
||||
releaseId: 2,
|
||||
dependent: 0,
|
||||
dockerImageId: 'id2',
|
||||
},
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/edfabc:latest',
|
||||
appId: 1234,
|
||||
serviceId: 23,
|
||||
serviceName: 'aservice',
|
||||
imageId: 12345,
|
||||
releaseId: 2,
|
||||
dependent: 0,
|
||||
dockerImageId: 'id1',
|
||||
},
|
||||
];
|
||||
|
||||
availableImages[2] = [
|
||||
{
|
||||
name: 'registry2.resin.io/superapp/foooo:latest',
|
||||
appId: 1234,
|
||||
serviceId: 24,
|
||||
serviceName: 'anotherService',
|
||||
imageId: 12347,
|
||||
releaseId: 2,
|
||||
dependent: 0,
|
||||
dockerImageId: 'id2',
|
||||
},
|
||||
];
|
@ -1,8 +0,0 @@
|
||||
chai = require 'chai'
|
||||
chaiAsPromised = require('chai-as-promised')
|
||||
sinonChai = require('sinon-chai')
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
chai.use(sinonChai)
|
||||
|
||||
module.exports = chai
|
8
test/lib/chai-config.ts
Normal file
8
test/lib/chai-config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import * as chai from 'chai';
|
||||
import chaiAsPromised = require('chai-as-promised');
|
||||
import sinonChai = require('sinon-chai');
|
||||
|
||||
chai.use(chaiAsPromised as any);
|
||||
chai.use(sinonChai);
|
||||
|
||||
export = chai;
|
@ -1,35 +0,0 @@
|
||||
express = require 'express'
|
||||
_ = require 'lodash'
|
||||
api = express()
|
||||
api.use(require('body-parser').json())
|
||||
|
||||
api.balenaBackend = {
|
||||
currentId: 1
|
||||
devices: {}
|
||||
registerHandler: (req, res) ->
|
||||
console.log('/device/register called with ', req.body)
|
||||
device = req.body
|
||||
device.id = api.balenaBackend.currentId++
|
||||
api.balenaBackend.devices[device.id] = device
|
||||
res.status(201).json(device)
|
||||
getDeviceHandler: (req, res) ->
|
||||
uuid = req.query['$filter']?.match(/uuid eq '(.*)'/)?[1]
|
||||
if uuid?
|
||||
res.json({ d: _.filter(api.balenaBackend.devices, (dev) -> dev.uuid is uuid ) })
|
||||
else
|
||||
res.json({ d: [] })
|
||||
deviceKeyHandler: (req, res) ->
|
||||
res.status(200).send(req.body.apiKey)
|
||||
}
|
||||
|
||||
|
||||
api.post '/device/register', (req, res) ->
|
||||
api.balenaBackend.registerHandler(req, res)
|
||||
|
||||
api.get '/v5/device', (req, res) ->
|
||||
api.balenaBackend.getDeviceHandler(req, res)
|
||||
|
||||
api.post '/api-key/device/:deviceId/device-key', (req, res) ->
|
||||
api.balenaBackend.deviceKeyHandler(req, res)
|
||||
|
||||
module.exports = api
|
66
test/lib/mocked-balena-api.ts
Normal file
66
test/lib/mocked-balena-api.ts
Normal file
@ -0,0 +1,66 @@
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Sanity-check the conversion and remove this comment.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS103: Rewrite code to no longer use __guard__
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import * as express from 'express';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
const api: express.Express & {
|
||||
balenaBackend?: {
|
||||
currentId: number;
|
||||
devices: { [key: string]: any };
|
||||
registerHandler: express.RequestHandler;
|
||||
getDeviceHandler: express.RequestHandler;
|
||||
deviceKeyHandler: express.RequestHandler;
|
||||
};
|
||||
} = express();
|
||||
|
||||
// tslint:disable-next-line
|
||||
api.use(require('body-parser').json());
|
||||
|
||||
api.balenaBackend = {
|
||||
currentId: 1,
|
||||
devices: {},
|
||||
registerHandler: (req, res) => {
|
||||
console.log('/device/register called with ', req.body);
|
||||
const device = req.body;
|
||||
device.id = api.balenaBackend!.currentId++;
|
||||
api.balenaBackend!.devices[device.id] = device;
|
||||
return res.status(201).json(device);
|
||||
},
|
||||
getDeviceHandler: (req, res) => {
|
||||
const uuid =
|
||||
req.query['$filter'] != null
|
||||
? req.query['$filter'].match(/uuid eq '(.*)'/)[1]
|
||||
: null;
|
||||
if (uuid != null) {
|
||||
return res.json({
|
||||
d: _.filter(api.balenaBackend!.devices, dev => dev.uuid === uuid),
|
||||
});
|
||||
} else {
|
||||
return res.json({ d: [] });
|
||||
}
|
||||
},
|
||||
deviceKeyHandler: (req, res) => {
|
||||
return res.status(200).send(req.body.apiKey);
|
||||
},
|
||||
};
|
||||
|
||||
api.post('/device/register', (req, res) =>
|
||||
api.balenaBackend!.registerHandler(req, res, _.noop),
|
||||
);
|
||||
|
||||
api.get('/v5/device', (req, res) =>
|
||||
api.balenaBackend!.getDeviceHandler(req, res, _.noop),
|
||||
);
|
||||
|
||||
api.post('/api-key/device/:deviceId/device-key', (req, res) =>
|
||||
api.balenaBackend!.deviceKeyHandler(req, res, _.noop),
|
||||
);
|
||||
|
||||
export = api;
|
@ -1,27 +0,0 @@
|
||||
fs = require('fs')
|
||||
|
||||
module.exports = ->
|
||||
try
|
||||
fs.unlinkSync(process.env.DATABASE_PATH)
|
||||
|
||||
try
|
||||
fs.unlinkSync(process.env.DATABASE_PATH_2)
|
||||
|
||||
try
|
||||
fs.unlinkSync(process.env.DATABASE_PATH_3)
|
||||
|
||||
try
|
||||
fs.unlinkSync(process.env.LED_FILE)
|
||||
|
||||
try
|
||||
fs.writeFileSync('./test/data/config.json', fs.readFileSync('./test/data/testconfig.json'))
|
||||
fs.writeFileSync('./test/data/config-apibinder.json', fs.readFileSync('./test/data/testconfig-apibinder.json'))
|
||||
|
||||
fs.writeFileSync(
|
||||
'./test/data/config-apibinder-offline.json',
|
||||
fs.readFileSync('./test/data/testconfig-apibinder-offline.json')
|
||||
)
|
||||
fs.writeFileSync(
|
||||
'./test/data/config-apibinder-offline2.json',
|
||||
fs.readFileSync('./test/data/testconfig-apibinder-offline2.json')
|
||||
)
|
56
test/lib/prepare.ts
Normal file
56
test/lib/prepare.ts
Normal file
@ -0,0 +1,56 @@
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Sanity-check the conversion and remove this comment.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
|
||||
export = function() {
|
||||
try {
|
||||
fs.unlinkSync(process.env.DATABASE_PATH!);
|
||||
} catch (e) {
|
||||
/* ignore /*/
|
||||
}
|
||||
|
||||
try {
|
||||
fs.unlinkSync(process.env.DATABASE_PATH_2!);
|
||||
} catch (e) {
|
||||
/* ignore /*/
|
||||
}
|
||||
|
||||
try {
|
||||
fs.unlinkSync(process.env.DATABASE_PATH_3!);
|
||||
} catch (e) {
|
||||
/* ignore /*/
|
||||
}
|
||||
|
||||
try {
|
||||
fs.unlinkSync(process.env.LED_FILE!);
|
||||
} catch (e) {
|
||||
/* ignore /*/
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
'./test/data/config.json',
|
||||
fs.readFileSync('./test/data/testconfig.json'),
|
||||
);
|
||||
fs.writeFileSync(
|
||||
'./test/data/config-apibinder.json',
|
||||
fs.readFileSync('./test/data/testconfig-apibinder.json'),
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'./test/data/config-apibinder-offline.json',
|
||||
fs.readFileSync('./test/data/testconfig-apibinder-offline.json'),
|
||||
);
|
||||
return fs.writeFileSync(
|
||||
'./test/data/config-apibinder-offline2.json',
|
||||
fs.readFileSync('./test/data/testconfig-apibinder-offline2.json'),
|
||||
);
|
||||
} catch (e) {
|
||||
/* ignore /*/
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user