Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(testworkflows): resolve env as map of environment variables / add entries function to process it #5544

Merged
merged 5 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/tcl/testworkflow-init/data/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ var EnvMachine = expressionstcl.NewMachine().
return os.Getenv(name[4:]), true
}
return nil, false
}).
RegisterAccessor(func(name string) (interface{}, bool) {
if name != "env" {
return nil, false
}
env := make(map[string]string)
for _, item := range os.Environ() {
key, value, _ := strings.Cut(item, "=")
env[key] = value
}
return env, true
})

var RefSuccessMachine = expressionstcl.NewMachine().
Expand Down
51 changes: 26 additions & 25 deletions docs/docs/articles/test-workflows-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ spec:
# ensure that the step won't fail for 5 executions
retry:
count: 5
until: 'self.failed'
until: 'self.failed'
```

#### Matrix and Shard
Expand Down Expand Up @@ -138,30 +138,31 @@ There are some functions that help to cast values to a different type. Additiona

### General

| Name | Returns | Description | Example |
|--------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| `join` | `string` | Join list elements | `join(["a", "b"])` is `"a,b"`<br />`join(["a", "b"], " - ")` is `"a - b"` |
| `split` | `list` | Split string to list | `split("a,b,c")` is `["a", "b", "c"]`<br />`split("a - b - c", " - ")` is `["a", "b", "c"]` |
| `trim` | `string` | Trim whitespaces from the string | `trim(" \nabc d ")` is `"abc d"` |
| `len` | `int` | Length of array, map or string | `len([ "a", "b" ])` is `2`<br />`len("foobar")` is `6`<br />`len({ "foo": "bar" })` is `1` |
| `floor` | `int` | Round value down | `floor(10.5)` is `10` |
| `ceil` | `int` | Round value up | `ceil(10.5)` is `11` |
| `round` | `int` | Round value to nearest integer | `round(10.5)` is `11` |
| `at` | anything | Get value of the element | `at([10, 2], 1)` is `2`<br />`at({"foo": "bar"}, "foo")` is `"bar"` |
| `tojson` | `string` | Serialize value to JSON | `tojson({ "foo": "bar" })` is `"{\"foo\":\"bar\"}"` |
| `json` | anything | Parse the JSON | `json("{\"foo\":\"bar\"}")` is `{ "foo": "bar" }` |
| `toyaml` | `string` | Serialize value to YAML | `toyaml({ "foo": "bar" })` is `"foo: bar\n` |
| `yaml` | anything | Parse the YAML | `yaml("foo: bar")` is `{ "foo": "bar" }` |
| `shellquote` | `string` | Sanitize arguments for shell | `shellquote("foo bar")` is `"\"foo bar\""`<br />`shellquote("foo", "bar baz")` is `"foo \"bar baz\""` |
| `shellparse` | `[]string` | Parse shell arguments | `shellparse("foo bar")` is `["foo", "bar"]`<br />`shellparse("foo \"bar baz\"")` is `["foo", "bar baz"]` |
| `map` | `list` or `map` | Map list or map values with expression; `_.value` and `_.index`/`_.key` are available | `map([1,2,3,4,5], "_.value * 2")` is `[2,4,6,8,10]` |
| `filter` | `list` | Filter list values with expression; `_.value` and `_.index` are available | `filter([1,2,3,4,5], "_.value > 2")` is `[3,4,5]` |
| `jq` | anything | Execute [**jq**](https://en.wikipedia.org/wiki/Jq_(programming_language)) against value | <code>jq([1,2,3,4,5], ". &#124; max")</code> is `[5]` |
| `range` | `[]int` | Build range of numbers | `range(5, 10)` is `[5, 6, 7, 8, 9]`<br />`range(5)` is `[0, 1, 2, 3, 4]` |
| `relpath` | `string` | Build relative path | `relpath("/a/b/c")` may be `./b/c`<br />`relpath("/a/b/c", "/a/b")` is `"./c"` |
| `abspath` | `string` | Build absolute path | `abspath("/a/b/c")` is `/a/b/c`<br />`abspath("b/c")` may be `/some/working/dir/b/c` |
| `chunk` | `[]list` | Split list to chunks of specified maximum size | `chunk([1,2,3,4,5], 2)` is `[[1,2], [3,4], [5]]` |
| `date` | `string` | Return current date (either `2006-01-02T15:04:05.000Z07:00` format or custom argument ([**Go syntax**](https://go.dev/src/time/format.go#L101)) | `date()` may be `"2024-06-04T11:59:32.308Z"`<br />`date("2006-01-02")` may be `2024-06-04` |
| Name | Returns | Description | Example |
|--------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| `join` | `string` | Join list elements | `join(["a", "b"])` is `"a,b"`<br />`join(["a", "b"], " - ")` is `"a - b"` |
| `split` | `list` | Split string to list | `split("a,b,c")` is `["a", "b", "c"]`<br />`split("a - b - c", " - ")` is `["a", "b", "c"]` |
| `trim` | `string` | Trim whitespaces from the string | `trim(" \nabc d ")` is `"abc d"` |
| `len` | `int` | Length of array, map or string | `len([ "a", "b" ])` is `2`<br />`len("foobar")` is `6`<br />`len({ "foo": "bar" })` is `1` |
| `floor` | `int` | Round value down | `floor(10.5)` is `10` |
| `ceil` | `int` | Round value up | `ceil(10.5)` is `11` |
| `round` | `int` | Round value to nearest integer | `round(10.5)` is `11` |
| `at` | anything | Get value of the element | `at([10, 2], 1)` is `2`<br />`at({"foo": "bar"}, "foo")` is `"bar"` |
| `tojson` | `string` | Serialize value to JSON | `tojson({ "foo": "bar" })` is `"{\"foo\":\"bar\"}"` |
| `json` | anything | Parse the JSON | `json("{\"foo\":\"bar\"}")` is `{ "foo": "bar" }` |
| `toyaml` | `string` | Serialize value to YAML | `toyaml({ "foo": "bar" })` is `"foo: bar\n` |
| `yaml` | anything | Parse the YAML | `yaml("foo: bar")` is `{ "foo": "bar" }` |
| `shellquote` | `string` | Sanitize arguments for shell | `shellquote("foo bar")` is `"\"foo bar\""`<br />`shellquote("foo", "bar baz")` is `"foo \"bar baz\""` |
| `shellparse` | `[]string` | Parse shell arguments | `shellparse("foo bar")` is `["foo", "bar"]`<br />`shellparse("foo \"bar baz\"")` is `["foo", "bar baz"]` |
| `map` | `list` | Map list with expression; `_.value` and `_.index` are available | `map([1,2,3,4,5], "_.value * 2")` is `[2,4,6,8,10]` |
| `entries` | `map` | Get list of entries in map | `entries({"A":"B", "C":"D"})` is `[{"key": "A", "value": "B"}, {"key": "C", "value": "D"}]` |
| `filter` | `list` | Filter list values with expression; `_.value` and `_.index` are available | `filter([1,2,3,4,5], "_.value > 2")` is `[3,4,5]` |
| `jq` | anything | Execute [**jq**](https://en.wikipedia.org/wiki/Jq_(programming_language)) against value | <code>jq([1,2,3,4,5], ". &#124; max")</code> is `[5]` |
| `range` | `[]int` | Build range of numbers | `range(5, 10)` is `[5, 6, 7, 8, 9]`<br />`range(5)` is `[0, 1, 2, 3, 4]` |
| `relpath` | `string` | Build relative path | `relpath("/a/b/c")` may be `./b/c`<br />`relpath("/a/b/c", "/a/b")` is `"./c"` |
| `abspath` | `string` | Build absolute path | `abspath("/a/b/c")` is `/a/b/c`<br />`abspath("b/c")` may be `/some/working/dir/b/c` |
| `chunk` | `[]list` | Split list to chunks of specified maximum size | `chunk([1,2,3,4,5], 2)` is `[[1,2], [3,4], [5]]` |
| `date` | `string` | Return current date (either `2006-01-02T15:04:05.000Z07:00` format or custom argument ([**Go syntax**](https://go.dev/src/time/format.go#L101)) | `date()` may be `"2024-06-04T11:59:32.308Z"`<br />`date("2006-01-02")` may be `2024-06-04` |

### File System

Expand Down
1 change: 1 addition & 0 deletions pkg/tcl/expressionstcl/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ a:
assert.Equal(t, `[0,2,4,6,8]`, MustCompile(`map([10,20,30,40,50], "_.index * 2")`).String())
assert.Equal(t, `[2,4,6,8,10]`, MustCompile(`map([1,2,3,4,5], "_.value * 2")`).String())
assert.Equal(t, `[0,2,4,6,8]`, MustCompile(`map([10,20,30,40,50], "_.index * 2")`).String())
assert.ElementsMatch(t, []interface{}{MapEntry{Key: "A", Value: "B"}, MapEntry{Key: "C", Value: 5.0}}, must(MustCompile(`entries({"A": "B", "C": 5})`).Static().SliceValue()))
assert.Equal(t, `[3,4,5]`, MustCompile(`filter([1,2,3,4,5], "_.value > 2")`).String())
assert.Equal(t, `[5]`, MustCompile(`jq([1,2,3,4,5], ". | max")`).String())
assert.Equal(t, `[{"b":{"v":2}}]`, MustCompile(`jq([{"a":{"v": 1}},{"b":{"v": 2}}], ". | max_by(.v)")`).String())
Expand Down
21 changes: 21 additions & 0 deletions pkg/tcl/expressionstcl/stdlib.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,22 @@ var stdFunctions = map[string]StdFunction{
return Compile(fmt.Sprintf("list(%s)", strings.Join(result, ",")))
},
},
"entries": {
Handler: func(value ...StaticValue) (Expression, error) {
if len(value) != 1 {
return nil, fmt.Errorf(`"entries" function expects 1 argument, %d provided`, len(value))
}
dict, err := value[0].MapValue()
if err != nil {
return nil, fmt.Errorf(`"entries" function expects 1st argument to be a map, %s provided: %v`, value[0], err)
}
list := make([]MapEntry, 0, len(dict))
for k, v := range dict {
list = append(list, MapEntry{Key: k, Value: v})
}
return NewValue(list), nil
},
},
"filter": {
Handler: func(value ...StaticValue) (Expression, error) {
if len(value) != 2 {
Expand Down Expand Up @@ -576,6 +592,11 @@ const (
floatCastStdFn = "float"
)

type MapEntry struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}

func CastToString(v Expression) Expression {
if v.Static() != nil {
return NewStringValue(v.Static().Value())
Expand Down
Loading