diff --git a/cloudflare/provider.go b/cloudflare/provider.go index a646c8ca28..0d7711a0ad 100644 --- a/cloudflare/provider.go +++ b/cloudflare/provider.go @@ -113,6 +113,7 @@ func Provider() terraform.ResourceProvider { "cloudflare_authenticated_origin_pulls": resourceCloudflareAuthenticatedOriginPulls(), "cloudflare_authenticated_origin_pulls_certificate": resourceCloudflareAuthenticatedOriginPullsCertificate(), "cloudflare_byo_ip_prefix": resourceCloudflareBYOIPPrefix(), + "cloudflare_certificate_pack": resourceCloudflareCertificatePack(), "cloudflare_custom_hostname": resourceCloudflareCustomHostname(), "cloudflare_custom_hostname_fallback_origin": resourceCloudflareCustomHostnameFallbackOrigin(), "cloudflare_custom_pages": resourceCloudflareCustomPages(), diff --git a/cloudflare/resource_cloudflare_certificate_pack.go b/cloudflare/resource_cloudflare_certificate_pack.go new file mode 100644 index 0000000000..3ff8ddc312 --- /dev/null +++ b/cloudflare/resource_cloudflare_certificate_pack.go @@ -0,0 +1,161 @@ +package cloudflare + +import ( + "fmt" + "log" + "strings" + + cloudflare "github.com/cloudflare/cloudflare-go" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/pkg/errors" +) + +func resourceCloudflareCertificatePack() *schema.Resource { + return &schema.Resource{ + // Intentionally no Update method as certificates require replacement for + // any changes made. + Create: resourceCloudflareCertificatePackCreate, + Read: resourceCloudflareCertificatePackRead, + Delete: resourceCloudflareCertificatePackDelete, + Importer: &schema.ResourceImporter{ + State: resourceCloudflareCertificatePackImport, + }, + + Schema: map[string]*schema.Schema{ + "zone_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"custom", "dedicated_custom", "advanced"}, false), + }, + "hosts": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "validation_method": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"txt", "http", "email"}, false), + }, + "validity_days": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntInSlice([]int{14, 30, 90, 365}), + }, + "certificate_authority": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"digicert", "lets_encrypt"}, false), + }, + "cloudflare_branding": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceCloudflareCertificatePackCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.API) + zoneID := d.Get("zone_id").(string) + certificatePackType := d.Get("type").(string) + certificateHostnames := expandInterfaceToStringList(d.Get("hosts").([]interface{})) + certificatePackID := "" + + if certificatePackType == "advanced" { + validationMethod := d.Get("validation_method").(string) + validityDays := d.Get("validity_days").(int) + ca := d.Get("certificate_authority").(string) + cloudflareBranding := d.Get("cloudflare_branding").(bool) + + cert := cloudflare.CertificatePackAdvancedCertificate{ + Type: "advanced", + Hosts: certificateHostnames, + ValidationMethod: validationMethod, + ValidityDays: validityDays, + CertificateAuthority: ca, + CloudflareBranding: cloudflareBranding, + } + certPackResponse, err := client.CreateAdvancedCertificatePack(zoneID, cert) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to create certificate pack: %s", err)) + } + certificatePackID = certPackResponse.ID + } else { + cert := cloudflare.CertificatePackRequest{ + Type: certificatePackType, + Hosts: certificateHostnames, + } + certPackResponse, err := client.CreateCertificatePack(zoneID, cert) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to create certificate pack: %s", err)) + } + certificatePackID = certPackResponse.ID + } + + d.SetId(certificatePackID) + + return resourceCloudflareCertificatePackRead(d, meta) +} + +func resourceCloudflareCertificatePackRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.API) + zoneID := d.Get("zone_id").(string) + + certificatePack, err := client.CertificatePack(zoneID, d.Id()) + if err != nil { + return errors.Wrap(err, "failed to fetch certificate pack") + } + + d.Set("type", certificatePack.Type) + d.Set("hosts", flattenStringList(certificatePack.Hosts)) + + return nil +} + +func resourceCloudflareCertificatePackDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.API) + zoneID := d.Get("zone_id").(string) + + err := client.DeleteCertificatePack(zoneID, d.Id()) + if err != nil { + return errors.Wrap(err, "failed to delete certificate pack") + } + + resourceCloudflareCertificatePackRead(d, meta) + + return nil +} + +func resourceCloudflareCertificatePackImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + attributes := strings.SplitN(d.Id(), "/", 2) + + if len(attributes) != 2 { + return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"zoneID/certificatePackID\"", d.Id()) + } + + zoneID, certificatePackID := attributes[0], attributes[1] + + log.Printf("[DEBUG] Importing Cloudflare Certificate Pack: id %s for zone %s", certificatePackID, zoneID) + + d.Set("zone_id", zoneID) + d.SetId(certificatePackID) + + resourceCloudflareCertificatePackRead(d, meta) + + return []*schema.ResourceData{d}, nil +} diff --git a/cloudflare/resource_cloudflare_certificate_pack_test.go b/cloudflare/resource_cloudflare_certificate_pack_test.go new file mode 100644 index 0000000000..9cb2ce949c --- /dev/null +++ b/cloudflare/resource_cloudflare_certificate_pack_test.go @@ -0,0 +1,130 @@ +package cloudflare + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccCertificatePackAdvancedDigicert(t *testing.T) { + rnd := generateRandomResourceName() + name := "cloudflare_certificate_pack." + rnd + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + domain := os.Getenv("CLOUDFLARE_DOMAIN") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCertificatePackAdvancedDigicertConfig(zoneID, domain, "advanced", rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "type", "advanced"), + resource.TestCheckResourceAttr(name, "hosts.0", fmt.Sprintf("%s.%s", rnd, domain)), + resource.TestCheckResourceAttr(name, "hosts.1", domain), + resource.TestCheckResourceAttr(name, "validation_method", "http"), + resource.TestCheckResourceAttr(name, "validity_days", "365"), + resource.TestCheckResourceAttr(name, "certificate_authority", "digicert"), + resource.TestCheckResourceAttr(name, "cloudflare_branding", "false"), + ), + }, + }, + }) +} + +func testAccCertificatePackAdvancedDigicertConfig(zoneID, domain, certType, rnd string) string { + return fmt.Sprintf(` +resource "cloudflare_certificate_pack" "%[3]s" { + zone_id = "%[1]s" + type = "%[4]s" + hosts = [ + "%[3]s.%[2]s", + "%[2]s" + ] + validation_method = "http" + validity_days = 365 + certificate_authority = "digicert" + cloudflare_branding = false +}`, zoneID, domain, rnd, certType) +} + +func TestAccCertificatePackAdvancedLetsEncrypt(t *testing.T) { + rnd := generateRandomResourceName() + name := "cloudflare_certificate_pack." + rnd + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + domain := os.Getenv("CLOUDFLARE_DOMAIN") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCertificatePackAdvancedLetsEncryptConfig(zoneID, domain, "advanced", rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "type", "advanced"), + resource.TestCheckResourceAttr(name, "hosts.0", fmt.Sprintf("*.%s", domain)), + resource.TestCheckResourceAttr(name, "hosts.1", domain), + resource.TestCheckResourceAttr(name, "validation_method", "txt"), + resource.TestCheckResourceAttr(name, "validity_days", "90"), + resource.TestCheckResourceAttr(name, "certificate_authority", "lets_encrypt"), + resource.TestCheckResourceAttr(name, "cloudflare_branding", "false"), + ), + }, + }, + }) +} + +func testAccCertificatePackAdvancedLetsEncryptConfig(zoneID, domain, certType, rnd string) string { + return fmt.Sprintf(` +resource "cloudflare_certificate_pack" "%[3]s" { + zone_id = "%[1]s" + type = "%[4]s" + hosts = [ + "*.%[2]s", + "%[2]s" + ] + validation_method = "txt" + validity_days = 90 + certificate_authority = "lets_encrypt" + cloudflare_branding = false +}`, zoneID, domain, rnd, certType) +} + +func TestAccCertificatePackDedicatedCustom(t *testing.T) { + rnd := generateRandomResourceName() + name := "cloudflare_certificate_pack." + rnd + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + domain := os.Getenv("CLOUDFLARE_DOMAIN") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCertificatePackDedicatedCustomConfig(zoneID, domain, "dedicated_custom", rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "type", "dedicated_custom"), + resource.TestCheckResourceAttr(name, "hosts.0", fmt.Sprintf("%s.%s", rnd, domain)), + resource.TestCheckResourceAttr(name, "hosts.1", domain), + ), + }, + }, + }) +} + +func testAccCertificatePackDedicatedCustomConfig(zoneID, domain, certType, rnd string) string { + return fmt.Sprintf(` +resource "cloudflare_certificate_pack" "%[3]s" { + zone_id = "%[1]s" + type = "%[4]s" + hosts = [ + "%[3]s.%[2]s", + "%[2]s" + ] +}`, zoneID, domain, rnd, certType) +} diff --git a/website/cloudflare.erb b/website/cloudflare.erb index 806eedb21e..77da4d859f 100644 --- a/website/cloudflare.erb +++ b/website/cloudflare.erb @@ -70,6 +70,9 @@