diff --git a/internal/app/cf-terraforming/cmd/generate.go b/internal/app/cf-terraforming/cmd/generate.go index 5a9236ef5..b96ddac29 100644 --- a/internal/app/cf-terraforming/cmd/generate.go +++ b/internal/app/cf-terraforming/cmd/generate.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/hc-install/releases" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/hashicorp/terraform-exec/tfexec" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tidwall/gjson" @@ -83,7 +84,9 @@ func generateResources() func(cmd *cobra.Command, args []string) { _, providerVersion, err := tf.Version(context.Background(), true) providerVersionString := providerVersion[providerRegistryHostname+"/cloudflare/cloudflare"].String() - log.Debugf("detected provider version: %s", providerVersionString) + log.WithFields(logrus.Fields{ + "version": providerVersionString, + }).Debug("detected provider") log.Debug("reading Terraform schema for Cloudflare provider") ps, err := tf.ProvidersSchema(context.Background()) @@ -99,7 +102,9 @@ func generateResources() func(cmd *cobra.Command, args []string) { resources := strings.Split(resourceType, ",") for _, resourceType := range resources { r := s.ResourceSchemas[resourceType] - log.Debugf("beginning to read and build %q resources", resourceType) + log.WithFields(logrus.Fields{ + "resource": resourceType, + }).Debug("reading and building resource") // Initialise `resourceCount` outside of the switch for supported resources // to allow it to be referenced further down in the loop that outputs the @@ -109,7 +114,9 @@ func generateResources() func(cmd *cobra.Command, args []string) { if strings.HasPrefix(providerVersionString, "5") { if resourceToEndpoint[resourceType]["list"] == "" && resourceToEndpoint[resourceType]["get"] == "" { - log.Debugf("did not find API endpoint for %q. does it exist in the mapping?", resourceType) + log.WithFields(logrus.Fields{ + "resource": resourceType, + }).Debug("did not find API endpoint. does it exist in the mapping?") continue } @@ -144,7 +151,10 @@ func generateResources() func(cmd *cobra.Command, args []string) { var apierr *cloudflare.Error if errors.As(err, &apierr) { if apierr.StatusCode == http.StatusNotFound { - log.Debugf("no resources found at %s. skipping...", endpoint) + log.WithFields(logrus.Fields{ + "resource": resourceType, + "endpoint": endpoint, + }).Debug("no resources found") continue } } @@ -158,7 +168,10 @@ func generateResources() func(cmd *cobra.Command, args []string) { value := gjson.Get(string(body), "result") if value.Type == gjson.Null { - log.Debugf("no result found at %s. skipping...", endpoint) + log.WithFields(logrus.Fields{ + "resource": resourceType, + "endpoint": endpoint, + }).Debug("no result found") continue } err = json.Unmarshal([]byte(value.String()), &jsonStructData) @@ -1402,7 +1415,10 @@ func generateResources() func(cmd *cobra.Command, args []string) { } } - log.Debugf("found %d resources to write out for %q", resourceCount, resourceType) + log.WithFields(logrus.Fields{ + "count": resourceCount, + "resource": resourceType, + }).Debug("found resources to generate output for") // If we don't have any resources to generate, just bail out early. if resourceCount == 0 { diff --git a/internal/app/cf-terraforming/cmd/import.go b/internal/app/cf-terraforming/cmd/import.go index 30db78d01..3ee86f606 100644 --- a/internal/app/cf-terraforming/cmd/import.go +++ b/internal/app/cf-terraforming/cmd/import.go @@ -4,13 +4,25 @@ import ( "context" "crypto/md5" "encoding/json" + "errors" "fmt" + "io" + "net/http" + "os" + "regexp" "strings" "time" - "github.com/cloudflare/cloudflare-go" + cfv0 "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/cloudflare-go/v4" + "github.com/hashicorp/go-version" + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/hashicorp/terraform-exec/tfexec" "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tidwall/gjson" "github.com/zclconf/go-cty/cty" ) @@ -55,6 +67,8 @@ var resourceImportStringFormats = map[string]string{ "cloudflare_zone": ":id", } +var providerVersionString string + func init() { rootCmd.AddCommand(importCommand) } @@ -68,546 +82,684 @@ var importCommand = &cobra.Command{ func runImport() func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) { - var jsonStructData []interface{} + zoneID = viper.GetString("zone") + accountID = viper.GetString("account") + workingDir := viper.GetString("terraform-install-path") + execPath := viper.GetString("terraform-binary-path") + + // Download terraform if no existing binary was provided + if execPath == "" { + tmpDir, err := os.MkdirTemp("", "tfinstall") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(tmpDir) - var identifier *cloudflare.ResourceContainer - if accountID != "" { - identifier = cloudflare.AccountIdentifier(accountID) - } else { - identifier = cloudflare.ZoneIdentifier(zoneID) + installConstraints, err := version.NewConstraint("~> 1.0") + if err != nil { + log.Fatal("failed to parse version constraints for installation version") + } + + installer := &releases.LatestVersion{ + Product: product.Terraform, + Constraints: installConstraints, + } + + execPath, err = installer.Install(context.Background()) + if err != nil { + log.Fatalf("error installing Terraform: %s", err) + } } - resources := strings.Split(resourceType, ",") - for _, resourceType := range resources { - switch resourceType { - case "cloudflare_access_application": - jsonPayload, _, err := api.ListAccessApplications(context.Background(), identifier, cloudflare.ListAccessApplicationsParams{}) - if err != nil { - log.Fatal(err) + // Setup and configure Terraform to operate in the temporary directory where + // the provider is already configured. + log.Debugf("initializing Terraform in %s", workingDir) + tf, err := tfexec.NewTerraform(workingDir, execPath) + if err != nil { + log.Fatal(err) + } + + _, providerVersion, err := tf.Version(context.Background(), true) + providerVersionString = providerVersion[providerRegistryHostname+"/cloudflare/cloudflare"].String() + log.Debugf("detected provider version: %s", providerVersionString) + + var jsonStructData []interface{} + + if strings.HasPrefix(providerVersionString, "5") { + resources := strings.Split(resourceType, ",") + for _, resourceType := range resources { + var result *http.Response + + // by default, we want to use the `list` operation however, there are times + // when resources exist only as `get` operations but contain multiple + // resources. + endpoint := resourceToEndpoint[resourceType]["list"] + if endpoint == "" { + endpoint = resourceToEndpoint[resourceType]["get"] + } + + // if we encounter a combined endpoint, we need to rewrite to use the correct + // endpoint depending on what parameters are being provided. + if strings.Contains(endpoint, "{account_or_zone}") { + if accountID != "" { + endpoint = strings.Replace(endpoint, "/{account_or_zone}/{account_or_zone_id}/", "/accounts/{account_id}/", 1) + } else { + endpoint = strings.Replace(endpoint, "/{account_or_zone}/{account_or_zone_id}/", "/zones/{zone_id}/", 1) + } } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) + // replace the URL placeholders with the actual values we have. + placeholderReplacer := strings.NewReplacer("{account_id}", accountID, "{zone_id}", zoneID) + endpoint = placeholderReplacer.Replace(endpoint) + + client := cloudflare.NewClient() + + err := client.Get(context.Background(), endpoint, nil, &result) if err != nil { - log.Fatal(err) + var apierr *cloudflare.Error + if errors.As(err, &apierr) { + if apierr.StatusCode == http.StatusNotFound { + log.Debugf("no resources found at %s. skipping...", endpoint) + continue + } + } + log.Fatalf("failed to fetch API endpoint: %s", err) } - case "cloudflare_access_group": - jsonPayload, _, err := api.ListAccessGroups(context.Background(), identifier, cloudflare.ListAccessGroupsParams{}) + + body, err := io.ReadAll(result.Body) if err != nil { - log.Fatal(err) + log.Fatalln(err) } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) + value := gjson.Get(string(body), "result") + if value.Type == gjson.Null { + log.Debugf("no result found at %s. skipping...", endpoint) + continue + } + err = json.Unmarshal([]byte(value.String()), &jsonStructData) if err != nil { - log.Fatal(err) + log.Fatalf("failed to unmarshal result: %s", err) } - case "cloudflare_access_rule": - if accountID != "" { - jsonPayload, err := api.ListAccountAccessRules(context.Background(), accountID, cloudflare.AccessRule{}, 1) + } + } else { + var identifier *cfv0.ResourceContainer + if accountID != "" { + identifier = cfv0.AccountIdentifier(accountID) + } else { + identifier = cfv0.ZoneIdentifier(zoneID) + } + + resources := strings.Split(resourceType, ",") + for _, resourceType := range resources { + switch resourceType { + case "cloudflare_access_application": + jsonPayload, _, err := api.ListAccessApplications(context.Background(), identifier, cfv0.ListAccessApplicationsParams{}) if err != nil { log.Fatal(err) } - m, _ := json.Marshal(jsonPayload.Result) + m, _ := json.Marshal(jsonPayload) err = json.Unmarshal(m, &jsonStructData) if err != nil { log.Fatal(err) } - } else { - jsonPayload, err := api.ListZoneAccessRules(context.Background(), zoneID, cloudflare.AccessRule{}, 1) + case "cloudflare_access_group": + jsonPayload, _, err := api.ListAccessGroups(context.Background(), identifier, cfv0.ListAccessGroupsParams{}) if err != nil { log.Fatal(err) } - m, _ := json.Marshal(jsonPayload.Result) + m, _ := json.Marshal(jsonPayload) err = json.Unmarshal(m, &jsonStructData) if err != nil { log.Fatal(err) } - } - case "cloudflare_account_member": - jsonPayload, _, err := api.AccountMembers(context.Background(), accountID, cloudflare.PaginationOptions{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_argo": - jsonPayload := []cloudflare.ArgoFeatureSetting{{ - ID: fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String()))), - }} + case "cloudflare_access_rule": + if accountID != "" { + jsonPayload, err := api.ListAccountAccessRules(context.Background(), accountID, cfv0.AccessRule{}, 1) + if err != nil { + log.Fatal(err) + } + + m, _ := json.Marshal(jsonPayload.Result) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + } else { + jsonPayload, err := api.ListZoneAccessRules(context.Background(), zoneID, cfv0.AccessRule{}, 1) + if err != nil { + log.Fatal(err) + } + + m, _ := json.Marshal(jsonPayload.Result) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + } + case "cloudflare_account_member": + jsonPayload, _, err := api.AccountMembers(context.Background(), accountID, cfv0.PaginationOptions{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_argo": + jsonPayload := []cfv0.ArgoFeatureSetting{{ + ID: fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String()))), + }} - m, _ := json.Marshal(jsonPayload) - err := json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_bot_management": - botManagement, err := api.GetBotManagement(context.Background(), identifier) - if err != nil { - log.Fatal(err) - } - var jsonPayload []cloudflare.BotManagement - jsonPayload = append(jsonPayload, botManagement) + m, _ := json.Marshal(jsonPayload) + err := json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_bot_management": + botManagement, err := api.GetBotManagement(context.Background(), identifier) + if err != nil { + log.Fatal(err) + } + var jsonPayload []cfv0.BotManagement + jsonPayload = append(jsonPayload, botManagement) - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } - jsonStructData[0].(map[string]interface{})["id"] = zoneID - case "cloudflare_byo_ip_prefix": - jsonPayload, err := api.ListPrefixes(context.Background(), accountID) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_certificate_pack": - jsonPayload, err := api.ListCertificatePacks(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } + jsonStructData[0].(map[string]interface{})["id"] = zoneID + case "cloudflare_byo_ip_prefix": + jsonPayload, err := api.ListPrefixes(context.Background(), accountID) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_certificate_pack": + jsonPayload, err := api.ListCertificatePacks(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } - var customerManagedCertificates []cloudflare.CertificatePack - for _, r := range jsonPayload { - if r.Type != "universal" { - customerManagedCertificates = append(customerManagedCertificates, r) + var customerManagedCertificates []cfv0.CertificatePack + for _, r := range jsonPayload { + if r.Type != "universal" { + customerManagedCertificates = append(customerManagedCertificates, r) + } } - } - jsonPayload = customerManagedCertificates + jsonPayload = customerManagedCertificates - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_custom_pages": - if accountID != "" { - jsonPayload, err := api.CustomPages(context.Background(), &cloudflare.CustomPageOptions{AccountID: accountID}) + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_custom_pages": + if accountID != "" { + jsonPayload, err := api.CustomPages(context.Background(), &cfv0.CustomPageOptions{AccountID: accountID}) + if err != nil { + log.Fatal(err) + } + + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + } else { + jsonPayload, err := api.CustomPages(context.Background(), &cfv0.CustomPageOptions{ZoneID: zoneID}) + if err != nil { + log.Fatal(err) + } + + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + } + case "cloudflare_filter": + jsonPayload, _, err := api.Filters(context.Background(), identifier, cfv0.FilterListParams{}) if err != nil { log.Fatal(err) } - m, _ := json.Marshal(jsonPayload) err = json.Unmarshal(m, &jsonStructData) if err != nil { log.Fatal(err) } - } else { - jsonPayload, err := api.CustomPages(context.Background(), &cloudflare.CustomPageOptions{ZoneID: zoneID}) + case "cloudflare_firewall_rule": + jsonPayload, _, err := api.FirewallRules(context.Background(), identifier, cfv0.FirewallRuleListParams{}) if err != nil { log.Fatal(err) } - m, _ := json.Marshal(jsonPayload) err = json.Unmarshal(m, &jsonStructData) if err != nil { log.Fatal(err) } - } - case "cloudflare_filter": - jsonPayload, _, err := api.Filters(context.Background(), identifier, cloudflare.FilterListParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_firewall_rule": - jsonPayload, _, err := api.FirewallRules(context.Background(), identifier, cloudflare.FirewallRuleListParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_healthcheck": - jsonPayload, err := api.Healthchecks(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_custom_hostname": - jsonPayload, _, err := api.CustomHostnames(context.Background(), zoneID, 1, cloudflare.CustomHostname{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_custom_ssl": - jsonPayload, err := api.ListSSL(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } + case "cloudflare_healthcheck": + jsonPayload, err := api.Healthchecks(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_custom_hostname": + jsonPayload, _, err := api.CustomHostnames(context.Background(), zoneID, 1, cfv0.CustomHostname{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_custom_ssl": + jsonPayload, err := api.ListSSL(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_ip_list": - jsonPayload, err := api.ListIPLists(context.Background(), accountID) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_load_balancer": - jsonPayload, err := api.ListLoadBalancers(context.Background(), identifier, cloudflare.ListLoadBalancerParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_load_balancer_pool": - jsonPayload, err := api.ListLoadBalancerPools(context.Background(), identifier, cloudflare.ListLoadBalancerPoolParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_load_balancer_monitor": - jsonPayload, err := api.ListLoadBalancerMonitors(context.Background(), identifier, cloudflare.ListLoadBalancerMonitorParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_logpush_job": - jsonPayload, err := api.ListLogpushJobs(context.Background(), identifier, cloudflare.ListLogpushJobsParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_origin_ca_certificate": - jsonPayload, err := api.ListOriginCACertificates(context.Background(), cloudflare.ListOriginCertificatesParams{ZoneID: zoneID}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_ip_list": + jsonPayload, err := api.ListIPLists(context.Background(), accountID) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_load_balancer": + jsonPayload, err := api.ListLoadBalancers(context.Background(), identifier, cfv0.ListLoadBalancerParams{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_load_balancer_pool": + jsonPayload, err := api.ListLoadBalancerPools(context.Background(), identifier, cfv0.ListLoadBalancerPoolParams{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_load_balancer_monitor": + jsonPayload, err := api.ListLoadBalancerMonitors(context.Background(), identifier, cfv0.ListLoadBalancerMonitorParams{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_logpush_job": + jsonPayload, err := api.ListLogpushJobs(context.Background(), identifier, cfv0.ListLogpushJobsParams{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_origin_ca_certificate": + jsonPayload, err := api.ListOriginCACertificates(context.Background(), cfv0.ListOriginCertificatesParams{ZoneID: zoneID}) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_page_rule": - jsonPayload, err := api.ListPageRules(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_page_rule": + jsonPayload, err := api.ListPageRules(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_rate_limit": - jsonPayload, err := api.ListAllRateLimits(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_rate_limit": + jsonPayload, err := api.ListAllRateLimits(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_record": - jsonPayload, _, err := api.ListDNSRecords(context.Background(), identifier, cloudflare.ListDNSRecordsParams{}) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_ruleset": - jsonPayload, err := api.ListRulesets(context.Background(), identifier, cloudflare.ListRulesetsParams{}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_record": + jsonPayload, _, err := api.ListDNSRecords(context.Background(), identifier, cfv0.ListDNSRecordsParams{}) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_ruleset": + jsonPayload, err := api.ListRulesets(context.Background(), identifier, cfv0.ListRulesetsParams{}) + if err != nil { + log.Fatal(err) + } - // Customers can read-only Managed Rulesets, so we don't want to - // have them try to import something they can't manage with terraform - var nonManagedRules []cloudflare.Ruleset - for _, r := range jsonPayload { - if r.Kind != string(cloudflare.RulesetKindManaged) { - nonManagedRules = append(nonManagedRules, r) + // Customers can read-only Managed Rulesets, so we don't want to + // have them try to import something they can't manage with terraform + var nonManagedRules []cfv0.Ruleset + for _, r := range jsonPayload { + if r.Kind != string(cfv0.RulesetKindManaged) { + nonManagedRules = append(nonManagedRules, r) + } } - } - m, _ := json.Marshal(nonManagedRules) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_spectrum_application": - jsonPayload, err := api.SpectrumApplications(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(nonManagedRules) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_spectrum_application": + jsonPayload, err := api.SpectrumApplications(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_teams_list": - jsonPayload, _, err := api.ListTeamsLists(context.Background(), identifier, cloudflare.ListTeamListsParams{}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_teams_list": + jsonPayload, _, err := api.ListTeamsLists(context.Background(), identifier, cfv0.ListTeamListsParams{}) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_teams_location": - jsonPayload, _, err := api.TeamsLocations(context.Background(), accountID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_teams_location": + jsonPayload, _, err := api.TeamsLocations(context.Background(), accountID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_teams_proxy_endpoint": - jsonPayload, _, err := api.TeamsProxyEndpoints(context.Background(), accountID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_teams_proxy_endpoint": + jsonPayload, _, err := api.TeamsProxyEndpoints(context.Background(), accountID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_teams_rule": - jsonPayload, err := api.TeamsRules(context.Background(), accountID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_teams_rule": + jsonPayload, err := api.TeamsRules(context.Background(), accountID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_tunnel": - log.Debug("only requesting the first 1000 active Cloudflare Tunnels due to the service not providing correct pagination responses") - jsonPayload, _, err := api.ListTunnels( - context.Background(), - cloudflare.AccountIdentifier(accountID), - cloudflare.TunnelListParams{ - IsDeleted: cloudflare.BoolPtr(false), - ResultInfo: cloudflare.ResultInfo{ - PerPage: 1000, - Page: 1, - }, - }) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_tunnel": + log.Debug("only requesting the first 1000 active Cloudflare Tunnels due to the service not providing correct pagination responses") + jsonPayload, _, err := api.ListTunnels( + context.Background(), + cfv0.AccountIdentifier(accountID), + cfv0.TunnelListParams{ + IsDeleted: cfv0.BoolPtr(false), + ResultInfo: cfv0.ResultInfo{ + PerPage: 1000, + Page: 1, + }, + }) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_turnstile_widget": - jsonPayload, _, err := api.ListTurnstileWidgets(context.Background(), identifier, cloudflare.ListTurnstileWidgetParams{}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_turnstile_widget": + jsonPayload, _, err := api.ListTurnstileWidgets(context.Background(), identifier, cfv0.ListTurnstileWidgetParams{}) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - for i := 0; i < len(jsonStructData); i++ { - jsonStructData[i].(map[string]interface{})["id"] = jsonStructData[i].(map[string]interface{})["sitekey"] - } - case "cloudflare_waf_override": - jsonPayload, err := api.ListWAFOverrides(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + for i := 0; i < len(jsonStructData); i++ { + jsonStructData[i].(map[string]interface{})["id"] = jsonStructData[i].(map[string]interface{})["sitekey"] + } + case "cloudflare_waf_override": + jsonPayload, err := api.ListWAFOverrides(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_waf_package": - jsonPayload, err := api.ListWAFPackages(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_waiting_room": - jsonPayload, err := api.ListWaitingRooms(context.Background(), zoneID) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_workers_kv_namespace": - jsonPayload, _, err := api.ListWorkersKVNamespaces(context.Background(), identifier, cloudflare.ListWorkersKVNamespacesParams{}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_waf_package": + jsonPayload, err := api.ListWAFPackages(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_waiting_room": + jsonPayload, err := api.ListWaitingRooms(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_workers_kv_namespace": + jsonPayload, _, err := api.ListWorkersKVNamespaces(context.Background(), identifier, cfv0.ListWorkersKVNamespacesParams{}) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_worker_route": - jsonPayload, err := api.ListWorkerRoutes(context.Background(), identifier, cloudflare.ListWorkerRoutesParams{}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_worker_route": + jsonPayload, err := api.ListWorkerRoutes(context.Background(), identifier, cfv0.ListWorkerRoutesParams{}) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload.Routes) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_zone": - jsonPayload, err := api.ListZones(context.Background()) - if err != nil { - log.Fatal(err) - } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) - } - case "cloudflare_zone_lockdown": - jsonPayload, _, err := api.ListZoneLockdowns(context.Background(), identifier, cloudflare.LockdownListParams{}) - if err != nil { - log.Fatal(err) - } + m, _ := json.Marshal(jsonPayload.Routes) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_zone": + jsonPayload, err := api.ListZones(context.Background()) + if err != nil { + log.Fatal(err) + } + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + case "cloudflare_zone_lockdown": + jsonPayload, _, err := api.ListZoneLockdowns(context.Background(), identifier, cfv0.LockdownListParams{}) + if err != nil { + log.Fatal(err) + } - m, _ := json.Marshal(jsonPayload) - err = json.Unmarshal(m, &jsonStructData) - if err != nil { - log.Fatal(err) + m, _ := json.Marshal(jsonPayload) + err = json.Unmarshal(m, &jsonStructData) + if err != nil { + log.Fatal(err) + } + default: + fmt.Fprintf(cmd.OutOrStderr(), "%q is not yet supported for state import", resourceType) + return } - default: - fmt.Fprintf(cmd.OutOrStderr(), "%q is not yet supported for state import", resourceType) - return } + } - importFile := hclwrite.NewEmptyFile() - importBody := importFile.Body() - - for _, data := range jsonStructData { - id := data.(map[string]interface{})["id"].(string) + importFile := hclwrite.NewEmptyFile() + importBody := importFile.Body() - 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)) - } - } + for _, data := range jsonStructData { + id := data.(map[string]interface{})["id"].(string) 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())) + idvalue := buildRawImportAddress(resourceType, id, resourceToEndpoint[resourceType]["get"]) + 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, resourceToEndpoint[resourceType]["get"])) } } + + 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())) + } } } // 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) +// +// Note: `endpoint` is only used on > v4. Otherwise it is ignored. +func buildTerraformImportCommand(resourceType, resourceID, endpoint string) string { + resourceImportAddress := buildRawImportAddress(resourceType, resourceID, endpoint) 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) - } +// +// Note: `endpoint` is only used on > v4. Otherwise it is ignored. +func buildRawImportAddress(resourceType, resourceID, endpoint string) string { + if strings.HasPrefix(providerVersionString, "5") { + prefix := "" + if strings.Contains(endpoint, "{account_or_zone}") { + if accountID != "" { + prefix = "accounts" + endpoint = strings.Replace(endpoint, "/{account_or_zone}/{account_or_zone_id}/", "/accounts/{account_id}/", 1) + } else { + prefix = "zones" + endpoint = strings.Replace(endpoint, "/{account_or_zone}/{account_or_zone_id}/", "/zones/{zone_id}/", 1) + } + } - var identiferType string - var identiferValue string + r, _ := regexp.Compile("({[a-z0-9_]*})") + matches := r.FindAllString(endpoint, -1) - if accountID != "" { - identiferType = "account" - identiferValue = accountID + if len(matches) == 1 { + matches[0] = resourceID + } else { + matches[1] = resourceID + } + + output := strings.Join(matches, "/") + + replacer := strings.NewReplacer( + "{account_id}", accountID, + "{zone_id}", zoneID, + ) + + if prefix != "" { + output = prefix + "/" + output + } + + return replacer.Replace(output) } else { - identiferType = "zone" - identiferValue = zoneID - } + if _, ok := resourceImportStringFormats[resourceType]; !ok { + log.Fatalf("%s does not have an import format defined", resourceType) + } - s := resourceImportStringFormats[resourceType] - replacer := strings.NewReplacer( - ":identifier_type", identiferType, - ":identifier_value", identiferValue, - ":zone_id", zoneID, - ":account_id", accountID, - ":id", resourceID, - ) + var identiferType string + var identiferValue string - return replacer.Replace(s) + if accountID != "" { + identiferType = "account" + identiferValue = accountID + } else { + identiferType = "zone" + identiferValue = zoneID + } + + s := resourceImportStringFormats[resourceType] + replacer := strings.NewReplacer( + ":identifier_type", identiferType, + ":identifier_value", identiferValue, + ":zone_id", zoneID, + ":account_id", accountID, + ":id", resourceID, + ) + + return replacer.Replace(s) + } }