Skip to content

Commit d360238

Browse files
committed
feat(tf): Add support for Cloudflare R2 as a TF backend
1 parent e5b2f27 commit d360238

3 files changed

Lines changed: 88 additions & 24 deletions

File tree

README.md

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ uvx copier copy git+https://github.com/stuartellis/tf-tasks my-project
4949
# Go to the working directory for the project
5050
cd my-project
5151

52-
# Ask tenv to detect and install the correct version of Terraform for the project
53-
tenv terraform install
52+
# Ask tenv to detect and install the correct version of OpenTofu for the project
53+
tenv opentofu install
5454

5555
# Create a configuration and a root module for the project
5656
TFT_CONTEXT=dev task tft:context:new
@@ -201,23 +201,59 @@ The `context.json` file is the configuration file for the context. It specifies
201201
"tfstate_dir": "dev",
202202
"region": "eu-west-2",
203203
"role_arn": "arn:aws:iam::789000123456:role/my-tf-state-role"
204-
},
204+
}
205+
}
206+
}
207+
```
208+
209+
The `backends.s3` section specifies the settings for a TF backend that uses S3 for storage. This uses the [S3 native locking feature](https://opentofu.org/docs/language/settings/backends/s3/) in current versions of OpenTofu and Terraform. It does not use DynamoDB. The tooling will use this backend by default.
210+
211+
To use Amazon S3 with DynamoDB for locking, specify an `s3ddb` backend:
212+
213+
```json
214+
{
215+
"metadata": {
216+
"description": "Cloud development environment",
217+
"environment": "dev"
218+
},
219+
"backends": {
205220
"s3ddb": {
206-
"tfstate_bucket": "",
207-
"tfstate_ddb_table": "",
208-
"tfstate_dir": "",
209-
"region": "",
210-
"role_arn": ""
221+
"tfstate_bucket": "789000123456-tf-state-dev-eu-west-2",
222+
"tfstate_ddb_table": "789000123456-tf-lock-dev-eu-west-2",
223+
"tfstate_dir": "dev",
224+
"region": "eu-west-2",
225+
"role_arn": "arn:aws:iam::789000123456:role/my-tf-state-role"
211226
}
212227
}
213228
}
214229
```
215230

216-
The `backends.s3` section specifies the settings for a TF backend that uses S3 for storage. This uses the [S3 native locking feature](https://opentofu.org/docs/language/settings/backends/s3/) in current versions of Terraform and OpenTofu. It does not use DynamoDB. The tooling will use this backend by default.
231+
> The tooling automatically enables encryption for S3 backends.
232+
233+
This tooling also supports [Cloudflare R2](https://developers.cloudflare.com/r2/) for remote state storage. To use Cloudflare R2, specify the backend as `r2` in the `context.json` file:
234+
235+
```json
236+
{
237+
"metadata": {
238+
"description": "Cloud development environment",
239+
"environment": "dev"
240+
},
241+
"backends": {
242+
"r2": {
243+
"tfstate_bucket": "my-weur-tf-state-dev",
244+
"tfstate_dir": "dev",
245+
"region": "auto",
246+
"s3_api_endpoint": "https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com"
247+
},
248+
}
249+
}
250+
```
251+
252+
> Always set the region as `auto` for R2.
217253
218-
The `backends.s3ddb` section specifies the settings for a legacy TF backend that uses S3 for storage and DynamoDB for locking. Only use this type of backend if you need to use an older version of Terraform or OpenTofu.
254+
Cloudflare R2 implements the S3 API, so you use the same `backend "s3"` block in your TF module with R2 as you would with Amazon S3.
219255

220-
> The tooling automatically enables encryption for both types of S3 backend.
256+
This also means that you need to specify an Access Key ID and a Secret Access Key for TF to connect to the R2 bucket. Each API token for R2 has an Access Key ID and a Secret Access Key for compatibility with S3, but other types of Cloudflare API tokens do not. The Cloudflare documentation explains [how to get S3 credentials for an R2 API token](https://developers.cloudflare.com/r2/api/tokens/). Set these S3 credentials as environment variables: `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY`.
221257

222258
### Setting the tfvars for a Context
223259

@@ -400,7 +436,7 @@ Set these variables to override the defaults:
400436
| tft:units | List the units. |
401437
| tft:validate | _terraform validate_ for a unit\* |
402438

403-
\*: These tasks require that you first [initialise](https://opentofu.org/docs/cli/commands/init/) the unit.
439+
\*: These tasks require that you first [initialize](https://opentofu.org/docs/cli/commands/init/) the unit.
404440

405441
### The `tft:context` Tasks
406442

@@ -424,6 +460,7 @@ Set these variables to override the defaults:
424460
| -------------- | ------------------------------------------------------------------------ |
425461
| tft:init | _terraform init_ for a unit. An alias for `tft:init:s3`. |
426462
| tft:init:local | _terraform init_ for a unit, with local state. |
463+
| tft:init:r2 | _terraform init_ for a unit, with Cloudflare R2 remote state. |
427464
| tft:init:s3 | _terraform init_ for a unit, with S3 remote state and native S3 locking. |
428465
| tft:init:s3ddb | _terraform init_ for a unit, with S3 remote state and DynamoDB locking. |
429466

@@ -437,7 +474,7 @@ Similarly, there are no restrictions on how you run tasks on multiple units. You
437474
438475
### Migrating to Terraform
439476

440-
By default, this tooling currently uses OpenTofu. Set `TFT_CLI_EXE` as an environment variable to specify the path to the tool that you wish to use. To use [OpenTofu](https://opentofu.org/), set `TFT_CLI_EXE` with the value `terraform`:
477+
By default, this tooling currently uses OpenTofu. Set `TFT_CLI_EXE` as an environment variable to specify the path to the tool that you wish to use. To use [Terraform](https://developer.hashicorp.com/terraform), set `TFT_CLI_EXE` with the value `terraform`:
441478

442479
```shell
443480
export TFT_CLI_EXE=terraform
@@ -448,12 +485,12 @@ TFT_CONTEXT=dev TFT_UNIT=my-app tft:init
448485
To specify which version of Terraform to use, create a `.terraform-version` file. This file should contain the version of Terraform and nothing else, like this:
449486

450487
```shell
451-
1.12.0
488+
1.15.3
452489
```
453490

454491
The `tenv` tool reads this file when installing or running Terraform.
455492

456-
> Remember that if you switch between Terraform and OpenTofu, you will need to initialize your unit again, and when you run `apply` it will migrate the TF state. The OpenTofu Website provides [migration guides](https://opentofu.org/docs/intro/migration/), which includes information about code changes that you may need to make.
493+
> Remember that if you switch between Terraform and OpenTofu, the state may not be compatible. You will need to initialize your unit again, and when you run `apply` it will attempt to migrate the TF state.
457494
458495
## Contributing
459496

template/tasks/tft/Taskfile.yaml.jinja

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#
66
# Maintained by Copier: DO NOT EDIT THIS FILE
77
#
8-
# Terraform and OpenTofu tasks for the Task runner.
8+
# OpenTofu and Terraform tasks for the Task runner.
99
#
1010
# https://github.com/stuartellis/tf-tasks
1111
#
@@ -327,15 +327,43 @@ tasks:
327327
## TF initialization tasks ##
328328

329329
init:local:
330-
desc: Initialise local state for TF unit
330+
desc: Initialize local state for TF unit
331331
deps: [clean]
332332
cmds:
333333
- "{{.CURRENT_TFT_CLI_EXE}} {{.TFT_CLI_CHDIR_OPT}} init -input=false {{.CLI_ARGS}}"
334334
requires:
335335
vars: [TFT_CONTEXT, TFT_UNIT]
336336

337+
init:r2:
338+
desc: Initialize remote state for TF unit - Cloudflare R2
339+
deps: [clean]
340+
cmds:
341+
- "{{.CURRENT_TFT_CLI_EXE}} {{.TFT_CLI_CHDIR_OPT}} init -input=false {{.TFT_CLI_BACKEND_OPT}} {{.CLI_ARGS}}"
342+
requires:
343+
vars: [TFT_CONTEXT, TFT_UNIT]
344+
vars:
345+
TFT_CLI_BACKEND_DOCUMENT:
346+
sh: cat {{.TFT_CONTEXTS_DIR}}/{{.TFT_CONTEXT}}/context.json
347+
TFT_CLI_BACKEND_CONFIG:
348+
ref: "fromJson .TFT_CLI_BACKEND_DOCUMENT"
349+
TFT_CLI_BACKEND_R2_OPT: >
350+
-backend-config=\"use_lockfile=true\"
351+
-backend-config=\"skip_credentials_validation=true\"
352+
-backend-config=\"skip_metadata_api_check=true\"
353+
-backend-config=\"skip_region_validation=true\"
354+
-backend-config=\"skip_requesting_account_id=true\"
355+
-backend-config=\"skip_s3_checksum=true\"
356+
-backend-config=\"use_path_style=true\"
357+
-backend-config=\"region={{.TFT_CLI_BACKEND_CONFIG.backends.r2.region}}\"
358+
-backend-config=\"bucket={{.TFT_CLI_BACKEND_CONFIG.backends.r2.tfstate_bucket}}\"
359+
-backend-config=\"endpoints={s3=\\\"{{.TFT_CLI_BACKEND_CONFIG.backends.r2.s3_api_endpoint}}\\\"}\"
360+
-backend-config=\"workspace_key_prefix=workspaces\"
361+
-backend-config=\"key=components/{{.CURRENT_TFT_PRODUCT_NAME}}/{{.TFT_CLI_BACKEND_CONFIG.backends.r2.tfstate_dir}}/{{.TFT_UNIT}}.tfstate\"
362+
TFT_CLI_BACKEND_OPT:
363+
sh: echo {{if eq .TFT_REMOTE_BACKEND "false"}}{{else}}{{.TFT_CLI_BACKEND_R2_OPT}}{{end}}
364+
337365
init:s3:
338-
desc: Initialise remote state for TF unit - S3-compatible with native locking
366+
desc: Initialize remote state for TF unit - Amazon S3 with native locking
339367
aliases:
340368
- init
341369
deps: [clean]
@@ -360,7 +388,7 @@ tasks:
360388
sh: echo {{if eq .TFT_REMOTE_BACKEND "false"}}{{else}}{{.TFT_CLI_BACKEND_S3_OPT}}{{end}}
361389

362390
init:s3ddb:
363-
desc: Initialise remote state for TF unit - S3 with DynamoDB locking
391+
desc: Initialize remote state for TF unit - Amazon S3 with DynamoDB locking
364392
deps: [clean]
365393
cmds:
366394
- "{{.CURRENT_TFT_CLI_EXE}} {{.TFT_CLI_CHDIR_OPT}} init -input=false {{.TFT_CLI_BACKEND_OPT}} {{.CLI_ARGS}}"

template/tf/contexts/template/context.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
"environment": "ENVIRONMENT_NAME"
55
},
66
"backends": {
7-
"s3": {
7+
"r2": {
88
"tfstate_bucket": "",
99
"tfstate_dir": "",
10-
"region": "",
11-
"role_arn": ""
10+
"region": "auto",
11+
"s3_api_endpoint": ""
1212
},
13-
"s3ddb": {
13+
"s3": {
1414
"tfstate_bucket": "",
15-
"tfstate_ddb_table": "",
1615
"tfstate_dir": "",
1716
"region": "",
1817
"role_arn": ""

0 commit comments

Comments
 (0)