Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import: add support for import blocks in Terraform 1.5+ #623

Merged
merged 1 commit into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,30 @@ If you use another OS, you will need to download the release directly from

## Importing with Terraform state

`cf-terraforming` will output the `terraform import` compatible commands for you
when you invoke the `import` command. This command assumes you have already ran
`cf-terraforming generate ...` to output your resources.
`cf-terraforming` has the ability to generate the configuration for you to import
existing resources.

In the future we aim to automate this however for now, it is a manual step to
allow flexibility in directory structure.
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
Expand All @@ -157,7 +171,7 @@ Any resources not listed are currently not supported.
| Resource | Resource Scope | Generate Supported | Import Supported |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | ------------------ | ---------------- |
| [cloudflare_access_application](https://www.terraform.io/docs/providers/cloudflare/r/access_application) | Account | ✅ | ❌ |
| [cloudflare_access_group](https://www.terraform.io/docs/providers/cloudflare/r/access_group) | Account | ✅ | ✅ |
| [cloudflare_access_group](https://www.terraform.io/docs/providers/cloudflare/r/access_group) | Account | ✅ | ✅ |
| [cloudflare_access_identity_provider](https://www.terraform.io/docs/providers/cloudflare/r/access_identity_provider) | Account | ✅ | ❌ |
| [cloudflare_access_mutual_tls_certificate](https://www.terraform.io/docs/providers/cloudflare/r/access_mutual_tls_certificate) | Account | ✅ | ❌ |
| [cloudflare_access_policy](https://www.terraform.io/docs/providers/cloudflare/r/access_policy) | Account | ❌ | ❌ |
Expand Down
43 changes: 35 additions & 8 deletions internal/app/cf-terraforming/cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"time"

"github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/spf13/cobra"
"github.com/zclconf/go-cty/cty"
)

// resourceImportStringFormats contains a mapping of the resource type to the
Expand Down Expand Up @@ -483,16 +485,43 @@ func runImport() func(cmd *cobra.Command, args []string) {
return
}

importFile := hclwrite.NewEmptyFile()
importBody := importFile.Body()

for _, data := range jsonStructData {
fmt.Fprint(cmd.OutOrStdout(), buildCompositeID(resourceType, data.(map[string]interface{})["id"].(string)))
id := data.(map[string]interface{})["id"].(string)

if useModernImportBlock {
idvalue := buildRawImportAddress(resourceType, id)
imp := importBody.AppendNewBlock("import", []string{}).Body()
imp.SetAttributeRaw("to", hclwrite.TokensForIdentifier(fmt.Sprintf("%s.%s", resourceType, fmt.Sprintf("%s_%s", terraformResourceNamePrefix, id))))
imp.SetAttributeValue("id", cty.StringVal(idvalue))
importFile.Body().AppendNewline()
} else {
fmt.Fprint(cmd.OutOrStdout(), buildTerraformImportCommand(resourceType, id))
}
}

if useModernImportBlock {
// don't format the output; there is a bug in hclwrite.Format that
// splits incorrectly on certain characters. instead, manually
// insert new lines on the block.
fmt.Fprint(cmd.OutOrStdout(), string(importFile.Bytes()))
}
}
}

// buildCompositeID takes the resourceType and resourceID in order to lookup the
// resource type import string and then return a suitable composite value that
// is compatible with `terraform import`.
func buildCompositeID(resourceType, resourceID string) string {
// buildTerraformImportCommand takes the resourceType and resourceID in order to
// lookup the resource type import string and then return a suitable composite
// value that is compatible with `terraform import`.
func buildTerraformImportCommand(resourceType, resourceID string) string {
resourceImportAddress := buildRawImportAddress(resourceType, resourceID)
return fmt.Sprintf("%s %s.%s_%s %s\n", terraformImportCmdPrefix, resourceType, terraformResourceNamePrefix, resourceID, resourceImportAddress)
}

// buildRawImportAddress takes the resourceType and resourceID in order to lookup
// the resource type import string and then return a suitable address.
func buildRawImportAddress(resourceType, resourceID string) string {
if _, ok := resourceImportStringFormats[resourceType]; !ok {
log.Fatalf("%s does not have an import format defined", resourceType)
}
Expand All @@ -508,16 +537,14 @@ func buildCompositeID(resourceType, resourceID string) string {
identiferValue = zoneID
}

s := ""
s += fmt.Sprintf("%s %s.%s_%s %s", terraformImportCmdPrefix, resourceType, terraformResourceNamePrefix, resourceID, resourceImportStringFormats[resourceType])
s := resourceImportStringFormats[resourceType]
replacer := strings.NewReplacer(
":identifier_type", identiferType,
":identifier_value", identiferValue,
":zone_id", zoneID,
":account_id", accountID,
":id", resourceID,
)
s += "\n"

return replacer.Replace(s)
}
4 changes: 3 additions & 1 deletion internal/app/cf-terraforming/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

var log = logrus.New()
var cfgFile, zoneID, hostname, apiEmail, apiKey, apiToken, accountID, terraformInstallPath, terraformBinaryPath string
var verbose bool
var verbose, useModernImportBlock bool
var api *cloudflare.API
var terraformImportCmdPrefix = "terraform import"
var terraformResourceNamePrefix = "terraform_managed_resource"
Expand Down Expand Up @@ -118,6 +118,8 @@ func init() {
if err = viper.BindEnv("terraform-install-path", "CLOUDFLARE_TERRAFORM_INSTALL_PATH"); err != nil {
log.Fatal(err)
}

rootCmd.PersistentFlags().BoolVarP(&useModernImportBlock, "modern-import-block", "", false, "Whether to generate HCL import blocks for generated resources instead of `terraform import` compatible CLI commands. This is only compatible with Terraform 1.5+")
}

// initConfig reads in config file and ENV variables if set.
Expand Down
Loading