Skip to content

Commit 4ef5515

Browse files
committed
Merge new spiff to spiff++ release 1.0.8-ms.6
2 parents 1079a87 + 229b188 commit 4ef5515

29 files changed

Lines changed: 2936 additions & 1216 deletions

README.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Contents:
2222
- [dynaml Templating Language](#dynaml-templating-language)
2323
- [(( foo ))](#-foo-)
2424
- [(( foo.bar.[1].baz ))](#-foobar1baz-)
25+
- [(( foo.[bar].baz ))](#-foobarbaz-)
26+
- [(( list.[1..3] ))](#-list13-)
2527
- [(( "foo" ))](#-foo--1)
2628
- [(( [ 1, 2, 3 ] ))](#--1-2-3--)
2729
- [(( { "alice" = 25 } ))](#--alice--25--)
@@ -54,6 +56,9 @@ Contents:
5456
- [(( join( ", ", list) ))](#-join---list-)
5557
- [(( split( ",", string) ))](#-split--string-)
5658
- [(( trim(string) ))](#-trimstring-)
59+
- [(( element(list, index) ))](#-elementlist-index-)
60+
- [(( element(map, key) ))](#-elementmap-key-)
61+
- [(( compact(list) ))](#-compactlist-)
5762
- [(( uniq(list) ))](#-uniqlist-)
5863
- [(( contains(list, "foobar") ))](#-containslist-foobar-)
5964
- [(( index(list, "foobar") ))](#-indexlist-foobar-)
@@ -64,11 +69,13 @@ Contents:
6469
- [(( defined(foobar) ))](#-definedfoobar-)
6570
- [(( valid(foobar) ))](#-validfoobar-)
6671
- [(( require(foobar) ))](#-requirefoobar-)
72+
- [(( stub(foo.bar) ))](#-stubfoobar-)
6773
- [(( exec( "command", arg1, arg2) ))](#-exec-command-arg1-arg2-)
6874
- [(( eval( foo "." bar ) ))](#-eval-foo--bar--)
6975
- [(( env( "HOME" ) ))](#-env-HOME--)
7076
- [(( read("file.yml") ))](#-readfileyml-)
7177
- [(( static_ips(0, 1, 3) ))](#-static_ips0-1-3-)
78+
- [(( ipset(ranges, 3, 3,4,5,6) ))](#-ipsetranges-3-3456-)
7279
- [(( list_to_map(list, "key") ))](#-list_to_maplist-key-)
7380
- [(( makemap(fieldlist) ))](#-makemapfieldlist-)
7481
- [(( makemap(key, value) ))](#-makemapkey-value-)
@@ -241,6 +248,72 @@ list:
241248

242249
can be referenced by using the path `list.alice.age`, instead of `list[0].age`.
243250

251+
## `(( foo.[bar].baz ))`
252+
253+
Look for the nearest 'foo' key, and from there follow through to the
254+
field(s) described by the expression `bar` and then to .baz.
255+
256+
The index may be an integer constant (without spaces) as described in the
257+
last section. But it might also be an arbitrary dynaml expression (even
258+
an integer, but with spaces). If the expression evaluates to a string,
259+
it lookups the dedicated field. If the expression evaluates to an integer,
260+
the array element with this index is addressed.
261+
262+
e.g.:
263+
264+
```yaml
265+
properties:
266+
name: alice
267+
foo: (( values.[name].bar ))
268+
values:
269+
alice:
270+
bar: 42
271+
```
272+
273+
This will resolve `foo` to the value `42`. The dynamic index may also be at
274+
the end of the expression (without `.bar`).
275+
276+
Basically this is the simplier way to express something like
277+
[eval("values." name ".bar")](#-eval-foo--bar--)
278+
279+
If the expression evaluates to a list, the list elements (strings or integers)
280+
are used as path elements to access deeper fields.
281+
282+
e.g.:
283+
284+
```yaml
285+
properties:
286+
name:
287+
- foo
288+
- bar
289+
foo: (( values.[name] ))
290+
values:
291+
foo:
292+
bar: 42
293+
```
294+
295+
resolves `foo` again to the value `42`.
296+
297+
## `(( list.[1..3] ))`
298+
299+
The slice expression can be used to extract a dedicated sub list from a list
300+
expression. The range *start* `..` *end* extracts a list of the length
301+
*end-start+1* with the elements from
302+
index *start* to *end*. If the start index is negative the slice is taken
303+
from the end of the list from *length-start* to *length-end*. If the end
304+
index is lower than the start index, the result is an empty array.
305+
306+
e.g.:
307+
308+
```yaml
309+
list:
310+
- a
311+
- b
312+
- c
313+
foo: (( list.[1..length(list) - 1] ))
314+
```
315+
316+
evaluates `foo` to the list `[b,c]`.
244317

245318
## `(( "foo" ))`
246319

@@ -822,6 +895,19 @@ next: 192.168.1.0
822895
num: 192.168.0.0+256=192.168.1.0
823896
```
824897

898+
Subtraction also works on two IP addresses to calculate the number of
899+
IP addresses between two IP addresses.
900+
901+
e.g.:
902+
903+
```yaml
904+
diff: (( 10.0.1.0 - 10.0.0.1 + 1 ))
905+
```
906+
907+
yields the value 256. IP address constants can be directly used in dynaml
908+
expressions. They are implicitly converted to strings and back to IP
909+
addresses if required by an operation.
910+
825911
## `(( a > 1 ? foo :bar ))`
826912

827913
Dynaml supports the comparison operators `<`, `<=`, `==`, `!=`, `>=` and `>`. The comparison operators work on
@@ -922,6 +1008,66 @@ list:
9221008
- bob
9231009
```
9241010

1011+
### `(( element(list, index) ))`
1012+
1013+
Return a dedicated list element given by its index.
1014+
1015+
e.g.:
1016+
1017+
```yaml
1018+
list: (( trim(split("," "alice, bob")) ))
1019+
elem: (( element(list,1) ))
1020+
```
1021+
1022+
yields:
1023+
1024+
```yaml
1025+
list:
1026+
- alice
1027+
- bob
1028+
elem: bob
1029+
```
1030+
1031+
### `(( element(map, key) ))`
1032+
1033+
Return a dedicated map field given by its key.
1034+
1035+
```yaml
1036+
map:
1037+
alice: 24
1038+
bob: 25
1039+
elem: (( element(map,"bob") ))
1040+
```
1041+
1042+
yields:
1043+
1044+
```yaml
1045+
map:
1046+
alice: 24
1047+
bob: 25
1048+
elem: 25
1049+
```
1050+
1051+
This function is also able to handle keys containing dots (.).
1052+
1053+
### `(( compact(list) ))`
1054+
1055+
Filter a list omitting empty entries.
1056+
1057+
e.g.:
1058+
1059+
```yaml
1060+
list: (( compact(trim(split("," "alice, , bob"))) ))
1061+
```
1062+
1063+
yields:
1064+
1065+
```yaml
1066+
list:
1067+
- alice
1068+
- bob
1069+
```
1070+
9251071
### `(( uniq(list) ))`
9261072

9271073
Uniq provides a list without dupliates.
@@ -1156,6 +1302,35 @@ bob: ~
11561302
alice: default
11571303
```
11581304

1305+
### `(( stub(foo.bar) ))`
1306+
1307+
The function `stub` yields the value of a dedicated field found in the first upstream stub defining it.
1308+
1309+
e.g.:
1310+
1311+
**template.yml**
1312+
```yaml
1313+
value: (( stub(foo.bar) ))
1314+
```
1315+
merged with stub
1316+
1317+
**stub.yml**
1318+
```yaml
1319+
foo:
1320+
bar: foobar
1321+
```
1322+
1323+
evaluates to
1324+
1325+
```yaml
1326+
value: foobar
1327+
```
1328+
1329+
The argument passed to this function must either be a reference literal or an expression evaluating to a string denoting a reference. If no argument is given, the actual field path is used.
1330+
1331+
Alternatively the `merge` operation could be used, for example `merge foo.bar`. The difference is that `stub` does not merge, therefore the field will still be merged (with the original path in the document).
1332+
1333+
11591334
### `(( exec( "command", arg1, arg2) ))`
11601335

11611336
Execute a command. Arguments can be any dynaml expressions including reference expressions evaluated to lists or maps. Lists or maps are passed as single arguments containing a yaml document with the given fragment.
@@ -1382,6 +1557,41 @@ networks:
13821557
static_ips: (( static_ips([1..5]) ))
13831558
```
13841559
1560+
### `(( ipset(ranges, 3, 3,4,5,6) ))`
1561+
1562+
While the function [static_ips](#-static_ips0-1-3-) for historical reasons
1563+
relies on the structure of a bosh manifest
1564+
and works only at dedicated locations in the manifest, the function *ipset*
1565+
offers a similar calculation purely based on its arguments. So, the available
1566+
ip ranges and the required numbers of IPs are passed as arguments.
1567+
1568+
The first (ranges) argument can be a single range as a simple string or a
1569+
list of strings. Every string might be
1570+
- a single IP address
1571+
- an explicit IP range described by two IP addresses separated by a dash (-)
1572+
- a CIDR
1573+
1574+
The second argument specifies the requested number of IP addresses in the
1575+
result set.
1576+
1577+
The additional arguments specify the indices of the IPs to choose (starting
1578+
from 0) in the given ranges. Here again lists of indices might be used.
1579+
1580+
e.g.:
1581+
1582+
```yaml
1583+
ranges:
1584+
- 10.0.0.0 - 10.0.0.255
1585+
- 10.0.2.0/24
1586+
ipset: (( ipset(ranges,3,[256..260]) ))
1587+
```
1588+
1589+
resolves *ipset* to `[ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]`.
1590+
1591+
If no IP indices are specified (only two arguments), the IPs are chosen
1592+
starting from the beginning of the first range up to the end of the last
1593+
given range, without indirection.
1594+
13851595
### `(( list_to_map(list, "key") ))`
13861596

13871597
A list of map entries with explicit name/key fields will be mapped to a map with the dedicated keys. By default the key field `name` is used, which can changed by the optional second argument. An explicitly denoted key field in the list will also be taken into account.
@@ -1602,6 +1812,10 @@ data:
16021812

16031813
The temporary marker can be combined with the [template marker](#templates) to omit templates from the final output.
16041814

1815+
The marker `&local` acts similar to `&temporary` but local nodes are always
1816+
removed from a stub directly after resolving dynaml expressions. Such nodes
1817+
are therefore not available for merging.
1818+
16051819
## Mappings
16061820

16071821
Mappings are used to produce a new list from the entries of a _list_ or _map_ containing the entries processed by a dynaml expression. The expression is given by a [lambda function](#-lambda-x-x--port-). 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).

dynaml/call.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,36 +18,38 @@ func (e CallExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluati
1818
var value interface{}
1919
var info EvaluationInfo
2020

21-
ref, ok := e.Function.(ReferenceExpr)
22-
if ok && len(ref.Path) == 1 && ref.Path[0] != "" && ref.Path[0] != "_" {
21+
ref, okf := e.Function.(ReferenceExpr)
22+
if okf && len(ref.Path) == 1 && ref.Path[0] != "" && ref.Path[0] != "_" {
2323
funcName = ref.Path[0]
2424
} else {
25-
value, info, ok = ResolveExpressionOrPushEvaluation(&e.Function, &resolved, &info, binding, false)
26-
if ok {
27-
_, ok = value.(LambdaValue)
28-
if !ok {
25+
value, info, okf = ResolveExpressionOrPushEvaluation(&e.Function, &resolved, &info, binding, false)
26+
if okf && resolved {
27+
_, okf = value.(LambdaValue)
28+
if !okf {
2929
debug.Debug("function: no string or lambda value: %T\n", value)
3030
return info.Error("function call '%s' requires function name or lambda value", e.Function)
3131
}
3232
}
3333
}
3434

35-
if !ok {
36-
debug.Debug("failed to resolve function: %s\n", info.Issue)
37-
return nil, info, false
38-
}
39-
4035
switch funcName {
4136
case "defined":
4237
return e.defined(binding)
4338
case "require":
4439
return e.require(binding)
4540
case "valid":
4641
return e.valid(binding)
42+
case "stub":
43+
return e.stub(binding)
4744
}
4845

4946
values, info, ok := ResolveExpressionListOrPushEvaluation(&e.Arguments, &resolved, nil, binding, false)
5047

48+
if !okf {
49+
debug.Debug("failed to resolve function: %s\n", info.Issue)
50+
return nil, info, false
51+
}
52+
5153
if !ok {
5254
debug.Debug("call args failed\n")
5355
return nil, info, false
@@ -83,6 +85,12 @@ func (e CallExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluati
8385
case "uniq":
8486
result, sub, ok = func_uniq(values, binding)
8587

88+
case "element":
89+
result, sub, ok = func_element(values, binding)
90+
91+
case "compact":
92+
result, sub, ok = func_compact(values, binding)
93+
8694
case "contains":
8795
result, sub, ok = func_contains(values, binding)
8896

@@ -131,6 +139,9 @@ func (e CallExpr) Evaluate(binding Binding, locally bool) (interface{}, Evaluati
131139
case "list_to_map":
132140
result, sub, ok = func_list_to_map(e.Arguments[0], values, binding)
133141

142+
case "ipset":
143+
result, sub, ok = func_ipset(values, binding)
144+
134145
default:
135146
return info.Error("unknown function '%s'", funcName)
136147
}

dynaml/compact.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package dynaml
2+
3+
import (
4+
"github.com/cloudfoundry-incubator/spiff/yaml"
5+
)
6+
7+
func func_compact(arguments []interface{}, binding Binding) (interface{}, EvaluationInfo, bool) {
8+
info := DefaultInfo()
9+
10+
if len(arguments) != 1 {
11+
return info.Error("compact takes exactly 1 argument")
12+
}
13+
14+
list, ok := arguments[0].([]yaml.Node)
15+
if !ok {
16+
return info.Error("invalid type for function compact")
17+
}
18+
19+
var newList []yaml.Node
20+
21+
for _, v := range list {
22+
found := true
23+
24+
if v != nil && v.Value() != nil {
25+
switch elem := v.Value().(type) {
26+
case string:
27+
found = len(elem) > 0
28+
case int64:
29+
case map[string]yaml.Node:
30+
found = len(elem) > 0
31+
case []yaml.Node:
32+
found = len(elem) > 0
33+
}
34+
if found {
35+
newList = append(newList, v)
36+
}
37+
}
38+
}
39+
return newList, info, true
40+
}

0 commit comments

Comments
 (0)