_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
是 spiff 的一个分支,它基于最新版本提供了 spiff 的兼容扩展,提供了 spiff 中尚未提供的一组丰富的新功能。原始 spiff 项目提供的所有修复也将合并到 spiff++ 中。因为无法返回到 spiff 源代码库,所以创建了一个新的独立 spiff++ 存储库来继续开发 spiff++。spiff是一个命令行工具和声明性域内混合 YAML 模板系统。虽然常规模板系统通过用从外部数据源获取的值替换模板表达式来处理模板文件,但域内意味着模板引擎了解已处理模板的语法和结构。因此,它可以直接从处理的文档中获取模板表达式的值,包括由模板表达式本身表示的那些部分。
例如:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
spiff不只使用外部值源,而是提供了一种合并机制,用于将模板与任意数量的合并存根合并以生成最终文档。
它是一个命令行工具和声明性 YAML 模板系统,专门用于生成部署清单(例如 BOSH、Kubernetes 或 Landscaper 清单)。
除了 CLI 之外,还有一个 golang 库,可以在任何 GO 程序(例如 Landscaper)中使用 spiff 模板处理。
模板引擎提供对基于可配置虚拟文件系统或进程系统的文件系统的访问,以执行命令并将输出合并到模板处理中。
内容:
<<if:
<<switch:
<<type:
<<for:
<<merge:
可以通过 Darwin、Linux 和 PowerPC 计算机(以及虚拟机)的 Github 版本下载官方版本的可执行二进制文件。
自上次正式发布以来,spiff 的一些依赖项已发生更改,并且 spiff 将不会更新以跟上这些依赖项。这些依赖项要么是固定的,要么是复制到本地代码库中的。
spiff merge template.yml [template2.yml ...]
将一堆模板文件合并到一个清单中,并将其打印出来。
有关模板文件的详细信息,请参阅“动态模板语言”,有关更复杂的示例,请参阅示例/子目录。
例子:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
可以使用文件名-
来从标准输入读取一个文件。它只能使用一次。这允许使用 spiff 作为管道的一部分来仅处理单个流或基于多个模板/存根处理流。
模板文件(第一个参数)可以是包含多个 YAML 文档的多文档流,这些文档由仅包含---
行分隔。每个 YAML 文档将使用给定的存根文件独立处理。结果是按相同顺序处理的文档流。如果文档的根节点被标记为临时,则该文档将从输出流中省略。例如,这可用于生成供kubectl
使用的kubernetes清单。
merge
命令提供了几个选项:
选项--partial
。如果指定此选项,spiff 会处理不完整的表达式求值。所有错误都将被忽略,并且 yaml 文档中无法解析的部分将作为字符串返回。
使用选项--json
输出将采用 JSON 格式而不是 YAML。
选项--path <path>
可用于输出嵌套路径,而不是完整的处理文档。
如果输出是列表,则选项--split
将每个列表元素输出为单独的文档。 yaml格式照常使用---
作为分隔线。 json格式输出一系列json文档,每行一个。
使用--select <field path>
可以选择已处理文档的专用字段用于输出
使用--evaluate <dynaml expression>
可以在已处理文档上评估给定的动态表达式以获取输出。在应用选择路径之前对表达式进行求值,然后选择路径将对求值结果起作用。
选项--state <path>
启用spiff的状态支持。如果给定文件存在,则将其放置在已配置存根列表的顶部,如果给定文件存在,则将其放置在已配置存根列表的顶部以进行合并处理。除了已处理文档的输出之外,还会过滤带有&state
标记的节点。然后,此过滤后的文档存储在指定文件下,以.bak
后缀保存旧状态文件。这可以与状态实用程序库提供的手动合并一起使用。
使用选项--bindings <path>
可以指定 yaml 文件,其内容用于构建用于处理的附加绑定。 yaml 文档必须包含一个映射。每个键都用作附加绑定。不处理绑定文档,值按定义使用。
使用选项--tag <tag>:<path>
可以指定 yaml 文件,其内容用作预定义全局标记的值(请参阅标记)。可以通过<tag>::<ref>
形式的引用表达式来访问标签。与绑定相比,标记内容不与文档中的节点竞争,它使用另一个引用名称空间。
使用选项--define <key>=<value>
(简写-D
)可以在命令行上指定附加绑定值,覆盖绑定文件中的绑定值。该选项可能会出现多次。
如果键包含点( .
),它将被解释为路径表达式来描述深度映射值中的字段。点(以及点之前的 )可以通过
进行转义,以将其保留在字段名称中。
选项--preserve-escapes
将保留动态表达式和列表/映射合并指令的转义。如果打算使用spiff对处理结果进行进一步的处理步骤,则可以使用此选项。
选项--preserve-temporary
将保留最终文档中标记为临时的字段。
选项--features=<featurelist>
将启用此给定功能。必须显式启用与旧行为不兼容的新功能。通常,这些功能不会破坏常见行为,而是为之前用作常规值的 yaml 值引入专用解释。
文件夹库提供了一些有用的实用程序库。它们也可以用作该模板引擎强大功能的示例。
spiff diff manifest.yml other-manifest.yml
显示两个部署清单之间的结构差异。这里还支持具有多个文档的流。为了表明没有差异,两个流中的文档数量必须相同,并且第一个流中的每个文档与第二个流中具有相同索引的文档相比必须没有差异。发现的差异会分别显示每个文档。
与基本比较工具甚至bosh diff
不同,此命令具有部署清单的语义知识,而不仅仅是基于文本。例如,如果两个清单是相同的,只是它们有一些以不同顺序列出的作业, spiff diff
将检测到这一点,因为作业顺序在清单中很重要。另一方面,例如,如果两个清单仅在其资源池的顺序上有所不同,那么它将产生并清空 diff,因为资源池顺序实际上对于部署并不重要。
与bosh diff
不同的是,此命令不会修改任何一个文件。
它专门用于检查一个部署与下一个部署之间的差异。
典型流程:
$ spiff merge template.yml [templates...] > deployment.yml
$ bosh download manifest [deployment] current.yml
$ spiff diff deployment.yml current.yml
$ bosh deployment deployment.yml
$ bosh deploy
spiff convert --json manifest.yml
convert
子命令可用于将输入文件转换为 json 或仅规范字段的顺序。可用选项为--json
、 --path
、 --split
或--select
根据其对于merge
子命令的含义。
spiff encrypt secret.yaml
encrypt
子命令可用于根据encrypt
动态函数对数据进行加密或解密。密码可以作为第二个参数给出,也可以从环境变量SPIFF_ENCRYPTION_KEY
中获取。最后一个参数可用于传递加密方法(请参阅encrypt
函数)
数据取自指定文件。如果给出-
,则从 stdin 读取。
如果给出选项-d
,则数据被解密,否则数据被读取为yaml文档并打印加密结果。
必须显式启用与旧行为不兼容的新功能。通常,这些功能不会破坏常见行为,但会为之前用作常规值的 yaml 值引入专用解释,因此可能会破坏现有用例。
目前支持以下功能标志:
特征 | 自从 | 状态 | 意义 |
---|---|---|---|
interpolation | 1.7.0-beta-1 | 阿尔法 | Dynaml 作为 yaml 字符串的一部分 |
control | 1.7.0-beta-4 | 阿尔法 | 基于 yaml 的控制结构 |
可以使用动态函数features()
作为字符串列表来查询活动功能标志。如果使用字符串参数调用此函数,它将返回给定的功能当前是否启用。
可以通过命令行使用--features
选项、通过 go 库使用WithFeatures
函数或通常通过将环境变量SPIFF_FEATURES
设置为功能列表来启用功能。此设置始终用作默认设置。通过使用 go 库中的Plain()
spiff 设置,所有环境变量都将被忽略。
功能可以通过名称指定,也可以通过名称前面加上前缀no
来禁用它。
库文件夹包含一些有用的spiff模板库。这些基本上只是添加到合并文件列表中的存根,以提供合并处理的实用功能。
Spiff 使用一种声明性的、无逻辑的模板语言,称为“dynaml”(动态 yaml)。
每个动态节点都保证解析为 YAML 节点。它不是字符串插值。这使得开发人员不必考虑如何在生成的模板中呈现值。
动态节点以字符串形式出现在 .yml 文件中,表示由两个括号括起来的表达式(( <dynaml> ))
。它们可以用作映射的值或列表中的条目。该表达式可能跨越多行。在任何情况下,yaml 字符串值都不能以换行符结尾(例如使用|-
)
如果括号内的值不应被解释为动态表达式并按原样保留在输出中,则可以在左括号后直接使用感叹号对其进行转义。
例如, ((! .field ))
映射到字符串值(( .field ))
, ((!! .field ))
映射到字符串值((! .field ))
。
以下是动态表达式的完整列表:
(( foo ))
在当前模板中查找最近的“foo”键(即词法范围)并将其引入。
例如:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
此示例将解析为:
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
以下内容将无法解析,因为键名称与要合并的值相同:
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
查找最近的“foo”键,然后从那里一直到.bar.[1].baz
。
路径是由点分隔的一系列步骤。步骤可以是用于映射的单词,也可以是用于列表索引的括号括起来的数字。索引可能为负数(减号后跟数字)。负索引从列表末尾获取(有效索引=索引+长度(列表))。
无法解析的路径会导致评估错误。如果预计有时不会提供引用,则应与“||”结合使用(见下文)以保证分辨率。
注意:现在,动态语法器已被重新设计以启用常用的索引语法。而不是foo.bar.[1]
现在可以使用foo.bar[1]
。
注意:引用始终位于模板或存根内,顺序并不重要。您可以引用另一个动态节点并假设它已解析,并且一旦依赖节点解析,引用节点最终将解析。
例如:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
只要“某事”是可解决的,并且只要它引入如下内容,这就会解决:
from :
the :
stub : foo
如果路径以点 ( .
) 开头,则始终从文档的根开始计算路径。如果文档根是一个列表,则第一个映射级别用于解析以.__map
开头的路径表达式。这可以用来避免使用自己的列表索引(如.[1].path
),如果添加列表条目,该索引可能会发生变化。
由带有name
字段的映射组成的列表条目可以直接通过其名称值作为路径组件来寻址。
注意:这也适用于列表文档的绝对路径。
例如:
爱丽丝的年龄
list :
- name : alice
age : 25
可以使用路径list.alice.age
来引用,而不是list[0].age
。
默认情况下,名称为name
的字段用作关键字段。如果应将另一个字段用作关键字段,则可以通过在字段名称前添加关键字key:
来将其在一个列表条目中标记为关键。该关键字将被处理删除,并且不会成为最终处理结果的一部分。
例如:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
将被解决
list :
- person : alice
age : 25
alice :
person : alice
age : 25
在合并列表期间也会观察到这个新的关键字段。
如果选定的关键字段以!
开头,关键功能被禁用。感叹号也从有效字段名称中删除。
如果关键字段的值不是唯一的,它也会被禁用。
(( foo.[bar].baz ))
查找最近的“foo”键,然后从那里一直到表达式bar
描述的字段,然后到 .baz。
索引可以是整数常量(不带空格),如上一节所述。但它也可能是任意动态表达式(甚至是整数,但带有空格)。如果表达式的计算结果为字符串,它将查找专用字段。如果表达式的计算结果为整数,则寻址具有该索引的数组元素。索引运算符前面的点 ( .
) 是可选的。
例如:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
这会将foo
解析为值42
。动态索引也可能位于表达式的末尾(没有.bar
)。
基本上,这是表达 eval("values." name ".bar") 之类的更简单的方法
如果表达式计算结果为列表,则列表元素(字符串或整数)将用作路径元素来访问更深的字段。
例如:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
再次将foo
解析为值42
。
注意:索引运算符也可用于根元素 ( .[index]
)。
可以为连续列表指定多个逗号分隔的索引( foo[0][1]
相当于 `foo[0,1] )。在这种情况下,索引可能不会再次列出。
(( list.[1..3] ))
切片表达式可用于从列表表达式中提取专用子列表。范围start ..
end提取长度为end-start+1的列表,其中元素从索引start到end 。如果起始索引为负,则从列表末尾从length+start到length+end获取切片。如果结束索引低于开始索引,则结果为空数组。
例如:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
开始或结束索引可能会被省略。然后根据列表的实际大小进行选择。因此list.[1..length(list)]
相当于list.[1..]
。
将foo
计算为列表[b,c]
。
(( 1.2e4 ))
整数和浮点值支持数字文字。
(( "foo" ))
字符串文字。支持所有 json 字符串编码(例如n
、 "
或uxxxx
)。
(( [ 1, 2, 3 ] ))
列出字面意思。列表元素可能又是表达式。有一个特殊的列表文字[1 .. -1]
,可用于将递增或递减的数字范围解析为列表。
例如:
list : (( [ 1 .. -1 ] ))
产量
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
地图文字可用于将地图描述为动态表达式的一部分。键和值都可能再次是表达式,其中键表达式的计算结果必须为字符串。这样就可以使用非静态键创建映射。选择赋值运算符=
而不是 yaml 中使用的常规冒号:
字符,因为这会导致与 yaml 语法发生冲突。
映射文字可能包含任意数量的由逗号,
分隔的字段分配。
例如:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
产量
name : peter
age : 23
map :
alice : {}
peter : 23
基于表达式组成列表的另一种方法是函数makemap
和list_to_map
。
(( ( "alice" = 25 ) alice ))
任何表达式都可以由任意数量的显式作用域文字开头。作用域文字描述了一个映射,其值可用于表达式的相对引用解析(静态作用域)。它为给定名称创建额外的本地绑定。
作用域文字可能由任意数量的由逗号,
分隔的字段分配组成。键和值均由表达式给出,而键表达式的计算结果必须为字符串。所有表达式都在下一个外部作用域中求值,这意味着作用域中的后续设置不能使用同一作用域文字中的早期设置。
例如:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
产量
scoped : 51
字段名称也可以用符号 ( $
name ) 表示。
(( foo bar ))
连接表达式用于连接动态表达式序列。
(( "foo" bar ))
连接(其中 bar 是另一个动态表达式)。任何简单值(字符串、整数和布尔值)序列都可以连接,由任何动态表达式给出。
例如:
domain : example.com
uri : (( "https://" domain ))
在此示例中uri
将解析为值"https://example.com"
。
(( [1,2] bar ))
将列表串联为表达式(其中 bar 是另一个动态表达式)。任何列表序列都可以连接,由任何动态表达式给出。
例如:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
在此示例中, static_ips
将解析为值[ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
。
如果第二个表达式的计算结果不是列表(整数、布尔值、字符串或映射),则该值将附加到第一个列表。
例如:
foo : 3
bar : (( [1] 2 foo "alice" ))
生成bar
的列表[ 1, 2, 3, "alice" ]
。
(( map1 map2 ))
将映射串联起来作为表达式。任何映射序列都可以连接,由任何动态表达式给出。从而条目将被合并。具有相同键的条目将从左到右被覆盖。
例如:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
产量
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
上下文相关的自动值计算。
在资源池的“大小”属性中,这意味着根据声明自己位于当前资源池中的所有作业的实例总数进行计算。
例如:
resource_pools :
- name : mypool
size : (( auto ))
jobs :
- name : myjob
resource_pool : mypool
instances : 2
- name : myotherjob
resource_pool : mypool
instances : 3
- name : yetanotherjob
resource_pool : otherpool
instances : 3
在这种情况下,资源池大小将解析为“5”。
(( merge ))
从正在合并的存根文件中引入当前路径。
例如:
foo :
bar :
baz : (( merge ))
将尝试从第一个存根或第二个存根等引入foo.bar.baz
,并从提供它的最后一个存根返回值。
如果对应的值没有定义,则返回nil。这与引用表达式具有相同的语义;零合并是一个未解决的模板。参见||
。
<<: (( merge ))
将映射或列表与某些存根中找到的相同元素的内容合并。
** 注意 ** 这种形式的merge
存在兼容性问题。在 1.0.8 之前的版本中,该表达式从未被解析,只有键<<:
的存在与否相关。因此,经常使用<<: (( merge ))
其中<<: (( merge || nil ))
的意思。第一个变体需要至少一个存根中的内容(与合并运算符一样)。现在,该表达式已正确计算,但这会破坏现有的清单模板集,该模板集使用第一个变体,但意味着第二个变体。因此,这种情况被显式处理以描述可选合并。如果确实需要合并意味着额外的显式限定符必须
注意:现在可以使用 <<<: 而不是使用<<<:
<<:
插入字段来放置合并表达式,这也允许对类似 spiff 的 yaml 文档使用常规 yaml 解析器。 <<:
保留是为了向后兼容。被使用( (( merge required ))
)。
如果合并键不应被解释为常规键而不是合并指令,则可以使用感叹号 ( !
) 对其进行转义。
例如,地图键<<<!
将产生字符串键<<<
和<<<!!
将产生一个字符串键<<<!
值.yml
foo :
a : 1
b : 2
模板.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
产生:
foo :
a : 1
b : 2
c : 4
值.yml
foo :
- 1
- 2
模板.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
产生:
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
能够将映射列表与关键字段合并。这些列表的处理方式类似于地图,以关键字段的值作为键。默认情况下使用键name
。但on
选择器可以为列表合并表达式指定任意键名称。
例如:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
合并于
list :
- key : alice
age : 20
- key : peter
age : 13
产量
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
如果不需要插入新条目(按照插入合并表达式的要求),而只想覆盖现有条目,则可以在一个现有键字段前面添加标签key:
以指示非标准键名称,例如- key:key: alice
。
<<: (( merge replace ))
用某些存根中找到的内容替换元素的完整内容,而不是对现有内容进行深度合并。
值.yml
foo :
a : 1
b : 2
模板.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
产生:
foo :
a : 1
b : 2
值.yml
foo :
- 1
- 2
模板.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
产生:
foo :
- 1
- 2
<<: (( foo ))
合并同一模板或存根中找到的地图和列表。
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
产量:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
该表达式只是将新条目添加到实际列表中。它不会将现有条目与合并表达式描述的内容合并。
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
产量:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
一个常见的用例是将静态 ip 或范围列表合并到 ip 列表中。另一种可能性是使用单个串联表达式。
<<: (( merge foo ))
将映射或列表与某些存根中找到的任意元素的内容合并(重定向合并)。将不会与某些存根中找到的同名元素进行进一步(深度)合并。 (列表的深度合并需要带有字段name
的映射)
重定向合并也可以用作直接字段值。它们可以与替换合并相结合,例如(( merge replace foo ))
。
值.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
模板.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
产生:
foo :
a : 1
b : 2
c : 4
与某些存根中的另一个元素进行合并的另一种方法也可以通过传统方式完成:
值.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
模板.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
但在这种情况下,合并仍然与原始元素名称执行深度合并。因此spiff merge template.yml values.yml
产生:
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
值.yml
foo :
- 10
- 20
bar :
- 1
- 2
模板.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
产生:
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
如果重定向合并的引用设置为常量none
,则根本不会进行任何合并。该表达式始终产生 nil 值。
例如:对于
模板.yml
map :
<< : (( merge none ))
value : notmerged
值.yml
map :
value : merged
spiff merge template.yml values.yml
产生:
map :
value : notmerged
这可用于使用stub
函数访问上游存根的专用部分的显式字段合并。
例如:
模板.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
值.yml
map :
value : bob
spiff merge template.yml values.yml
产生:
test :
value : alice+bob
这也适用于专用字段:
模板.yml
map :
value : (( merge none // "alice" "+" stub() ))
值.yml
map :
value : bob
spiff merge template.yml values.yml
产生:
test :
value : alice+bob
(( a || b ))
如果 a 无法解析,则使用 a,或 b。
例如:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
这将尝试合并到mything.complicated_structure
中,或者,如果无法合并到其中,则使用foo.bar
中指定的默认值。
运算符//
另外检查a
是否可以解算为有效值(不等于~
)。
(( 1 + 2 * foo ))
动态表达式可用于执行整数和浮点算术计算。支持的操作有+
、 -
、 *
和/
。模运算符 ( %
) 仅支持整数操作数。
例如:
值.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
为bar
生成7
。这可以与串联结合使用(计算的优先级高于动态表达式中的串联):
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
结果是字符串3 times 2 yields 6
。
(( "10.10.10.10" - 11 ))
除了整数算术之外,还可以对 ip 地址和 cidrs 使用加法和减法。
例如:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
产量
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
减法还适用于两个 IP 地址或 cidr,以计算两个 IP 地址之间的 IP 地址数量。
例如:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
产生值 256。IP 地址常量可以直接在动态表达式中使用。如果操作需要,它们会隐式转换为字符串并返回 IP 地址。
乘法和除法可用于处理 CIDR 上的 IP 范围变化。通过划分,网络可以被划分。网络规模增加,以允许在原始 CIDR 之下至少存在专用数量的子网。然后可以使用乘法来获得相同大小的第 n 个下一个子网。
例如:
subnet : (( "10.1.2.1/24" / 12 )) # first subnet CIDR for 16 subnets
next : (( "10.1.2.1/24" / 12 * 2)) # 2nd next (3rd) subnet CIDRS
产量
subnet : 10.1.2.0/28
next : 10.1.2.32/28
此外,还有一些适用于 IPv4 CIDR 的功能:
cidr : 192.168.0.1/24
range : (( min_ip(cidr) "-" max_ip(cidr) ))
next : (( max_ip(cidr) + 1 ))
num : (( min_ip(cidr) "+" num_ip(cidr) "=" min_ip(cidr) + num_ip(cidr) ))
contains : (( contains_ip(cidr, "192.168.0.2") ))
产量
cidr : 192.168.0.1/24
range : 192.168.0.0-192.168.0.255
next : 192.168.1.0
num : 192.168.0.0+256=192.168.1.0
contains : true
(( a > 1 ? foo :bar ))
Dynaml 支持比较运算符<
、 <=
、 ==
、 !=
、 >=
和>
。比较运算符适用于整数值。相等性检查也适用于列表和地图。结果始终是布尔值。要否定某个条件,可以使用一元非运算符 ( !
)。
此外,还有三元条件运算符?:
,可用于根据条件计算表达式。第一个操作数用作条件。如果条件为真,则表达式计算为第二个操作数,否则计算为第三个操作数。
例如:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
生成属性name
的值bob
。
如果一个表达式的计算结果为假,则该表达式被认为是false
false
否则视为true
评论
如果完整的表达式不是带引号的字符串值,则使用符号:
可能会与 yaml 语法发生冲突。
运算符-or
和-and
可用于组合比较运算符以组成更复杂的条件。
评论:
更传统的运算符符号||
(和&&
) 不能在这里使用,因为运算符||
已经存在于具有不同语义的 dynaml 中,该语义不适用于逻辑操作。表达式false || true
计算结果为false
,因为如果定义了第一个操作数,则无论其值如何,它都会生成第一个操作数。为了尽可能兼容,不能更改此名称,并且不能使用裸符号or
和and
,因为这将使具有此类名称的引用的串联无效。
(( 5 -or 6 ))
如果-or
或-and
运算符两边的计算结果均为整数值,则执行按位运算,结果再次为整数。因此表达式5 -or 6
计算结果为7
。
Dynaml 支持一组预定义函数。函数通常被称为
result : (( functionname(arg, arg, ...) ))
可以使用 lambda 表达式将其他函数定义为 yaml 文档的一部分。函数名称可以是分组表达式,也可以是托管 lambda 表达式的节点的路径。
(( format( "%s %d", alice, 25) ))
根据动态表达式给出的参数格式化字符串。此函数还有第二种风格: error
格式化错误消息并将评估设置为失败。
(( join( ", ", list) ))
使用给定的分隔符字符串将列表条目或直接值连接到单个字符串值。 join 的参数可以是对列表求值的动态表达式,其值同样是字符串或整数,或者字符串或整数值。
例如:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
为join
生成字符串值bob, foo, bar, alice, 10
。
(( split( ",", string) ))
将字符串拆分为专用分隔符。结果是一个列表。可以给出整数值而不是分隔符字符串,该整数值将给定字符串拆分为长度有限的字符串列表。长度以符文计算,而不是字节。
例如:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
产量:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
可以指定可选的第三个参数。它限制返回列表条目的数量。值 -1 导致列表长度不受限制。
如果需要使用正则表达式作为分隔符字符串,可以使用split_match
函数。
(( trim(string) ))
修剪字符串或字符串列表的所有元素。有一个可选的第二个字符串参数。它可用于指定将被剪切的一组字符。默认切割集由空格和制表符组成。
例如:
list : (( trim(split("," "alice, bob")) ))
产量:
list :
- alice
- bob
(( element(list, index) ))
返回由索引给出的专用列表元素。
例如:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
产量:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
返回由其键指定的专用地图字段。
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
产量:
map :
alice : 24
bob : 25
elem : 25
此函数还能够处理包含点 (.) 的键。
(( compact(list) ))
过滤列表,忽略空条目。
例如:
list : (( compact(trim(split("," "alice, , bob"))) ))
产量:
list :
- alice
- bob
(( uniq(list) ))
Uniq 提供了一个没有重复的列表。
例如:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
字段uniq
的产量:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
检查列表是否包含专用值。值也可能是列表或地图。
例如:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
产量:
list :
- foo
- bar
- foobar
contains : true
该函数contains
还适用于字符串以查找子字符串或映射以查找键。在这些情况下,元素必须是字符串。
例如:
contains : (( contains("foobar", "bar") ))
产生true
。
(( basename(path) ))
函数basename
返回路径最后一个元素的名称。该参数可以是常规路径名或 URL。
例如:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
产量:
pathbase : bob
urlbase : bob
(( dirname(path) ))
函数dirname
返回路径的父目录。该参数可以是常规路径名或 URL。
例如:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
产量:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
此函数解析 URL 并生成包含 URL 的所有元素的映射。 port
、 userinfo
和password
是可选的。
例如:
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
产量:
url :
scheme : https
host : github.com
port : 443
path : /mandelsoft/spiff
fragment : anchor
query : branch=master&tag=v1
values :
branch : [ master ]
tag : [ v1 ]
userinfo :
username : user
password : pass
(( index(list, "foobar") ))
检查列表是否包含专用值并返回第一个匹配项的索引。值也可能是列表或地图。如果找不到条目,则返回-1
。
例如:
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
产量:
list :
- foo
- bar
- foobar
index : 2
函数index
也适用于字符串来查找子字符串。
例如:
index : (( index("foobar", "bar") ))
产量3
。
(( lastindex(list, "foobar") ))
函数lastindex
工作方式与index
类似,但返回最后一次出现的索引。
函数sort
可用于对整数或字符串列表进行排序。排序操作是稳定的。
例如:
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
sorted
的收率
- alice
- bob
- foobar
如果要分类其他类型,尤其是诸如列表或地图之类的复杂类型,或者需要不同的比较规则,则可以将比较函数指定为可选的第二个参数。比较函数必须是lambda表达式,以两个参数为例。结果类型必须是integer
或bool
以指示a比b少。如果返回整数,应该是
例如:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
sorted
的收率
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
用替换字符串替换字符串中的所有出现。对于可选的第四整数参数,可以限制替换的数量(-1平均无限)。
例如:
string : (( replace("foobar", "o", "u") ))
产生fuubar
。
如果应将正则表达式用作搜索字符串,则可以使用replace_match
。在这里,搜索字符串被评估为正则表达式。它可能会构成子表达式。这些匹配可以在替换字符串中使用
例如:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
产生fbooar
。
替换参数也可能是lambda函数。在这种情况下,对于每场匹配,函数都被调用以确定替换值。单个输入参数是实际子表达式匹配的列表。
例如:
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
产生fOObar-barfoo
。
(( substr(string, 1, 2) ))
从字符串中提取一个存根字符串,从给定的启动索引从可选端索引(独家)开始。如果没有端索引,则提取到字符串末端之前的子索引。这两个指数可能为负。在这种情况下,它们是从字符串的末端带走的。
例如:
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
评估
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
返回给定字符串值的正则表达式的匹配。该匹配是正则表达式中包含的子表达式的匹配值列表。索引0是指完整的正则表达式的匹配。如果字符串值不匹配,则返回一个空列表。
例如:
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
产量:
matches :
- foobar
- foo
- bar
可以给出第三个类型整数的参数,以要求最大n个重复的多匹配。如果值为负,则报告所有重复。结果是所有匹配的列表,每个匹配项都以上述格式。
(( keys(map) ))
确定地图中使用的键的排序列表。
例如:
map :
alice : 25
bob : 25
keys : (( keys(map) ))
产量:
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
确定列表的长度,地图或字符串值。
例如:
list :
- alice
- bob
length : (( length(list) ))
产量:
list :
- alice
- bob
length : 2
(( base64(string) ))
函数base64
生成给定字符串的base64编码。 base64_decode
解码一个base64编码的字符串。
例如:
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
评估
base54 : dGVzdA==
test : test
可选的第二个参数可用于指定最大线长度。在这种情况下,结果将是多行字符串。
(( hash(string) ))
函数hash
为给定的字符串生成几种哈希。默认情况下,生成了sha256
哈希。可选的第二个参数指定哈希类型。可能的类型是md4
, md5
, sha1
, sha224
, sha256
, sha384
, sha2512
, sha512/224
或sha512/256
。
md5
哈希仍然可以由不弃用的Finctio md5(string)
生成。
例如:
data : alice
hash :
deprecated : (( md5(data) ))
md4 : (( hash(data,"md4") ))
md5 : (( hash(data,"md5") ))
sha1 : (( hash(data,"sha1") ))
sha224 : (( hash(data,"sha224") ))
sha256 : (( hash(data,"sha256") ))
sha384 : (( hash(data,"sha384") ))
sha512 : (( hash(data,"sha512") ))
sha512_224 : (( hash(data,"sha512/224") ))
sha512_256 : (( hash(data,"sha512/256") ))
评估
data : alice
hash :
deprecated : 6384e2b2184bcbf58eccf10ca7a6563c
md4 : 616c69636531d6cfe0d16ae931b73c59d7e0c089c0
md5 : 6384e2b2184bcbf58eccf10ca7a6563c
sha1 : 522b276a356bdf39013dfabea2cd43e141ecc9e8
sha224 : 38b7e5d5651aaf85694a7a7c6d5db1275af86a6df93a36b8a4a2e771
sha256 : 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90
sha384 : 96a5353e625adc003a01bdcd9b21b21189bdd9806851829f45b81d3dfc6721ee21f6e0e98c4dd63bc559f66c7a74233a
sha512 : 408b27d3097eea5a46bf2ab6433a7234a33d5e49957b13ec7acc2ca08e1a13c75272c90c8d3385d47ede5420a7a9623aad817d9f8a70bd100a0acea7400daa59
sha512_224 : c3b8cfaa37ae15922adf3d21606e3a9836ba2a9d7838b040b7c96fd7
sha512_256 : ad0a339b08dc090fe3b16eae376f7e162836e8728da9c45466842e19508d7627
(( bcrypt("password", 10) ))
该功能bcrypt
使用指定的成本系数(如果丢失,默认为10)为给定的字符串生成BCRypt密码哈希。
例如:
hash : (( bcrypt("password", 10) ))
评估
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
bcrypt_check
函数对给定的bcrypt哈希验证了密码。
例如:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
评估
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
该函数md5crypt
为给定的字符串生成Apache MD5加密密码哈希。
例如:
hash : (( md5crypt("password") ))
评估
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
功能md5crypt_check
验证了给定的Apache MD5加密哈希的密码。
例如:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
评估
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
此功能可用于将加密的秘密存储在Spiff Yaml文件中。然后,处理的结果将包含解密的值。所有节点类型都可以加密和解密,包括完整的地图和列表。
解密的密码可以作为第二个参数给出,也可以(首选)可以由环境变量SPIFF_ENCRYPTION_KEY
指定。
可选的最后一个参数可以选择加密方法。到目前为止,唯一支持的方法是3DES
。通过使用SPIFF库提供的加密方法注册,可以为专用SPIFF版本添加其他方法。
可以使用encrypt("secret")
函数来加密值。
例如:
password : this a very secret secret and may never be exposed to unauthorized people
encrypted : (( encrypt("spiff is a cool tool", password) ))
decrypted : (( decrypt(encrypted, password) ))
评估到类似
decrypted : spiff is a cool tool
encrypted : d889f9e4cc7ae13effcbc8bb8cd0c38d1fb2197738444f753c48796d7946083e6639e5a1bf8f77648f2a1ddf37023c65ff57d52d0519d1d92cbcf87d3e263cba
password : this a very secret secret and may never be exposed to unauthorized people
(( rand("[:alnum:]", 10) ))
函数rand
生成随机值。第一个参数决定请求哪种值。没有参数,它会在int64
范围内产生正随机数。
参数类型 | 结果 |
---|---|
整数 | 负n的范围内的整数值[0, n )和负n的( n ,0 ] |
布尔值 | 布尔值 |
细绳 | 一个符文字符串,其中符文在给定的字符范围内,可以使用字符类或字符的任何组合范围可用于Regexp。如果指定了额外的长度参数,则结果字符串将具有给定的长度。 |
例如:
int : (( rand() ))
int10 : (( rand(10) ))
neg10 : (( rand(-10) ))
bool : (( rand(true) ))
string : (( rand("[:alpha:][:digit:]-", 10) ))
upper : (( rand("A-Z", 10) ))
punct : (( rand("[:punct:]", 10) ))
alnum : (( rand("[:alnum:]", 10) ))
评估
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
函数type
产生的字符串表示给定表达式的类型。
例如:
template :
<< : (( &template ))
types :
- int : (( type(1) ))
- float : (( type(1.0) ))
- bool : (( type(true) ))
- string : (( type("foobar") ))
- list : (( type([]) ))
- map : (( type({}) ))
- lambda : (( type(|x|->x) ))
- template : (( type(.template) ))
- nil : (( type(~) ))
- undef : (( type(~~) ))
评估类型
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
函数defined
检查是否可以成功评估表达式。如果可以评估该表达式,则会产生布尔值true
,否则为false
。
例如:
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
评估
zero : 0
div_ok : false
zero_def : true
null_def : false
该函数可用于结合条件运算符,以根据另一个表达式的可分辨性评估表达式。
(( valid(foobar) ))
该函数valid
检查是否可以成功地评估表达式并评估为定义的值,而不nil
。如果可以评估该表达式,则会产生布尔值true
,否则为false
。
例如:
zero : 0
empty :
map : {}
list : []
div_ok : (( valid(1 / zero ) ))
zero_def : (( valid( zero ) ))
null_def : (( valid( ~ ) ))
empty_def : (( valid( empty ) ))
map_def : (( valid( map ) ))
list_def : (( valid( list ) ))
评估
zero : 0
empty : null
map : {}
list : []
div_ok : false
zero_def : true
null_def : false
empty_def : false
map_def : true
list_def : true
(( require(foobar) ))
如果给定参数不确定或nil
,则功能require
产生错误,否则会产生给定值。
例如:
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
评估
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
该函数stub
会产生在定义它的第一个上游存根中发现的专用字段的值。
例如:
模板.yml
value : (( stub(foo.bar) ))
与存根合并
stub.yml
foo :
bar : foobar
评估
value : foobar
传递给此函数的参数必须是评估引用的字符串或表示参考路径元素列表的字符串列表的字符串。如果没有给出参数或未定义( ~~
),则使用实际的场路径。
请注意,给定的唯一参考将不会被评估为表达式,如果使用其值,则必须将其转换为表达式,例如,以表示列表表达式(ref)
或[] ref
。
或者,可以使用merge
操作,例如merge foo.bar
。不同之处在于stub
不合并,因此该字段仍将合并(文档中的原始路径)。
(( tagdef("tag", value) ))
函数tagdef
可用于定义动态标签(请参阅标签)。与标记标记相反,此功能允许通过表达式指定标签名称及其预期值。因此,它可以用于组合诸如map
或sum
的元素中,以创建具有计算值的动态标签。
可选的第三个参数可用于指定预期的范围( local
或global
)。默认情况下,创建了本地标签。本地标签仅在实际处理级别(模板或子)上可见,而一旦定义的全局标签可以在所有进一步的处理级别(存根或模板)中使用。
或者,可以将标签名称带有启动( *
)以声明全局标签。
指定的标签值将用作函数的结果。
例如:
模板.yml
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
评估
value : 25
alice : 25
(( eval(foo "." bar ) ))
再次评估字符串表达式作为动态表达式的评估结果。例如,这可以用于实现间接。
例如:中的表达
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
计算通往一个磁场的路径,然后再次对其进行评估以产生该组成场的值:
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
读取名称为动态表达式的环境变量的值。如果未设置环境变量,则评估失败。
第二个口味函数env
接受多个参数和/或列表参数,这些参数与单个列表连接在一起。此列表中的每个条目都用作环境变量的名称,该函数的结果是给定变量作为YAML元素的地图。因此,省略了不存在的环境变量。
(( parse(yamlorjson) ))
解析YAML或JSON字符串,并将内容返回为YAML值。因此,它可用于进一步的动态评估。
例如:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
产生值25
的现场result
。
该函数parse
支持可选的第二个参数,即解析模式。在这里,与读取功能相同的模式是可能的。默认解析模式是import
,内容只是解析,并且在此步骤中没有进一步的评估。
(( asjson(expr) ))
此函数将其参数给出的YAML值转换为JSON字符串。相应的函数asyaml
作为YAML文档字符串产生YAML值。
例如:
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
决心
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
此功能执行表达式并产生一些评估信息图。即使表达失败,它也总是成功。该地图包括以下字段:
姓名 | 类型 | 意义 |
---|---|---|
valid | 布尔值 | 表达是有效的 |
error | 细绳 | 评估的错误消息文本 |
value | 任何 | 表达的价值,如果评估成功 |
例如:
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
决心
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
为工作生成静态IP的列表。
例如:
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
这将从mynetwork
的子网中创建3个IP,并返回两个条目,因为只有两个实例。这两个条目将是网络定义的静态IP范围的0和第三个输入。
例如,给定文件bye.yml :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
和文件hi.yml :
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
spiff merge bye.yml hi.yml
回报
jobs :
- instances : 3
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
- 10.60.3.70
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
。
如果bye.yml是
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
而是返回
jobs :
- instances : 2
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
static_ips
还接受列表参数,只要所有透明包含的元素都是列表还是整数值。这允许缩写IPS的列表如下:
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
尽管出于历史原因的函数static_ips依赖于BOSH清单的结构,并且仅在清单中的专用位置起作用,但函数IPSET纯粹基于其参数提供了类似的计算。因此,可用的IP范围和所需的IP数量作为参数传递。
第一个(范围)参数可以是简单字符串或字符串列表的单个范围。每个字符串可能是
第二个参数指定结果集中请求的IP地址数量。
附加参数指定了给定范围内选择(从0开始)的IP的索引。在这里再次使用索引列表。
例如:
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
将IPSET解析到[ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
。
如果未指定IP索引(只有两个参数),则选择IPS从第一范围的开始到最后给定范围的末尾,而无需间接。
(( list_to_map(list, "key") ))
带有明确名称/键字段的地图条目列表将映射到带有专用键的地图。默认情况下,使用键字段name
,可以通过可选的第二个参数更改。列表中的明确表示的密钥字段也将考虑在内。
例如:
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
将被映射到
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
结合模板和lambda表达式结合使用,这可以用于生成具有任意命名键值的映射,尽管不允许键值动态表达式。
(( makemap(fieldlist) ))
在这个风味中, makemap
创建了一个地图,其条目由给定的字段列表描述。该列表有望包含带有条目key
和value
地图,描述了专用的地图条目。
例如:
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
产量
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
如果钥匙值是布尔值或整数,则将映射到字符串。
(( makemap(key, value) ))
在这个风味中, makemap
创建了一个地图,其中带有给定参数对描述的条目。参数可能是一个键/值对的序列(由单独的参数给出)。
例如:
map : (( makemap("peter", 23, "paul", 22) ))
产量
map :
paul : 22
peter : 23
与以前的makemap
风味相反,该味道也可以通过地图文字来处理。
(( merge(map1, map2) ))
除了关键字merge
以外,还有一个称为merge
函数(必须始终遵循开头支架)。它可用于合并从类似于存根合并过程的实际文档中获取的隔板图。如果图通过参考表达式指定,它们将无法包含任何动态表达式,因为在评估参数之前,它们始终在实际文档的上下文中进行评估。
例如:
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
解决result
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
另外,可以传递映射模板(无需评估操作员!)。在这种情况下,在将给定文档合并时,评估了模板中的动态表达式,以定期使用SPIFF合并。
例如:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
解决result
result :
alice : 26
bob : 26
地图也可以通过地图表达式给出。在这里可以使用常规语法指定动态表达式:
例如:
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
解决result
result :
alice : 26
bob : 100
而不是多个参数可以给出单个列表参数。该列表必须包含要合并的地图。
嵌套合并可以访问所有外部绑定。相对引用首先在实际文档中搜索。如果找不到它们,则所有外部绑定都用于查找从内部结合到外部结合的参考。另外,上下文( __ctx
)提供了一个字段OUTER
,这是嵌套合并的所有外部文档的列表,可用于查找绝对引用。
例如:
data :
alice :
age : 24
template :
<< : (( &template ))
bob : 25
outer1 : (( __ctx.OUTER.[0].data )) # absolute access to outer context
outer2 : (( data.alice.age )) # relative access to outer binding
sum : (( .bob + .outer2 ))
merged : (( merge(template) ))
解决merged
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
该函数intersect
相交的多个列表。列表可以包含任何类型的条目。
例如:
list1 :
- - a
- - b
- a
- b
- { a: b }
- { b: c }
- 0
- 1
- " 0 "
- " 1 "
list2 :
- - a
- - c
- a
- c
- { a: b }
- { b: b }
- 0
- 2
- " 0 "
- " 2 "
intersect : (( intersect(list1, list2) ))
解决intersect
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
该功能reverse
逆转列表的顺序。该列表可以包含任何类型的条目。
例如:
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
reverse
解决
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
该函数validate
使用一组验证器验证表达式。第一个论点是要验证的价值,所有其他参数都是必须成功接受该值的验证者。如果至少一个验证器失败,就会生成适当的错误消息,以解释失败原因。
验证器用字符串或包含验证器类型为字符串及其参数的列表表示。验证者可以用预先否定!
以其名字。
可用以下验证器:
类型 | 论据 | 意义 |
---|---|---|
empty | 没有任何 | 空列表,地图或字符串 |
dnsdomain | 没有任何 | DNS域名 |
wildcarddnsdomain | 没有任何 | 通配符DNS域名 |
dnslabel | 没有任何 | DNS标签 |
dnsname | 没有任何 | DNS域或通配符域 |
ip | 没有任何 | IP地址 |
cidr | 没有任何 | 西德 |
publickey | 没有任何 | PEM格式的公钥 |
privatekey | 没有任何 | PEM格式的私钥 |
certificate | 没有任何 | PEM格式证书 |
ca | 没有任何 | CA证书 |
semver | 约束的可选列表 | 验证SEMVER版本针对约束 |
type | 接受类型键的列表 | 至少一种类型的密钥必须匹配 |
valueset | 列出值的参数 | 可能的值 |
value 或= | 价值 | 检查专用值 |
gt 或> | 价值 | 大于(数字/字符串) |
lt 或< | 价值 | 小于(数字/字符串) |
ge 或>= | 价值 | 更大或等于(数字/字符串) |
le 或<= | 价值 | 较少或等于(数字/字符串) |
match 或~= | 正则表达式 | 弦值匹配正则表达式 |
list | 输入验证器的可选列表 | 是列表和条目匹配给定验证者 |
map | [[<键验证器>,] <输入验证器>] | 是地图和键和条目匹配给定验证器 |
mapfield | <字段名称> [,<验证器>] | 要求进入地图 |
optionalfield | <字段名称> [,<验证器>] | 可选进入地图 |
and | 验证者列表 | 所有验证者都必须成功 |
or | 验证者列表 | 至少一个验证者必须成功 |
not 或! | 验证者 | 否定验证者参数 |
如果验证成功,则返回值。
例如:
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
评估
dnstarget : 192.168.42.42
如果验证未能通过错误解释失败原因的错误。
例如:
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
产生以下错误:
*condition 1 failed: (is no ip address: alice+bob and is no dns domain: [a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')])
验证器也可能是lambda表达式,至少有一个论点并返回一个布尔值。这样,可以作为YAML文档的一部分提供自己的验证器。
例如:
val : (( validate( 0, |x|-> x > 1 ) ))
如果声明了多个参数,则必须将其他参数指定为验证者参数。第一个参数始终是要检查的值。
例如:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
Lambda功能也可以返回带有1、2或3个元素的列表。这可用于提供适当的消息。
指数 | 意义 |
---|---|
0 | 第一个索引始终是匹配结果,必须评估为布尔 |
1 | 如果给出了两个元素,则第二个索引是描述实际结果的消息 |
2 | 这里索引1删除成功消息和2失败消息 |
例如:
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
只是提到,可以在上面的示例中显示验证器规范,但也可以作为参考表达式。 not
, and
or
验证者接受深度嵌套的验证器规范。
例如:
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
该功能check
可用于将YAML结构与基于YAML的值检查器匹配。因此,可以使用已经描述的相同检查描述。呼叫的结果是指示匹配结果的布尔值。如果支票失败,则不会失败。
(( error("message") ))
该功能error
可用于带有专用消息的显式评估失败。
例如,可以使用这将复杂的处理错误通过将错误函数附加为潜在失败的pers expression的默认值来减少复杂的处理错误。
例如:
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
另一种情况可能是通过将错误表达式使用为(默认)值(默认值)值省略了在上游存根中定义的字段的描述性消息。
动态支持各种数学功能:
返回整数: ceil
, floor
, round
和roundtoeven
返回的浮子或整数: abs
返回的浮子: sin
, cos
, sinh
, cosh
, asin
, acos
, asinh
, acosh
, sqrt
, exp
, log
, log10
,
Dynaml通过适当的功能支持integer
, float
, bool
和string
值之间的各种类型转换。
例如:
value : (( integer("5") ))
将字符串转换为整数值。
将整数转换为字符串接受可选的附加整数参数以指定转换基础,例如string(55,2)
将导致"110111"
。默认基础为10。基数必须在2到36之间。
SPIFF支持在模板和子文件之外访问内容。可以读取文件,执行命令和管道。所有这些功能都以两种口味存在。
sync
函数,则旨在将模板处理与专用状态(由外部内容提供)同步。在这里,缓存操作将无用,因此有第二个未缓解的风味。每个功能都可以使用后缀_uncached
(例如read_uncached()
) (( read("file.yml") ))
阅读文件并返回其内容。有三种内容类型的支持: yaml
文件, text
文件和binary
文件。在二进制模式下读取将导致基本64编码的多行字符串。
如果文件后缀为.yml
, .yaml
或.json
,则默认情况下使用YAML类型。如果应该将文件读取为text
,则必须明确指定此类型。在所有其他情况下,默认值是text
,因此读取二进制文件(例如存档)急需指定binary
模式。
可选的第二个参数可用于明确指定所需的返回类型: yaml
或text
。对于YAML文档,支持了一些添加类型: multiyaml
, template
importmulti
templates
, import
。
将解析YAML文档,并返回树。可以通过常规动态表达式访问树的元素。
另外,YAML文件可能再次包含动态表达式。所有包含的动态表达式将在阅读表达式的背景下进行评估。这意味着,YAML文档中不同位置包含的相同文件可能会导致不同的子树,具体取决于使用的动态表达式。
如果可以阅读多文件yaml。如果给出类型的multiyaml
,则返回带有YAML文档根节点的列表节点。
YAML或JSON文档也可以通过指定类型template
来读取为模板。在这里,结果将是模板值,可以像常规内联模板一样使用。如果指定了templates
,则将多文件映射到模板列表。
如果将读取类型设置为import
,则将文件内容读取为YAML文档,并且使用根节点替换表达式。文档中包含的潜在动态表达式将不会通过表达式与读取呼叫的实际绑定进行评估,而是原始文件的一部分。因此,只有在没有进一步的读取结果处理或未加工的值进行处理的情况下,才能使用此模式。
可以将其与链式参考(用于审查(( read(...).selection ))
)一起粉碎导入文档的专用片段。然后,仅针对选定部分进行评估。其他部分中的表达式和参考文献没有评估,根本无法导致错误。
例如:
template.yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
import.yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
不会失败,因为second
部分从未得到评估。
该模式应谨慎采用,因为它通常会导致意外的结果。
读取类型importmulti
可用于导入多文件YAML文件作为节点列表。
文本文档将作为单字符串返回。
也可以阅读二进制文档。该内容不能直接用作字符串(或YAML文档)。因此,必须指定读取模式binary
。内容作为base64编码的多行字符串值返回。
(( exec("command", arg1, arg2) ))
执行命令。参数可以是任何动态表达式,包括评估列表或地图的参考表达式。列表或地图作为包含具有给定片段的YAML文档的单个参数传递。
结果是通过解析命令的标准输出来确定的。它可能是YAML文档或单个多行字符串或整数值。 YAML文档应从文档前缀开始---
。如果命令失败,则表达式被处理为未定义。
例如
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
产量
arg :
- a
- b
list :
- a
- b
string : a
另外,可以用单个列表参数完全描述命令行的exec
。
即使在多个表达式中使用了同一命令,也只能执行一次。
(( pipe(data, "command", arg1, arg2) ))
执行命令并使用专用数据馈送其标准输入。命令参数必须是字符串。该命令的论点可以是任何动态表达式,包括评估列表或地图的参考表达式。列表或地图作为包含具有给定片段的YAML文档的单个参数传递。
输入流是从给定数据生成的。如果这是一个简单的类型,则使用其字符串表示。否则,将从输入数据生成YAML文档。结果是通过解析命令的标准输出来确定的。它可能是YAML文档或单个多行字符串或整数值。 YAML文档应从文档前缀开始---
。如果命令失败,则表达式被处理为未定义。
例如
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
产量
arg :
- a
- b
list :
- z
- b
另外,可以使用数据调用pipe
,并完全描述命令行的列表参数。
即使在多个表达式中使用了同一命令,也只能执行一次。
(( write("file.yml", data) ))
写一个文件并返回其内容。如果可以将结果解析为YAML文档,则将返回文档。可选的第三参数可用于传递写入选项。选项参数可能是表示文件权限的整数(默认为0644
)或带有选项的逗号分隔字符串。支持的选项是
binary
:数据是在写作之前解码的基础640
指示八处值。 (( tempfile("file.yml", data) ))
编写AA临时文件并返回其路径名。可选的第三参数可用于传递写入选项。它基本上的行为就像write
注意:仅在合并处理过程中存在临时文件。之后将删除。
例如,可以使用它为exec
函数提供临时文件参数。
(( lookup_file("file.yml", list) ))
查找文件是目录列表。结果是现有文件的列表。使用lookup_dir
,可以查找目录。
如果找不到现有文件,则返回空列表。
可以传递多个列表或字符串参数来构成搜索路径。
(( mkdir("dir", 0755) ))
如果尚不存在,创建一个目录及其所有中间目录。
权限部分是可选的(默认为0755)。目录的路径可以由Artring之类的值或作为路径组件的列表给出。
(( list_files(".") ))
列出目录中的文件。结果是现有文件的列表。使用list_dirs
可以列出目录。
(( archive(files, "tar") ))
创建一个给定类型的存档(默认为tar
),其中包含列出的文件。结果是基本64编码的存档。
支持的档案类型是tar
和targz
。
files
可能是文件条目的列表或地图。在映射的情况下,地图密钥用作文件路径的默认值。文件条目是具有以下字段的地图:
场地 | 类型 | 意义 |
---|---|---|
path | 细绳 | 可选的地图,档案中的文件路径,由地图密钥默认 |
mode | int或int string | 文件模式或写入选项。它基本上的行为就像write 的选项参数一样。 |
data | 任何 | 文件内容,YAML将作为YAML文档编组。如果mode 表示二进制模式,则将string值base64解码。 |
base64 | 细绳 | BASE64编码二进制数据 |
例如:
yaml :
alice : 26
bob : 27
files :
" data/a/test.yaml " :
data : (( yaml ))
" data/b/README.md " :
data : |+
### Test Docu
**Note**: This is a test
archive : (( archive(files,"targz") ))
content : (( split("n", exec_uncached("tar", "-tvf", tempfile(archive,"binary"))) ))
产量:
archive : |-
H4sIAAAAAAAA/+zVsQqDMBAG4Mx5igO3gHqJSQS3go7tUHyBqIEKitDEoW9f
dLRDh6KlJd/yb8ll+HOd8SY1qbfOJw8zDmQHiIhayjURcZuIQhOeSZlphVwL
glwsAXvM8mJ23twJ4qfnbB/3I+I4pmboW1uA0LSZmgJETr89VXCUtf9Neq1O
5blKxm6PO972X/FN/7nKVej/EaIogto6D+XUzpQydpm8ZayA+tY76B0YWHYD
DV9CEATBf3kGAAD//5NlAmIADAAA
content :
- -rw-r--r-- 0/0 22 2019-03-18 09:01 data/a/test.yaml
- -rw-r--r-- 0/0 41 2019-03-18 09:01 data/b/README.md
files :
data/a/test.yaml :
data :
alice : 26
bob : 27
data/b/README.md :
data : |+
### Test Docu
**Note**: This is a test
yaml :
alice : 26
bob : 27
Spiff支持处理语义版本名称。它支持来自MasterMinds SEMVER软件包接受或不带有领先v
版本的所有功能。
(( semver("v1.2-beta.1") ))
检查给定的字符串是否为语义版本,并返回其归一化表单(没有引导v
,并使用Major,Minor和Patch版本编号完整释放部分)。
例如:
normalized : (( semver("v1.2-beta.1") ))
决心
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
返回语义版本的发行部分,以省略元数据和预发行信息。
例如:
release : (( semverrelease("v1.2.3-beta.1") ))
决心
release : v1.2.3
如果给出了其他字符串参数,此功能将通过保留元数据和预发行信息的给定语义版本的发布来代替发布。
例如:
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
决心
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
确定给定语义版本的主要版本编号。结果是整数。
例如:
major : (( semvermajor("1.2.3-beta.1") ))
决心
major : 1
函数semverincmajor
可用于增加主要版本编号并重置次要版本,补丁版和发行后缀。
例如:
new : (( semverincmajor("1.2.3-beta.1") ))
决心
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
确定给定语义版本的次要版本编号。结果是整数。
例如:
minor : (( semverminor("1.2.3-beta.1") ))
决心
minor : 2
函数semverincminor
可用于增加次要版本编号并重置补丁版本并发布后缀。
例如:
new : (( semverincmajor("v1.2.3-beta.1") ))
决心
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
确定给定语义版本的补丁版本。结果是整数。
例如:
patch : (( semverpatch("1.2.3-beta.1") ))
决心
patch : 3
函数semverincpatch
可用于递增补丁版本号或重置版本后缀。如果有rleas后缀,则将其删除,并且发布信息保持不变,否则补丁版本编号会增加。
例如:
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
决心
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
确定给定语义版本的预读。结果是一个字符串。
例如:
prerelease : (( semverprerelease("1.2.3-beta.1") ))
决心
prerelease : beta.1
如果给出了其他字符串参数,则此功能集,替换或清除(如果设置为空字符串)
例如:
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
决心
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
确定给定语义版本的元数据。结果是一个字符串。
例如:
metadata : (( semvermetadata("1.2.3+demo") ))
决心
metadata : demo
如果给出了其他字符串参数,则此功能集,替换或清除(如果设置为空字符串)元数据。
例如:
new : (( semvermetadata("1.2.3-test", "demo) ))
决心
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
比较两个语义版本。预发行总是比最终版本小。结果是一个具有以下值的整数:
结果 | 意义 |
---|---|
-1 | 第一个版本是在第二版之前 |
0 | 这两个版本均等 |
1 | 第一个versuon是第二个 |
例如:
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
决心
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
将给定语义版本与列表匹配。结果是布尔人。可以指定任何数量的版本约束。如果没有给出任何约束,则该功能只需检查给定的字符串是否是语义版本。
例如:
match : (( semvermatch("1.2.3", "~1.2") ))
决心
match : true
可以在此处找到可能的约束规范的完整列表。
(( semversort("1.2.3", "1.2.1") ))
按升序排序版本的列表。保留了领先的v
例如:
sorted : (( semversort("1.2.3", "1.2.1") ))
决心
sorted :
- 1.2.1
- 1.2.3
也可以使用单个列表参数指定要排序的版本列表。
SPIFF支持一些有用的功能,以使用X509证书和钥匙。请参考有用的了解部分,以找到一些提供状态的提示。
(( x509genkey(spec) ))
可以使用此功能生成私有RSA或ECDSA密钥。结果将是一个PEM编码的密钥作为多线字符串值。如果给出的密钥大小(整数或字符串)作为参数,则将使用给定的密钥大小(例如2048)生成RSA键。给定一个字符串值之一
该功能将生成适当的ECDA密钥。
例如:
keys :
key : (( x509genkey(2048) ))
解决类似的事情
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
对于以PEM格式的给定密钥或证书(例如,使用X509GENKEY函数生成),此功能将提取公共密钥,并以PEM格式再次将其作为多行字符串返回。
例如:
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
解决类似的事情
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
public : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rnsCxMx
vSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb325Bf/
...
VzYqyeQyvvRbNe73BXc5temCaQayzsbghkoWK+Wrc33yLsvpeVQBcB93Xhus+Lt1
1lxsoIrQf/HBsiu/5Q3M8L6klxeAUcDbYwIDAQAB
-----END RSA PUBLIC KEY-----
要生成SSH公共密钥,可以将可选的附加格式参数设置为ssh
。结果将是可用于SSH的常规公钥格式。默认格式为pem
提供上面显示的PEM输出格式。
默认情况下,RSA键在PEM中以PKCS#1格式( RSA PUBLIC KEY
)进行编组。如果需要通用PKIX格式( PUBLIC KEY
),则必须给出格式参数pkix
。
使用格式ssh
此功能也可以用于将PEM格式的公钥转换为SSH键,
(( x509cert(spec) ))
该功能x509cert
会创建本地签名的证书,或者由给定CA签名的自身签名或证书。它将PEM编码证书作为多行字符串值返回。
单个规格参数获取一个可选和非可选字段的地图,用于指定证书信息。它可以是内联映射表达式,也可以是YAML文档其余部分的任何地图引用。
观察到以下地图字段:
字段名称 | 类型 | 必需的 | 意义 |
---|---|---|---|
commonName | 细绳 | 选修的 | 主题的通用字段 |
organization | 字符串或字符串列表 | 选修的 | 主题的组织领域 |
country | 字符串或字符串列表 | 选修的 | 该主题的国家领域 |
isCA | 布尔值 | 选修的 | CA证书选项 |
usage | 字符串或字符串列表 | 必需的 | 证书的使用键(见下文) |
validity | 整数 | 选修的 | 小时的有效间隔 |
validFrom | 细绳 | 选修的 | 以“ 2019年1月1日1:22:31”格式开始时间 |
hosts | 字符串或字符串列表 | 选修的 | DNS名称或IP地址列表 |
privateKey | 细绳 | 必需或公共关键 | 生成证书的私钥 |
publicKey | 细绳 | 必需或私人关键 | 生成证书的公钥 |
caCert | 细绳 | 选修的 | 签名的证书 |
caPrivateKey | 细绳 | 选修的 | priavte caCert 的钥匙 |
对于自我签名的证书,必须设置privateKey
字段。应省略publicKey
和ca
领域。如果给出了caCert
字段,也需要caKey
。如果将privateKey
段与caCert
一起提供,则从私钥中提取证书的公钥。
其他字段被默默地忽略。
支持以下用法键(忽略了情况):
钥匙 | 意义 |
---|---|
Signature | x509 |
Commitment | X509 |
KeyEncipherment | X509 |
DataEncipherment | X509 |
KeyAgreement | x509 |
CertSign | x509.KeyusageCertsign |
CRLSign | x509.keyusagecrlsign |
EncipherOnly | x509.keyusageencipheronly |
DecipherOnly | x509 |
Any | X5099 |
ServerAuth | x509.extkeyusageserverauth |
ClientAuth | x509.extkeyusageclientauth |
codesigning | x509.extKeyUsageCodesigning |
EmailProtection | x509.extkeyusageemailailprotection |
IPSecEndSystem | X5099 |
IPSecTunnel | X5099 |
IPSecUser | x509.extkeyusageipsecuser |
TimeStamping | X5099 |
OCSPSigning | x509.extkeyusageocspsigning |
MicrosoftServerGatedCrypto | x509.extKeyusagemicrosoftServergatedCrypto |
NetscapeServerGatedCrypto | x509.extKeyUsagenetScapeservergatedCrypto |
MicrosoftCommercialCodeSigning | X5099 |
MicrosoftKernelCodeSigning | x509.extKeyusagemicrosoftkernelCodesigning |
例如:
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
生成自签名的根证书,然后解决类似
cakey : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
cacert : |+
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIQb5ex4iGfyCcOa1RvnKSkMDANBgkqhkiG9w0BAQsFADAk
MQ8wDQYDVQQKEwZTQVAgU0UxETAPBgNVBAMTCGdhcmRlbmVyMB4XDTE4MTIzMTE0
...
pOUBE3Tgim5rnpa9K9RJ/m8IVqlupcONlxQmP3cCXm/lBEREjODPRNhU11DJwDdJ
5fd+t5SMEit2BvtTNFXLAwz48EKTxsDPdnHgiQKcbIV8NmgUNPHwXaqRMBLqssKl
Cyvds9xGtAtmZRvYNI0=
-----END CERTIFICATE-----
(( x509parsecert(cert) ))
此功能解析了以PEM格式给出的证书,并返回字段地图:
字段名称 | 类型 | 必需的 | 意义 |
---|---|---|---|
commonName | 细绳 | 选修的 | 主题的通用字段 |
organization | 字符串列表 | 选修的 | 主题的组织领域 |
country | 字符串列表 | 选修的 | 该主题的国家领域 |
isCA | 布尔值 | 总是 | CA证书选项 |
usage | 字符串列表 | 总是 | 证书的使用键(见下文) |
validity | 整数 | 总是 | 小时的有效间隔 |
validFrom | 细绳 | 总是 | 以“ 2019年1月1日1:22:31”格式开始时间 |
validUntil | 细绳 | 总是 | 以“ 2019年1月1日1:22:31”格式开始时间 |
hosts | 字符串列表 | 选修的 | DNS名称或IP地址列表 |
dnsNames | 字符串列表 | 选修的 | DNS名称列表 |
ipAddresses | 字符串列表 | 选修的 | IP地址列表 |
publicKey | 细绳 | 总是 | 生成证书的公钥 |
例如:
data :
<< : (( &temporary ))
spec :
commonName : test
organization : org
validity : 100
isCA : true
privateKey : (( gen.key ))
hosts :
- localhost
- 127.0.0.1
usage :
- ServerAuth
- ClientAuth
- CertSign
gen :
key : (( x509genkey() ))
cert : (( x509cert(spec) ))
cert : (( x509parsecert(data.gen.cert) ))
决心
cert :
commonName : test
dnsNames :
- localhost
hosts :
- 127.0.0.1
- localhost
ipAddresses :
- 127.0.0.1
isCA : true
organization :
- org
publickey : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+UIZQUTa/j+WlXC394bccBTltV+Ig3+zB1l4T6Vo36pMBmU4JIkJ
...
TCsrEC5ey0cCeFij2FijOJ5kmm4cK8jpkkb6fLeQhFEt1qf+QqgBw3targ3LnZQf
uE9t5MIR2X9ycCQSDNBxcuafHSwFrVuy7wIDAQAB
-----END RSA PUBLIC KEY-----
usage :
- CertSign
- ServerAuth
- ClientAuth
validFrom : Mar 11 15:34:36 2019
validUntil : Mar 15 19:34:36 2019
validity : 99 # yepp, that's right, there has already time passed since the creation
Spiff支持一些有用的功能,可与Wireguard键一起使用。请参考有用的了解部分,以找到一些提供状态的提示。
(( wggenkey() ))
可以使用此功能生成私人线芯键。结果将基础64编码。
例如:
keys :
key : (( wggenkey() ))
解决类似的事情
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
(( wgpublickey(key) ))
对于给定的键(例如,使用Wggenkey函数生成)此函数提取公共密钥,并以Base64格式再次返回它
例如:
keys :
key : (( wggenkey() ))
public : (( wgpublickey(key)
解决类似的事情
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
public : n405KfwLpfByhU9pOu0A/ENwp0njcEmmQQJvfYHHQ2M=
(( lambda |x|->x ":" port ))
Lambda表达式可用于定义其他匿名功能。 They can be assigned to yaml nodes as values and referenced with path expressions to call the function with approriate arguments in other dynaml expressions. For the final document they are mapped to string values.
There are two forms of lambda expressions.尽管
lvalue : (( lambda |x|->x ":" port ))
yields a function taking one argument by directly taking the elements from the dynaml expression,
string : " |x|->x " : " port "
lvalue : (( lambda string ))
evaluates the result of an expression to a function. The expression must evaluate to a function or string. If the expression is evaluated to a string it parses the function from the string.
Since the evaluation result of a lambda expression is a regular value, it can also be passed as argument to function calls and merged as value along stub processing.
一个完整的示例可能如下所示:
lvalue : (( lambda |x,y|->x + y ))
mod : (( lambda|x,y,m|->(lambda m)(x, y) + 3 ))
value : (( .mod(1,2, lvalue) ))
产量
lvalue : lambda |x,y|->x + y
mod : lambda|x,y,m|->(lambda m)(x, y) + 3
value : 6
If a complete expression is a lambda expression the keyword lambda
can be omitted.
Lambda expressions evaluate to lambda values, that are used as final values in yaml documents processed by spiff .
Note : If the final document still contains lambda values, they are transferred to a textual representation. It is not guaranteed that this representation can correctly be parsed again, if the document is re-processed by spiff . Especially for complex scoped and curried functions this is not possible.
Therefore function nodes should always be temporary or local to be available during processing or merging, but being omitted for the final document.
A typical function call uses positional arguments. Here the given arguments satisfy the declared function parameters in the given order. For lambda values it is also possible to use named arguments in the call expression. Here an argument is assigned to a dedicated parameter as declared by the lambda expression. The order of named arguments can be arbitrarily chosen.
例如:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, b=2, a=1) ))
It is also posible to combine named with positional arguments. Hereby the positional arguments must follow the named ones.
例如:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, 1, 2) ))
The same argument MUST NOT be satified by both, a named and a positional argument.
Instead of using the parameter name it is also possible to use the parameter index, instead.
例如:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(3=1, 1) ))
As such, this feature seems to be quite useless, but it shows its power if combined with optional parameters or currying as shown in the next paragraphs.
A lambda expression might refer to absolute or relative nodes of the actual yaml document of the call. Relative references are evaluated in the context of the function call.所以
lvalue : (( lambda |x,y|->x + y + offset ))
offset : 0
values :
offset : 3
value : (( .lvalue(1,2) ))
yields 6
for values.value
.
Besides the specified parameters, there is an implicit name ( _
), that can be used to refer to the function itself. It can be used to define self recursive function. Together with the logical and conditional operators a fibunacci function can be defined:
fibonacci : (( lambda |x|-> x <= 0 ? 0 :x == 1 ? 1 :_(x - 2) + _( x - 1 ) ))
value : (( .fibonacci(5) ))
yields the value 8
for the value
property.
By default reference expressions in a lambda expression are evaluated in the static scope of the lambda dedinition followed by the static yaml scope of the caller. Absolute references are always evalated in the document scope of the caller.
The name _
can also be used as an anchor to refer to the static definition scope of the lambda expression in the yaml document that was used to define the lambda function. Those references are always interpreted as relative references related to the this static yaml document scope. There is no denotation for accessing the root element of this definition scope.
Relative names can be used to access the static definition scope given inside the dynaml expression (outer scope literals and parameters of outer lambda parameters)
例如:
env :
func : (( |x|->[ x, scope, _, _.scope ] ))
scope : definition
call :
result : (( env.func("arg") ))
scope : call
yields the result
list:
call :
result :
- arg
- call
- (( lambda|x|->[x, scope, _, _.scope] )) # the lambda expression as lambda value
- definition
This also works across multiple stubs. The definition context is the stub the lambda expression is defined in, even if it is used in stubs down the chain. Therefore it is possible to use references in the lambda expression, not visible at the caller location, they carry the static yaml document scope of their definition with them.
Inner lambda expressions remember the local binding of outer lambda expressions. This can be used to return functions based on arguments of the outer function.
例如:
mult : (( lambda |x|-> lambda |y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
yields 6
for property value
.
Trailing parameters may be defaulted in the lambda expression by assigning values in the declaration. Those parameter are then optional, it is not required to specify arguments for those parameters in function calls.
例如:
mult : (( lambda |x,y=2|-> x * y ))
value : (( .mult(3) ))
yields 6
for property value
.
It is possible to default all parameters of a lambda expression. The function can then be called without arguments. There might be no non-defaulted parameters after a defaulted one.
A call with positional arguments may only omit arguments for optional parameters from right to left. If there should be an explicit argument for the right most parameter, arguments for all parameters must be specified or named arguments must be used. Here the desired optional parameter can explicitly be set prior to the regular positional arguments.
例如:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=3, 2) ))
evaluates result
to
result :
a : 2
b : 1
c : 3
The expression for the default does not need to be a constant value or even expression, it might refer to other nodes in the yaml document. The default expression is always evaluated in the scope of the lambda expression declaration at the time the lambda expression is evaluated.
例如:
stub.yaml
default : 2
mult : (( lambda |x,y=default * 2|-> x * y ))
template.yaml
mult : (( merge ))
scope :
default : 3
value : (( .mult(3) ))
evaluates value
to 12
The last parameter in the parameter list of a lambda expression may be a varargs parameter consuming additional argument in a fnction call. This parameter is always a list of values, one entry per additional argument.
A varargs parameter is denoted by a ...
following the last parameter name.
例如:
func : (( |a,b...|-> [a] b ))
result : (( .func(1,2,3) ))
yields the list [1, 2, 3]
for property result
.
If no argument is given for the varargs parameter its value is the empty list.
The ...
operator can also be used for inline list expansion.
If a vararg parameter should be set by a named argument its value must be a list.
Using the currying operator ( *(
) a lambda function may be transformed to another function with less parameters by specifying leading argument values.
The result is a new function taking the missing arguments (currying) and using the original function body with a static binding for the specified parameters.
例如:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult*(2) ))
value : (( .mult2(3) ))
Currying may be combined with defaulted parameters. But the resulting function does not default the leading parameters, it is just a new function with less parameters pinning the specified ones.
If the original function uses a variable argument list, the currying may span any number of the variable argument part, but once at least one such argument is given, the parameter for the variable part is satisfied. It cannot be extended by a function call of the curried function.
例如:
func : (( |a,b...|->join(a,b) ))
func1 : (( .func*(",","a","b")))
# invalid: (( .func1("c") ))
value : (( .func1() ))
evaluates value
to "a,b"
.
It is also possible to use currying for builtin functions, like join
.
例如:
stringlist : (( join*(",") ))
value : (( .stringlist("a", "b") ))
evaluates value
to "a,b"
.
There are several builtin functions acting on unevaluated or unevaluatable arguments, like defined
. For these functions currying is not possible.
Using positional arguments currying is only possible from right to left. But currying can also be done for named arguments. Here any parameter combination, regardless of the position in the parameter list, can be preset. The resulting function then has the unsatisfied parameters in their original order. Switching the parameter order is not possible.
例如:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry : (( .func(c=3, 2) ))
result : (( .curry(5) ))
evalutes result
to
result :
a : 2
b : 5
c : 3
The resulting function keeps the parameter b
. Hereby the default value will be kept. Therefore it can just be called without argument ( .curry()
), which would produce
result :
a : 2
b : 1
c : 3
注意力:
For compatibility reasons currying is also done, if a lambda function without defaulted parameters is called with less arguments than declared parameters.
This behaviour is deprecated and will be removed in the future. It is replaced by the currying operator.
例如:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
evaluates value
to 6.
(( catch[expr|v,e|->v] ))
This expression evaluates an expression ( expr
) and then executes a lambda function with the evaluation state of the expression. It always succeeds, even if the expression fails. The lambda function may take one or two arguments, the first is always the evaluated value (or nil
in case of an error). The optional second argument gets the error message the evaluation of the expression failed (or nil
otherwise)
The result of the function is the result of the whole expression. If the function fails, the complete expression fails.
例如:
data :
fail : (( catch[1 / 0|v,e|->{$value=v, $error=e}] ))
valid : (( catch[5 * 5|v,e|->{$value=v, $error=e}] ))
resolves to
data :
fail :
error : division by zero
value : null
valid :
error : null
value : 25
(( sync[expr|v,e|->defined(v.field),v.field|10] ))
If an expression expr
may return different results for different evaluations, it is possible to synchronize the final output with a dedicated condition on the expression value. Such an expression could, for example, be an uncached read
, exec
or pipe
call.
The second element must evaluate to a lambda value, given by either a regular expression or by a lambda literal as shown in the title. It may take one or two arguments, the actual value of the value expression and optionally an error message in case of a failing evaluation. The result of the evaluation of the lamda expression decides whether the state of the evaluation of the value expression is acceptable ( true
) or not ( false
).
If the value is accepted, an optional third expression is used to determine the final result of the sync[]
expression. It might be given as an expression evaluating to a lambda value, or by a comma separated expression using the same binding as the preceeding lambda literal. If not given, the value of the synched expression is returned.
If the value is not acceptable, the evaluation is repeated until a timeout applies. The timeout in seconds is given by an optional fourth expression (default is 5 min). Either the fourth, or the both, the third and the fourth elements may be omitted.
The lambda values might be given as literal, or by expression, leading to the following flavors:
sync[expr|v,e|->cond,value|10]
sync[expr|v,e|->cond|valuelambda|10]
sync[expr|v,e|->cond|v|->value|10]
sync[expr|condlambda|valuelambda|10]
sync[expr|condlambda|v|->value|10]
with or without the timeout expression.
例如:
data :
alice : 25
result : (( sync[data|v|->defined(v.alice),v.alice] ))
resolves to
data :
alice : 25
result : 25
This example is quite useless, because the sync expression is a constant. It just demonstrates the usage.
Mappings are used to produce a new list from the entries of a list or map , or a new map from entries of a map containing the entries processed by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the mapping function: It can be inlined as in (( map[list|x|->x ":" port] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( map[list|mapping.expression))
(here the mapping is taken from the property mapping.expression
, which should hold an approriate lambda function).
The mapping comes in two target flavors: with []
or {}
in the syntax. The first flavor always produces a list from the entries of the given source. The second one takes only a map source and produces a filtered or transformed map .
Additionally the mapping uses three basic mapping behaviours:
map
. Here the result of the lambda function is used as new value to replace the original one.或者select
. Here the result of the lambda function is used as a boolean to decide whether the entry should be kept ( true
) or omitted ( false
).sum
. Here always the list flavor is used, but the result type and content is completely determined by the parameterization of the statement by successively aggregating one entry after the other into an arbitrary initial value. Note : The special reference _
is not set for inlined lambda functions as part of the mapping syntax. Therefore the mapping statements (and all other statements using inlined lambda functions as part of their syntax) can be used inside regular lambda functions without hampering the meaning of this special refrence for the surrounding explicit lambda expression.
(( map[list|elem|->dynaml-expr] ))
Execute a mapping expression on members of a list to produce a new (mapped) list. The first expression ( list
) must resolve to a list. The last expression ( x ":" port
) defines the mapping expression used to map all members of the given list. Inside this expression an arbitrarily declared simple reference name (here x
) can be used to access the actually processed list element.
例如
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
产量
port : 4711
hosts :
- alice
- bob
mapped :
- alice:4711
- bob:4711
This expression can be combined with others, for example:
port : 4711
list :
- alice
- bob
joined : (( join( ", ", map[list|x|->x ":" port] ) ))
which magically provides a comma separated list of ported hosts:
port : 4711
list :
- alice
- bob
joined : alice:4711, bob:4711
(( map[list|idx,elem|->dynaml-expr] ))
In this variant, the first argument idx
is provided with the index and the second elem
with the value for the index.
例如
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
产量
list :
- name : alice
age : 25
- name : bob
age : 24
ages :
- 1. alice is 25
- 2. bob is 24
(( map[map|key,value|->dynaml-expr] ))
Mapping of a map to a list using a mapping expression. The expression may have access to the key and/or the value. If two references are declared, both values are passed to the expression, the first one is provided with the key and the second one with the value for the key. If one reference is declared, only the value is provided.
例如
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
产量
ages :
alice : 25
bob : 24
keys :
- alice
- bob
(( map{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys and the new entry values. As for a list mapping additionally a key variable can be specified in the variable list.
persons :
alice : 27
bob : 26
older : (( map{persons|x|->x + 1} ) ))
just increments the value of all entries by one in the field older
:
older :
alice : 28
bob : 27
评论
An alternate way to express the same is to use sum[persons|{}|s,k,v|->s { k = v + 1 }]
.
(( map{list|elem|->dynaml-expr} ))
Using {}
instead of []
together with a list in the mapping syntax, the result is again a map with the list elements as key and the mapped entry values. For this all list entries must be strings. As for a list mapping additionally an index variable can be specified in the variable list.
persons :
- alice
- bob
length : (( map{persons|x|->length(x)} ) ))
just creates a map mapping the list entries to their length:
length :
alice : 5
bob : 3
(( select[expr|elem|->dynaml-expr] ))
With select
a map or list can be filtered by evaluating a boolean expression for every entry. An entry is selected if the expression evaluates to true equivalent value. (see conditions).
Basically it offers all the mapping flavors available for map[]
例如
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
评论
An alternate way to express the same is to use map[list|v|->v.age > 25 ? v :~]
.
(( select{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys filtered by the given expression.
persons :
alice : 25
bob : 26
older : (( select{persons|x|->x > 25} ))
just keeps all entries with a value greater than 25 and omits all others:
selected :
bob : 26
This flavor only works on maps .
评论
An alternate way to express the same is to use sum[persons|{}|s,k,v|->v > 25 ? s {k = v} :s]
.
Aggregations are used to produce a single result from the entries of a list or map aggregating the entries by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the aggregation function: It can be inlined as in (( sum[list|0|s,x|->s + x] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( sum[list|0|aggregation.expression))
(here the aggregation function is taken from the property aggregation.expression
, which should hold an approriate lambda function).
(( sum[list|initial|sum,elem|->dynaml-expr] ))
Execute an aggregation expression on members of a list to produce an aggregation result. The first expression ( list
) must resolve to a list. The second expression is used as initial value for the aggregation. The last expression ( s + x
) defines the aggregation expression used to aggregate all members of the given list. Inside this expression an arbitrarily declared simple reference name (here s
) can be used to access the intermediate aggregation result and a second reference name (here x
) can be used to access the actually processed list element.
例如
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
产量
list :
- 1
- 2
sum : 3
(( sum[list|initial|sum,idx,elem|->dynaml-expr] ))
In this variant, the second argument idx
is provided with the index and the third elem
with the value for the index.
例如
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
产量
list :
- 1
- 2
- 3
prod : 8
(( sum[map|initial|sum,key,value|->dynaml-expr] ))
Aggregation of the elements of a map to a single result using an aggregation expression. The expression may have access to the key and/or the value. The first argument is always the intermediate aggregation result. If three references are declared, both values are passed to the expression, the second one is provided with the key and the third one with the value for the key. If two references are declared, only the second one is provided with the value of the map entry.
例如
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
产量
ages :
alice : 25
bob : 24
sum : 49
Projections work over the elements of a list or map yielding a result list. Hereby every element is mapped by an optional subsequent reference expression. This may contain again projections, dynamic references or lambda calls. Basically this is a simplified form of the more general mapping yielding a list working with a lambda function using only a reference expression based on the elements.
(( expr.[*].value ))
All elements of a map or list given by the expression expr
are dereferenced with the subsequent reference expression (here .expr
). If this expression works on a map the elements are ordered accoring to their key values. If the subsequent reference expression is omitted, the complete value list isreturned. For a list expression this means the identity operation.
例如:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[*].name ))
yields for names
:
names :
- alice
- bob
- peter
or for maps:
networks :
ext :
cidr : 10.8.0.0/16
zone1 :
cidr : 10.9.0.0/16
cidrs : (( .networks.[*].cidr ))
yields for cidrs
:
cidrs :
- 10.8.0.0/16
- 10.9.0.0/16
(( list.[1..2].value ))
This projection flavor only works for lists. The projection is done for a dedicated slice of the initial list.
例如:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[1..2].name ))
yields for names
:
names :
- bob
- peter
In argument lists or list literals the list expansion operator ( ...
) can be used. It is a postfix operator on any list expression. It substituted the list expression by a sequence of the list members. It can be be used in combination with static list argument denotation.
例如:
list :
- a
- b
result : (( [ 1, list..., 2, list... ] ))
evaluates result
to
result :
- 1
- a
- b
- 2
- a
- b
The following example demonstrates the usage in combination with the varargs operator in functions:
func : (( |a,b...|-> [a] b ))
list :
- a
- b
a : (( .func(1,2,3) ))
b : (( .func("x",list..., "z") ))
c : (( [ "x", .func(list...)..., "z" ] ))
evaluates the following results:
a :
- 1
- 2
- 3
b :
- x
- a
- b
- z
c :
- x
- a
- b
- z
Please note, that the list expansion might span multiple arguments (including the varargs parameter) in lambda function calls.
Nodes of the yaml document can be marked to enable dedicated behaviours for this node. Such markers are part of the dynaml syntax and may be prepended to any dynaml expression. They are denoted by the &
character directly followed by a marker name. If the expression is a combination of markers and regular expressions, the expression follows the marker list enclosed in brackets (for example (( &temporary( a + b ) ))
).
Note : Instead of using a <<:
insert field to place markers it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( &temporary ))
Maps, lists or simple value nodes can be marked as temporary . Temporary nodes are removed from the final output document, but are available during merging and dynaml evaluation.
例如:
temp :
<< : (( &temporary ))
foo : bar
value : (( temp.foo ))
产量:
value : bar
Adding - <<: (( &temporary ))
to a list can be used to mark a list as temporary.
The temporary marker can be combined with regular dynaml expressions to tag plain fields. Hereby the parenthesised expression is just appended to the marker
例如:
data :
alice : (( &temporary ( "bar" ) ))
foo : (( alice ))
产量:
data :
foo : bar
The temporary marker can be combined with the template marker to omit templates from the final output.
(( &local ))
The marker &local
acts similar to &temporary
but local nodes are always removed from a stub directly after resolving dynaml expressions. Such nodes are therefore not available for merging and they are not used for further merging of stubs and finally the template.
(( &dynamic ))
This marker can be used to mark a template expression (direct or referenced) to enforce the re-evaluation of the template in the usage context whenever the node is used to override or inject a node value along the processing chain. It can also be used together with &inject
or &default
.
例如:
template.yaml
data : 1
合并于
stub.yaml
id : (( &dynamic &inject &template(__ctx.FILE) ))
will resolve to
id : template.yaml
data : 1
The original template is kept along the merge chain and is evaluated separately in the context of the very stub or template it is used.
Using this marker for nodes not evaluationg to a template value is not possible.
(( &inject ))
This marker requests the marked item to be injected into the next stub level, even is the hosting element (list or map) does not requests a merge. This only works if the next level stub already contains the hosting element.
例如:
template.yaml
alice :
foo : 1
stub.yaml
alice :
bar : (( &inject(2) ))
nope : not injected
bob :
<< : (( &inject ))
foobar : yep
被合并到
alice :
foo : 1
bar : 2
bob :
foobar : yep
(( &default ))
Nodes marked as default will be used as default values for downstream stub levels. If no such entry is set there it will behave like &inject
and implicitly add this node, but existing settings will not be overwritten.
Maps (or lists) marked as default will be considered as values. The map is used as a whole as default if no such field is defined downstream.
例如:
template.yaml
data : { }
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
被合并到
data :
foobar :
foo : claude
bar : peter
Their entries will neither be used for overwriting existing downstream values nor for defaulting non-existng fields of a not defaulted map field.
例如:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
被合并到
data :
foobar :
bar : bob
If sub sequent defaulting is desired, the fields of a default map must again be marked as default.
例如:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : (( &default ("claude") ))
bar : peter
被合并到
data :
foobar :
foo : claude
bar : bob
Note : The behaviour of list entries marked as default is undefined.
(( &state ))
Nodes marked as state are handled during the merge processing as if the marker would not be present. But there will be a special handling for enabled state processing (option --state <path>
) at the end of the template processing. Additionally to the regular output a document consisting only of state nodes (plus all nested nodes) will be written to a state file. This file will be used as top-level stub for further merge processings with enabled state support.
This enables to keep state between two merge processings. For regular merging sich nodes are only processed during the first processing. Later processings will keep the state from the first one, because those nodes will be overiden by the state stub added to the end of the sub list.
If those nodes additionally disable merging (for example using (( &state(merge none) ))
) dynaml expressions in sub level nodes may perform explicit merging using the function stub()
to refer to values provided by already processed stubs (especially the implicitly added state stub). For an example please refer to the state library.
(( &template ))
Nodes marked as template will not be evaluated at the place of their occurrence. Instead, they will result in a template value stored as value for the node. They can later be instantiated inside a dynaml expression (see below).
(( &tag:name ))
The tag marker can be used to assign a logical name to a node value. This name can then be used in tagged reference expressions to refer to this node value (see below).
A tagged reference has the form <tagname>::<path>
. The <path>
may denote any sub node of a tagged node. If the value of a complete node (or a simple value node) should be used, the <path>
must denote the root path ( .
).
Tags can be used to label node values in multi-document streams (used as template). After defined for a document the tag can then be used to reference node values from the actual or previous document(s) of a document sequence in a multi-document stream. Tags can be added for complex or simple value nodes. A tagged reference may be used to refer to the tagged value as a whole or sub structure.
(( &tag:name(value) ))
This syntax is used to tag a node whose value is defined by a dynaml expression. It can also be used to denote tagged simple value nodes. (As usual the value part is optional for adding markers to structured values (see Markers).)
例如:
template.yaml
data :
<< : (( &tag:persons ))
alice : (( &tag:alice(25)
If the name is prefixed with a star ( *
), the tag is defined globally. Gobal tags surive stub processing and their value is visible in subsequent stub (and template) processings.
A tag name may consist of multiple components separated by a colon ( :
).
Tags can also be defined dynamically by the dynaml function tagdef.
(( tag::foo ))
Reference a sub path of the value of a tagged node.
例如:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
例如:
template.yaml
data :
alice : (( &tag:alice(25) ))
tagref : (( alice::. ))
resolves tagref
to 25
(( foo.bar::alice ))
Tag names may be structured. A tag name consists of a non-empty list of tag components separated by a dot or colon ( :
). A tag component may contain ASCII letters or numbers, starting wit a letter. Multi-component tags are subject to Tag Resolution.
A tag reference always contains a tag name and a path separated by a double colon ( ::
). The standard use-case is to describe a dedicated sub node for a tagged node value.
for example, if the tag X
describes the value
data :
alice : 25
bob : 24
the tagged reference X::data.alice
describes the value 25
.
For tagged references with a path other than .
(the whole tag value), structured tags feature a more sophisticated resolution mechanism. A structured tag consist of multiple tag components separated by a colon ( :
), for example lib:mylib
. Therefore, tags span a tree of namespaces or scopes used to resolve path references. A tag-less reference just uses the actual document or binding to resolve a path expression.
Evaluation of a path reference for a tag tries to resolve the path in the first tag tree level where the path is available (breadth-first search). If this level contains multiple tags that could resolve the given path, the resolution fails because it cannot be unambigiously resolved.
例如:
tags :
- << : (( &tag:lib:alice ))
data : alice.alice
- << : (( &tag:lib:alice:v1))
data : alice.v1
- << : (( &tag:lib:bob))
other : bob
usage :
data : (( lib::data ))
effectively resolves usage.data
to lib:alice::data
and therefore to the value alice.alice
.
To achieve this all matching sub tags are orderd by their number of tag components. The first sub-level tag containing such a given path is selected. For this level, the matching tag must be non-ambigious. There must only be one tag with this level containing a matching path. If there are multiple ones the evaluation fails. In the above example this would be the case if tag lib:bob
would contain a field data
instead of or additional to other
.
This feature can be used in library stubs to provide qualified names for their elements that can be used with merging the containing document nodes into the template.
If the template file is a multi-document stream the tags are preserved during the complete processing. This means tags defined in a earlier document can be used in all following documents, also. But the tag names must be unique across all documents in a multi-document stream.
例如:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
---
bob : (( persons::bob ))
resolves to
---
alice : 25
---
bob : 24
Tags defined by tag markers are available for stubs and templates. Global tags are available down the stub processing to the templates. Local tags are only avaialble on the processing level they are declared.
Additionally to the tags explicitly set by tag markers, there are implicit document tags given by the document index during the processing of a (multi-document) template. The implicit document tags are qualified with the prefix doc.
。 This prefix should not be used to own tags in the documents
例如:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
prev : (( doc.1::. ))
---
bob : (( persons::bob ))
prev : (( doc.2::. ))
resolves to
---
alice : 25
prev :
data :
alice : 25
bob : 24
---
bob : 24
prev :
alice : 25
prev :
data :
alice : 25
bob : 24
If the given document index is negative it denotes the document relative to the one actually processed (so, the tag doc.-1
denotes the previous document). The index doc.0
can be used to denote the actual document. Here always a path must be specified, it is not possible to refer to the complete document (with .
).
A map can be tagged by a dynaml expression to be used as template. Dynaml expressions in a template are not evaluated at its definition location in the document, but can be inserted at other locations using dynaml. At every usage location it is evaluated separately.
<<: (( &template ))
The dynaml expression &template
can be used to tag a map node as template:
例如:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
The template will be the value of the node foo.bar
. As such it can be overwritten as a whole by settings in a stub during the merge process. Dynaml expressions in the template are not evaluated. A map can have only a single <<
field. Therefore it is possible to combine the template marker with an expression just by adding the expression in parenthesis.
Adding - <<: (( &template ))
to a list it is also possible to define list templates. It is also possible to convert a single expression value into a simple template by adding the template marker to the expression, for example foo: (( &template (expression) ))
The template marker can be combined with the temporary marker to omit templates from the final output.
Note : Instead of using a <<:
insert field to place the template marker it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( *foo.bar ))
The dynaml expression *<reference expression>
can be used to evaluate a template somewhere in the yaml document. Dynaml expressions in the template are evaluated in the context of this expression.
例如:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst : (( *foo.bar ))
verb : loves
verb : hates
evaluates to
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst :
alice : alice
bob : loves alice
verb : loves
verb : hates
_
The special reference _
( self ) can be used inside of lambda functions and templates . They refer to the containing element (the lambda function or template).
Additionally it can be used to lookup relative reference expressions starting with the defining document scope of the element skipping intermediate scopes.
例如:
node :
data :
scope : data
funcs :
a : (( |x|->scope ))
b : (( |x|->_.scope ))
c : (( |x|->_.data.scope ))
scope : funcs
call :
scope : call
a : (( node.funcs.a(1) ))
b : (( node.funcs.b(1) ))
c : (( node.funcs.c(1) ))
evaluates call
to
call :
a : call
b : funcs
c : data
scope : call
__
The special reference __
can be used to lookup references as relative references starting with the document node hosting the actually evaluated dynaml expression skipping intermediate scopes.
This can, for example be used to relatively access a lambda value field besides the actual field in a map. The usage of plain function names is reserved for builtin functions and are not used as relative references.
This special reference is also available in expressions in templates and refer to the map node in the template hosting the actually evaluated expression.
例如:
templates :
templ :
<< : (( &template ))
self : (( _ ))
value : (( ($self="value") __.self ))
result : (( scope ))
templ : (( _.scope ))
scope : templates
result :
inst : (( *templates.templ ))
scope : result
evaluates result
to
result :
inst :
result : result
templ : templates
self :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
value :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
scope : result
or with referencing upper nodes:
templates :
templ :
<< : (( &template ))
alice : root
data :
foo : (( ($bob="local") __.bob ))
bar : (( ($alice="local") __.alice ))
bob : static
result : (( *templates.templ ))
evaluates result
to
result :
alice : root
data :
bar : root
foo : static
bob : static
___
The special reference ___
can be used to lookup references in the outer most scope. It can therefore be used to access processing bindings specified for a document processing via command line or API. If no bindings are specified the document root is used.
Calling spiff merge template.yaml --bindings bindings.yaml
with a binding of
bindings.yaml
input1 : binding1
input2 : binding2
和模板
template.yaml
input1 : top1
map :
input : map
input1 : map1
results :
frommap : (( input1 ))
fromroot : (( .input1 ))
frombinding1 : (( ___.input1 ))
frombinding2 : (( input2 ))
evaluates map.results
to
results :
frombinding1 : binding1
frombinding2 : binding2
frommap : map1
fromroot : top1
__ctx.OUTER
The context field OUTER
is used for nested merges. It is a list of documents, index 0 is the next outer document, and so on.
(( {} ))
Provides an empty map.
(( [] ))
Provides an empty list. Basically this is not a dedicated literal, but just a regular list expression without a value.
(( ~ ))
Provides the null value.
(( ~~ ))
This literal evaluates to an undefined expression. The element (list entry or map field) carrying this value, although defined, will be removed from the document and handled as undefined for further merges and the evaluation of referential expressions.
例如:
foo : (( ~~ ))
bob : (( foo || ~~ ))
alice : (( bob || "default"))
evaluates to
alice : default
Inside every dynaml expression a virtual field __ctx
is available. It allows access to information about the actual evaluation context. It can be accessed by a relative reference expression.
支持以下字段:
字段名称 | 类型 | 意义 |
---|---|---|
VERSION | 细绳 | current version of spiff |
FILE | 细绳 | name of actually processed template file |
DIR | 细绳 | name of directory of actually processed template file |
RESOLVED_FILE | 细绳 | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | 细绳 | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | 细绳 | path name of actually processed field |
PATH | list[string] | path name as component list |
OUTER | yaml doc | outer documents for nested merges, index 0 is the next outer document |
BINDINGS | yaml doc | the external bindings for the actual processing (see also ___) |
If external bindings are specified they are the last elements in OUTER
.
例如:
模板.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
例如:
foo :
bar :
dir : .
file : template.yml
path :
- foo
- bar
- path
str : foo.bar.str
Dynaml expressions are evaluated obeying certain priority levels. This means operations with a higher priority are evaluated first. For example the expression 1 + 2 * 3
is evaluated in the order 1 + ( 2 * 3 )
. Operations with the same priority are evaluated from left to right (in contrast to version 1.0.7). This means the expression 6 - 3 - 2
is evaluated as ( 6 - 3 ) - 2
.
The following levels are supported (from low priority to high priority)
||
, //
foo bar
)-or
, -and
==
, !=
, <=
, <
, >
, >=
+
, -
*
, /
, %
( )
, !
, constants, references ( foo.bar
), merge
, auto
, lambda
, map[]
, and functionsThe complete grammar can be found in dynaml.peg.
Feature state: alpha
Attention: This is an alpha feature. It must be enabled on the command line with the --interpolation
or --features=interpolation
option. Also for the spiff library it must explicitly be enabled. By adding the key interpolation
to the feature list stored in the environment variable SPIFF_FEATURES
this feature will be enabled by default.
Typically a complete value can either be a literal or a dynaml expression. For string literals it is possible to use an interpolation syntax to embed dynaml expressions into strings.
例如
data : test
interpolation : this is a (( data ))
replaces the part between the double brackets by the result of the described expression evaluation. Here the brackets can be escaped by the usual escaping ( ((!
) syntax.
Those string literals will implicitly be converted to complete flat dynaml expressions. The example above will therefore be converted into
(( "this is a " data ))
which is the regular dynaml equivalent. The escaping is very ticky, and may be there are still problems. Quotes inside an embedded dynaml expression can be escaped to enable quotes in string literals.
Incomplete or partial interpolation expressions will be ignored and just used as string.
Strings inside a dynaml expression are NOT directly interpolated again, thus
data : " test "
interpolated : " this is a (( length( " (( data )) " ) data )) "
will resolve interpolation
to this is 10test
and not to this is 4test
.
But if the final string after the expression evaluation again describes a string interpolation it will be processed, again.
data : test
interpolation : this is a (( "(( data ))" data ))
will resolve interpolation
to this is testtest
.
The embedded dynaml expression must be concatenatable with strings.
Feature state: alpha
In addition to describe conditions and loops with dynaml expressions it is also possible to use elements of the document structure to embed control structures.
Such a YAML-based control structure is always described as a map in YAML/JSON. The syntactical elements are expressed as map fields starting with <<
. Additionally, depending on the control structure, regular fields are possible. Control structures finally represent a value for the containing node. They may contain marker expressions ( <<
), also.
例如:
temp :
<< : (( &temporary ))
<<if : (( features("control") ))
<<then :
alice : 25
bob : 26
resolves to
final :
alice : 25
bob : 26
Tu use this alpha feature the feature flag control
must be enabled.
Please be aware: Control structure maps typically are always completely resolved before they are evaluated.
A control structure itself is always a dedicated map node in a document. It is substituted by a regular value node determined by the execution of the control structure.
The fields of a control structure map are not subject to overwriting by stubs, but the complete structure can be overwritten.
If used as value for a map field the resulting value is just used as effective value for this field.
If a map should be enriched by maps resulting from multiple control structures the special control structure <<merge:
can be used. It allows to specify a list of maps which should be merged with the actual control structure map to finally build the result value.
A control structure can be used as list value, also. In this case there is a dedicated interpretation of the resulting value of the control structure. If it is NOT a list value, for convenience, the value is directly used as list entry and substitutes the control structure map.
If the resulting value is again a list, it is inserted into the containing list at the place of occurrence of the control structure. So, if a list value should be used as dedicated entry in a list, the result of a control structure must be a list with the intended list as entry.
例如:
list :
- <<if : (( features("control") ))
<<then : alice
- <<if : (( features("control") ))
<<then :
- - peter
- <<if : (( features("control") ))
<<then :
- bob
resolves to
list :
- alice
- - peter
- bob
<<if:
The condition structure is defined by the syntax field <<if
. It additionally accepts the fields <<then
and <<else
.
The condition field must provide a boolean value. If it is true
the optional <<then
field is used to substitute the control structure, otherwise the optional <<else
field is used.
If the appropriate case is not specified, the result is the undefined (( ~~ ))
value. The containing field is therefore completely omitted from the output.
。例如:
x : test1
cond :
field :
<<if : (( x == "test" ))
<<then : alice
<<else : bob
evaluates cond.field
to bob
If the else case is omitted, the cond
field would be an empty map ( field
is omitted, because the contained control structure evaluates to undefined )
A comparable way to do this with regular dynaml could look like this:
cond : (( x == "test" ? "alice" :"bob" ))
A better way more suitable for complex cases would be:
local :
<< : (( &local))
then : alice
else : bob
cond : (( x == "test" ? local.then :local.else ))
<<switch:
The switch
control structure evaluates the switch value of the <<switch
field to a string and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
The nil value matches the default
case. If the switch value is undefined the control evaluates to the undefined value (( ~~ ))
.
例如:
x : alice
value :
<<switch : (( x ))
alice : 25
bob : 26
<<default : other
evaluates value
to 25
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
alice : 25
bob : 26
default : other
value : (( local.cases[x] || local.default ))
<<type:
The type
control structure evaluates the type of the value of the <<type
field and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
例如:
x : alice
value :
<<type : (( x ))
string : alice
<<default : unknown
evaluates value
to alice
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
string : alice
default : unknown
value : (( local.cases[type(x)] || local.default ))
For more complex scenarios not only switching on strings a second syntax can be used. Instead of using fields in the control map as cases, a dedicated field <<cases
may contain a list of cases, that are checked sequentially (In this flavor regular fields are not allowed anymore).
Every case is described again by a map containing the fields:
case
: the expected value to match the switch valuematch
: a lambda function taking one argument and yielding a boolean value used to match the given switch valuevalue
: (optional) the resulting value in case of a match. If not defined the result will be the undefined value. One of case
or match
must be present.
例如:
x : 5
selected :
<<switch : (( x ))
<<cases :
- case :
alice : 25
value : alice
- match : (( |v|->v == 5 ))
value : bob
<<default : unknown
resolves to
x : 5
selected : bob
If x
would be set to the complex value
x :
alice : 25
it would resolve to
x :
alice : 25
selected : alice
<<for:
The loop control is able to execute a multi-dimensional loop and produce a list or map based on the value combinations.
The loop ranges are specified by the value of the <<for
field. It is possible ol loop over lists or maps. The range specification can be given by either a map or list:
map : the keys of the map are the names of the control variables and the values must be lists or maps specifying the ranges.
The map key might optionally be a comma-separated pair (for example key,value
) of variable names. In this case the first name is the name for the index variable and the second one for the value variable.
If multiple ranges are specified iterations are alphabetically ordered by value variable name (first) and index variable name (second) to determine the traversing order.
list : if the control variables are defined by a list, each list element must contain two mandatory and one optional field(s):
name
: the name of the (list or map entry value) control variablevalues
: a list to define the value range.index
: (optional) the name of the variable providing the list index or map key of the loop range (defaulted to index-<name>
)Here the order in the list determine the traversal order.
Traversal is done by recursively iterating follow up ranges for every entry in the actual range. This means the last range is completely iterated for the first values of the first ranges first.
If no index variable is specified for a loop range there is an additional implicit binding for every control variable describing the actual list index or map key of the processed value for this dimension. It is denoted by index-<control variable>
If multiple loop ranges are specified, the ranges may mix iterations over maps and lists.
The iteration result value is determined by the value of the <<do
field. It is implicitly handled as template and is evaluated for every set of iteration values.
The result of the evaluation using only the <<do
value field is a list.
例如:
alice :
- a
- b
bob :
- 1
- 2
- 3
list :
<<for :
key,alice : (( .alice )) # sorted by using alice as primary sort key
bob : (( .bob ))
<<do :
value : (( alice "-" key "-" bob "-" index-bob ))
evaluates list
to
list :
- value : a-0-1-0
- value : a-0-2-1
- value : a-0-3-2
- value : b-1-1-0
- value : b-1-2-1
- value : b-1-3-2
It first iterates over the values for alice
. For each such value it then iterates over the values of bob
.
A comparable way to do this with regular dynaml could look like this:
list : (( sum[alice|[]|s,key,alice|-> s sum[bob|[]|s,index_bob,bob|->s (alice "-" key "-" bob "-" index_bob)]] ))
A result list may omit entries if the value expression evaluates to the undefined value ( ~~
). The nil value ( ~
) is kept. This way a for
control can be used to filter lists.
例如:
bob :
- 1
- 2
- 3
filtered :
<<for :
bob : (( .bob ))
<<do : (( bob == 2 ? ~~ :bob ))
resolves to
bob :
- 1
- 2
- 3
filtered :
- 1
- 3
If the result should be a map it is required to additionally specify a key value for every iteration. This is specified by the optional <<mapkey
field. Like the <<do
field it is implicitly handled as template and re-evaluated for every iteration.
例如:
x : suffix
alice :
- a
- b
bob :
- 1
- 2
- 3
map :
<<for :
- name : alice
values : (( .alice ))
- name : bob
values : (( .bob ))
<<mapkey : (( alice bob ))
<<do :
value : (( alice bob x ))
evaluates the field map
to
map :
a1 :
value : a1suffix
a2 :
value : a2suffix
a3 :
value : a3suffix
b1 :
value : b1suffix
b2 :
value : b2suffix
b3 :
value : b3suffix
Here the traversal order is irrelevant as long as the generated key values are unique. If several evaluations of the key expression yield the same value the last one will win.
A comparable way to do this with regular dynaml could look like this:
map : (( sum[alice|{}|s,index_alice,alice|-> s sum[bob|{}|s,index_bob,bob|->s {(alice bob)=alice bob x}]] ))
An iteration value is ignored if the key or the value evaluate to the undefined value (( ~~ ))
. Additionally the key may evaluate to the nil value (( ~ ))
, also.
例如:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
或者
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( bob == 2 ? ~~ :key ))
<<do : (( bob ))
resolve to
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
b1 : 1
b3 : 3
<<merge:
With merge
it is possible to merge maps given as list value of the <<merge
field with regular map fields from the control structure to determine the final map value.
The value for <<merge:
may be a single map or a list of maps to join with the directly given fields.
例如:
map :
<<merge :
- bob : 26
charlie : 1
- charlie : 27
alice : 25
charlie : 2
resolves to
map :
alice : 25
bob : 26
charlie : 27
If multiple maps contain the same key, the last value (in order of list) will win.
This might be combined with other control structures, for example to conditionally merge multiple maps:
例如:
x : charlie
map :
<<merge :
- <<if : (( x == "charlie" ))
<<then :
charlie : 27
- <<if : (( x == "alice" ))
<<then :
alice : 20
alice : 25
charlie : 2
resolves to
x : charlie
map :
alice : 25
charlie : 27
By default spiff
performs a deep structural merge of its first argument, the template file, with the given stub files. The merge is processed from right to left, providing an intermediate merged stub for every step. This means, that for every step all expressions must be locally resolvable.
Structural merge means, that besides explicit dynaml merge
expressions, values will be overridden by values of equivalent nodes found in right-most stub files. In general, flat value lists are not merged. Only lists of maps can be merged by entries in a stub with a matching index.
There is a special support for the auto-merge of lists containing maps, if the maps contain a name
field. Hereby the list is handled like a map with entries according to the value of the list entries' name
field. If another key field than name
should be used, the key field of one list entry can be tagged with the prefix key:
to indicate the indended key name. Such tags will be removed for the processed output.
In general the resolution of matching nodes in stubs is done using the same rules that apply for the reference expressions (( foo.bar.[1].baz )).
For example, given the file template.yml :
foo :
- name : alice
bar : template
- name : bob
bar : template
plip :
- id : 1
plop : template
- id : 2
plop : template
bar :
- foo : template
list :
- a
- b
and file stub.yml :
foo :
- name : bob
bar : stub
plip :
- key:id : 1
plop : stub
bar :
- foo : stub
list :
- c
- d
spiff merge template.yml stub.yml
回报
foo :
- bar : template
name : alice
- bar : stub
name : bob
plip :
- id : 1
plop : stub
- id : 2
plop : template
bar :
- foo : stub
list :
- a
- b
Be careful that any name:
key in the template for the first element of the plip
list will defeat the key:id: 1
selector from the stub. When a name
field exist in a list element, then this element can only be targeted by this name. When the selector is defeated, the resulting value is the one provided by the template.
Merging the following files in the given order
部署.yml
networks : (( merge ))
cf.yml
utils : (( merge ))
network : (( merge ))
meta : (( merge ))
networks :
- name : cf1
<< : (( utils.defNet(network.base.z1,meta.deployment_no,30) ))
- name : cf2
<< : (( utils.defNet(network.base.z2,meta.deployment_no,30) ))
infrastructure.yml
network :
size : 16
block_size : 256
base :
z1 : 10.0.0.0
z2 : 10.1.0.0
rules.yml
utils :
defNet : (( |b,n,s|->(*.utils.network).net ))
network :
<< : (( &template ))
start : (( b + n * .network.block_size ))
first : (( start + ( n == 0 ? 2 :0 ) ))
lower : (( n == 0 ? [] :b " - " start - 1 ))
upper : (( start + .network.block_size " - " max_ip(net.subnets.[0].range) ))
net :
subnets :
- range : (( b "/" .network.size ))
reserved : (( [] lower upper ))
static :
- (( first " - " first + s - 1 ))
instance.yml
meta :
deployment_no : 1
will yield a network setting for a dedicated deployment
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0 - 10.0.255.255
static :
- 10.0.1.0 - 10.0.1.29
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.0.0 - 10.1.0.255
- 10.1.2.0 - 10.1.255.255
static :
- 10.1.1.0 - 10.1.1.29
Using the same config for another deployment of the same type just requires the replacement of the instance.yml
. Using a different instance.yml
meta :
deployment_no : 0
will yield a network setting for a second deployment providing the appropriate settings for a unique other IP block.
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.1.0 - 10.0.255.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.1.0 - 10.1.255.255
static :
- 10.1.0.2 - 10.1.0.31
If you move to another infrastructure you might want to change the basic IP layout. You can do it just by adapting the infrastructure.yml
network :
size : 17
block_size : 128
base :
z1 : 10.0.0.0
z2 : 10.0.128.0
Without any change to your other settings you'll get
networks :
- name : cf1
subnets :
- range : 10.0.0.0/17
reserved :
- 10.0.0.128 - 10.0.127.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.0.128.0/17
reserved :
- 10.0.128.128 - 10.0.255.255
static :
- 10.0.128.2 - 10.0.128.31
There are several scenarios yielding results that do not seem to be obvious. Here are some typical pitfalls.
The auto merge never adds nodes to existing structures
For example, merging
模板.yml
foo :
alice : 25
和
stub.yml
foo :
alice : 24
bob : 26
产量
foo :
alice : 24
Use <<: (( merge )) to change this behaviour, or explicitly add desired nodes to be merged:
模板.yml
foo :
alice : 25
bob : (( merge ))
Simple node values are replaced by values or complete structures coming from stubs, structures are deep merged.
For example, merging
模板.yml
foo : (( ["alice"] ))
和
stub.yml
foo :
- peter
- paul
产量
foo :
- peter
- paul
但模板
foo : [ (( "alice" )) ]
is merged without any change.
Expressions are subject to be overridden as a whole
A consequence of the behaviour described above is that nodes described by an expession are basically overridden by a complete merged structure, instead of doing a deep merge with the structues resulting from the expression evaluation.
For example, merging
模板.yml
men :
- bob : 24
women :
- alice : 25
people : (( women men ))
和
stub.yml
people :
- alice : 13
产量
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
To request an auto-merge of the structure resulting from the expression evaluation, the expression has to be preceeded with the modifier prefer
( (( prefer women men ))
). This would yield the desired result:
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
- bob : 24
Nested merge expressions use implied redirections
merge
expressions implicity use a redirection implied by an outer redirecting merge.在下面的例子中
meta :
<< : (( merge deployments.cf ))
properties :
<< : (( merge ))
alice : 42
the merge expression in meta.properties
is implicity redirected to the path deployments.cf.properties
implied by the outer redirecting merge
. Therefore merging with
deployments :
cf :
properties :
alice : 24
bob : 42
产量
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
例如:
pot : (( lambda |x,y|-> y == 0 ? 1 :(|m|->m * m)(_(x, y / 2)) * ( 1 + ( y % 2 ) * ( x - 1 ) ) ))
seq : (( lambda |b,l|->map[l|x|-> .pot(b,x)] ))
values : (( .seq(2,[ 0..4 ]) ))
yields the list [ 1,2,4,8,16 ]
for the property values
.
Functions can be used to parameterize templates
The combination of functions with templates can be use to provide functions yielding complex structures. The parameters of a function are part of the scope used to resolve reference expressions in a template used in the function body.
例如:
relation :
template :
<< : (( &template ))
bob : (( x " " y ))
relate : (( |x,y|->*relation.template ))
banda : (( relation.relate("loves","alice") ))
evaluates to
relation :
relate : lambda|x,y|->*(relation.template)
template :
<< : (( &template ))
bob : (( x " " y ))
banda :
bob : loves alice
Scopes can be used to parameterize templates
Scope literals are also considered when instantiating templates. Therefore they can be used to set explicit values for relative reference expressions used in templates.
例如:
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped : (( ( $alice = 25, "bob" = 26 ) *template ))
evaluates to
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped :
sum : 51
Aggregations may yield complex values by using templates
The expression of an aggregation may return complex values by returning inline lists or instantiated templates. The binding of the function will be available (as usual) for the evaluation of the template. In the example below the aggregation provides a map with both the sum and the product of the list entries containing the integers from 1 to 4.
例如:
sum : (( sum[[1..4]|init|s,e|->*temp] ))
temp :
<< : (( &template ))
sum : (( s.sum + e ))
prd : (( s.prd * e ))
init :
sum : 0
prd : 1
yields for sum
the value
sum:
prd: 24
sum: 10
Taking advantage of the undefined value
At first glance it might look strange to introduce a value for undefined . But it can be really useful as will become apparent with the following examples.
Whenever a stub syntactically defines a field it overwrites the default in the template during merging. Therefore it would not be possible to define some expression for that field that eventually keeps the default value. Here the undefined value can help:
eg: merging
模板.yml
alice : 24
bob : 25
和
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
产量
alice : ~
bob : 25
There is a problem accessing upstream values. This is only possible if the local stub contains the definition of the field to use. But then there will always be a value for this field, even if the upstream does not overwrite it.
Here the undefined value can help by providing optional access to upstream values. Optional means, that the field is only defined, if there is an upstream value. Otherwise it is undefined for the expressions in the local stub and potential downstream templates. This is possible because the field is formally defined, and will therefore be merged, only after evaluating the expression if it is not merged it will be removed again.
eg: merging
模板.yml
alice : 24
bob : 25
peter : 26
和
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
和
配置.yml
config :
alice : 4711
peter : 0815
产量
alice : 4711 # transferred from config's config value
bob : 25 # kept default value, because not set in config.yml
peter : 26 # kept, because mapping source not available in mapping.yml
This can be used to add an intermediate stub, that offers a dedicated configuration interface and contains logic to map this interface to a manifest structure already defining default values.
Templates versus map literals
As described earlier templates can be used inside functions and mappings to easily describe complex data structures based on expressions refering to parameters. Before the introduction of map literals this was the only way to achieve such behaviour. The advantage is the possibility to describe the complex structure as regular part of a yaml document, which allows using the regular yaml formatting facilitating readability.
例如:
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ *templates.job ] ] ))
templates :
job :
<< : (( &template ))
name : (( k ))
instances : (( v ))
evaluates to
scaling :
runner_z1 : 10
router_z1 : 4
jobs :
- instances : 4
name : router_z1
- instances : 10
name : runner_z1
...
With map literals this construct can significantly be simplified
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ {"name"=k, "value"=v} ] ] ))
Nevertheless the first, template based version might still be useful, if the data structures are more complex, deeper or with complex value expressions. For such a scenario the description of the data structure as template should be preferred. It provides a much better readability, because every field, list entry and value expression can be put into dedicated lines.
But there is still a qualitative difference. While map literals are part of a single expression always evaluated as a whole before map fields are available for referencing, templates are evaluated as regular yaml documents that might contain multiple fields with separate expressions referencing each other.
例如:
range : (( (|cidr,first,size|->(*templates.addr).range)("10.0.0.0/16",10,255) ))
templates :
addr :
<< : (( &template ))
base : (( min_ip(cidr) ))
start : (( base + first ))
end : (( start + size - 1 ))
range : (( start " - " end ))
evaluates range
to
range : 10.0.0.10 - 10.0.1.8
...
Defaulting and Requiring Fields
Traditionally defaulting in spiff is done by a downstream template where the playload data file is used as stub.
Fields with simple values can just be specified with their values. They will be overwritten by stubs using the regular spiff document merging mechanisms.
It is more difficult for maps or lists. If a map is specified in the template only its fields will be merged (see above), but it is never replaced as a whole by settings in the playload definition files. And Lists are never merged.
Therefore maps and lists that should be defaulted as a whole must be specified as initial expressions (referential or inline) in the template file.
eg: merging of
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
和
payload.yaml
config :
value2 : configured
othervalue : I want this but don't get it
evaluates to
config :
person :
age : bob
name : alice
value1 : defaultvalue
value2 : configured
In such a scenario the structure of the resulting document is defined by the template. All kinds of variable fields or sub-structures must be forseen by the template by using <<: (( merge ))
expressions in maps.
eg: changing template to
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
<< : (( merge ))
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
Known optional fields can be described using the undefined ( ~~
) expression:
template.yaml
config :
optional : (( ~~ ))
Such fields will only be part of the final document if they are defined in an upstream stub, otherwise they will be completely removed.
Required fields can be defined with the expression (( merge ))
. If no stub contains a value for this field, the merge cannot be fullfilled and an error is reported. If a dedicated message should be shown instead, the merge expression can be defaulted with an error function call.
例如:
template.yaml
config :
password : (( merge || error("the field password is required") ))
will produce the following error if no stub contains a value:
error generating manifest: unresolved nodes:
(( merge || error("the field password is required") )) in c.yaml config.password () *the field password is required
This can be simplified by reducing the expression to the sole error
expression.
Besides this template based defaulting it is also possible to provide defaults by upstream stubs using the &default
marker. Here the payload can be a downstream file.
X509 and providing State
When generating keys or certificates with the X509 Functions there will be new keys or certificates for every execution of spiff . But it is also possible to use spiff to maintain key state. A very simple script could look like this:
#! /bin/bash
DIR= " $( dirname " $0 " ) /state "
if [ ! -f " $DIR /state.yaml " ] ; then
echo " state: " > " $DIR /state.yaml "
fi
spiff merge " $DIR /template.yaml " " $DIR /state.yaml " > " $DIR /. $$ " && mv " $DIR /. $$ " " $DIR /state.yaml "
It uses a template file (containing the rules) and a state file with the actual state as stub. The first time it is executed there is an empty state and the rules are not overridden, therefore the keys and certificates are generated. Later on, only additional new fields are calculated, the state fields already containing values just overrule the dynaml expressions for those fields in the template.
If a re-generation is required, the state file can just be deleted.
A template may look like this:
state/template.yaml
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : rootca
privateKey : (( state.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
peer :
organization : Mandelsoft
commonName : etcd
publicKey : (( state.pub ))
caCert : (( state.cacert ))
caPrivateKey : (( state.cakey ))
validity : 100
usage :
- ServerAuth
- ClientAuth
- KeyEncipherment
hosts :
- etcd.mandelsoft.org
state :
cakey : (( x509genkey(2048) ))
capub : (( x509publickey(cakey) ))
cacert : (( x509cert(spec.ca) ))
key : (( x509genkey(2048) ))
pub : (( x509publickey(key) ))
peer : (( x509cert(spec.peer) ))
The merge then generates a rootca and some TLS certificate signed with this CA.
Generating, Deploying and Accessing Status for Kubernetes Resources
The sync
function offers the possibility to synchronize the template processing with external content. This can also be the output of a command execution. Therefore the template processing can not only be used to generate a deployment manifest, but also for applying this to a target system and retrieving deployment status values for the further processing.
A typical scenario of this kind could be a kubernetes setup including a service of type LoadBalancer . Once deployed it gets assigned status information about the IP address or hostname of the assigned load balancer. This information might be required for some other deployment manifest.
A simple template for such a deployment could like this:
service :
apiVersion : v1
kind : Service
metadata :
annotations :
dns.mandelsoft.org/dnsnames : echo.test.garden.mandelsoft.org
dns.mandelsoft.org/ttl : " 500 "
name : test-service
namespace : default
spec :
ports :
- name : http
port : 80
protocol : TCP
targetPort : 8080
sessionAffinity : None
type : LoadBalancer
deployment :
testservice : (( sync[pipe_uncached(service, "kubectl", "apply", "-f", "-", "-o", "yaml")|value|->defined(value.status.loadBalancer.ingress)] ))
otherconfig :
lb : (( deployment.testservice.status.loadBalancer.ingress ))
Crazy Shit: Graph Analaysis with spiff
It is easy to describe a simple graph with knots and edges (for example for a set of components and their dependencies) just by using a map of lists.
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
Now it would be useful to figure out whether there are dependency cycles or to determine ordered transitive dependencies for a component.
Let's say something like this:
graph :
utilities :
closures : (( utilities.graph.evaluate(graph) ))
cycles : (( utilities.graph.cycles(closures) ))
Indeed, this can be done with spiff. The only thing required is a "small utilities stub" .
utilities :
<< : (( &temporary ))
graph :
_dep : (( |model,comp,closure|->contains(closure,comp) ? { $deps=[], $err=closure [comp]} :($deps=_._deps(model,comp,closure [comp]))($err=sum[deps|[]|s,e|-> length(s) >= length(e.err) ? s :e.err]) { $deps=_.join(map[deps|e|->e.deps]), $err=err} ))
_deps : (( |model,comp,closure|->map[model.[comp]|dep|->($deps=_._dep(model,dep,closure)) { $deps=[dep] deps.deps, $err=deps.err }] ))
join : (( |lists|->sum[lists|[]|s,e|-> s e] ))
min : (( |list|->sum[list|~|s,e|-> s ? e < s ? e :s :e] ))
normcycle : (( |cycle|->($min=_.min(cycle)) min ? sum[cycle|cycle|s,e|->s.[0] == min ? s :(s.[1..] [s.[1]])] :cycle ))
cycle : (( |list|->list ? ($elem=list.[length(list) - 1]) _.normcycle(sum[list|[]|s,e|->s ? s [e] :e == elem ? [e] :s]) :list ))
norm : (( |deps|->{ $deps=_.reverse(uniq(_.reverse(deps.deps))), $err=_.cycle(deps.err) } ))
reverse : (( |list|->sum[list|[]|s,e|->[e] s] ))
evaluate : (( |model|->sum[model|{}|s,k,v|->s { k=_.norm(_._dep(model,k,[]))}] ))
cycles : (( |result|->uniq(sum[result|[]|s,k,v|-> v.err ? s [v.err] :s]) ))
And magically spiff does the work just by calling
spiff merge closure.yaml graph.yaml utilities.yaml
closures :
a :
deps :
- c
- b
- a
err :
- a
- c
- a
b :
deps : []
err : []
c :
deps :
- a
- b
- c
err :
- a
- c
- a
d :
deps :
- b
err : []
e :
deps :
- d
- b
err : []
cycles :
- - a
- c
- a
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
The evaluation of dynaml expressions may fail because of several reasons:
If a dynaml expression cannot be resolved to a value, it is reported by the spiff merge
operation using the following layout:
(( <failed expression> )) in <file> <path to node> (<referred path>) <tag><issue>
(( min_ip("10") )) in source.yml node.a.[0] () *CIDR argument required
Cyclic dependencies are detected by iterative evaluation until the document is unchanged after a step. Nodes involved in a cycle are therefore typically reported just as unresolved node without a specific issue.
The order of the reported unresolved nodes depends on a classification of the problem, denoted by a dedicated tag. The following tags are used (in reporting order):
标签 | 意义 |
---|---|
* | error in local dynaml expression |
@ | dependent or involved in cyclic dependencies |
- | subsequent error because of refering to a yaml node with an error |
Problems occuring during inline template processing are reported as nested problems. The classification is propagated to the outer node.
If a problem occurs in nested lamba calls the call stack together with the lamba function and is local binding is listed.
(( 2 + .func(2) )) in local/err.yaml value () *evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 2}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 1}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 0}
... resolution of template 'template' failed
(( z )) in local/err.yaml val ()*'z' not found
In case of parsing errors in dynaml expressions, the error location is shown now. If it is a multi line expression the line a character/symbol number in that line is show, otherwise the line numer is omitted.
((
2 ++ .func(2)
)) in local/err.yaml faulty () *parse error near line 2 symbol 2 - line 2 symbol 3: " "
Spiff provides a Go package ( spiffing
) that can be used to include spiff templates in Go programs.
An example program could look like this:
import (
"fmt"
"math"
"os"
"github.com/mandelsoft/spiff/dynaml"
"github.com/mandelsoft/spiff/spiffing"
)
func func_pow ( arguments [] interface {}, binding dynaml. Binding ) ( interface {}, dynaml. EvaluationInfo , bool ) {
info := dynaml . DefaultInfo ()
if len ( arguments ) != 2 {
return info . Error ( "pow takes 2 arguments" )
}
a , b , err := dynaml . NumberOperands ( arguments [ 0 ], arguments [ 1 ])
if err != nil {
return info . Error ( "%s" , err )
}
_ , i := a .( int64 )
if i {
r := math . Pow ( float64 ( a .( int64 )), float64 ( b .( int64 )))
if float64 ( int64 ( r )) == r {
return int64 ( r ), info , true
}
return r , info , true
} else {
return math . Pow ( a .( float64 ), b .( float64 )), info , true
}
}
var state = `
state: {}
`
var stub = `
unused: (( input ))
ages:
alice: (( pow(2,5) ))
bob: (( alice + 1 ))
`
var template = `
state:
<<<: (( &state ))
random: (( rand("[:alnum:]", 10) ))
ages: (( &temporary ))
example:
name: (( input )) # direct reference to additional values
sum: (( sum[ages|0|s,k,v|->s + v] ))
int: (( pow(2,4) ))
float: 2.1
pow: (( pow(1.1e1,2.1) ))
`
func Error ( err error ) {
if err != nil {
fmt . Fprintf ( os . Stderr , "Error: %s n " , err )
os . Exit ( 1 )
}
}
func main () {
values := map [ string ] interface {}{}
values [ "input" ] = "this is an input"
functions := spiffing . NewFunctions ()
functions . RegisterFunction ( "pow" , func_pow )
spiff , err := spiffing . New (). WithFunctions ( functions ). WithValues ( values )
Error ( err )
pstate , err := spiff . Unmarshal ( "state" , [] byte ( state ))
Error ( err )
pstub , err := spiff . Unmarshal ( "stub" , [] byte ( stub ))
Error ( err )
ptempl , err := spiff . Unmarshal ( "template" , [] byte ( template ))
Error ( err )
result , err := spiff . Cascade ( ptempl , []spiffing. Node { pstub }, pstate )
Error ( err )
b , err := spiff . Marshal ( result )
Error ( err )
newstate , err := spiff . Marshal ( spiff . DetermineState ( result ))
Error ( err )
fmt . Printf ( "==== new state === n " )
fmt . Printf ( "%s n " , string ( newstate ))
fmt . Printf ( "==== result === n " )
fmt . Printf ( "%s n " , string ( b ))
}
它支持