Skip to content

Commit

Permalink
Merge pull request #313 from heroku/small-tweaks-to-ssl
Browse files Browse the repository at this point in the history
Improvements to `heroku_ssl` resource
  • Loading branch information
davidji99 authored Jun 22, 2021
2 parents ff745fe + 0ce215a commit bd878ad
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 80 deletions.
6 changes: 4 additions & 2 deletions docs/resources/cert.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ layout: "heroku"
page_title: "Heroku: heroku_cert"
sidebar_current: "docs-heroku-resource-cert"
description: |-
Provides a Heroku SSL certificate resource. It allows to set a given certificate for a Heroku app. This resource is deprecated in favor of `heroku_ssl`.
Provides a Heroku SSL certificate resource to manage a certificate for a Heroku app.
---

# heroku\_cert

Provides a Heroku SSL certificate resource. It allows to set a given certificate for a Heroku app.This resource is deprecated in favor of `heroku_ssl`.
This resource manages an SSL certificate for a Heroku app.

!> **WARNING:** This resource is deprecated in favor of `heroku_ssl`.

## Example Usage

Expand Down
26 changes: 18 additions & 8 deletions docs/resources/ssl.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ layout: "heroku"
page_title: "Heroku: heroku_ssl"
sidebar_current: "docs-heroku-resource-ssl"
description: |-
Provides a Heroku SSL certificate resource. It allows to set a given certificate for a domain on a Heroku app.
Provides a Heroku SSL certificate resource to manage a certificate for a Heroku app.
---

# heroku\_ssl

Provides a Heroku SSL certificate resource. It allows to set a given certificate for a domain on a Heroku app.
This resource manages an SSL certificate for a Heroku app.

-> **IMPORTANT!**
This resource renders the "private_key" attribute in plain-text in your state file.
Please ensure that your state file is properly secured and encrypted at rest.

## Example Usage

Expand All @@ -33,15 +37,17 @@ resource "heroku_formation" "web" {
type = "web"
size = "hobby"
quantity = 1
# Wait until the build has completed before attempting to scale
depends_on = [heroku_build.default]
}
# Create the certificate
resource "heroku_ssl" "one" {
app = heroku_app.default.name
app_id = heroku_app.default.uuid
certificate_chain = file("server.crt")
private_key = file("server.key")
# Wait until the process_tier changes to hobby before attempting to create a cert
depends_on = [heroku_formation.web]
}
Expand All @@ -55,7 +61,7 @@ resource "heroku_domain" "one" {
# Create another certificate
resource "heroku_ssl" "two" {
app = heroku_app.default.name
app_id = heroku_app.default.uuid
certificate_chain = file("server.crt")
private_key = file("server.key")
# Wait until the process_tier changes to hobby before attempting to create a cert
Expand All @@ -74,9 +80,11 @@ resource "heroku_domain" "two" {

The following arguments are supported:

* `app` - (Required) The Heroku app to add to.
* `certificate_chain` - (Required) The certificate chain to add
* `private_key` - (Required) The private key for a given certificate chain
* `app_id` - (Required) The Heroku app UUID to add to.
* `certificate_chain` - (Required) The certificate chain to add.
* `private_key` - (Optional) The private key for a given certificate chain. You **must** set this attribute when creating or
updating an SSL resource. However, **do not** set a value for this attribute if you are initially importing an existing
SSL resource. The attribute value does not get displayed in logs or regular output.

## Attributes Reference

Expand All @@ -87,7 +95,9 @@ The following attributes are exported:

## Importing

When importing a Heroku ssl resource, the ID must be built using the app name colon the unique ID from the Heroku API. For an app named `production-api` with a certificate ID of `b85d9224-310b-409b-891e-c903f5a40568`, you would import it as:
An existing SSL resource can be imported using a composite value of the app name and certificate UUID separated by a colon.

For example:

```
$ terraform import heroku_ssl.production_api production-api:b85d9224-310b-409b-891e-c903f5a40568
Expand Down
8 changes: 5 additions & 3 deletions heroku/import_heroku_ssl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ func TestAccHerokuSSL_importBasic(t *testing.T) {
Config: testAccCheckHerokuSSLConfig(appName, certFile, keyFile),
},
{
ResourceName: "heroku_ssl.one",
ImportStateIdPrefix: appName + ":",
ImportState: true,
ResourceName: "heroku_ssl.one",
ImportStateIdPrefix: appName + ":",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"private_key"},
},
},
})
Expand Down
8 changes: 4 additions & 4 deletions heroku/resource_heroku_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ resource "heroku_formation" "web" {
}
resource "heroku_ssl" "one" {
app = "${heroku_app.one.id}"
app_id = heroku_app.one.uuid
certificate_chain="${file("%s")}"
private_key="${file("%s")}"
# Wait until the process_tier changes to hobby before attempting to create a cert
Expand Down Expand Up @@ -262,14 +262,14 @@ resource "heroku_formation" "web" {
}
resource "heroku_ssl" "one" {
app = "${heroku_app.one.id}"
app_id = heroku_app.one.uuid
certificate_chain="${file("%s")}"
private_key="${file("%s")}"
depends_on = [heroku_formation.web]
}
resource "heroku_ssl" "two" {
app = "${heroku_app.one.id}"
app_id = heroku_app.one.uuid
certificate_chain="${file("%s")}"
private_key="${file("%s")}"
depends_on = [heroku_formation.web]
Expand Down Expand Up @@ -314,7 +314,7 @@ resource "heroku_formation" "web" {
}
resource "heroku_ssl" "one" {
app = "${heroku_app.one.id}"
app_id = heroku_app.one.uuid
certificate_chain="${file("%s")}"
private_key="${file("%s")}"
depends_on = [heroku_formation.web]
Expand Down
2 changes: 1 addition & 1 deletion heroku/resource_heroku_pipeline_config_var.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func resourceHerokuPipelineConfigVar() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateUUID,
ValidateFunc: validation.IsUUID,
},

"pipeline_stage": {
Expand Down
2 changes: 1 addition & 1 deletion heroku/resource_heroku_pipeline_coupling.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func resourceHerokuPipelineCoupling() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateUUID,
ValidateFunc: validation.IsUUID,
},
"stage": {
Type: schema.TypeString,
Expand Down
99 changes: 51 additions & 48 deletions heroku/resource_heroku_ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package heroku

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"log"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -11,20 +12,23 @@ import (

func resourceHerokuSSL() *schema.Resource {
return &schema.Resource{
Create: resourceHerokuSSLCreate,
Read: resourceHerokuSSLRead,
Update: resourceHerokuSSLUpdate,
Delete: resourceHerokuSSLDelete,
DeprecationMessage: "This resource is deprecated in favor of `heroku_ssl`.",

CreateContext: resourceHerokuSSLCreate,
ReadContext: resourceHerokuSSLRead,
UpdateContext: resourceHerokuSSLUpdate,
DeleteContext: resourceHerokuSSLDelete,

Importer: &schema.ResourceImporter{
State: resourceHerokuSSLImport,
},

Schema: map[string]*schema.Schema{
"app": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
"app_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.IsUUID,
},

"certificate_chain": {
Expand All @@ -34,8 +38,8 @@ func resourceHerokuSSL() *schema.Resource {

"private_key": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
Optional: true, // This should be 'Required' using 'Optional' to make things easier during resource import.
},

"name": {
Expand All @@ -49,101 +53,100 @@ func resourceHerokuSSL() *schema.Resource {
func resourceHerokuSSLImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
client := meta.(*Config).Api

app, id, err := parseCompositeID(d.Id())
app, certID, err := parseCompositeID(d.Id())
if err != nil {
return nil, err
}

ep, err := client.SniEndpointInfo(context.Background(), app, id)
ep, err := client.SniEndpointInfo(context.Background(), app, certID)
if err != nil {
return nil, err
}

d.SetId(ep.ID)
setErr := d.Set("app", app)
if setErr != nil {
return nil, setErr
}
d.Set("app_id", ep.App.ID)
d.Set("certificate_chain", ep.CertificateChain)
d.Set("name", ep.Name)

return []*schema.ResourceData{d}, nil
}

func resourceHerokuSSLCreate(d *schema.ResourceData, meta interface{}) error {
func resourceHerokuSSLCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Config).Api
app := d.Get("app").(string)
appID := getAppId(d)

opts := heroku.SniEndpointCreateOpts{
CertificateChain: d.Get("certificate_chain").(string),
PrivateKey: d.Get("private_key").(string),
}

log.Printf("[DEBUG] SSL Certificate create configuration: %#v, %#v", app, opts)
a, err := client.SniEndpointCreate(context.TODO(), app, opts)
log.Printf("[DEBUG] Creating SSL certificate for app %#v", appID)

ep, err := client.SniEndpointCreate(context.TODO(), appID, opts)
if err != nil {
return fmt.Errorf("Error creating SniEndpoint: %s", err)
return diag.Errorf("Error creating SSL certificate for app %s: %v", appID, err.Error())
}

d.SetId(a.ID)
log.Printf("[INFO] SSL Certificate ID: %s", d.Id())
log.Printf("[DEBUG] Created SSL Certificate %s", ep.ID)

return resourceHerokuSSLRead(d, meta)
d.SetId(ep.ID)

return resourceHerokuSSLRead(ctx, d, meta)
}

func resourceHerokuSSLRead(d *schema.ResourceData, meta interface{}) error {
func resourceHerokuSSLRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Config).Api

cert, err := resourceHerokuSSLRetrieve(d.Get("app").(string), d.Id(), client)
ep, err := client.SniEndpointInfo(context.Background(), getAppId(d), d.Id())
if err != nil {
return err
return diag.FromErr(err)
}

d.Set("certificate_chain", cert.CertificateChain)
d.Set("name", cert.Name)
d.Set("app_id", ep.App.ID)
d.Set("certificate_chain", ep.CertificateChain)
d.Set("name", ep.Name)
// TODO: need to add d.Set("private_key")

return nil
}

func resourceHerokuSSLUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceHerokuSSLUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Config).Api

app := d.Get("app").(string)
appID := getAppId(d)

if d.HasChange("certificate_chain") || d.HasChange("private_key") {
opts := heroku.SniEndpointUpdateOpts{
CertificateChain: d.Get("certificate_chain").(string),
PrivateKey: d.Get("private_key").(string),
}

log.Printf("[DEBUG] SSL Certificate update configuration: %#v, %#v", app, opts)
_, err := client.SniEndpointUpdate(context.TODO(), app, d.Id(), opts)
log.Printf("[DEBUG] Updating SSL Certificate configuration: %#v, %#v", appID, opts)

_, err := client.SniEndpointUpdate(context.TODO(), appID, d.Id(), opts)
if err != nil {
return fmt.Errorf("Error updating Sni endpoint: %s", err)
return diag.Errorf("Error updating Sni endpoint: %s", err)
}

log.Printf("[DEBUG] Updated SSL Certificate configuration: %#v, %#v", appID, opts)
}

return resourceHerokuSSLRead(d, meta)
return resourceHerokuSSLRead(ctx, d, meta)
}

func resourceHerokuSSLDelete(d *schema.ResourceData, meta interface{}) error {
func resourceHerokuSSLDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Config).Api

log.Printf("[INFO] Deleting SSL Cert: %s", d.Id())

_, err := client.SniEndpointDelete(context.TODO(), d.Get("app").(string), d.Id())
_, err := client.SniEndpointDelete(context.TODO(), getAppId(d), d.Id())
if err != nil {
return fmt.Errorf("Error deleting SSL Cert: %s", err)
return diag.Errorf("Error deleting SSL Cert: %s", err)
}

d.SetId("")
return nil
}

func resourceHerokuSSLRetrieve(app string, id string, client *heroku.Service) (*heroku.SniEndpoint, error) {
endpoint, err := client.SniEndpointInfo(context.TODO(), app, id)
log.Printf("[INFO] Deleted SSL Cert: %s", d.Id())

if err != nil {
return nil, fmt.Errorf("Error retrieving SSL Cert: %s", err)
}
d.SetId("")

return endpoint, nil
return nil
}
Loading

0 comments on commit bd878ad

Please sign in to comment.