Home

Awesome

Cloudflare Terraforming

Overview

cf-terraforming is a command line utility to facilitate terraforming your existing Cloudflare resources. It does this by using your account credentials to retrieve your configurations from the Cloudflare API and converting them to Terraform configurations that can be used with the Terraform Cloudflare provider.

This tool is ideal if you already have Cloudflare resources defined but want to start managing them via Terraform, and don't want to spend the time to manually write the Terraform configuration to describe them.

Read the announcement blog for further details on using cf-terraforming in your workflow.

[!NOTE] If you would like to export resources compatible with Terraform < 0.12.x, you will need to download an older release as this tool no longer supports it.

Usage

Usage:
  cf-terraforming [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  generate    Fetch resources from the Cloudflare API and generate the respective Terraform stanzas
  help        Help about any command
  import      Output `terraform import` compatible commands in order to import resources into state
  version     Print the version number of cf-terraforming

Flags:
  -a, --account string                      Target the provided account ID for the command
  -c, --config string                       Path to config file (default "~/.cf-terraforming.yaml")
  -e, --email string                        API Email address associated with your account
  -h, --help                                help for cf-terraforming
      --hostname string                     Hostname to use to query the API
  -k, --key string                          API Key generated on the 'My Profile' page. See: https://dash.cloudflare.com/profile
      --modern-import-block                 Whether to generate HCL import blocks for generated resources instead of terraform import compatible CLI commands. This is only compatible with Terraform 1.5+
      --provider-registry-hostname string   Hostname to use for provider registry lookups (default "registry.terraform.io")
      --resource-type string                Comma delimitered string of which resource(s) you wish to generate
      --terraform-binary-path string        Path to an existing Terraform binary (otherwise, one will be downloaded)
      --terraform-install-path string       Path to an initialized Terraform working directory (default ".")
  -t, --token string                        API Token
  -v, --verbose                             Specify verbose output (same as setting log level to debug)
  -z, --zone string                         Target the provided zone ID for the command

Use "cf-terraforming [command] --help" for more information about a command.

Authentication

Cloudflare supports two authentication methods to the API:

Both can be retrieved on the user profile page.

[!TIP] We recommend that you store your Cloudflare credentials (API key, email, token) as environment variables as demonstrated below.

# if using API Token
export CLOUDFLARE_API_TOKEN='Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j'

# if using API Key
export CLOUDFLARE_EMAIL='user@example.com'
export CLOUDFLARE_API_KEY='1150bed3f45247b99f7db9696fffa17cbx9'

# specify zone ID
export CLOUDFLARE_ZONE_ID='81b06ss3228f488fh84e5e993c2dc17'

# now call cf-terraforming, e.g.
cf-terraforming generate \
  --resource-type "cloudflare_record" \
  --zone $CLOUDFLARE_ZONE_ID

cf-terraforming supports the following environment variables:

Alternatively, if using a config file, then specify the inputs using the same names the flag names. Example:

cat ~/.cf-terraforming.yaml
email: "email@domain.com"
key: "<key>"
#or
token: "<token>"

Example usage

cf-terraforming generate \
  --zone $CLOUDFLARE_ZONE_ID \
  --resource-type "cloudflare_record"

will contact the Cloudflare API on your behalf and result in a valid Terraform configuration representing the resource you requested:

resource "cloudflare_record" "terraform_managed_resource" {
  name = "example.com"
  proxied = false
  ttl = 120
  type = "A"
  value = "198.51.100.4"
  zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
}

Prerequisites

Installation

Homebrew

brew tap cloudflare/cloudflare
brew install cloudflare/cloudflare/cf-terraforming

[!NOTE] If you have installed an older version of cf-terraforming via Homebrew, you may need to first uninstall cf-terraforming and then install it to pick up the updated install process and address the signing/notarisation issues.

Go

go install github.com/cloudflare/cf-terraforming/cmd/cf-terraforming@latest

If you use another OS, you will need to download the release directly from GitHub Releases or build the Go source.

Importing with Terraform state

cf-terraforming has the ability to generate the configuration for you to import existing resources.

Depending on your version of Terraform, you can generate the import block (Terraform 1.5+) using the --modern-import-block flag or the terraform import compatible CLI output (all versions).

This command assumes you have already ran cf-terraforming generate ... to output your resources.

# All versions of Terraform
cf-terraforming import \
  --resource-type "cloudflare_record" \
  --email $CLOUDFLARE_EMAIL \
  --key $CLOUDFLARE_API_KEY \
  --zone $CLOUDFLARE_ZONE_ID
# Terraform 1.5+ only
cf-terraforming import \
  --resource-type "cloudflare_record" \
  --modern-import-block \
  --email $CLOUDFLARE_EMAIL \
  --key $CLOUDFLARE_API_KEY \
  --zone $CLOUDFLARE_ZONE_ID

Using non-standard registries

By default, we use the Hashicorp registry (registry.terraform.io) for looking up the provider to introspect the schema. If you are attempting to use another registry, you will need to provide the --provider-registry-hostname flag or CLOUDFLARE_PROVIDER_REGISTRY_HOSTNAME environment variable to query the correct registry.

Using non-standard Terraform binaries

Internally, we use terraform-exec library to run Terraform operations in the same way that the CLI tooling would. If a terraform binary is not available on your system path, we will attempt to download the latest to use it.

Should you have the binary stored in a non-standard location, want to use an existing binary, or you wish to provide a Terraform compatible binary (such as tofu), you need to provide the --terraform-binary-path flag or CLOUDFLARE_TERRAFORM_BINARY_PATH environment variable to instruct cf-terraforming which you expect to use.

Supported Resources

Any resources not listed are currently not supported.

ResourceResource ScopeGenerate SupportedImport Supported
cloudflare_access_applicationAccount
cloudflare_access_groupAccount
cloudflare_access_identity_providerAccount
cloudflare_access_mutual_tls_certificateAccount
cloudflare_access_policyAccount
cloudflare_access_ruleAccount
cloudflare_access_service_tokenAccount
cloudflare_account_memberAccount
cloudflare_api_shieldZone
cloudflare_api_tokenUser
cloudflare_argoZone
cloudflare_authenticated_origin_pullsZone
cloudflare_authenticated_origin_pulls_certificateZone
cloudflare_bot_managementZone
cloudflare_byo_ip_prefixAccount
cloudflare_certificate_packZone
cloudflare_custom_hostnameZone
cloudflare_custom_hostname_fallback_originAccount
cloudflare_custom_pagesAccount or Zone
cloudflare_custom_sslZone
cloudflare_filterZone
cloudflare_firewall_ruleZone
cloudflare_healthcheckZone
cloudflare_ip_listAccount
cloudflare_listAccount
cloudflare_load_balancerZone
cloudflare_load_balancer_monitorAccount
cloudflare_load_balancer_poolAccount
cloudflare_logpull_retentionZone
cloudflare_logpush_jobZone
cloudflare_logpush_ownership_challengeZone
cloudflare_magic_firewall_rulesetAccount
cloudflare_origin_ca_certificateZone
cloudflare_page_ruleZone
cloudflare_rate_limitZone
cloudflare_recordZone
cloudflare_rulesetAccount or Zone
cloudflare_spectrum_applicationZone
cloudflare_tiered_cacheZone
cloudflare_teams_listAccount
cloudflare_teams_locationAccount
cloudflare_teams_proxy_endpointAccount
cloudflare_teams_ruleAccount
cloudflare_tunnelAccount
cloudflare_turnstile_widgetAccount
cloudflare_url_normalization_settingsZone
cloudflare_waf_groupZone
cloudflare_waf_overrideZone
cloudflare_waf_packageZone
cloudflare_waf_ruleZone
cloudflare_waiting_roomZone
cloudflare_worker_cron_triggerAccount
cloudflare_worker_routeZone
cloudflare_worker_scriptAccount
cloudflare_workers_kvAccount
cloudflare_workers_kv_namespaceAccount
cloudflare_zoneAccount
cloudflare_zone_dnssecZone
cloudflare_zone_lockdownZone
cloudflare_zone_settings_overrideZone

Testing

To ensure changes don't introduce regressions this tool uses an automated test suite consisting of HTTP mocks via go-vcr and Terraform configuration files to assert against. The premise is that we mock the HTTP responses from the Cloudflare API to ensure we don't need to create and delete real resources to test. The Terraform files then allow us to build what the resource structure is expected to look like and once the tool parses the API response, we can compare that to the static file.

Suggested local testing steps:

  1. Create a file with the basic provider configuration (do not commit this file)
cat > main.tf <<EOF
terraform {
  required_providers {
    cloudflare = {
      source = "cloudflare/cloudflare"
      version = "~> 4"
    }
  }
}
EOF
  1. Initialize terraform
terraform init
  1. Run tests (Cloudflare Install path should be path to repository)
make test

If you want to run a specific test case you can do so with the TESTARGS variable and -run flag

TESTARGS="-run '^TestResourceGeneration/cloudflare_teams_list'" make test

Updating VCR cassettes

Periodically, it is a good idea to recreate the VCR cassettes used in our testing to ensure they haven't drifted from actual responses. To do this, you will need to:

  OVERWRITE_VCR_CASSETTES=true \
    CLOUDFLARE_DOMAIN="terraform.cfapi.net" \
    CLOUDFLARE_EMAIL="jb@example.com" \
    CLOUDFLARE_API_KEY="..." \
    TESTARGS="-run '^TestResourceGeneration/cloudflare_record_caa'"  \
    make test