Merge branch 'master' into v1-healthcheck-stub-fix

This commit is contained in:
Christina Wang 2021-02-19 11:07:33 +09:00 committed by GitHub
commit 8948bde02a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 128 additions and 49 deletions

View File

@ -1,3 +1,18 @@
- commits:
- subject: Refactor checkTruthy to return more predictable values
hash: ec23d1d37141c81379fd9af487a4b3b7c907dad1
body: ''
footer:
Change-type: patch
change-type: patch
Closes: '#1595'
closes: '#1595'
Signed-off-by: Miguel Casqueira <miguel@balena.io>
signed-off-by: Miguel Casqueira <miguel@balena.io>
author: Miguel Casqueira
nested: []
version: 12.4.1
date: 2021-02-18T17:38:51.848Z
- commits: - commits:
- subject: 'Complete /v1/device/host-config unit tests, modify PATCH route' - subject: 'Complete /v1/device/host-config unit tests, modify PATCH route'
hash: b3b1d47b34e0e2109f2d0b08551415f588457480 hash: b3b1d47b34e0e2109f2d0b08551415f588457480

View File

@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
# v12.4.1
## (2021-02-18)
* Refactor checkTruthy to return more predictable values [Miguel Casqueira]
# v12.4.0 # v12.4.0
## (2021-02-18) ## (2021-02-18)

View File

@ -1 +1 @@
12.4.0 12.4.1

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "balena-supervisor", "name": "balena-supervisor",
"version": "12.4.0", "version": "12.4.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,7 +1,7 @@
{ {
"name": "balena-supervisor", "name": "balena-supervisor",
"description": "This is balena'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 the balena API informs it to.", "description": "This is balena'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 the balena API informs it to.",
"version": "12.4.0", "version": "12.4.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -3,7 +3,7 @@ import * as t from 'io-ts';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { InternalInconsistencyError } from '../lib/errors'; import { InternalInconsistencyError } from '../lib/errors';
import { checkTruthy } from '../lib/validation'; import { checkBooleanish, checkTruthy } from '../lib/validation';
const permissiveValue = t.union([ const permissiveValue = t.union([
t.boolean, t.boolean,
@ -23,11 +23,10 @@ export const PermissiveBoolean = new t.Type<boolean, t.TypeOf<PermissiveType>>(
case 'string': case 'string':
case 'boolean': case 'boolean':
case 'number': case 'number':
const val = checkTruthy(v); if (!checkBooleanish(v)) {
if (val == null) {
return t.failure(v, c); return t.failure(v, c);
} }
return t.success(val); return t.success(checkTruthy(v));
case 'undefined': case 'undefined':
return t.success(false); return t.success(false);
case 'object': case 'object':

View File

@ -15,7 +15,7 @@ import { getApp } from '../device-state/db-format';
export function createV1Api(router: express.Router) { export function createV1Api(router: express.Router) {
router.post('/v1/restart', (req: AuthorizedRequest, res, next) => { router.post('/v1/restart', (req: AuthorizedRequest, res, next) => {
const appId = checkInt(req.body.appId); const appId = checkInt(req.body.appId);
const force = checkTruthy(req.body.force) ?? false; const force = checkTruthy(req.body.force);
eventTracker.track('Restart container (v1)', { appId }); eventTracker.track('Restart container (v1)', { appId });
if (appId == null) { if (appId == null) {
return res.status(400).send('Missing app id'); return res.status(400).send('Missing app id');
@ -42,7 +42,7 @@ export function createV1Api(router: express.Router) {
action: 'start' | 'stop', action: 'start' | 'stop',
) => { ) => {
const appId = checkInt(req.params.appId); const appId = checkInt(req.params.appId);
const force = checkTruthy(req.body.force) ?? false; const force = checkTruthy(req.body.force);
if (appId == null) { if (appId == null) {
return res.status(400).send('Missing app id'); return res.status(400).send('Missing app id');
} }
@ -168,7 +168,7 @@ export function createV1Api(router: express.Router) {
router.post('/v1/purge', (req: AuthorizedRequest, res, next) => { router.post('/v1/purge', (req: AuthorizedRequest, res, next) => {
const appId = checkInt(req.body.appId); const appId = checkInt(req.body.appId);
const force = checkTruthy(req.body.force) ?? false; const force = checkTruthy(req.body.force);
if (appId == null) { if (appId == null) {
const errMsg = 'Invalid or missing appId'; const errMsg = 'Invalid or missing appId';
return res.status(400).send(errMsg); return res.status(400).send(errMsg);

View File

@ -577,8 +577,8 @@ export function createV2Api(router: Router) {
}); });
router.post('/v2/journal-logs', (req, res) => { router.post('/v2/journal-logs', (req, res) => {
const all = checkTruthy(req.body.all) || false; const all = checkTruthy(req.body.all);
const follow = checkTruthy(req.body.follow) || false; const follow = checkTruthy(req.body.follow);
const count = checkInt(req.body.count, { positive: true }) || undefined; const count = checkInt(req.body.count, { positive: true }) || undefined;
const unit = req.body.unit; const unit = req.body.unit;
const format = req.body.format || 'short'; const format = req.body.format || 'short';

View File

@ -633,10 +633,8 @@ async function isVPNEnabled(): Promise<boolean> {
} }
} }
async function setVPNEnabled(value?: string | boolean) { async function setVPNEnabled(value: string | boolean = true) {
const v = checkTruthy(value || true); const enable = checkTruthy(value);
const enable = v != null ? v : true;
if (enable) { if (enable) {
await dbus.startService(vpnServiceName); await dbus.startService(vpnServiceName);
} else { } else {

View File

@ -225,8 +225,7 @@ export function validateTargetContracts(
serviceContracts[svc.serviceName] = { serviceContracts[svc.serviceName] = {
contract: svc.contract, contract: svc.contract,
optional: optional: checkTruthy(svc.labels?.['io.balena.features.optional']),
checkTruthy(svc.labels?.['io.balena.features.optional']) || false,
}; };
} catch (e) { } catch (e) {
throw new ContractValidationError(svc.serviceName, e.message); throw new ContractValidationError(svc.serviceName, e.message);

View File

@ -13,6 +13,8 @@ export interface CheckIntOptions {
const ENV_VAR_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/; const ENV_VAR_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const LABEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\.\-]*$/; const LABEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\.\-]*$/;
const NUMERALS_REGEX = /^-?[0-9]+\.?0*$/; // Allows trailing 0 decimals const NUMERALS_REGEX = /^-?[0-9]+\.?0*$/; // Allows trailing 0 decimals
const TRUTHY = ['1', 'true', true, 'on', 1];
const FALSEY = ['0', 'false', false, 'off', 0];
/** /**
* checkInt * checkInt
@ -55,32 +57,40 @@ export function checkString(s: unknown): string | void {
return s; return s;
} }
/**
* checkBooleanish
*
* Given an unknown value, determine if it can be evaluated to truthy/falsey.
*
*/
export function checkBooleanish(v: unknown): boolean {
return checkTruthy(v) || checkFalsey(v);
}
/** /**
* checkTruthy * checkTruthy
* *
* Given a value which can be a string, boolean or number, return a boolean * Given an unknown value, determine if it evaluates to true.
* which represents if the input was truthy *
*/ */
export function checkTruthy(v: unknown): boolean | undefined { export function checkTruthy(v: unknown): boolean {
if (_.isString(v)) { if (typeof v === 'string') {
v = v.toLowerCase(); v = v.toLowerCase();
} }
switch (v) { return TRUTHY.includes(v as any);
case '1':
case 'true':
case true:
case 'on':
case 1:
return true;
case '0':
case 'false':
case false:
case 'off':
case 0:
return false;
default:
return;
} }
/**
* checkFalsey
*
* Given an unknown value, determine if it evaluates to false.
*
*/
export function checkFalsey(v: unknown): boolean {
if (typeof v === 'string') {
v = v.toLowerCase();
}
return FALSEY.includes(v as any);
} }
/* /*

View File

@ -7,7 +7,7 @@ import * as url from 'url';
import * as constants from './lib/constants'; import * as constants from './lib/constants';
import { EEXIST } from './lib/errors'; import { EEXIST } from './lib/errors';
import { checkTruthy } from './lib/validation'; import { checkFalsey } from './lib/validation';
import blink = require('./lib/blink'); import blink = require('./lib/blink');
@ -106,8 +106,8 @@ export const startConnectivityCheck = _.once(
); );
export function enableConnectivityCheck(enable: boolean) { export function enableConnectivityCheck(enable: boolean) {
const boolEnable = checkTruthy(enable); // Only disable if value explicitly matches falsey
enable = boolEnable != null ? boolEnable : true; enable = !checkFalsey(enable);
enableCheck(enable); enableCheck(enable);
log.debug(`Connectivity check enabled: ${enable}`); log.debug(`Connectivity check enabled: ${enable}`);
} }

View File

@ -6,6 +6,59 @@ import * as validation from '../src/lib/validation';
const almostTooLongText = _.times(255, () => 'a').join(''); const almostTooLongText = _.times(255, () => 'a').join('');
describe('validation', () => { describe('validation', () => {
describe('checkBooleanish', () => {
it('returns true for a truthy or falsey value', () => {
expect(validation.checkBooleanish(true)).to.equal(true);
expect(validation.checkBooleanish('true')).to.equal(true);
expect(validation.checkBooleanish('1')).to.equal(true);
expect(validation.checkBooleanish(1)).to.equal(true);
expect(validation.checkBooleanish('on')).to.equal(true);
expect(validation.checkBooleanish(false)).to.equal(true);
expect(validation.checkBooleanish('false')).to.equal(true);
expect(validation.checkBooleanish('0')).to.equal(true);
expect(validation.checkBooleanish(0)).to.equal(true);
expect(validation.checkBooleanish('off')).to.equal(true);
});
it('returns false for invalid values', () => {
expect(validation.checkBooleanish({})).to.equal(false);
expect(validation.checkBooleanish(10)).to.equal(false);
expect(validation.checkBooleanish('on1')).to.equal(false);
expect(validation.checkBooleanish('foo')).to.equal(false);
expect(validation.checkBooleanish(undefined)).to.equal(false);
expect(validation.checkBooleanish(null)).to.equal(false);
expect(validation.checkBooleanish('')).to.equal(false);
});
});
describe('checkFalsey', () => {
it('returns false for a truthy value', () => {
expect(validation.checkFalsey(true)).to.equal(false);
expect(validation.checkFalsey('true')).to.equal(false);
expect(validation.checkFalsey('1')).to.equal(false);
expect(validation.checkFalsey(1)).to.equal(false);
expect(validation.checkFalsey('on')).to.equal(false);
});
it('returns true for a falsey value', () => {
expect(validation.checkFalsey(false)).to.equal(true);
expect(validation.checkFalsey('false')).to.equal(true);
expect(validation.checkFalsey('0')).to.equal(true);
expect(validation.checkFalsey(0)).to.equal(true);
expect(validation.checkFalsey('off')).to.equal(true);
});
it('returns false for invalid values', () => {
expect(validation.checkFalsey({})).to.equal(false);
expect(validation.checkFalsey(10)).to.equal(false);
expect(validation.checkFalsey('on1')).to.equal(false);
expect(validation.checkFalsey('foo')).to.equal(false);
expect(validation.checkFalsey(undefined)).to.equal(false);
expect(validation.checkFalsey(null)).to.equal(false);
expect(validation.checkFalsey('')).to.equal(false);
});
});
describe('checkTruthy', () => { describe('checkTruthy', () => {
it('returns true for a truthy value', () => { it('returns true for a truthy value', () => {
expect(validation.checkTruthy(true)).to.equal(true); expect(validation.checkTruthy(true)).to.equal(true);
@ -15,7 +68,7 @@ describe('validation', () => {
expect(validation.checkTruthy('on')).to.equal(true); expect(validation.checkTruthy('on')).to.equal(true);
}); });
it('returns false for a falsy value', () => { it('returns false for a falsey value', () => {
expect(validation.checkTruthy(false)).to.equal(false); expect(validation.checkTruthy(false)).to.equal(false);
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);
@ -23,14 +76,14 @@ describe('validation', () => {
expect(validation.checkTruthy('off')).to.equal(false); expect(validation.checkTruthy('off')).to.equal(false);
}); });
it('returns undefined for invalid values', () => { it('returns false for invalid values', () => {
expect(validation.checkTruthy({})).to.be.undefined; expect(validation.checkTruthy({})).to.equal(false);
expect(validation.checkTruthy(10)).to.be.undefined; expect(validation.checkTruthy(10)).to.equal(false);
expect(validation.checkTruthy('on1')).to.be.undefined; expect(validation.checkTruthy('on1')).to.equal(false);
expect(validation.checkTruthy('foo')).to.be.undefined; expect(validation.checkTruthy('foo')).to.equal(false);
expect(validation.checkTruthy(undefined)).to.be.undefined; expect(validation.checkTruthy(undefined)).to.equal(false);
expect(validation.checkTruthy(null)).to.be.undefined; expect(validation.checkTruthy(null)).to.equal(false);
expect(validation.checkTruthy('')).to.be.undefined; expect(validation.checkTruthy('')).to.equal(false);
}); });
}); });