mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-20 17:52:51 +00:00
Add contract resolution code, which checks release requirements
Change-type: minor Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
2d168784b2
commit
5ce8ba8acf
1087
package-lock.json
generated
1087
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,7 @@
|
||||
"node": "^6.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/contrato": "^0.2.1",
|
||||
"@types/bluebird": "^3.5.25",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/common-tags": "^1.8.0",
|
||||
|
66
src/lib/contracts.ts
Normal file
66
src/lib/contracts.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { Blueprint, Contract, ContractObject } from '@balena/contrato';
|
||||
|
||||
import constants = require('./constants');
|
||||
import { InternalInconsistencyError } from './errors';
|
||||
import * as osRelease from './os-release';
|
||||
import supervisorVersion = require('./supervisor-version');
|
||||
|
||||
export async function containerContractsFulfilled(
|
||||
containers: ContractObject[],
|
||||
): Promise<boolean> {
|
||||
const osContract = new Contract({
|
||||
slug: 'balenaOS',
|
||||
type: 'sw.os',
|
||||
name: 'balenaOS',
|
||||
version: await osRelease.getOSSemver(constants.hostOSVersionPath),
|
||||
});
|
||||
|
||||
const supervisorContract = new Contract({
|
||||
slug: 'balena-supervisor',
|
||||
type: 'sw.supervisor',
|
||||
name: 'balena-supervisor',
|
||||
version: supervisorVersion,
|
||||
});
|
||||
|
||||
const blueprint = new Blueprint(
|
||||
{
|
||||
'sw.os': 1,
|
||||
'sw.supervisor': 1,
|
||||
'sw.container': '1+',
|
||||
},
|
||||
{
|
||||
type: 'sw.runnable.configuration',
|
||||
slug: '{{children.sw.container.slug}}',
|
||||
},
|
||||
);
|
||||
|
||||
const universe = new Contract({
|
||||
type: 'meta.universe',
|
||||
});
|
||||
|
||||
universe.addChildren(
|
||||
[osContract, supervisorContract].concat(
|
||||
containers.map(c => new Contract(c)),
|
||||
),
|
||||
);
|
||||
|
||||
const solution = blueprint.reproduce(universe);
|
||||
|
||||
if (solution.length > 1) {
|
||||
throw new InternalInconsistencyError(
|
||||
'More than one solution available for container contracts when only one is expected!',
|
||||
);
|
||||
}
|
||||
if (solution.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect how many containers are present in the resulting
|
||||
// solution
|
||||
const children = solution[0].getChildren({
|
||||
types: new Set(['sw.container']),
|
||||
});
|
||||
return children.length === containers.length;
|
||||
}
|
212
test/24-contracts.ts
Normal file
212
test/24-contracts.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as semver from 'balena-semver';
|
||||
|
||||
import * as constants from '../src/lib/constants';
|
||||
import { containerContractsFulfilled } from '../src/lib/contracts';
|
||||
import * as osRelease from '../src/lib/os-release';
|
||||
import supervisorVersion = require('../src/lib/supervisor-version');
|
||||
|
||||
describe('Container contracts', () => {
|
||||
// Because the supervisor version will change whenever the
|
||||
// package.json will, we generate values which are above
|
||||
// and below the current value, and use these to reason
|
||||
// about the contract engine results
|
||||
const supervisorVersionGreater = `${semver.major(supervisorVersion)! +
|
||||
1}.0.0`;
|
||||
const supervisorVersionLesser = `${semver.major(supervisorVersion)! - 1}.0.0`;
|
||||
|
||||
before(async () => {
|
||||
// We ensure that the versions we're using for testing
|
||||
// are the same as the time of implementation, otherwise
|
||||
// these tests could fail or succeed when they shouldn't
|
||||
expect(await osRelease.getOSSemver(constants.hostOSVersionPath)).to.equal(
|
||||
'2.0.6',
|
||||
);
|
||||
assert(semver.gt(supervisorVersionGreater, supervisorVersion));
|
||||
assert(semver.lt(supervisorVersionLesser, supervisorVersion));
|
||||
});
|
||||
|
||||
it('Should correctly run containers with no requirements', async () => {
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container',
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container1',
|
||||
},
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container2',
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('should correctly run containers whose requirements are satisfied', async () => {
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.os',
|
||||
version: '>2.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `<${supervisorVersionGreater}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `>${supervisorVersionLesser}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `>${supervisorVersionLesser}`,
|
||||
},
|
||||
{
|
||||
type: 'sw.os',
|
||||
version: '<3.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container1',
|
||||
slug: 'user-container1',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `>${supervisorVersionLesser}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container1',
|
||||
slug: 'user-container1',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.os',
|
||||
version: '<3.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('Should refuse to run containers whose requirements are not satisfied', async () => {
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container',
|
||||
slug: 'user-container',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.os',
|
||||
version: '>=3.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(false);
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container2',
|
||||
slug: 'user-container2',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `>=${supervisorVersionLesser}`,
|
||||
},
|
||||
{
|
||||
type: 'sw.os',
|
||||
version: '>3.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(false);
|
||||
expect(
|
||||
await containerContractsFulfilled([
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container1',
|
||||
slug: 'user-container1',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `>=${supervisorVersionLesser}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'sw.container',
|
||||
name: 'user-container2',
|
||||
slug: 'user-container2',
|
||||
requires: [
|
||||
{
|
||||
type: 'sw.supervisor',
|
||||
version: `<=${supervisorVersionLesser}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).to.equal(false);
|
||||
});
|
||||
});
|
@ -1,2 +1,3 @@
|
||||
PRETTY_NAME="Resin OS 2.0.6 (fake)"
|
||||
PRETTY_NAME="balenaOS 2.0.6 (fake)"
|
||||
VARIANT_ID="dev"
|
||||
VERSION="2.0.6"
|
||||
|
@ -1 +1,2 @@
|
||||
PRETTY_NAME="Resin OS 2.0.6 (fake)"
|
||||
PRETTY_NAME="balenaOS 2.0.6 (fake)"
|
||||
VERSION="2.0.6"
|
||||
|
Loading…
x
Reference in New Issue
Block a user