# Template

Templates can be used in Rules in the following places:

1. `spec.audit`
2. `spec.collect[].audit`
3. `spec.remediation[].skip`
4. `spec.remediation[].value`
5. `spec.skip_finding`

The `spec.audit`, `spec.collect[].audit`, and `spec.remediation[].value` use pre-pass inline templating. An inline template is denoted by `{{ }}` and fully expanded and converted to a string value. Once all inline templates are fully expanded and the value is a single string, it is used.

The `spec.skip_finding` and `spec.remediation[].skip` are evaluated as a complete template and then converted to a boolean value, as explained below. In these cases the `{{ }}` is optional. If the value evaluates to truthy then the remediation or remediation step is skipped.

{% hint style="info" %}
The values that can be extracted by a template depends on where in the Rule execution cycle the template is evaluated. For example, you can use collection values in an audit, but you cannot use audit findings in a collection.
{% endhint %}

## Example

```yaml
...
  remediation:
    - command: "insert_after"
      path: "block"
      flags:
        prefix: "\n\n"
      skip: "vars.default.STAGE != 'PROD'"
      value: |
        resource "aws_s3_bucket_versioning" "{{ vars.default.STAGE }}_{{ vars.default.BUCKET_NAME }}_versioning" {
          bucket = aws_s3_bucket.{{ vars.default.BUCKET_NAME }}.id

          versioning_configuration {
            status = "Enabled"
          }
        }
```

In the above example let's say the variable `vars.default.STAGE` is "DEV". In this case the conditional would template to `'DEV' != 'PROD'` which would evaluate to true. This remediation would then be skipped.

If `vars.default.STAGE` is `PROD` then it would evaluate to `'PROD' != 'PROD'` and thus be false and the value template would be expanded and then inserted after `block` within the AST.

## Language

**Expr** is an expression language that is evaluated within the `{{ }}` blocks. There are a [set of built-in functions](https://expr-lang.org/docs/language-definition) for working with various data structures. And `{{ }}` blocks can span multiple lines.

### Handling Complex Templates

Template can be very complex which can have strange results in the real world. Here are some suggestions for handling complex templates:

1. Use the Rule execution lifecycle to reduce template complexity as much as possible
2. Use the audit query's inline logic to reduce the collections and findings as much as possible
3. Don't rely on template logic `spec.remediation[].value`, instead use multiple steps and `spec.remediation[].skip` to skip the unwanted ones.
4. Use the `audit` remediation command with a `level` of `DEBUG` to print all the stages of the template evaluation. These will show up in the remediation report only if the log level is set to "DEBUG" (i.e., `-vvv`).

#### Audit `$env`

`$env` is a special variable that represents the entire environment of the template. This can be useful in debugging rules.

```
# rule
spec:
  remediation:
    command: audit
    flags:
      level: DEBUG
    value: |
      {{ $env | keys() }}

      Everything: {{ $env }}
```

This will only show up if ORL log level is set to DEBUG. Otherwise nothing is shown, this can allow you to keep debug statements in the rules.

## Access Patterns

Within templates, data can be as follows:

* Path: `<item>.<path>[.<path>]` - The path element cannot contain special characters, but can be either a key or an index (i.e., `item.0`). `?.` can be used to return nil (instead of error) if the path element is missing (i.e., `this.that?.foo` will still work if `that` is missing).
* Index: `<item>[<index>]` - Index is number. This will error if the index is out of bounds
* Key: `<item>["<key>"]` - Key can be any string. If the key is missing then `nil` is returned.

### Objects

The high-level objects that currently exist:

* `vars` - Each variable file is loaded here. See [variables](https://docs.gomboc.ai/orl/concepts/variables) for details.
* `collections` - each named audit finding from each [`collect`](https://docs.gomboc.ai/orl/concepts/collections) is here.
* `finding` (or `$`) - each named capture group from each [finding](https://docs.gomboc.ai/orl/concepts/finding).

## Skip Truthy Evaluation

The `skip*` conditionals are special in that they are evaluated and then converted to a truthy value as follows.

| Type   | Value                             | Truthy | Comment                                                                         |
| ------ | --------------------------------- | ------ | ------------------------------------------------------------------------------- |
| Array  | `[]`                              | false  | Empty array is false                                                            |
| Bool   | `false`                           | false  | Unquoted false is false                                                         |
| Map    | `{}`                              | false  | Empty map is false                                                              |
| Nil    | `nil`                             | false  | Nil is false                                                                    |
| Number | `0`                               | false  | 0 is false                                                                      |
| String | `""`, `"0"`, `"false"`, `"<nil>"` | false  | Empty string is false. Note, use "" because that is the value of `string(nil)`. |
| Array  | `[1,2,3]`                         | true   | Non-empty array is true                                                         |
| Bool   | `true`                            | true   | Unquoted true is true                                                           |
| Map    | `{1: false}`                      | true   | Non-empty map is true                                                           |
| Number | `-1`                              | true   | any value not 0 is true                                                         |
| String | `"true"`, `"this"`                | true   | Any non-empty string that isn't specifically false is true                      |

### Examples

#### Simple variable expansion

`vars.default.STAGE == 'PROD'`

In this simple case, first, the variable is expanded. If missing an error is thrown and eval fails. If the value of `STAGE` is comparable to a string then the value is compared and a bool is returned. Otherwise an error is thrown.

#### Complex expansion

`vars.default.name in collect(collections, 'all_names.*.name')`

In this case variable `vars.default.name` is extracted, and the full collection of `all_names` is reduced to an array of names. The `in` operator checks if the variable's value is within the list. That executes as follows:

1. `Jane in ["John", "Jake"]`
2. `false`
3. `false` is `false`

#### Lazy Eval

Because of the way items are evaluated all the following are equivalent:

1. `collect(collections, "any_names.*")` -> `[]` => `false`
2. `collections.any_names != {}` -> `{} != {}` -> `false` => `false`
3. `len(collections.any_names)` -> `len({})` -> `0` => `false`
4. `len(collections.any_names) > 0` -> `len({}) > 0` -> `0 > 0` -> `false` => `false`

## Custom Functions

Expr has a ton of [built-in functions](https://expr-lang.org/docs/language-definition) that can be used. We have added additional global ones as below. Additionally, each [language](https://docs.gomboc.ai/orl/concepts/languages) can add additional functions.

### Collect

`{{ collect(root, "path.*.to.collect") }}` -> `["array", "of", "leaves"]`

This function takes a root object, and a path construct. It traverses the object looking for root items. `*` can be used to mean "any" key.

### hasSubString

`{{ hasSubString("This and that", "This") }}` -> `true`

`{{ hasSubString(["This", "that"], "this") }}` -> `false`

`{{ ["This", "that"] | hasSubString("This") }}` -> `true`

This function takes two arguments. The first argument can be a string or a slice of strings. In the string case it checks if the second argument is a substring of the first. In the case of slices it checks each element for substring. If any have the substring then it returns true.
