Terraform
Manage RedSwitches servers, SSH keys and reverse DNS as code with the redswitches/redswitches Terraform provider.
Set up
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 |
data "redswitches_offers" "ams" {
location = "Amsterdam"
max_price = 150
in_stock = true
}
locals {
offer = data.redswitches_offers.ams.offers[0]
}
Resources
redswitches_ssh_key
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
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 notexpected_total, the plan fails and shows the total to set.expected_totalis 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 withoutordercannot run it; the plan then only warns. - Create places an order paid from account credit (
POST /orders) withexpected_total, then waits until the server is ready (timeouts.create, default 60 minutes). If the price moved since the plan, apply fails withprice_changedand nothing is ordered. - Read comes from
GET /servers/{id}. - Update in place:
label(POST /servers/{id}/label) andpower_state,onoroff(POST /servers/{id}/power, then the provider waits for the action). Changingssh_key_idorexpected_totallater only updates the state: they belong to the order. - Replace: changing
offer_id,os_idorbilling_cyclecannot 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 byGET /servers(andredswitches_servers) until then.GET /servers/{id}/cancel-requestshowsrequested. - 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
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.