Merge pull request #844 from balena-io/local-mode-unmanaged

Unmanaged + local mode fixes
This commit is contained in:
Pablo Carranza Vélez 2018-12-17 13:57:37 -03:00 committed by GitHub
commit d3d0e19a16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 157 additions and 80 deletions

View File

@ -57,9 +57,9 @@ module.exports = class APIBinder
@readyForUpdates = false @readyForUpdates = false
healthcheck: => healthcheck: =>
@config.getMany([ 'appUpdatePollInterval', 'offlineMode', 'connectivityCheckEnabled' ]) @config.getMany([ 'appUpdatePollInterval', 'unmanaged', 'connectivityCheckEnabled' ])
.then (conf) => .then (conf) =>
if conf.offlineMode if conf.unmanaged
return true return true
timeSinceLastFetch = process.hrtime(@lastTargetStateFetch) timeSinceLastFetch = process.hrtime(@lastTargetStateFetch)
timeSinceLastFetchMs = timeSinceLastFetch[0] * 1000 + timeSinceLastFetch[1] / 1e6 timeSinceLastFetchMs = timeSinceLastFetch[0] * 1000 + timeSinceLastFetch[1] / 1e6
@ -72,10 +72,10 @@ module.exports = class APIBinder
release() release()
initClient: => initClient: =>
@config.getMany([ 'offlineMode', 'apiEndpoint', 'currentApiKey' ]) @config.getMany([ 'unmanaged', 'apiEndpoint', 'currentApiKey' ])
.then ({ offlineMode, apiEndpoint, currentApiKey }) => .then ({ unmanaged, apiEndpoint, currentApiKey }) =>
if offlineMode if unmanaged
console.log('Offline Mode is set, skipping API client initialization') console.log('Unmanaged Mode is set, skipping API client initialization')
return return
baseUrl = url.resolve(apiEndpoint, '/v5/') baseUrl = url.resolve(apiEndpoint, '/v5/')
passthrough = _.cloneDeep(requestOpts) passthrough = _.cloneDeep(requestOpts)
@ -102,10 +102,10 @@ module.exports = class APIBinder
@loadBackupFromMigration(retryDelay) @loadBackupFromMigration(retryDelay)
start: => start: =>
@config.getMany([ 'apiEndpoint', 'offlineMode', 'bootstrapRetryDelay' ]) @config.getMany([ 'apiEndpoint', 'unmanaged', 'bootstrapRetryDelay' ])
.then ({ apiEndpoint, offlineMode, bootstrapRetryDelay }) => .then ({ apiEndpoint, unmanaged, bootstrapRetryDelay }) =>
if offlineMode if unmanaged
console.log('Offline Mode is set, skipping API binder initialization') console.log('Unmanaged Mode is set, skipping API binder initialization')
# If we are offline because there is no apiEndpoint, there's a chance # If we are offline because there is no apiEndpoint, there's a chance
# we've went through a deprovision. We need to set the initialConfigReported # we've went through a deprovision. We need to set the initialConfigReported
# value to '', to ensure that when we do re-provision, we'll report # value to '', to ensure that when we do re-provision, we'll report
@ -258,15 +258,15 @@ module.exports = class APIBinder
provisionDependentDevice: (device) => provisionDependentDevice: (device) =>
@config.getMany([ @config.getMany([
'offlineMode' 'unmanaged'
'provisioned' 'provisioned'
'apiTimeout' 'apiTimeout'
'userId' 'userId'
'deviceId' 'deviceId'
]) ])
.then (conf) => .then (conf) =>
if conf.offlineMode if conf.unmanaged
throw new Error('Cannot provision dependent device in offline mode') throw new Error('Cannot provision dependent device in unmanaged mode')
if !conf.provisioned if !conf.provisioned
throw new Error('Device must be provisioned to provision a dependent device') throw new Error('Device must be provisioned to provision a dependent device')
# TODO: when API supports it as per https://github.com/resin-io/hq/pull/949 remove userId # TODO: when API supports it as per https://github.com/resin-io/hq/pull/949 remove userId
@ -283,13 +283,13 @@ module.exports = class APIBinder
patchDevice: (id, updatedFields) => patchDevice: (id, updatedFields) =>
@config.getMany([ @config.getMany([
'offlineMode' 'unmanaged'
'provisioned' 'provisioned'
'apiTimeout' 'apiTimeout'
]) ])
.then (conf) => .then (conf) =>
if conf.offlineMode if conf.unmanaged
throw new Error('Cannot update dependent device in offline mode') throw new Error('Cannot update dependent device in unmanaged mode')
if !conf.provisioned if !conf.provisioned
throw new Error('Device must be provisioned to update a dependent device') throw new Error('Device must be provisioned to update a dependent device')
@balenaApi.patch @balenaApi.patch
@ -338,6 +338,9 @@ module.exports = class APIBinder
(currentState, targetConfig, defaultConfig, deviceId) => (currentState, targetConfig, defaultConfig, deviceId) =>
currentConfig = currentState.local.config currentConfig = currentState.local.config
Promise.mapSeries _.toPairs(currentConfig), ([ key, value ]) => Promise.mapSeries _.toPairs(currentConfig), ([ key, value ]) =>
# We want to disable local mode when joining a cloud
if key == 'SUPERVISOR_LOCAL_MODE'
value = 'false'
# We never want to disable VPN if, for instance, it failed to start so far # We never want to disable VPN if, for instance, it failed to start so far
if key == 'SUPERVISOR_VPN_CONTROL' if key == 'SUPERVISOR_VPN_CONTROL'
value = 'true' value = 'true'

View File

@ -9,7 +9,6 @@ import supervisorVersion = require('../lib/supervisor-version');
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import * as osRelease from '../lib/os-release'; import * as osRelease from '../lib/os-release';
import { ConfigValue } from '../lib/types'; import { ConfigValue } from '../lib/types';
import { checkTruthy } from '../lib/validation';
// A provider for schema entries with source 'func' // A provider for schema entries with source 'func'
type ConfigProviderFunctionGetter = () => Bluebird<any>; type ConfigProviderFunctionGetter = () => Bluebird<any>;
@ -47,17 +46,6 @@ export function createProviderFunctions(
}); });
}, },
}, },
offlineMode: {
get: () => {
return config
.getMany(['apiEndpoint', 'supervisorOfflineMode'])
.then(({ apiEndpoint, supervisorOfflineMode }) => {
return (
checkTruthy(supervisorOfflineMode as boolean) || !apiEndpoint
);
});
},
},
provisioned: { provisioned: {
get: () => { get: () => {
return config return config
@ -149,5 +137,12 @@ export function createProviderFunctions(
]); ]);
}, },
}, },
unmanaged: {
get: () => {
return config.get('apiEndpoint').then(apiEndpoint => {
return !apiEndpoint;
});
},
},
}; };
} }

View File

@ -46,14 +46,11 @@ class Config extends EventEmitter {
default: constants.defaultMixpanelToken, default: constants.defaultMixpanelToken,
}, },
bootstrapRetryDelay: { source: 'config.json', default: 30000 }, bootstrapRetryDelay: { source: 'config.json', default: 30000 },
supervisorOfflineMode: { source: 'config.json', default: false },
hostname: { source: 'config.json', mutable: true }, hostname: { source: 'config.json', mutable: true },
persistentLogging: { source: 'config.json', default: false, mutable: true }, persistentLogging: { source: 'config.json', default: false, mutable: true },
localMode: { source: 'config.json', mutable: true, default: 'false' },
version: { source: 'func' }, version: { source: 'func' },
currentApiKey: { source: 'func' }, currentApiKey: { source: 'func' },
offlineMode: { source: 'func' },
provisioned: { source: 'func' }, provisioned: { source: 'func' },
osVersion: { source: 'func' }, osVersion: { source: 'func' },
osVariant: { source: 'func' }, osVariant: { source: 'func' },
@ -61,6 +58,7 @@ class Config extends EventEmitter {
mixpanelHost: { source: 'func' }, mixpanelHost: { source: 'func' },
extendedEnvOptions: { source: 'func' }, extendedEnvOptions: { source: 'func' },
fetchOptions: { source: 'func' }, fetchOptions: { source: 'func' },
unmanaged: { source: 'func' },
// NOTE: all 'db' values are stored and loaded as *strings*, // NOTE: all 'db' values are stored and loaded as *strings*,
apiSecret: { source: 'db', mutable: true }, apiSecret: { source: 'db', mutable: true },
@ -82,6 +80,7 @@ class Config extends EventEmitter {
pinDevice: { source: 'db', mutable: true, default: 'null' }, pinDevice: { source: 'db', mutable: true, default: 'null' },
currentCommit: { source: 'db', mutable: true }, currentCommit: { source: 'db', mutable: true },
targetStateSet: { source: 'db', mutable: true, default: 'false' }, targetStateSet: { source: 'db', mutable: true, default: 'false' },
localMode: { source: 'db', mutable: true, default: 'false' },
}; };
public constructor({ db, configPath }: ConfigOpts) { public constructor({ db, configPath }: ConfigOpts) {
@ -187,7 +186,7 @@ class Config extends EventEmitter {
if (oldValues[key] !== value) { if (oldValues[key] !== value) {
return this.db.upsertModel( return this.db.upsertModel(
'config', 'config',
{ key, value }, { key, value: (value || '').toString() },
{ key }, { key },
tx, tx,
); );
@ -281,15 +280,15 @@ class Config extends EventEmitter {
'uuid', 'uuid',
'deviceApiKey', 'deviceApiKey',
'apiSecret', 'apiSecret',
'offlineMode', 'unmanaged',
]).then(({ uuid, deviceApiKey, apiSecret, offlineMode }) => { ]).then(({ uuid, deviceApiKey, apiSecret, unmanaged }) => {
// These fields need to be set regardless // These fields need to be set regardless
if (uuid == null || apiSecret == null) { if (uuid == null || apiSecret == null) {
uuid = uuid || this.newUniqueKey(); uuid = uuid || this.newUniqueKey();
apiSecret = apiSecret || this.newUniqueKey(); apiSecret = apiSecret || this.newUniqueKey();
} }
return this.set({ uuid, apiSecret }).then(() => { return this.set({ uuid, apiSecret }).then(() => {
if (offlineMode) { if (unmanaged) {
return; return;
} }
if (!deviceApiKey) { if (!deviceApiKey) {

View File

@ -13,6 +13,7 @@ module.exports = class DeviceConfig
@rebootRequired = false @rebootRequired = false
@configKeys = { @configKeys = {
appUpdatePollInterval: { envVarName: 'SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' } appUpdatePollInterval: { envVarName: 'SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' }
localMode: { envVarName: 'SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' }
connectivityCheckEnabled: { envVarName: 'SUPERVISOR_CONNECTIVITY_CHECK', varType: 'bool', defaultValue: 'true' } connectivityCheckEnabled: { envVarName: 'SUPERVISOR_CONNECTIVITY_CHECK', varType: 'bool', defaultValue: 'true' }
loggingEnabled: { envVarName: 'SUPERVISOR_LOG_CONTROL', varType: 'bool', defaultValue: 'true' } loggingEnabled: { envVarName: 'SUPERVISOR_LOG_CONTROL', varType: 'bool', defaultValue: 'true' }
delta: { envVarName: 'SUPERVISOR_DELTA', varType: 'bool', defaultValue: 'false' } delta: { envVarName: 'SUPERVISOR_DELTA', varType: 'bool', defaultValue: 'false' }
@ -74,11 +75,16 @@ module.exports = class DeviceConfig
db('deviceConfig').update(confToUpdate) db('deviceConfig').update(confToUpdate)
getTarget: ({ initial = false } = {}) => getTarget: ({ initial = false } = {}) =>
@db.models('deviceConfig').select('targetValues') Promise.all([
.then ([ devConfig ]) => @config.get('unmanaged')
@db.models('deviceConfig').select('targetValues')
])
.then ([unmanaged, [ devConfig ]]) =>
conf = JSON.parse(devConfig.targetValues) conf = JSON.parse(devConfig.targetValues)
if initial or !conf.SUPERVISOR_VPN_CONTROL? if initial or !conf.SUPERVISOR_VPN_CONTROL?
conf.SUPERVISOR_VPN_CONTROL = 'true' conf.SUPERVISOR_VPN_CONTROL = 'true'
if unmanaged and !conf.SUPERVISOR_LOCAL_MODE?
conf.SUPERVISOR_LOCAL_MODE = 'true'
for own k, { envVarName, defaultValue } of @configKeys for own k, { envVarName, defaultValue } of @configKeys
conf[envVarName] ?= defaultValue conf[envVarName] ?= defaultValue
return conf return conf
@ -131,10 +137,10 @@ module.exports = class DeviceConfig
target = _.clone(targetState.local?.config ? {}) target = _.clone(targetState.local?.config ? {})
steps = [] steps = []
Promise.all [ Promise.all [
@config.getMany([ 'deviceType', 'offlineMode' ]) @config.getMany([ 'deviceType', 'unmanaged' ])
@getConfigBackend() @getConfigBackend()
] ]
.then ([{ deviceType, offlineMode }, configBackend ]) => .then ([{ deviceType, unmanaged }, configBackend ]) =>
configChanges = {} configChanges = {}
humanReadableConfigChanges = {} humanReadableConfigChanges = {}
match = { match = {
@ -162,7 +168,7 @@ module.exports = class DeviceConfig
return return
# Check if we need to perform special case actions for the VPN # Check if we need to perform special case actions for the VPN
if !checkTruthy(offlineMode) && if !checkTruthy(unmanaged) &&
!_.isEmpty(target['SUPERVISOR_VPN_CONTROL']) && !_.isEmpty(target['SUPERVISOR_VPN_CONTROL']) &&
@checkBoolChanged(current, target, 'SUPERVISOR_VPN_CONTROL') @checkBoolChanged(current, target, 'SUPERVISOR_VPN_CONTROL')
steps.push({ steps.push({

View File

@ -140,12 +140,12 @@ module.exports = class DeviceState extends EventEmitter
@applications.on('change', @reportCurrentState) @applications.on('change', @reportCurrentState)
healthcheck: => healthcheck: =>
@config.getMany([ 'appUpdatePollInterval', 'offlineMode' ]) @config.getMany([ 'appUpdatePollInterval', 'unmanaged' ])
.then (conf) => .then (conf) =>
cycleTime = process.hrtime(@lastApplyStart) cycleTime = process.hrtime(@lastApplyStart)
cycleTimeMs = cycleTime[0] * 1000 + cycleTime[1] / 1e6 cycleTimeMs = cycleTime[0] * 1000 + cycleTime[1] / 1e6
cycleTimeWithinInterval = cycleTimeMs - @applications.timeSpentFetching < 2 * conf.appUpdatePollInterval cycleTimeWithinInterval = cycleTimeMs - @applications.timeSpentFetching < 2 * conf.appUpdatePollInterval
applyTargetHealthy = conf.offlineMode or !@applyInProgress or @applications.fetchesInProgress > 0 or cycleTimeWithinInterval applyTargetHealthy = conf.unmanaged or !@applyInProgress or @applications.fetchesInProgress > 0 or cycleTimeWithinInterval
return applyTargetHealthy return applyTargetHealthy
migrateLegacyApps: (balenaApi) => migrateLegacyApps: (balenaApi) =>
@ -255,7 +255,7 @@ module.exports = class DeviceState extends EventEmitter
@config.getMany([ @config.getMany([
'initialConfigSaved', 'listenPort', 'apiSecret', 'osVersion', 'osVariant', 'initialConfigSaved', 'listenPort', 'apiSecret', 'osVersion', 'osVariant',
'version', 'provisioned', 'apiEndpoint', 'connectivityCheckEnabled', 'legacyAppsPresent', 'version', 'provisioned', 'apiEndpoint', 'connectivityCheckEnabled', 'legacyAppsPresent',
'targetStateSet', 'offlineMode' 'targetStateSet', 'unmanaged'
]) ])
.then (conf) => .then (conf) =>
@applications.init() @applications.init()
@ -297,8 +297,8 @@ module.exports = class DeviceState extends EventEmitter
.then => .then =>
@triggerApplyTarget({ initial: true }) @triggerApplyTarget({ initial: true })
initNetworkChecks: ({ apiEndpoint, connectivityCheckEnabled, offlineMode }) => initNetworkChecks: ({ apiEndpoint, connectivityCheckEnabled, unmanaged }) =>
return if validation.checkTruthy(offlineMode) return if validation.checkTruthy(unmanaged)
network.startConnectivityCheck apiEndpoint, connectivityCheckEnabled, (connected) => network.startConnectivityCheck apiEndpoint, connectivityCheckEnabled, (connected) =>
@connected = connected @connected = connected
@config.on 'change', (changedConfig) -> @config.on 'change', (changedConfig) ->
@ -351,7 +351,7 @@ module.exports = class DeviceState extends EventEmitter
.then => .then =>
@deviceConfig.setTarget(target.local.config, trx) @deviceConfig.setTarget(target.local.config, trx)
.then => .then =>
if localSource if localSource or not apiEndpoint
@applications.setTarget(target.local.apps, target.dependent, 'local', trx) @applications.setTarget(target.local.apps, target.dependent, 'local', trx)
else else
@applications.setTarget(target.local.apps, target.dependent, apiEndpoint, trx) @applications.setTarget(target.local.apps, target.dependent, apiEndpoint, trx)

View File

@ -11,7 +11,7 @@ export type EventTrackProperties = Dictionary<any>;
interface InitArgs { interface InitArgs {
uuid: string; uuid: string;
offlineMode: boolean; unmanaged: boolean;
mixpanelHost: { host: string; path: string } | null; mixpanelHost: { host: string; path: string } | null;
mixpanelToken: string; mixpanelToken: string;
} }
@ -41,7 +41,7 @@ export class EventTracker {
} }
public init({ public init({
offlineMode, unmanaged,
mixpanelHost, mixpanelHost,
mixpanelToken, mixpanelToken,
uuid, uuid,
@ -52,7 +52,7 @@ export class EventTracker {
uuid, uuid,
supervisorVersion, supervisorVersion,
}; };
if (offlineMode || mixpanelHost == null) { if (unmanaged || mixpanelHost == null) {
return; return;
} }
this.client = Mixpanel.init(mixpanelToken, { this.client = Mixpanel.init(mixpanelToken, {

View File

@ -40,9 +40,20 @@ export class LocalModeManager {
} }
}); });
const localMode = checkTruthy( // On startup, check if we're in unmanaged mode,
(await this.config.get('localMode')) || false, // as local mode needs to be set
); let unmanagedLocalMode = false;
if (checkTruthy((await this.config.get('unmanaged')) || false)) {
console.log('Starting up in unmanaged mode, activating local mode');
await this.config.set({ localMode: true });
unmanagedLocalMode = true;
}
const localMode =
// short circuit the next get if we know we're in local mode
unmanagedLocalMode ||
checkTruthy((await this.config.get('localMode')) || false);
if (!localMode) { if (!localMode) {
// Remove any leftovers if necessary // Remove any leftovers if necessary
await this.removeLocalModeArtifacts(); await this.removeLocalModeArtifacts();

View File

@ -17,7 +17,7 @@ interface LoggerSetupOptions {
apiEndpoint: string; apiEndpoint: string;
uuid: string; uuid: string;
deviceApiKey: string; deviceApiKey: string;
offlineMode: boolean; unmanaged: boolean;
enableLogs: boolean; enableLogs: boolean;
localMode: boolean; localMode: boolean;
} }
@ -59,7 +59,7 @@ export class Logger {
apiEndpoint, apiEndpoint,
uuid, uuid,
deviceApiKey, deviceApiKey,
offlineMode, unmanaged,
enableLogs, enableLogs,
localMode, localMode,
}: LoggerSetupOptions) { }: LoggerSetupOptions) {
@ -68,7 +68,7 @@ export class Logger {
this.backend = localMode ? this.localBackend : this.balenaBackend; this.backend = localMode ? this.localBackend : this.balenaBackend;
this.backend.offlineMode = offlineMode; this.backend.unmanaged = unmanaged;
this.backend.publishEnabled = enableLogs; this.backend.publishEnabled = enableLogs;
} }

View File

@ -70,7 +70,7 @@ export class BalenaLogBackend extends LogBackend {
} }
public log(message: LogMessage) { public log(message: LogMessage) {
if (this.offlineMode || !this.publishEnabled) { if (this.unmanaged || !this.publishEnabled) {
return; return;
} }

View File

@ -1,7 +1,7 @@
export type LogMessage = Dictionary<any>; export type LogMessage = Dictionary<any>;
export abstract class LogBackend { export abstract class LogBackend {
public offlineMode: boolean; public unmanaged: boolean;
public publishEnabled: boolean = true; public publishEnabled: boolean = true;
public abstract log(message: LogMessage): void; public abstract log(message: LogMessage): void;

41
src/migrations/M00002.js Normal file
View File

@ -0,0 +1,41 @@
const fs = require('fs');
const configJsonPath = process.env.CONFIG_MOUNT_POINT;
const { checkTruthy } = require('../lib/validation');
exports.up = function (knex, Promise) {
return new Promise(resolve => {
if (!configJsonPath) {
console.log('Unable to locate config.json! Things may fail unexpectedly!');
return resolve(false);
}
fs.readFile(configJsonPath, (err, data) => {
if (err) {
console.log('Failed to read config.json! Things may fail unexpectedly!');
return resolve();
}
try {
const parsed = JSON.parse(data.toString());
if (parsed.localMode != null) {
return resolve(checkTruthy(parsed.localMode));
}
return resolve(false);
} catch (e) {
console.log('Failed to parse config.json! Things may fail unexpectedly!');
return resolve(false);
}
});
}).then(localMode => {
// We can be sure that this does not already exist in the db because of the previous
// migration
return knex('config').insert({
key: 'localMode',
value: localMode.toString(),
});
});
};
exports.down = function (knex, Promise) {
return Promise.reject(new Error('Not Implemented'));
};

View File

@ -11,19 +11,23 @@ authenticate = (config) ->
header = req.get('Authorization') ? '' header = req.get('Authorization') ? ''
match = header.match(/^ApiKey (\w+)$/) match = header.match(/^ApiKey (\w+)$/)
headerKey = match?[1] headerKey = match?[1]
config.getMany([ 'apiSecret', 'localMode' ]) config.getMany([ 'apiSecret', 'localMode', 'unmanaged', 'osVariant' ])
.then (conf) -> .then (conf) ->
if queryKey? && bufferEq(new Buffer(queryKey), new Buffer(conf.apiSecret)) needsAuth = if conf.unmanaged
next() conf.osVariant is 'prod'
else if headerKey? && bufferEq(new Buffer(headerKey), new Buffer(conf.apiSecret))
next()
else if checkTruthy(conf.localMode)
next()
else else
res.sendStatus(401) not conf.localMode
if needsAuth
key = queryKey ? headerKey
if bufferEq(Buffer.from(key), Buffer.from(conf.apiSecret))
next()
else
res.sendStatus(401)
else
next()
.catch (err) -> .catch (err) ->
# This should never happen... res.status(503).send("Unexpected error: #{err}")
res.status(503).send('Invalid API key in supervisor')
module.exports = class SupervisorAPI module.exports = class SupervisorAPI
constructor: ({ @config, @eventTracker, @routers, @healthchecks }) -> constructor: ({ @config, @eventTracker, @routers, @healthchecks }) ->

View File

@ -17,7 +17,7 @@ startupConfigFields = [
'apiEndpoint' 'apiEndpoint'
'apiSecret' 'apiSecret'
'apiTimeout' 'apiTimeout'
'offlineMode' 'unmanaged'
'deviceApiKey' 'deviceApiKey'
'mixpanelToken' 'mixpanelToken'
'mixpanelHost' 'mixpanelHost'
@ -57,7 +57,7 @@ module.exports = class Supervisor extends EventEmitter
apiEndpoint: conf.apiEndpoint, apiEndpoint: conf.apiEndpoint,
uuid: conf.uuid, uuid: conf.uuid,
deviceApiKey: conf.deviceApiKey, deviceApiKey: conf.deviceApiKey,
offlineMode: checkTruthy(conf.offlineMode), unmanaged: checkTruthy(conf.unmanaged),
enableLogs: checkTruthy(conf.loggingEnabled), enableLogs: checkTruthy(conf.loggingEnabled),
localMode: checkTruthy(conf.localMode) localMode: checkTruthy(conf.localMode)
}) })

View File

@ -21,6 +21,7 @@ mockedInitialConfig = {
'RESIN_SUPERVISOR_DELTA_RETRY_COUNT': '30' 'RESIN_SUPERVISOR_DELTA_RETRY_COUNT': '30'
'RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL': '10000' 'RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
'RESIN_SUPERVISOR_DELTA_VERSION': '2' 'RESIN_SUPERVISOR_DELTA_VERSION': '2'
'RESIN_SUPERVISOR_LOCAL_MODE': 'false'
'RESIN_SUPERVISOR_LOG_CONTROL': 'true' 'RESIN_SUPERVISOR_LOG_CONTROL': 'true'
'RESIN_SUPERVISOR_OVERRIDE_LOCK': 'false' 'RESIN_SUPERVISOR_OVERRIDE_LOCK': 'false'
'RESIN_SUPERVISOR_POLL_INTERVAL': '60000' 'RESIN_SUPERVISOR_POLL_INTERVAL': '60000'
@ -39,6 +40,7 @@ testTarget1 = {
'SUPERVISOR_DELTA_RETRY_COUNT': '30' 'SUPERVISOR_DELTA_RETRY_COUNT': '30'
'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000' 'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
'SUPERVISOR_DELTA_VERSION': '2' 'SUPERVISOR_DELTA_VERSION': '2'
'SUPERVISOR_LOCAL_MODE': 'false'
'SUPERVISOR_LOG_CONTROL': 'true' 'SUPERVISOR_LOG_CONTROL': 'true'
'SUPERVISOR_OVERRIDE_LOCK': 'false' 'SUPERVISOR_OVERRIDE_LOCK': 'false'
'SUPERVISOR_POLL_INTERVAL': '60000' 'SUPERVISOR_POLL_INTERVAL': '60000'
@ -120,6 +122,7 @@ testTargetWithDefaults2 = {
'SUPERVISOR_DELTA_RETRY_COUNT': '30' 'SUPERVISOR_DELTA_RETRY_COUNT': '30'
'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000' 'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
'SUPERVISOR_DELTA_VERSION': '2' 'SUPERVISOR_DELTA_VERSION': '2'
'SUPERVISOR_LOCAL_MODE': 'false'
'SUPERVISOR_LOG_CONTROL': 'true' 'SUPERVISOR_LOG_CONTROL': 'true'
'SUPERVISOR_OVERRIDE_LOCK': 'false' 'SUPERVISOR_OVERRIDE_LOCK': 'false'
'SUPERVISOR_POLL_INTERVAL': '60000' 'SUPERVISOR_POLL_INTERVAL': '60000'

View File

@ -23,9 +23,9 @@ describe 'EventTracker', ->
EventTracker.prototype.logEvent.restore() EventTracker.prototype.logEvent.restore()
mixpanel.init.restore() mixpanel.init.restore()
it 'initializes in offline mode', -> it 'initializes in unmanaged mode', ->
promise = @eventTrackerOffline.init({ promise = @eventTrackerOffline.init({
offlineMode: true unmanaged: true
uuid: 'foobar' uuid: 'foobar'
mixpanelHost: { host: '', path: '' } mixpanelHost: { host: '', path: '' }
}) })
@ -33,11 +33,11 @@ describe 'EventTracker', ->
.then => .then =>
expect(@eventTrackerOffline.client).to.be.null expect(@eventTrackerOffline.client).to.be.null
it 'logs events in offline mode, with the correct properties', -> it 'logs events in unmanaged mode, with the correct properties', ->
@eventTrackerOffline.track('Test event', { appId: 'someValue' }) @eventTrackerOffline.track('Test event', { appId: 'someValue' })
expect(@eventTrackerOffline.logEvent).to.be.calledWith('Event:', 'Test event', JSON.stringify({ appId: 'someValue' })) expect(@eventTrackerOffline.logEvent).to.be.calledWith('Event:', 'Test event', JSON.stringify({ appId: 'someValue' }))
it 'initializes a mixpanel client when not in offline mode', -> it 'initializes a mixpanel client when not in unmanaged mode', ->
promise = @eventTracker.init({ promise = @eventTracker.init({
mixpanelToken: 'someToken' mixpanelToken: 'someToken'
uuid: 'barbaz' uuid: 'barbaz'

View File

@ -135,12 +135,12 @@ describe 'APIBinder', ->
@apiBinder.fetchDevice.restore() @apiBinder.fetchDevice.restore()
balenaAPI.balenaBackend.deviceKeyHandler.restore() balenaAPI.balenaBackend.deviceKeyHandler.restore()
describe 'offline mode', -> describe 'unmanaged mode', ->
before -> before ->
initModels.call(this, '/config-apibinder-offline.json') initModels.call(this, '/config-apibinder-offline.json')
it 'does not generate a key if the device is in offline mode', -> it 'does not generate a key if the device is in unmanaged mode', ->
@config.get('offlineMode').then (mode) => @config.get('unmanaged').then (mode) =>
# Ensure offline mode is set # Ensure offline mode is set
expect(mode).to.equal(true) expect(mode).to.equal(true)
# Check that there is no deviceApiKey # Check that there is no deviceApiKey
@ -148,12 +148,12 @@ describe 'APIBinder', ->
expect(conf['deviceApiKey']).to.be.empty expect(conf['deviceApiKey']).to.be.empty
expect(conf['uuid']).to.not.be.undefined expect(conf['uuid']).to.not.be.undefined
describe 'Minimal config offline mode', -> describe 'Minimal config unmanaged mode', ->
before -> before ->
initModels.call(this, '/config-apibinder-offline2.json') initModels.call(this, '/config-apibinder-offline2.json')
it 'does not generate a key with the minimal config', -> it 'does not generate a key with the minimal config', ->
@config.get('offlineMode').then (mode) => @config.get('unmanaged').then (mode) =>
expect(mode).to.equal(true) expect(mode).to.equal(true)
@config.getMany([ 'deviceApiKey', 'uuid' ]).then (conf) -> @config.getMany([ 'deviceApiKey', 'uuid' ]).then (conf) ->
expect(conf['deviceApiKey']).to.be.empty expect(conf['deviceApiKey']).to.be.empty

View File

@ -31,7 +31,7 @@ describe 'Logger', ->
apiEndpoint: 'https://example.com' apiEndpoint: 'https://example.com'
uuid: 'deadbeef' uuid: 'deadbeef'
deviceApiKey: 'secretkey' deviceApiKey: 'secretkey'
offlineMode: false unmanaged: false
enableLogs: true enableLogs: true
localMode: false localMode: false
}) })

View File

@ -1 +1,16 @@
{"applicationName":"supertestrpi3","applicationId":78373,"deviceType":"raspberrypi3","userId":1001,"username":"someone","appUpdatePollInterval":3000,"listenPort":2345,"vpnPort":443,"apiEndpoint":"http://0.0.0.0:3000","vpnEndpoint":"vpn.resin.io","registryEndpoint":"registry2.resin.io","deltaEndpoint":"https://delta.resin.io","mixpanelToken":"baz","apiKey":"boo","version":"2.0.6+rev3.prod","supervisorOfflineMode":true} {
"applicationName": "supertestrpi3",
"applicationId": 78373,
"deviceType": "raspberrypi3",
"userId": 1001,
"username": "someone",
"appUpdatePollInterval": 3000,
"listenPort": 2345,
"vpnPort": 443,
"vpnEndpoint": "vpn.resin.io",
"registryEndpoint": "registry2.resin.io",
"deltaEndpoint": "https://delta.resin.io",
"mixpanelToken": "baz",
"apiKey": "boo",
"version": "2.0.6+rev3.prod"
}