相对
一个简单而有效的能力/要求说明和验证系统,通过 合同。 。 。 .
快速启动
import { Contract } from 'contrato';
const osContract = new Contract({
type: 'sw.os',
slug: 'balenaos',
version: '6.1.2',
children: [
{ type: 'sw.service', slug: 'balena-engine', version: '20.10.43' },
{ type: 'sw.service', slug: 'NetworkManager', version: '0.6.0' },
{ type: 'sw.feature', slug: 'secureboot' },
],
});
const serviceContract = new Contract({
type: 'sw.application',
slug: 'myapp',
requires: [
{ type: 'sw.service', slug: 'balena-engine', version: '>=20' },
{ type: 'sw.feature', slug: 'secureboot' },
],
});
if (osContract.satisfiesChildContract(serviceContract)) {
console.log('myapp can be installed!');
}
关于合同
合同提供了一个标准机制,用以描述 东西. 某物一般指可版本的东西,如软件库,功能,API等. 可以通过组成和参考来建立事物之间的关系(children 财务报告和审定财务报表 requires) (中文(简体) ). 通过这个图书馆,合同可以被验证,组成和合并.
为什么造这个?
巴莱娜 使用户能够部署、管理和扩大大型IOT设备。 这些机队可能由使用不同组合的硬件和软件组件以及不同的OS版本的设备所组成. 合同提供了一个描述能力和要求的接口,使用户能够安全地将更新推给车队,并确保其软件只运行在符合运行要求的设备上.
合同怎么办?
说明a 东西
{
"type": "sw.library",
"slug": "glibc",
"version": "2.40",
"assets": {
"license": {
"name": "GNU Lesser General Public License",
"url": "https://www.gnu.org/licenses/lgpl-3.0.html#license-text"
}
}
}
说明a 东西 这需要 东西
{
"type": "sw.utility",
"slug": "curl",
"version": "8.11.1",
"requires": [{ "type": "sw.library", "slug": "glibc", "version": ">=2.17" }],
"data": {
"protocols": ["HTTP", "HTTPS", "FTP"]
}
}
描述一个复合体 东西 通过综合合同
{
"type": "sw.os",
"slug": "balenaos",
"version": "4.1.5",
"children": [
{
"type": "sw.library",
"slug": "glibc",
"version": "2.16",
"assets": {
"license": {
"name": "GNU Lesser General Public License",
"url": "https://www.gnu.org/licenses/lgpl-3.0.html#license-text"
}
}
}
]
}
儿童也是合同如何宣布它向下列机构提供的能力:
其背景:任何儿童都可以与另一合同的匹配 requires。 。 。 .
通过 诱惑
{
"slug": "alpine",
"type": "sw.os",
"version": "1",
"data": {
"libc": "musl-libc",
"latest": "3.20",
"versionList": "`3.20 (latest)`, `3.19`"
},
"name": "Alpine Linux {{this.version}}",
"requires": [{ "type": "sw.blob", "slug": "balena-idle" }],
"variants": [
{
"requires": [
{ "type": "sw.blob", "slug": "qemu" },
{
"or": [
{ "type": "arch.sw", "slug": "armv7hf" },
{ "type": "arch.sw", "slug": "rpi" },
{ "type": "arch.sw", "slug": "aarch64" }
]
}
],
"variants": [{ "version": "3.19" }, { "version": "3.20" }]
},
{
"requires": [
{
"or": [
{ "type": "arch.sw", "slug": "i386" },
{ "type": "arch.sw", "slug": "amd64" }
]
}
],
"variants": [{ "version": "3.19" }, { "version": "3.20" }]
}
]
}
补充资料
见 合同规格 关于合同格式的额外文件.
关于反面
Contrato是Balena合同的执行。 它提供搜索、比较、验证和交叉参照合同的能力,以及从特定背景生成合同组合(通过 蓝图),并使用这些组合来构建模板. 下文对这些特征作了一些补充说明.
合同验证
如果合同的全部要求在特定情况下得到满足,则合同在某种情况下有效。 如果在这种情况下(包括儿童在内)有符合要求的合同,则符合要求。 举个例子
import { Contract } from 'contrato';
import assert from 'assert';
const osContract = new Contract({
type: 'sw.os',
slug: 'balenaos',
version: '4.1.5',
children: [
{
type: 'sw.library',
slug: 'glibc',
version: '2.16',
},
],
});
// This is true
assert(
osContract.satisfiesChildContract(
new Contract({
type: 'sw.utility',
slug: 'myapp',
version: '8.11.1',
requires: [{ type: 'sw.library', slug: 'glibc', version: '>=2.15' }],
}),
),
);
// This is false
assert(
!osContract.satisfiesChildContract(
new Contract({
type: 'sw.utility',
slug: 'myapp',
version: '8.11.1',
requires: [{ type: 'sw.library', slug: 'glibc', version: '<2' }],
}),
),
);
Contrato也允许找到不满足的要求,例如.
// Will print [{ type: 'sw.library', slug: 'glibc', version: '>=2.17' }]
console.log(
osContract.getNotSatisfiedChildRequirements(
new Contract({
type: 'sw.utility',
slug: 'curl',
version: '8.11.1',
requires: [{ type: 'sw.library', slug: 'glibc', version: '>=2.17' }],
}),
),
);
合同诱导
合同的字符串属性可以使用下列方法引用在同一合同上声明的其他编号或字符串属性: this 关键字与手柄栏标记 .
举个例子
{
"slug": "mycontract",
"version": "1.0.0",
"name": "This is my contract",
"aliases": ["my-contract"],
"data": {
"number": 5
},
"assets": {
"file": "https://files.contracts.io/{{this.slug}}/{{this.componentVersion}}/{{this.data.number}}.data"
}
}
可用单一模板生成多个合同 variants 合同规格上的不动产。 一个很好的例子是 Alpine OS 合同模板 描述不同架构的组合,构建一个版本列表,可以被汇编成一套合同. 另见 节点JS合同 对于一个更复杂的例子.
在Contrato中,模板可以使用 合约 构建 函数.
import { Contract } from 'contrato';
// Build the template into the resulting contracts
const contracts = Contract.build({ slug: 'mycontract' /* ... */ });
宇宙
一个宇宙是符合正在运行的"物"集合的复合合同. 例如,2001年12月31日的一套合同 balena-io/合同 组成了巴莱纳合同的宇宙,包含巴莱纳及其产品可用的设备类型,架构,OS版本和软件堆栈等知识.
反向提供 宇宙类型 与一系列合同合作.
import { Contract, Universe } from 'contrato';
// Load the universe of contracts from ./contracts
const universe = await Universe.fromFs('./contracts');
// Find all contracts for the Debian OS
const children = universe.findChildren(
Contract.createMatcher({
type: 'sw.os',
slug: 'debian',
}),
);
蓝图
蓝图是一份合同,它定义了如何从宇宙中产生一定的组合合同. "应用"宇宙上蓝图的结果是一组上下文. A 类 上下文 是一个满足所有要求的宇宙.
import { Universe, Blueprint } from 'contrato';
// Load the universe of contracts from balena-io/contracts
const universe = await Universe.fromFs('./contracts');
// Create a blueprint
const blueprint = new Blueprint(
// specify a layout combining one instance of device-type, arch and os contracts
{ 'hw.device-type': 1, 'arch.sw': 1, 'sw.os': 1 },
// use this skeleton as the parent contract for each context
{ type: 'meta.context' },
);
// Reproduce the blueprint over the universe
const contexts = blueprint.reproduce(universe);
for (const context of contexts) {
// Do something with each context, e.g. build a template
}
模板
合同(通常为上下文)可与模板合同相合并,以产生基于模板的文本制品 部分模板使用 调试语言。 。 。 .
示例:从上下文生成OS安装指令.
1. {{import partial="download" combination="sw.os"}}
2. {{import partial="download" combination="sw.image-writer"}}. {{import partial="flash" combination="sw.image-writer+hw.device-type"}}.
3. {{import partial="insert-install-media" combination="hw.device-type"}}. {{import partial="prepare-network" combination="hw.device-type"}}. {{import partial="boot-external" combination="hw.device-type"}}
{{#hw.device-type.storage.internal}}
4. The device should appear on you dashboard in a configuring state. {{import partial="description-of-internal-process" combination="sw.image-writer"}}. {{import partial="visual-appearance-when-off" combination="hw.device-type"}}
5. {{import partial="remove-install-media" combination="hw.device-type"}}. {{import partial="boot-internal" combination="hw.device-type"}}
{{/hw.device-type.storage.internal}}
6. Your device should appear here in the IDLE state in 30 seconds or so. Have fun!
**Troubleshooting:** If, upon boot, the device LED is blinking in groups of four, it is an indication that the device cannot connect to the internet. Please ensure the network adapter is functional.
{{#compare hw.device-type.media.installation "!==" "dfu"}}
**Pro tip:** You can repeat the initialisation steps for any amount of {{hw.device-type.name}}'s you have available, using the same {{hw.device-type.media.installation}}.
{{/compare}}
订立合同时可使用 构建模板 函数
import { buildTemplate } from 'contrato';
import { promises as fs } from 'fs';
// Reproduce the blueprint over the universe
const contexts = blueprint.reproduce(universe);
const template = await fs.readFile('instructions.tpl', 'utf8');
for (const context of contexts) {
console.log(await buildTemplate(template, context));
}
部分
每项合同或合同的组合可能包含与之相关的部分,这些部分可被装入模板系统的文本文物中.
公约将有一个合同目录,其结构如下:
<type combinations>/<slug combinations>/contract.json
<type combinations>/<slug combinations>/partial1.tpl
<type combinations>/<slug combinations>/partial2.tpl
<type combinations>/<slug combinations>/partialN.tpl
路径的种类和弹片片段可能是一个实体,或者用+符号相隔的一组实体. 例如:
sw.os+arch.sw/debian+amd64/installation.tpl
hw.device-type/ts4900/remove-install-media.tpl
类型组合部分具体规定了特定部分子树的参赛合同类型,由 + 符号。 如果组合是 sw.os+arch.sw,然后意味着子树会考虑操作系统和架构的组合. 注意,单类型可以有组合.
弹道组合部分定义了一组与组合类型相匹配的特定合同的子树. 如果类型组合是 sw.os+arch.sw,有效的弹道组合可以是 debian+amd64,这是在将Debian GNU/Linux合同与amd64架构合同相匹配时选中的子树.
请注意,弹道组合可能使用 @ 符号定义组合中一个或多个合同的特定版本的子树。 譬如说, debian@wheezy+amd64 将是一个树下,里面有Debian Wheezy和amd64的组合部分.
也可以省略后缀组合的部分来执行通配符. 如果类型组合是 sw.os+arch.sw 而弹道组合是 debian,这意味着这种子树将适用于Debian GNU/Linux与任何架构的组合.
然后将部分树从特定到一般,直到找到匹配。 这是合同制度在搜索 download 模板 sw.os+arch.sw 组合 :
sw.os+arch.sw/<os>@<version>+<arch>@<version>/download.tpl
sw.os+arch.sw/<os>@<version>+<arch>/download.tpl
sw.os+arch.sw/<os>+<arch>@<version>/download.tpl
sw.os+arch.sw/<os>+<arch>/download.tpl
sw.os+arch.sw/<os>/download.tpl
同样,您可以看到一些部分使用在 balena-io/合同存放处。 。 。 .
对抗的限制
Contrato在它所执行的多数任务上相当有效率,然而大多数操作都要求验证上下文被存储在内存中,这限制了宇宙的大小. 速度的考虑在复制蓝图覆盖大宇宙时变得特别明显,在大宇宙中,选择者越多,组合数量和处理时间就越多.
建筑
核心合同引擎(建造、散列、儿童管理、匹配者搜索、要求验证)在Rust中实施,并通过WebAssembly编译成 wasm-bindgen. TypeScript层是一个薄薄的包装层,代表进入了WASM模块,并添加了依赖于 JSON 专用库的蓝图专用逻辑(组合, JSON Schema 验证, Handlebars turlating).
contrato-rs/ Pure Rust library crate (contrato)
contrato-wasm/ WASM bindings crate (wasm-bindgen cdylib)
lib/ TypeScript wrapper + blueprint/template logic
见 contrato-rs/README.md (原始内容存档于2018-09-30). Experience on the Rust criticle API.
先决条件
- 节点.js 20
- 与该工具链(1.89+)相连的粗口工具链
wasm32-unknown-unknown目标 - 包装
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
大楼
npm run build # builds WASM then TypeScript
npm run build:wasm # builds only the WASM module
测试
运行完整的测试套件( 构建 + lint + 测试) :
npm test
只运行 Rust 测试 :
cargo test --workspace
贡献
在提交PR前,请确保您包括测试,并确保 iinter 运行时无任何警告 :
npm run lint
cargo clippy --workspace -- -D warnings
支助
如果你有什么问题,请 提高 问题 (原始内容存档于2018-09-29). on GitHub.
许可证
该项目根据Apache 2.0许可证获得许可.