# Terraform Input Variables Validation

Use [Open Policy Agent](https://www.openpolicyagent.org/docs/latest/) (OPA) policies to validate Terraform input variables.

## Introduction

When executing `atmos terraform <sub-command>` commands, you can provide
[Terraform input variables](https://developer.hashicorp.com/terraform/language/values/variables) on the command line
using the `-var` flag. These variables will override the variables configured in Atmos stack manifests.

For example:

```shell
atmos terraform apply <component> -s <stack> -- -var name=api

atmos terraform apply <component> -s <stack> -- -var name=api -var 'tags={"Team":"api", "Group":"web"}'
```

:::tip
Use double-dash `--` to signify the end of the options for Atmos and the start
of the additional native arguments and flags for the Terraform commands.

Refer to [Terraform CLI commands usage](/cli/commands/terraform/usage) for more details.

:::

:::info
Terraform processes variables in the following order of precedence (from highest to lowest):

- Explicit `-var` flags: these variables have the highest priority and will override any other variable values, including those specified in `--var-file`.

- Variables in `--var-file`: values in a variable file override default values set in the Terraform configuration.
  Atmos generates varfiles from stack configurations and provides it to Terraform using the `--var-file` flag.

- Environment variables: variables set as environment variables using the `TF_VAR_` prefix.

- Default values in the Terraform configuration files: these have the lowest priority.
  :::

When log level `Trace` is used, Atmos prints the Terraform variables specified on the command line in the "CLI variables" output.
For example:

```shell
ATMOS_LOGS_LEVEL=Trace /
atmos terraform apply my-component -s plat-ue2-dev -- -var name=api -var 'tags={"Team":"api", "Group":"web"}'

Variables for the component 'my-component' in the stack 'plat-ue2-dev':
environment: ue2
namespace: cp
region: us-east-2
stage: dev
tenant: plat

Writing the variables to file:
components/terraform/my-component/plat-ue2-dev-my-component.terraform.tfvars.json

CLI variables (will override the variables defined in the stack manifests):
name: api
tags:
    Team: api
    Group: web
```

Atmos exposes the Terraform variables passed on the command line in the `tf_cli_vars` section, and also provides access to
the variables from the [`TF_CLI_ARGS`](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_cli_args-and-tf_cli_args_name)
environment variable in the `env_tf_cli_vars` section. Both can be used in OPA policies for validation.

## Terraform Variables Validation using OPA Policies

In `atmos.yaml`, configure the `schemas.opa` section:

**File:** `atmos.yaml`

```yaml
# Validation schemas
schemas:
  # https://www.openpolicyagent.org
  opa:
    # Can also be set using `ATMOS_SCHEMAS_OPA_BASE_PATH` ENV var, or `--schemas-opa-dir` command-line arguments
    # Supports both absolute and relative paths
    base_path: "stacks/schemas/opa"
```

In the component manifest, add the `settings.validation` section to point to the OPA policy file:

**File:** `stack.yaml`

```yaml
components:
  terraform:
    my-component:
      settings:
        # All validation steps must succeed to allow the component to be provisioned
        validation:
          check-template-functions-test-component-with-opa-policy:
            schema_type: opa
            # 'schema_path' can be an absolute path or a path relative to 'schemas.opa.base_path' defined in `atmos.yaml`
            schema_path: "my-component/validate-my-component.rego"
            description: Check 'my-component' component using OPA policy
            # Validation timeout in seconds
            timeout: 5
```

### Require a Terraform variable to be specified on the command line

If you need to enforce that a Terraform variable must be specified on the command line (and not in Atmos stack manifests),
add the following OPA policy in the file `stacks/schemas/opa/my-component/validate-my-component.rego`

**File:** `stacks/schemas/opa/my-component/validate-my-component.rego`

```
# 'package atmos' is required in all `atmos` OPA policies
package atmos

# Atmos looks for the 'errors' (array of strings) output from all OPA policies.
# If the 'errors' output contains one or more error messages, Atmos considers the policy failed.

errors["for the 'my-component' component, the variable 'name' must be provided on the command line using the '-var' flag"] {
    not input.tf_cli_vars.name
}
```

When executing the following command (and not passing the `name` variable on the command line), Atmos will validate
the component using the OPA policy, which will fail and prevent the component from being provisioned:

```shell
atmos terraform apply my-component -s plat-ue2-dev

Validating the component 'my-component' using OPA file 'my-component/validate-my-component.rego'

for the 'my-component' component, the variable 'name' must be provided on the command line using the '-var' flag
```

On the other hand, when passing the `name` variable on the command line using the `-var name=api` flag, the command will succeed:

```shell
atmos terraform apply my-component -s plat-ue2-dev -- -var name=api
```

### Restrict a Terraform variable from being provided on the command line

If you need to prevent a Terraform variable from being passed (and overridden) on the command line,
add the following OPA policy in the file `stacks/schemas/opa/my-component/validate-my-component.rego`

**File:** `stacks/schemas/opa/my-component/validate-my-component.rego`

```
package atmos

errors["for the 'my-component' component, the variable 'name' cannot be overridden on the command line using the '-var' flag"] {
    input.tf_cli_vars.name
}
```

When executing the following command, Atmos will validate the component using the OPA policy, which will fail and prevent
the component from being provisioned:

```shell
atmos terraform apply my-component -s plat-ue2-dev -- -var name=api

Validating the component 'my-component' using OPA file 'my-component/validate-my-component.rego'

for the 'my-component' component, the variable 'name' cannot be overridden on the command line using the '-var' flag
```

This command will pass the validation and succeed:

```shell
atmos terraform apply my-component -s plat-ue2-dev
```

## Environment Variables Validation using OPA Policies

In addition to `tf_cli_vars` (which contains variables passed via `-var` flags on the command line),
Atmos also provides access to the variables through the `env_tf_cli_vars` section passed via the `TF_CLI_ARGS` environment variable.

### Require a variable to be set via the `TF_CLI_ARGS` environment variable

If you need to enforce that a specific Terraform variable must be set, add the following OPA policy:

**File:** `stacks/schemas/opa/my-component/validate-my-component.rego`

```
package atmos

errors["for the 'my-component' component, 'environment' must be set in the 'TF_CLI_ARGS' environment variable"] {
    not input.env_tf_cli_vars.environment
}
```

This policy will fail if the `'environment'` variable is not set in the 'TF\_CLI\_ARGS' environment variable.

```shell
# This will fail validation
atmos terraform apply my-component -s plat-ue2-dev

# This will pass validation
TF_CLI_ARGS="-var environment=production" atmos terraform apply my-component -s plat-ue2-dev
```

### Validate environment variable values

You can also validate the actual values of variables passed via `TF_CLI_ARGS`. For example, to ensure that the `environment` variable is set to one of the allowed values:

**File:** `stacks/schemas/opa/my-component/validate-my-component.rego`

```
package atmos

# Define allowed environment values
allowed_environments := ["development", "staging", "production"]

errors["for the 'my-component' component, 'environment' variable in TF_CLI_ARGS must be one of: development, staging, production"] {
    input.env_tf_cli_vars.environment
    not input.env_tf_cli_vars.environment in allowed_environments
}
```

### Combine command-line and environment variable validation

You can create policies that validate both command-line variables (`tf_cli_vars`) and environment variables (`env_tf_cli_vars`) together:

**File:** `stacks/schemas/opa/my-component/validate-my-component.rego`

```
package atmos

# Ensure that if a variable is set via TF_CLI_ARGS, it cannot be overridden via command line
errors["for the 'my-component' component, when 'environment' is set in TF_CLI_ARGS, it cannot be overridden with -var"] {
    input.env_tf_cli_vars.environment
    input.tf_cli_vars.environment
}

# Require either TF_CLI_ARGS variable OR command-line variable, but not both
errors["for the 'my-component' component, 'environment' must be specified either via TF_CLI_ARGS or -var, but not both"] {
    input.env_tf_cli_vars.environment
    input.tf_cli_vars.environment
}

# Require at least one method of setting the environment
errors["for the 'my-component' component, 'environment' must be specified via either TF_CLI_ARGS or -var flag"] {
    not input.env_tf_cli_vars.environment
    not input.tf_cli_vars.environment
}
```

### Complex validation with type checking

Variables passed via `TF_CLI_ARGS` are automatically parsed and converted to their appropriate types when possible, so you can validate their format and values:

**File:** `stacks/schemas/opa/my-component/validate-my-component.rego`

```
package atmos

import rego.v1

# Validate that instance_count is a valid number
errors["for the 'my-component' component, 'instance_count' in TF_CLI_ARGS must be a valid positive integer"] {
    input.env_tf_cli_vars.instance_count
    input.env_tf_cli_vars.instance_count <= 0
}

# Validate that tags is a valid object
errors["for the 'my-component' component, 'tags' in TF_CLI_ARGS must be a valid object"] {
    input.env_tf_cli_vars.tags
    not is_object(input.env_tf_cli_vars.tags)
}

# Validate specific object structure
errors["for the 'my-component' component, 'tags' in TF_CLI_ARGS must contain 'Environment' key"] {
    input.env_tf_cli_vars.tags
    is_object(input.env_tf_cli_vars.tags)
    not input.env_tf_cli_vars.tags.Environment
}

errors["for the 'my-component' component, 'tags' in TF_CLI_ARGS must contain 'Team' key"] {
    input.env_tf_cli_vars.tags
    is_object(input.env_tf_cli_vars.tags)
    not input.env_tf_cli_vars.tags.Team
}
```

:::tip
Variables in `env_tf_cli_vars` are automatically parsed and converted to their appropriate types when possible. For example:

- `TF_CLI_ARGS="-var count=5"` becomes `input.env_tf_cli_vars.count` with integer value `5`
- `TF_CLI_ARGS="-var enabled=true"` becomes `input.env_tf_cli_vars.enabled` with boolean value `true`
- `TF_CLI_ARGS='-var tags={"env":"prod"}'` becomes `input.env_tf_cli_vars.tags` with object value `{"env":"prod"}`

This makes it easier to write OPA policies that work with the actual data types rather than just strings.
:::

:::info
The `env_tf_cli_vars` section provides a way to validate and control variables passed via the `TF_CLI_ARGS` environment variable, complementing the `tf_cli_vars` section which handles command-line variables.
Together, they give you complete control over how variables are passed to Terraform.
:::
