Only uninstall 'fleet' apps when localMode is set

Local mode is still a device level config. Eventually it will become a
property of an app, but for now, we don't want the supervisor trying to
uninstall supervisor or host app when local mode is set
This commit is contained in:
Felipe Lalanne 2021-09-21 20:33:32 +00:00
parent f1cd3d367c
commit 8bf8792583
4 changed files with 165 additions and 62 deletions

View File

@ -84,7 +84,13 @@ export async function getTargetApps(): Promise<DatabaseApp[]> {
]);
const source = localMode ? 'local' : apiEndpoint;
targetState = await db.models('app').where({ source });
targetState = await db
.models('app')
.where({ source })
// Local mode only applies for fleet "applications"
// this prevents the supervisor trying to uninstall
// the supervisor or host app for tri-app
.orWhereNot({ class: 'fleet' });
}
return targetState!;
}

View File

@ -67,7 +67,7 @@ describe('device-state', () => {
// TODO: all these stubs are internal implementation details of
// deviceState, we should refactor deviceState to use dependency
// injection instead of initializing everything in memory
sinon.stub(Service as any, 'extendEnvVars').callsFake((env) => {
sinon.stub(Service as any, 'extendEnvVars').callsFake((env: any) => {
env['ADDITIONAL_ENV_VAR'] = 'foo';
return env;
});

View File

@ -10,7 +10,7 @@ import LocalModeManager, {
} from '../src/local-mode';
import ShortStackError from './lib/errors';
describe.skip('LocalModeManager', () => {
describe('LocalModeManager', () => {
let localMode: LocalModeManager;
let dockerStub: sinon.SinonStubbedInstance<typeof docker>;

View File

@ -8,7 +8,6 @@ import * as dbFormat from '../src/device-state/db-format';
import log from '../src/lib/supervisor-console';
import { TargetApps } from '../src/types/state';
import * as dbHelper from './lib/db-helper';
import { withMockerode } from './lib/mockerode';
function getDefaultNetwork(appId: number) {
return {
@ -132,36 +131,32 @@ describe('db-format', () => {
apiEndpoint,
);
// getApp creates a new app instance which requires a docker instance
// withMockerode mocks engine
await withMockerode(async () => {
const app = await dbFormat.getApp(1);
expect(app).to.be.an.instanceOf(App);
expect(app).to.have.property('appId').that.equals(1);
expect(app).to.have.property('commit').that.equals('one');
expect(app).to.have.property('appName').that.equals('test-app');
expect(app).to.have.property('source').that.equals(apiEndpoint);
expect(app).to.have.property('services').that.has.lengthOf(1);
expect(app).to.have.property('volumes').that.deep.equals({});
expect(app)
.to.have.property('networks')
.that.deep.equals(getDefaultNetwork(1));
const app = await dbFormat.getApp(1);
expect(app).to.be.an.instanceOf(App);
expect(app).to.have.property('appId').that.equals(1);
expect(app).to.have.property('commit').that.equals('one');
expect(app).to.have.property('appName').that.equals('test-app');
expect(app).to.have.property('source').that.equals(apiEndpoint);
expect(app).to.have.property('services').that.has.lengthOf(1);
expect(app).to.have.property('volumes').that.deep.equals({});
expect(app)
.to.have.property('networks')
.that.deep.equals(getDefaultNetwork(1));
const [service] = app.services;
expect(service).to.have.property('appId').that.equals(1);
expect(service).to.have.property('serviceId').that.equals(1);
expect(service).to.have.property('imageId').that.equals(1);
expect(service).to.have.property('releaseId').that.equals(1);
expect(service.config)
.to.have.property('image')
.that.equals('ubuntu:latest');
expect(service.config)
.to.have.property('labels')
.that.deep.includes({ 'my-label': 'true' });
expect(service.config)
.to.have.property('command')
.that.deep.equals(['sleep', 'infinity']);
});
const [service] = app.services;
expect(service).to.have.property('appId').that.equals(1);
expect(service).to.have.property('serviceId').that.equals(1);
expect(service).to.have.property('imageId').that.equals(1);
expect(service).to.have.property('releaseId').that.equals(1);
expect(service.config)
.to.have.property('image')
.that.equals('ubuntu:latest');
expect(service.config)
.to.have.property('labels')
.that.deep.includes({ 'my-label': 'true' });
expect(service.config)
.to.have.property('command')
.that.deep.equals(['sleep', 'infinity']);
});
it('should retrieve multiple apps from the database', async () => {
@ -208,26 +203,133 @@ describe('db-format', () => {
apiEndpoint,
);
await withMockerode(async () => {
const apps = Object.values(await dbFormat.getApps());
expect(apps).to.have.lengthOf(2);
const apps = Object.values(await dbFormat.getApps());
expect(apps).to.have.lengthOf(2);
const [app, otherapp] = apps;
expect(app).to.be.an.instanceOf(App);
expect(app).to.have.property('appId').that.equals(1);
expect(app).to.have.property('commit').that.equals('one');
expect(app).to.have.property('appName').that.equals('test-app');
expect(app).to.have.property('source').that.equals(apiEndpoint);
expect(app).to.have.property('services').that.has.lengthOf(1);
expect(app).to.have.property('volumes').that.deep.equals({});
expect(app)
.to.have.property('networks')
.that.deep.equals(getDefaultNetwork(1));
const [app, otherapp] = apps;
expect(app).to.be.an.instanceOf(App);
expect(app).to.have.property('appId').that.equals(1);
expect(app).to.have.property('commit').that.equals('one');
expect(app).to.have.property('appName').that.equals('test-app');
expect(app).to.have.property('source').that.equals(apiEndpoint);
expect(app).to.have.property('services').that.has.lengthOf(1);
expect(app).to.have.property('volumes').that.deep.equals({});
expect(app)
.to.have.property('networks')
.that.deep.equals(getDefaultNetwork(1));
expect(otherapp).to.have.property('appId').that.equals(2);
expect(otherapp).to.have.property('commit').that.equals('two');
expect(otherapp).to.have.property('appName').that.equals('other-app');
});
expect(otherapp).to.have.property('appId').that.equals(2);
expect(otherapp).to.have.property('commit').that.equals('two');
expect(otherapp).to.have.property('appName').that.equals('other-app');
});
it('should retrieve non-fleet apps from the database if local mode is set', async () => {
await dbFormat.setApps(
{
deadbeef: {
id: 1,
name: 'test-app',
class: 'fleet',
releases: {
one: {
id: 1,
services: {
ubuntu: {
id: 1,
image_id: 1,
image: 'ubuntu:latest',
environment: {},
labels: {},
composition: {
command: ['sleep', 'infinity'],
},
},
},
volumes: {},
networks: {},
},
},
},
deadc0de: {
id: 2,
name: 'other-app',
class: 'app',
releases: {
two: {
id: 2,
services: {},
volumes: {},
networks: {},
},
},
},
},
apiEndpoint,
);
// Once local mode is set to true, only 'other-app' should be returned
// as part of the target
await config.set({ localMode: true });
const apps = Object.values(await dbFormat.getApps());
expect(apps).to.have.lengthOf(1);
const [app] = apps;
expect(app).to.be.an.instanceOf(App);
expect(app).to.have.property('appId').that.equals(2);
expect(app).to.have.property('commit').that.equals('two');
expect(app).to.have.property('appName').that.equals('other-app');
// Set the app as local now
await dbFormat.setApps(
{
deadbeef: {
id: 1,
name: 'test-app',
class: 'fleet',
releases: {
one: {
id: 1,
services: {
ubuntu: {
id: 1,
image_id: 1,
image: 'ubuntu:latest',
environment: {},
labels: {},
composition: {
command: ['sleep', 'infinity'],
},
},
},
volumes: {},
networks: {},
},
},
},
},
'local',
);
// Now both apps should be returned
const newapps = Object.values(await dbFormat.getApps());
expect(newapps).to.have.lengthOf(2);
const [newapp, otherapp] = newapps;
expect(newapp).to.be.an.instanceOf(App);
expect(newapp).to.have.property('appId').that.equals(1);
expect(newapp).to.have.property('commit').that.equals('one');
expect(newapp).to.have.property('appName').that.equals('test-app');
expect(newapp).to.have.property('source').that.equals('local');
expect(newapp).to.have.property('services').that.has.lengthOf(1);
expect(newapp).to.have.property('volumes').that.deep.equals({});
expect(newapp)
.to.have.property('networks')
.that.deep.equals(getDefaultNetwork(1));
expect(otherapp).to.have.property('appId').that.equals(2);
expect(otherapp).to.have.property('commit').that.equals('two');
expect(otherapp).to.have.property('appName').that.equals('other-app');
});
it('should retrieve app target state from database', async () => {
@ -272,16 +374,11 @@ describe('db-format', () => {
};
await dbFormat.setApps(srcApps, apiEndpoint);
// getApp creates a new app instance which requires a docker instance
// withMockerode mocks engine
await withMockerode(async () => {
const result = await dbFormat.getTargetJson();
expect(
isRight(TargetApps.decode(result)),
'resulting target apps is a valid TargetApps object',
);
expect(result).to.deep.equal(srcApps);
});
const result = await dbFormat.getTargetJson();
expect(
isRight(TargetApps.decode(result)),
'resulting target apps is a valid TargetApps object',
);
expect(result).to.deep.equal(srcApps);
});
});