mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-16 23:38:52 +00:00
commit
6229e3ee61
@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file
|
||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## v7.5.3 - 2018-05-02
|
||||
|
||||
* Add typescript linting to supervisor tests #638 [Cameron Diver]
|
||||
* Convert conversions module to typescript #638 [Cameron Diver]
|
||||
* Move shared types to separate module #638 [Cameron Diver]
|
||||
* Convert blink module to typescript #638 [Cameron Diver]
|
||||
* Convert lib/constants module to typescript #638 [Cameron Diver]
|
||||
* Type parameters for validation functions better #638 [Cameron Diver]
|
||||
|
||||
## v7.5.2 - 2018-05-01
|
||||
|
||||
* Add some more unit tests to the multicontainer supervisor #519 [Pablo Carranza Velez]
|
||||
|
@ -41,6 +41,7 @@ RUN JOBS=MAX npm install --no-optional --unsafe-perm
|
||||
COPY webpack.config.js fix-jsonstream.js hardcode-migrations.js tsconfig.json /usr/src/app/
|
||||
COPY src /usr/src/app/src
|
||||
COPY test /usr/src/app/test
|
||||
COPY typings /usr/src/app/typings
|
||||
|
||||
RUN npm test \
|
||||
&& npm run build
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "resin-supervisor",
|
||||
"description": "This is resin.io's Supervisor, a program that runs on IoT devices and has the task of running user Apps (which are Docker containers), and updating them as Resin's API informs it to.",
|
||||
"version": "7.5.2",
|
||||
"version": "7.5.3",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -10,7 +10,7 @@
|
||||
"scripts": {
|
||||
"start": "./entry.sh",
|
||||
"build": "webpack",
|
||||
"lint": "resin-lint src/ test/",
|
||||
"lint": "resin-lint --typescript src/ test/",
|
||||
"test": "npm run lint && JUNIT_REPORT_PATH=report.xml mocha --exit -r ts-node/register -r coffee-script/register -r register-coffee-coverage test/*.{js,coffee} && npm run coverage",
|
||||
"versionist": "versionist",
|
||||
"coverage": "istanbul report text && istanbul report html"
|
||||
@ -58,7 +58,7 @@
|
||||
"pubnub": "^3.7.13",
|
||||
"register-coffee-coverage": "0.0.1",
|
||||
"request": "^2.51.0",
|
||||
"resin-lint": "^1.3.1",
|
||||
"resin-lint": "^1.5.7",
|
||||
"resin-register-device": "^3.0.0",
|
||||
"resin-sync": "^9.3.0",
|
||||
"resumable-request": "^2.0.0",
|
||||
|
@ -1,2 +0,0 @@
|
||||
constants = require './constants'
|
||||
module.exports = require('blinking')(constants.ledFile)
|
5
src/lib/blink.ts
Normal file
5
src/lib/blink.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import blinking = require('blinking');
|
||||
|
||||
import constants = require('./constants');
|
||||
|
||||
export = blinking(constants.ledFile);
|
@ -1,43 +0,0 @@
|
||||
{ checkString } = require './validation'
|
||||
|
||||
bootMountPointFromEnv = checkString(process.env.BOOT_MOUNTPOINT)
|
||||
rootMountPoint = checkString(process.env.ROOT_MOUNTPOINT) ? '/mnt/root'
|
||||
|
||||
supervisorNetworkInterface = 'supervisor0'
|
||||
|
||||
module.exports =
|
||||
rootMountPoint: rootMountPoint
|
||||
databasePath: checkString(process.env.DATABASE_PATH) ? '/data/database.sqlite'
|
||||
dockerSocket: process.env.DOCKER_SOCKET ? '/var/run/docker.sock'
|
||||
supervisorImage: checkString(process.env.SUPERVISOR_IMAGE) ? 'resin/rpi-supervisor'
|
||||
ledFile: checkString(process.env.LED_FILE) ? '/sys/class/leds/led0/brightness'
|
||||
forceSecret: # Only used for development
|
||||
api: checkString(process.env.RESIN_SUPERVISOR_SECRET) ? null
|
||||
logsChannel: checkString(process.env.RESIN_SUPERVISOR_LOGS_CHANNEL) ? null
|
||||
vpnStatusPath: checkString(process.env.VPN_STATUS_PATH) ? "#{rootMountPoint}/run/openvpn/vpn_status"
|
||||
hostOSVersionPath: checkString(process.env.HOST_OS_VERSION_PATH) ? "#{rootMountPoint}/etc/os-release"
|
||||
privateAppEnvVars: [
|
||||
'RESIN_SUPERVISOR_API_KEY'
|
||||
'RESIN_API_KEY'
|
||||
]
|
||||
dataPath: checkString(process.env.RESIN_DATA_PATH) ? '/resin-data'
|
||||
bootMountPointFromEnv: bootMountPointFromEnv
|
||||
bootMountPoint: bootMountPointFromEnv ? '/boot'
|
||||
configJsonPathOnHost: checkString(process.env.CONFIG_JSON_PATH)
|
||||
proxyvisorHookReceiver: checkString(process.env.RESIN_PROXYVISOR_HOOK_RECEIVER) ? 'http://0.0.0.0:1337'
|
||||
apiEndpointFromEnv: checkString(process.env.API_ENDPOINT)
|
||||
configJsonNonAtomicPath: '/boot/config.json'
|
||||
defaultPubnubSubscribeKey: process.env.DEFAULT_PUBNUB_SUBSCRIBE_KEY
|
||||
defaultPubnubPublishKey: process.env.DEFAULT_PUBNUB_PUBLISH_KEY
|
||||
defaultMixpanelToken: process.env.DEFAULT_MIXPANEL_TOKEN
|
||||
supervisorNetworkInterface: supervisorNetworkInterface
|
||||
allowedInterfaces: ['resin-vpn', 'tun0', 'docker0', 'lo', supervisorNetworkInterface]
|
||||
appsJsonPath: process.env.APPS_JSON_PATH ? '/boot/apps.json'
|
||||
ipAddressUpdateInterval: 30 * 1000
|
||||
imageCleanupErrorIgnoreTimeout: 3600 * 1000
|
||||
maxDeltaDownloads: 3
|
||||
defaultVolumeLabels: {
|
||||
'io.resin.supervised': 'true'
|
||||
}
|
||||
|
||||
process.env.DOCKER_HOST ?= "unix://#{module.exports.dockerSocket}"
|
48
src/lib/constants.ts
Normal file
48
src/lib/constants.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { checkString } from './validation';
|
||||
|
||||
const bootMountPointFromEnv = checkString(process.env.BOOT_MOUNTPOINT);
|
||||
const rootMountPoint = checkString(process.env.ROOT_MOUNTPOINT) || '/mnt/root';
|
||||
|
||||
const supervisorNetworkInterface = 'supervisor0';
|
||||
|
||||
const constants = {
|
||||
rootMountPoint,
|
||||
databasePath: checkString(process.env.DATABASE_PATH) || '/data/database.sqlite',
|
||||
dockerSocket: process.env.DOCKER_SOCKET || '/var/run/docker.sock',
|
||||
supervisorImage: checkString(process.env.SUPERVISOR_IMAGE) || 'resin/rpi-supervisor',
|
||||
ledFile: checkString(process.env.LED_FILE) || '/sys/class/leds/led0/brightness',
|
||||
vpnStatusPath:
|
||||
checkString(process.env.VPN_STATUS_PATH) || `${rootMountPoint}/run/openvpn/vpn_status`,
|
||||
hostOSVersionPath:
|
||||
checkString(process.env.HOST_OS_VERSION_PATH) || `${rootMountPoint}/etc/os-release`,
|
||||
privateAppEnvVars: [
|
||||
'RESIN_SUPERVISOR_API_KEY',
|
||||
'RESIN_API_KEY',
|
||||
],
|
||||
dataPath: checkString(process.env.RESIN_DATA_PATH) || '/resin-data',
|
||||
bootMountPointFromEnv,
|
||||
bootMountPoint: bootMountPointFromEnv || '/boot',
|
||||
configJsonPathOnHost: checkString(process.env.CONFIG_JSON_PATH),
|
||||
proxyvisorHookReceiver:
|
||||
checkString(process.env.RESIN_PROXYVISOR_HOOK_RECEIVER) || 'http://0.0.0.0:1337',
|
||||
apiEndpointFromEnv: checkString(process.env.API_ENDPOINT),
|
||||
configJsonNonAtomicPath: '/boot/config.json',
|
||||
defaultPubnubSubscribeKey: process.env.DEFAULT_PUBNUB_SUBSCRIBE_KEY,
|
||||
defaultPubnubPublishKey: process.env.DEFAULT_PUBNUB_PUBLISH_KEY,
|
||||
defaultMixpanelToken: process.env.DEFAULT_MIXPANEL_TOKEN,
|
||||
supervisorNetworkInterface: supervisorNetworkInterface,
|
||||
allowedInterfaces: [ 'resin-vpn', 'tun0', 'docker0', 'lo', supervisorNetworkInterface ],
|
||||
appsJsonPath: process.env.APPS_JSON_PATH || '/boot/apps.json',
|
||||
ipAddressUpdateInterval: 30 * 1000,
|
||||
imageCleanupErrorIgnoreTimeout: 3600 * 1000,
|
||||
maxDeltaDownloads: 3,
|
||||
defaultVolumeLabels: {
|
||||
'io.resin.supervised': 'true',
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.DOCKER_HOST == null) {
|
||||
process.env.DOCKER_HOST = `unix://${constants.dockerSocket}`;
|
||||
}
|
||||
|
||||
export = constants;
|
@ -1,12 +0,0 @@
|
||||
_ = require 'lodash'
|
||||
|
||||
exports.envArrayToObject = (env) ->
|
||||
# env is an array of strings that say 'key=value'
|
||||
toPair = (keyVal) ->
|
||||
m = keyVal.match(/^([^=]+)=(.*)$/)
|
||||
if !m?
|
||||
console.log("WARNING: Could not correctly parse env var #{keyVal}. " +
|
||||
'Please fix this var and recreate the container.')
|
||||
return null
|
||||
return m[1..]
|
||||
_(env).map(toPair).filter(([_, v]) -> v?).fromPairs().value()
|
21
src/lib/conversions.ts
Normal file
21
src/lib/conversions.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { EnvVarObject } from './types';
|
||||
|
||||
export function envArrayToObject(env: string[]): EnvVarObject {
|
||||
const toPair = (keyVal: string) => {
|
||||
const m = keyVal.match(/^([^=]+)=(.*)$/);
|
||||
if (m == null) {
|
||||
console.log(`WARNING: Could not correctly parse env var ${keyVal}. ` +
|
||||
'Please fix this var and recreate the container.');
|
||||
return [null, null];
|
||||
}
|
||||
return m.slice(1);
|
||||
};
|
||||
|
||||
return _(env)
|
||||
.map(toPair)
|
||||
.filter(([_, v]) => v != null)
|
||||
.fromPairs()
|
||||
.value();
|
||||
}
|
7
src/lib/types.ts
Normal file
7
src/lib/types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface EnvVarObject {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export interface LabelObject {
|
||||
[name: string]: string;
|
||||
}
|
@ -1,28 +1,24 @@
|
||||
import * as _ from 'lodash';
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { EnvVarObject, LabelObject } from './types';
|
||||
|
||||
export interface CheckIntOptions {
|
||||
positive?: boolean;
|
||||
}
|
||||
|
||||
export interface EnvVarObject {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export interface LabelObject {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
const ENV_VAR_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
const LABEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\.\-]*$/;
|
||||
|
||||
type NullableString = string | undefined | null;
|
||||
|
||||
/**
|
||||
* checkInt
|
||||
*
|
||||
* Check an input string as a number, optionally specifying a requirement
|
||||
* to be positive
|
||||
*/
|
||||
export function checkInt(s: string | null, options: CheckIntOptions = {}): number | void {
|
||||
export function checkInt(s: NullableString, options: CheckIntOptions = {}): number | void {
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
@ -45,7 +41,7 @@ export function checkInt(s: string | null, options: CheckIntOptions = {}): numbe
|
||||
*
|
||||
* Check that a string exists, and is not an empty string, 'null', or 'undefined'
|
||||
*/
|
||||
export function checkString(s: string): string | void {
|
||||
export function checkString(s: NullableString): string | void {
|
||||
if (s == null || !_.isString(s) || _.includes([ 'null', 'undefined', '' ], s)) {
|
||||
return;
|
||||
}
|
||||
@ -104,13 +100,13 @@ export function isValidEnv(obj: EnvVarObject): boolean {
|
||||
return _.every(obj, (val, key) => {
|
||||
if (!isValidShortText(key)) {
|
||||
console.log('debug: Non-valid short text env var key passed to validation.isValidEnv');
|
||||
console.log(`\tKey: ${inspect(key)}`)
|
||||
console.log(`\tKey: ${inspect(key)}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ENV_VAR_KEY_REGEX.test(key)) {
|
||||
console.log('debug: Invalid env var key passed to validation.isValidEnv');
|
||||
console.log(`\tKey: ${inspect(key)}`)
|
||||
console.log(`\tKey: ${inspect(key)}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -230,7 +226,7 @@ export function isValidDependentAppsObject(apps: any): boolean {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -282,7 +278,7 @@ function isValidService(service: any, serviceId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -340,7 +336,7 @@ export function isValidAppsObject(obj: any): boolean {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -405,10 +401,10 @@ export function isValidDependentDevicesObject(devices: any): boolean {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
38
test/15-conversions.spec.coffee
Normal file
38
test/15-conversions.spec.coffee
Normal file
@ -0,0 +1,38 @@
|
||||
m = require 'mochainon'
|
||||
{ expect } = m.chai
|
||||
|
||||
conversion = require '../src/lib/conversions'
|
||||
|
||||
describe 'conversions', ->
|
||||
|
||||
describe 'envArrayToObject', ->
|
||||
it 'should convert an env array to an object', ->
|
||||
expect(conversion.envArrayToObject([
|
||||
'key=value'
|
||||
'test1=test2'
|
||||
'k=v'
|
||||
'equalsvalue=thisvaluehasan=char'
|
||||
'asd='
|
||||
'number=123'
|
||||
])).to.deep.equal({
|
||||
key: 'value'
|
||||
test1: 'test2'
|
||||
k: 'v'
|
||||
equalsvalue: 'thisvaluehasan=char'
|
||||
asd: ''
|
||||
number: '123'
|
||||
})
|
||||
|
||||
it 'should ignore invalid env array entries', ->
|
||||
expect(conversion.envArrayToObject([
|
||||
'key1',
|
||||
'key1=value1'
|
||||
])).to.deep.equal({
|
||||
key1: 'value1'
|
||||
})
|
||||
|
||||
it 'should return an empty object with an empty input', ->
|
||||
expect(conversion.envArrayToObject(null)).to.deep.equal({})
|
||||
expect(conversion.envArrayToObject('')).to.deep.equal({})
|
||||
expect(conversion.envArrayToObject([])).to.deep.equal({})
|
||||
expect(conversion.envArrayToObject(1)).to.deep.equal({})
|
18
typings/blinking.d.ts
vendored
Normal file
18
typings/blinking.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
declare module 'blinking' {
|
||||
|
||||
interface Pattern {
|
||||
blinks?: number;
|
||||
onDuration?: number;
|
||||
offDuration?: number;
|
||||
pause?: number;
|
||||
}
|
||||
|
||||
interface Blink {
|
||||
start: (pattern: Pattern) => void
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
function blinking(ledFile: string): Blink;
|
||||
|
||||
export = blinking;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user