Merge pull request #818 from balena-io/fix-unmanaged-selects

Fixes for unmanaged balenaOS
This commit is contained in:
CameronDiver 2018-11-28 16:11:14 +01:00 committed by GitHub
commit 80b4872d7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 21 deletions

View File

@ -296,7 +296,7 @@ module.exports = class ApplicationManager extends EventEmitter
).get(appId)
getTargetApp: (appId) =>
@config.get('apiEndpoint').then (endpoint = '') ->
@config.get('apiEndpoint').then (endpoint) ->
@db.models('app').where({ appId, source: endpoint }).select()
.then ([ app ]) =>
if !app?

View File

@ -9,6 +9,7 @@ import supervisorVersion = require('../lib/supervisor-version');
import * as constants from '../lib/constants';
import * as osRelease from '../lib/os-release';
import { ConfigValue } from '../lib/types';
import { checkTruthy } from '../lib/validation';
// A provider for schema entries with source 'func'
type ConfigProviderFunctionGetter = () => Bluebird<any>;
@ -51,7 +52,9 @@ export function createProviderFunctions(
return config
.getMany(['apiEndpoint', 'supervisorOfflineMode'])
.then(({ apiEndpoint, supervisorOfflineMode }) => {
return Boolean(supervisorOfflineMode) || !Boolean(apiEndpoint);
return (
checkTruthy(supervisorOfflineMode as boolean) || !apiEndpoint
);
});
},
},
@ -108,6 +111,9 @@ export function createProviderFunctions(
mixpanelHost: {
get: () => {
return config.get('apiEndpoint').then(apiEndpoint => {
if (!apiEndpoint) {
return null;
}
const url = new URL(apiEndpoint as string);
return { host: url.host, path: '/mixpanel' };
});

View File

@ -4,16 +4,13 @@ import { Transaction } from 'knex';
import * as _ from 'lodash';
import { generateUniqueKey } from 'resin-register-device';
import ConfigJsonConfigBackend from './config/configJson';
import ConfigJsonConfigBackend from './configJson';
import {
ConfigProviderFunctions,
createProviderFunctions,
} from './config/functions';
import * as constants from './lib/constants';
import { ConfigMap, ConfigSchema, ConfigValue } from './lib/types';
import { ConfigProviderFunctions, createProviderFunctions } from './functions';
import * as constants from '../lib/constants';
import { ConfigMap, ConfigSchema, ConfigValue } from '../lib/types';
import DB = require('./db');
import DB = require('../db');
interface ConfigOpts {
db: DB;
@ -26,14 +23,14 @@ class Config extends EventEmitter {
private providerFunctions: ConfigProviderFunctions;
public schema: ConfigSchema = {
apiEndpoint: { source: 'config.json' },
apiEndpoint: { source: 'config.json', default: '' },
apiTimeout: { source: 'config.json', default: 15 * 60 * 1000 },
listenPort: { source: 'config.json', default: 48484 },
deltaEndpoint: { source: 'config.json', default: 'https://delta.resin.io' },
uuid: { source: 'config.json', mutable: true },
apiKey: { source: 'config.json', mutable: true, removeIfNull: true },
deviceApiKey: { source: 'config.json', mutable: true },
deviceType: { source: 'config.json', default: 'raspberry-pi' },
deviceApiKey: { source: 'config.json', mutable: true, default: '' },
deviceType: { source: 'config.json', default: 'unknown' },
username: { source: 'config.json' },
userId: { source: 'config.json' },
deviceId: { source: 'config.json', mutable: true },
@ -52,6 +49,7 @@ class Config extends EventEmitter {
supervisorOfflineMode: { source: 'config.json', default: false },
hostname: { source: 'config.json', mutable: true },
persistentLogging: { source: 'config.json', default: false, mutable: true },
localMode: { source: 'config.json', mutable: true, default: 'false' },
version: { source: 'func' },
currentApiKey: { source: 'func' },
@ -70,7 +68,6 @@ class Config extends EventEmitter {
initialConfigReported: { source: 'db', mutable: true, default: 'false' },
initialConfigSaved: { source: 'db', mutable: true, default: 'false' },
containersNormalised: { source: 'db', mutable: true, default: 'false' },
localMode: { source: 'db', mutable: true, default: 'false' },
loggingEnabled: { source: 'db', mutable: true, default: 'true' },
connectivityCheckEnabled: { source: 'db', mutable: true, default: 'true' },
delta: { source: 'db', mutable: true, default: 'false' },
@ -295,7 +292,7 @@ class Config extends EventEmitter {
if (offlineMode) {
return;
}
if (deviceApiKey == null) {
if (!deviceApiKey) {
return this.set({ deviceApiKey: this.newUniqueKey() });
}
});

View File

@ -13,6 +13,7 @@ validation = require './lib/validation'
systemd = require './lib/systemd'
updateLock = require './lib/update-lock'
{ singleToMulticontainerApp } = require './lib/migration'
{ ENOENT, EISDIR } = require './lib/errors'
DeviceConfig = require './device-config'
ApplicationManager = require './application-manager'
@ -167,7 +168,7 @@ module.exports = class DeviceState extends EventEmitter
@config.getMany([
'initialConfigSaved', 'listenPort', 'apiSecret', 'osVersion', 'osVariant',
'version', 'provisioned', 'apiEndpoint', 'connectivityCheckEnabled', 'legacyAppsPresent',
'targetStateSet'
'targetStateSet', 'offlineMode'
])
.then (conf) =>
Promise.try =>
@ -213,7 +214,8 @@ module.exports = class DeviceState extends EventEmitter
.then =>
@triggerApplyTarget({ initial: true })
initNetworkChecks: ({ apiEndpoint, connectivityCheckEnabled }) =>
initNetworkChecks: ({ apiEndpoint, connectivityCheckEnabled, offlineMode }) =>
return if validation.checkTruthy(offlineMode)
network.startConnectivityCheck apiEndpoint, connectivityCheckEnabled, (connected) =>
@connected = connected
@config.on 'change', (changedConfig) ->
@ -383,6 +385,12 @@ module.exports = class DeviceState extends EventEmitter
commit: commitToPin,
app: appToPin,
}
# Ensure that this is actually a file, and not an empty path
# It can be an empty path because if the file does not exist
# on host, the docker daemon creates an empty directory when
# the bind mount is added
.catch ENOENT, EISDIR, ->
console.log('No apps.json file present, skipping preload')
.catch (err) =>
@eventTracker.track('Loading preloaded apps failed', { error: err })

View File

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

View File

@ -25,6 +25,10 @@ export function EEXIST(err: CodedSysError): boolean {
return err.code === 'EEXIST';
}
export function EISDIR(err: CodedSysError): boolean {
return err.code === 'EISDIR';
}
export function UnitNotLoadedError(err: string[]): boolean {
return endsWith(err[0], 'not loaded.');
}

57
src/migrations/M00001.js Normal file
View File

@ -0,0 +1,57 @@
const fs = require('fs');
const configJsonPath = process.env.CONFIG_MOUNT_POINT;
const { checkTruthy } = require('../lib/validation');
exports.up = function (knex, Promise) {
return knex('config').where({ key: 'localMode' }).select('value')
.then((results) => {
if (results.length === 0) {
// We don't need to do anything
return;
}
let value = checkTruthy(results[0].value);
value = value != null ? value : false;
return new Promise((resolve) => {
if (!configJsonPath) {
console.log('Unable to locate config.json! Things may fail unexpectedly!');
resolve();
return;
}
fs.readFile(configJsonPath, (err, data) => {
if (err) {
console.log('Failed to read config.json! Things may fail unexpectedly!');
resolve();
return;
}
try {
const parsed = JSON.parse(data.toString());
// Assign the local mode value
parsed.localMode = value;
fs.writeFile(configJsonPath, JSON.stringify(parsed), (err) => {
if (err) {
console.log('Failed to write config.json! Things may fail unexpectedly!');
return;
}
resolve();
});
} catch (e) {
console.log('Failed to parse config.json! Things may fail unexpectedly!');
resolve();
}
});
})
.then(() => {
return knex('config').where('key', 'localMode').del();
});
});
};
exports.down = function (knex, Promise) {
return Promise.reject(new Error('Not Implemented'));
}

View File

@ -145,7 +145,7 @@ describe 'APIBinder', ->
expect(mode).to.equal(true)
# Check that there is no deviceApiKey
@config.getMany([ 'deviceApiKey', 'uuid' ]).then (conf) ->
expect(conf['deviceApiKey']).to.be.undefined
expect(conf['deviceApiKey']).to.be.empty
expect(conf['uuid']).to.not.be.undefined
describe 'Minimal config offline mode', ->
@ -156,5 +156,5 @@ describe 'APIBinder', ->
@config.get('offlineMode').then (mode) =>
expect(mode).to.equal(true)
@config.getMany([ 'deviceApiKey', 'uuid' ]).then (conf) ->
expect(conf['deviceApiKey']).to.be.undefined
expect(conf['deviceApiKey']).to.be.empty
expect(conf['uuid']).to.not.be.undefined