# Terraform

Manage RedSwitches servers, SSH keys and reverse DNS as code with the `redswitches/redswitches` Terraform provider.

> **Note.** The provider is not published to the Terraform Registry yet. This page describes how it works so you can plan for it.

## Set up

```hcl
terraform {
  required_providers {
    redswitches = {
      source = "redswitches/redswitches"
    }
  }
}

provider "redswitches" {
  # api_key = var.redswitches_api_key  # or set REDSWITCHES_API_KEY
}
```

The API key comes from the `REDSWITCHES_API_KEY` environment variable or the provider block. It is never written to the Terraform state. `base_url` (or `REDSWITCHES_BASE_URL`) changes the API root; leave it unset.

The key needs the permissions for what you manage: `read` always, `ssh_keys` for SSH keys, `manage` for labels and reverse DNS, `order` to create servers, `power` to change `power_state`, and `cancel` to destroy servers.

## Data sources

| Data source | Reads | API |
|---|---|---|
| `redswitches_offers` | Instant servers in stock, with prices and OS choices. Optional filters: `location` (city or country code), `country`, `max_price` (monthly, before tax), `in_stock`, `os` (part of an OS name). | `GET /offers` |
| `redswitches_offer` | One offer by `id`. Fails when it is not on offer now. | `GET /offers/{id}` |
| `redswitches_servers` | Your servers, every page. Optional filters: `label_prefix`, `status`. | `GET /servers` |
| `redswitches_server` | One server by `id`, with its IP addresses, OS, hardware and delivery state. | `GET /servers/{id}` |
| `redswitches_ssh_keys` | Your saved SSH keys. | `GET /ssh-keys` |
| `redswitches_account` | Account id, status, currency (and credit with `read_billing`). | `GET /account` |

```hcl
data "redswitches_offers" "ams" {
  location  = "Amsterdam"
  max_price = 150
  in_stock  = true
}

locals {
  offer = data.redswitches_offers.ams.offers[0]
}
```

## Resources

### redswitches_ssh_key

```hcl
resource "redswitches_ssh_key" "laptop" {
  name       = "laptop"
  public_key = file("~/.ssh/id_ed25519.pub")
}
```

Create adds the key (`POST /ssh-keys`); destroy removes it (`DELETE /ssh-keys/{id}`). Removing a saved key does not remove it from servers where it is already installed. Changing `name` or `public_key` replaces the resource. Import: `terraform import redswitches_ssh_key.laptop sshkey_7`.

### redswitches_server

```hcl
resource "redswitches_server" "web1" {
  offer_id       = "ofr_12"
  billing_cycle  = "monthly"
  os_id          = "os_31"
  ssh_key_id     = redswitches_ssh_key.laptop.id
  label          = "web-1"
  expected_total = { amount = "15480", currency = "USD" }
  power_state    = "on"

  timeouts {
    create = "60m"
  }
}

output "web1_ip" {
  value = redswitches_server.web1.primary_ip
}
```

- **Plan** runs an order dry run (`POST /orders?dry_run=true`; nothing is ordered) for every server it would create. If the quoted total is not `expected_total`, the plan fails and shows the total to set. `expected_total` is in minor units: `"15480"` is 154.80 USD, setup and tax included. The dry run also catches a sold-out offer, missing credit and reached limits before you apply. A key without `order` cannot run it; the plan then only warns.
- **Create** places an order paid from account credit (`POST /orders`) with `expected_total`, then waits until the server is ready (`timeouts.create`, default 60 minutes). If the price moved since the plan, apply fails with `price_changed` and nothing is ordered.
- **Read** comes from `GET /servers/{id}`.
- **Update** in place: `label` (`POST /servers/{id}/label`) and `power_state`, `on` or `off` (`POST /servers/{id}/power`, then the provider waits for the action). Changing `ssh_key_id` or `expected_total` later only updates the state: they belong to the order.
- **Replace**: changing `offer_id`, `os_id` or `billing_cycle` cannot be done in place. Terraform orders a **new server** (paid now) and files a cancellation request for the old one, which keeps running and is billed until the end of its billing period. The plan warns when it does this.
- **Destroy** files a cancellation request at the end of the billing period (`POST /servers/{id}/cancel-request`) and removes the server from the state. The server is **not** removed at once: it keeps running and is billed until its next due date, and it is still listed by `GET /servers` (and `redswitches_servers`) until then. `GET /servers/{id}/cancel-request` shows `requested`.
- **Import**: `terraform import redswitches_server.web1 srv_123`. The API does not know the order attributes (`offer_id`, `os_id`, `ssh_key_id`, `expected_total`), so the values in your configuration are adopted without ordering a new server.

Creating a server spends money. Review every plan that adds or replaces a `redswitches_server`, and use `lifecycle { prevent_destroy = true }` on servers that must not be replaced or cancelled by accident. The root password never goes into the state; log in with the SSH key.

The API does not report power state, so a power change made in the client area is not seen by Terraform. Leave `power_state` unset to not manage power.

### redswitches_rdns

```hcl
resource "redswitches_rdns" "web1" {
  server_id = redswitches_server.web1.id
  ip        = redswitches_server.web1.primary_ip
  hostname  = "web-1.example.com"
}
```

Create and update file a reverse DNS request (`POST /servers/{id}/rdns`). Requests are applied shortly after, not at once; when the answer is an action, the provider waits for it. Destroy clears the name (hostname `""`, beta); where clearing is not available yet, destroy only removes it from the state, with a warning. Import: `terraform import redswitches_rdns.web1 srv_123/203.0.113.10`.

## Idempotency and retries

Every write the provider makes carries an Idempotency-Key: a new one for each create, update or destroy, reused for every retry of that call. The provider retries only on network errors, `429 rate_limited`, `503` and `in_progress`, with the same key, so a lost answer never orders a second server. Once an order is placed, the server is written to the state even if the wait for delivery is cut short (timeout or Ctrl-C), with a warning; the next `apply` does not order it again. If Terraform itself is killed while an order is being sent, list your orders (`GET /orders`) before you apply again. See [Idempotency and retries](idempotency.md).
