# Variables

> Declaring variables, overriding them at run time, and how empty values behave.

Source: https://refpoint.dev/docs/rules/variables

Variables let one profile serve several environments without forking it. This
page covers declaring them, the rules for overriding them, and what happens
when a value is empty. The function set available in expressions is on the
[Functions](/docs/rules/functions) page.

## Declaring a variable

```hcl
variable "ntp_servers" {
  description = "Approved NTP servers"
  type        = "list(string)"
  default     = ["time1.example.org", "time2.example.org"]

  validation {
    condition     = length(var.ntp_servers) > 0
    error_message = "At least one NTP server must be configured."
  }
}
```

The supported types are `string`, which is the default, `number`, `bool`, and
`list(string)`. Anything else is a load-time error.

`validation` conditions are evaluated against the finished variable context, so
a condition may reference any variable, not only its own. The first failing rule
stops the load.

## Resolution order

Per variable: an override if one exists, otherwise the declared default,
otherwise `""` for an untyped or string variable. A non-string variable with
neither a default nor an override is a load-time error.

Overrides come from two places.

```shell
refpoint run baseline --var-file production.rpvar --var max_password_age=60
```

`--var-file` keeps native HCL types, so numbers, bools, and lists arrive as
themselves, and a null value is rejected. `--var` is always a string, and for a
list target a comma-separated string is split and trimmed. `--var` wins over
`--var-file`.

Overrides for variables the profile never declared pass through with their
native type. Dependency profiles rely on this.

## Coercion is not type checking

Conversion is coercion. A `false` assigned to a `type = "string"` variable
becomes the string `"false"`. The type guarantee is real only for `number`,
`bool`, and `list(string)` targets. Use `validation` blocks to reject coercion
artifacts rather than assuming the declared type rejected them for you.

## Empty variables skip a check

If a check attribute draws on a profile variable and evaluates to nothing, an
empty or whitespace-only string, or an empty collection, the check is skipped
and the attribute is named in the reason.

The reason is that an empty pattern matches anything and an empty expected hash
matches nothing. Both silently misreport.

A literal `""` written in the profile is left alone, because the author said it
explicitly. An interpolation that still yields text, such as
`"prefix-${var.suffix}"`, runs normally.

## Using a variable in a check

```hcl
variable "sshd_config_path" {
  type    = "string"
  default = "/etc/ssh/sshd_config"
}

control "ssh-02" {
  title  = "SSH protocol settings are managed"
  impact = 0.7

  check "config-owner" "file" {
    path  = var.sshd_config_path
    owner = "root"
    mode  = "0600"
  }
}
```

Only the address attribute, `path` here, is evaluated with the variable
context at load time. The remaining assertions are decoded by the `file`
provider when the check runs.