RFC-0215:结构化配置父级替换 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 允许父组件在运行时为其子组件提供配置。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-03-21 |
审核日期(年-月-日) | 2023-04-26 |
摘要
允许父组件覆盖其子组件结构中的值 配置。
设计初衷
根据 RFC-0127 中提议的方向,父组件应该
能够为其子项提供配置值。例如:
如果能够传递自己的配置,则 starnix_runner
会受益匪浅
值直接映射到相应的 starnix_kernel
无需创建配置目录和动态优惠即可启动。
父组件应该能够在启动 动态子级。例如,在 Rust 中实现的父级应该能够编写 例如:
let overrides = vec![ConfigOverride {
key: Some("parent_provided".into()),
value: Some(ConfigValue::Single(ConfigSingleValue::String(
"foo".to_string(),
))),
..ConfigOverride::EMPTY
}];
connect_to_protocol::<RealmMarker>()
.unwrap()
.create_child(
&mut CollectionRef { name: "...".into() },
Child {
// name, url, startup, ...
config_overrides: Some(overrides),
..Child::EMPTY
},
CreateChildArgs::EMPTY,
)
.await
.unwrap()
.unwrap();
将来,父级组件应该可以配置 静态子文件并使用生成的 库来降低详细程度 确保替换项类型正确无误。
利益相关方
教员:jamesr@google.com
审核者:
- geb@google.com (CF)
- ypomortsev@google.com (CF)
- Markdittmer@google.com(安全)
已咨询:lindkvist@google.com、hjfreyer@google.com
社交化:此提案已提交给组件框架团队 然后再作为 RFC 提交。
要求
- 最初,父级可以为动态子级提供配置值, 包括那些使用 RealmBuilder 实例化的样式。最终应该 可以在 CML 中配置静态子级,包括使用 父资源的配置
- 组件作者可以改进内部配置/仅限测试配置/开发者专用配置 而无需与父级组件协调
- 父组件和子组件可以独立更新。儿童可能会发育 通过协调软转换和父项来配置架构 组件。
设计
为使此功能正常运行,我们需要定义以下内容:
可由父级更改的配置字段
根据要求 (2),父级应只能替换配置 由子组件标记为可变的字段。
组件作者会向所有配置字段添加 mutable_by
属性
应允许其接收替换值该属性将接受列表
字符串,以适应未来的替换机制。最初是唯一的字符串
将为 "parent"
。CML 示例:
{
// ...
config: {
fields: {
enable_new_feature: {
type: "bool",
mutable_by: [ "parent" ],
},
},
}
}
可变性将指定为可能的来源列表,以便轻松 为新的替换来源扩展此语法,包括开发者替换 数据库(最初由 RFC-0127 限定)。
我们未来可以寻求一种被拒绝的替代方法,那就是 不同配置字段的可变性。
字符串键与偏移/序数
组件管理器目前使用 已编译配置架构中字段的偏移量,进行了小幅优化 这需要组件的清单、配置值 以就配置架构的确切布局达成一致。这不会 其中值的提供程序和配置的 不同(但兼容)的配置架构构建的。
父替换值将改为通过检查 键和提供的替换值,以允许顺序和 子级架构中字段的数量,无需子级即可更改 组件以指定显式序数,或指定父组件以更新其 替换。
此方法遭拒的替代方案是使用整数 序数,用于解析以及要求子组件明确选择 序数。
打包的默认值
如果组件包含可由父级更改的配置字段,则仍然需要 在其封装配置中提供默认值/基本值。这将使 新增父可见字段的添加属于软过渡。
将来,我们可能会允许组件要求自身的 父项提供一些配置值。
没有过度预配
父级提供的配置值文件只能包含字段的值 在子组件配置中存在并由父级更改的这些属性 架构。防止过度预配配置将使配置具有可预测性 而不必手动验证 配置方式与预期相同
来自父级的值的优先顺序
组件配置“定制组件的行为” 实例传递给它的运行环境这意味着 权威配置值应该是 组件实例的上下文。
根据 RFC-0127,组件管理器将为每个配置解析一个值 字段中,按以下顺序优先显示每个来源:
- (将来)来自开发者替换服务的值
- 来自组件父级的值
- 来自组件自身软件包的值
从事工程 build 的组件开发者可能了解 这样会使系统上的所有对象被覆盖 权威性的内容。父级可以理解它为其子级提供的上下文。 最后,由于组件自身包中的值只能对 组件的打包方式,则其他所有信息都必须 外部来源提供的数据
在 Realm.CreateChild
中提供值
我们将扩展 fuchsia.component.decl/Child
以允许指定配置
替换代码:
library fuchsia.component.decl;
@available(added=HEAD)
type ConfigOverride = table {
1: key ConfigKey;
2: value ConfigValue;
};
type Child = table {
// ...
@available(added=HEAD)
6: config_overrides vector<ConfigOverride>:MAX;
};
虽然父级可以为动态子级设置配置
如果我们将此字段添加到 fuchsia.component.CreateChildArgs
,则不会
提供为静态定义的组件指定父替换值的路径
CML 格式的内容。所提议的方法确保
父组件提供的值的单一来源,供组件管理器在
。
在运行时提供值的父组件必须与 与配置字段完全相同无类型推断、类型转换或整数 促销。
实现
所提议的设计已完成原型设计。
fuchsia.component.decl
FIDL 库需要能够访问 Value
类型,当前位于 fuchsia.component.config
中。不过,
fuchsia.component.config
目前依赖于 fuchsia.component.decl
,因此
反转与软硬件的依赖关系需要很长时间
过渡效果。而是将 fuchsia.component.config
全部移至
当前 API 级别的 fuchsia.component.decl
,弃用并最终弃用
移除 fuchsia.component.config
。在合并这两个库时,我们将添加
为相应类型添加 Config*
前缀,例如Value
会变为 ConfigValue
。
此提案的新定义的 FIDL API Surface 将仅在 API 上提供
HEAD
级,直到我们熟悉该功能和树外功能
用户能够使用结构化配置。
除了对组件声明所做的更改外,RealmBuilder
客户端
库需要更新,以允许传递配置替换,
Child
声明的一部分。
性能
此 RFC 提议:组件管理器将使用字符串等式来进行匹配 替换相应的预期字段,执行起来会比 目前用于解析打包的配置值的 O(1) 索引/偏移值比较。 这会为组件启动增加很少的计算量,但不太可能 因为组件的开始时间通常是 数百毫秒的时间。对于上下文,框架开销 组件开始时间尚未成为任何面向用户的 产品质量问题。
工效学设计
在该功能的第一次迭代中,父组件需要知道 被替换的配置字段的确切类型,这不符合人体工程学 因为我们最终可以实现这一功能。例如,组件作者 需要与数字配置的整数宽度和符号完全一致 字段。将来,我们可以定义更宽松的类型解析规则, 支持配置静态子级,我们可能还会 从子级的配置架构生成代码 这样,语言编译器就可以代表 开发者。
无法描述组件配置的版本序列 架构演变将是一个手动的社交过程。这可能是 将来解决的问题。
某些组件最终可能会有多个具有相同可变性的字段 限制,通过为 。目前我们还没有符合这一模式的应用场景, 用于解决此问题的语法是遭拒的 的替代方案。
后退 &向前兼容性
组件的作者将负责协调软过渡, 在修改其父组件可变的父组件部分时, 配置架构本部分介绍了 对配置架构的修改
没有 mutable_by
属性的配置字段不需要任何
需要特别注意版本控制,因为这些字段的值只能
来自组件自己的软件包
将来,这些步骤可能更符合基于 FIDL 的人体工程学调解 版本控制。
添加按父级更改的新字段,为现有字段添加可变性
无需特别考虑,因为所有配置字段仍然 要求将基值/默认值放在组件自己的打包值文件中。 当该字段出现在组件的配置架构中后,父项将 从而为该字段提供值
移除按父级可变的配置键,同时移除可变性修饰符
从组件的 则需要先使用父级组件,以确保它们 传递将被移除的字段的任何覆盖。
例如,如需从组件的parent_provided
配置界面:
- 组件作者传达了弃用并移除
parent_provided
- 父级组件停止指定
parent_provided
的值 - 组件作者从其配置架构中移除了
parent_provided
重命名按父项更改的字段
重命名字段等同于同时添加和移除,并且应该 通常无法在一个步骤中完成。
更改父字段的可变类型
更改字段类型等同于同时添加和移除并 通常不应在单个步骤中执行。
更改按父级可变的字段的限制条件
增加字段的 max_len
或 max_size
约束条件始终是安全的。
只有在所有max_len
max_size
父级提供的值在新范围内。
安全注意事项
scrutiny
工具可以对最终结果进行断言
在构建的系统映像中配置组件配置。这是一项非常重要的
防护机制,以防止在配置过程中发生意外配置错误
和组装流程
我们将扩展 scrutiny
,使其拒绝
具有由政策实施的值,并且可由父级更改。这样可以确保
父级绝不会更改对安全性要求至关重要的配置字段
组件超出了审查的静态验证范围。
隐私注意事项
通过父级替换项,组件可以将运行时数据作为配置值传递。 虽然某些组件可能会选择使用此功能来传递用户数据, 目前还没有任何功能 日志、指标或遥测数据中的配置值。不应该保护隐私 对实现此功能的影响。
测试
config_encoder
库用于解析组件中的配置
Manager、scrutiny
和相关工具。其单元测试将扩展为
确保配置不正确的父级替换项(未知键、错误类型、
缺失可变性),则会被拒绝。
我们将扩展结构化配置集成测试,以确保
父级组件可以提供使用 Realm
协议和
RealmBuilder
。
scrutiny
测试将扩展,以确保它拒绝父级可变字段
这些对象在政策文件中具有静态声明的值。
文档
CML 参考文档将更新,以与更新后的配置架构匹配 语法。
我们将编写有关结构化配置架构演变的指南。涵盖的内容 添加和移除字段的最佳做法。其中将提供以下方面的指南: 管理软过渡
新增了关于结构化配置值源及其 优先级。
有关验证已构建映像安全属性的现有文档将 扩展,并解释为什么不允许使用父级可变字段 以进行安全相关配置
未来工作
为替换项生成的绑定
上述方案意味着父组件的作者需要使用 字符串键和“动态输入”用于提供替换值。这将 会生成一些样板文件,这样父项便能够提供 键/值有误,或者忘记更新会生成替换项的代码路径 因配置不正确而导致的错误 仅在启动子节点时, 组件。
我们最终可以允许 用于生成“父级替换”的父级配置的组件库。这些 可以被父组件的作者用来减少输入的字符和 让编译器检查是否正确地替换了子级的 配置:
let overrides = FooConfigOverrides {
parent_provided: Some("foo".to_string()),
..FooConfigOverrides::EMPTY
};
connect_to_protocol::<RealmMarker>()
.unwrap()
.create_child(
&mut CollectionRef { name: "...".into() },
Child {
// name, url, startup, ...
config_overrides: overrides.to_values(),
..Child::EMPTY
},
CreateChildArgs::EMPTY,
)
.await
.unwrap()
.unwrap();
在 CML 中配置子级
具有静态子项的父组件最终应能够提供 在 CML 中声明子级时指定配置值。例如:
{
children: [
{
name: "...",
url: "...",
config: {
parent_provided: "foo",
},
},
],
}
我们可能还想提供语法,让父项转发自己的 配置值直接传递给静态子级。
我们需要确定 scrutiny
是否允许按父级可变字段
当提供的配置值是已知的时。
如果我们针对 CML 构建这一功能,传递字面量值就需要进行设计
努力弥合 JSON5 中数字的宽松类型与结构化类型之间的差距
配置的确切类型。我们需要选择
适用于所有可能的 JSON5 的 fuchsia.component.decl.ConfigValue
表示法
我们需要定义一些规则,以便组件管理器
将这些值用于可能范围较窄的配置字段
限制条件。如果我们等待基于 FIDL 的 FIDL,
父级/子级关系的表示形式,因为 fidlc
能够
根据子级的配置架构精确检查类型。这项设计工作
父级发布商自己的配置值可以传递给
子节点。
必需的父级配置值
有些组件可能需要要求其父级提供特定的 配置值,而不必依赖打包的默认值。
这就需要允许打包的值文件省略值,并指导 这些库解析了如何处理它们的配置
这并非满足当前应用场景的必要条件,但对于 未来发展方向。
基于 FIDL 的架构和版本控制
组件框架团队探索了如何将 FIDL 用于组件 清单。如果实现 FIDL,我们或许可以使用 FIDL 可用性 注解来协调配置架构演变。
使用字符串键解析打包的配置
如果采用此 RFC 中提议的方法,将会使组件管理器只留下两个 用于解析配置值的标识符:
- 打包的值将使用编译后的整数偏移进行解析 配置架构
- 将使用配置中的字符串键解析父级提供的值 架构
正如下文的替代方案中所述,我们有充分的理由 首选使用字符串键作为父级替换值,以及使用 整数偏移/序数在解析打包值时用处不大。
我们应该将打包的值移至对字符串键进行编码,以减少碎片化 与组件管理器语义相关。此更改应该基本上 但会简化配置解析和 以便日后更轻松地进行调试
模糊配置解析
将来,我们将添加模糊测试工具,以便组件管理器的清单解析和 组件分辨率。如果这样做,我们将会 扩展模糊测试工具,以包含来自父组件的配置值,并 断言遵循可变性修饰符。
缺点、替代方案和未知问题
对具有共同可变性限制条件的字段进行分组
如果某个组件有许多字段都具有相同的可变性约束, 我们可以考虑允许组件作者对配置字段进行分组 以及具有共同属性对于(非规范性)样本,我们可以定义 多个配置部分:
{
// ...
config: {
// fields which can only be resolved from a component's package
},
parent_config: {
// fields which are mutable by parent
},
}
这种方向对于人体工学设计可能很有用,但不适用于 已经确定的现有应用场景如果我们发现 在实践中,详细程度是一项高昂的成本
请注意,这种方法可能会使组件作者更难以定义 具有多个可变性说明符的配置字段,例如“由 父级和替换服务”。
替换 API 的整数序数
由于历史原因,打包的配置值会被解析为 字段。这提供了一个 编码效率更高,运行时开销更少,与检查字符串相比 是否相等。
若要为父级替换项实现同样的好处,我们需要 子组件的其字段明确选择序数,类似于 FIDL 表。父组件的作者需要指定其替换项 值来表示这些序数,也可以使用生成的 库提供简单易懂的名称。
我们还需要设计相应的机制来引导子组件作者离开 可避免重复使用整数序号,而精心选择的字符串键应位于 降低易用 bug 再利用的风险。
最终,通过解析这些文档,我们可以提高二进制文件大小和运行时开销, 根据我们的预期,使用整数的键可以忽略不计 结构化配置我们可以使用字符串键延迟生成的绑定, 使子组件作者无需费心管理 序数,且系统性能开销极低。