Merge pull request #672 from plan3-labs/heroku_cert_support

Heroku SSL certificate support
This commit is contained in:
Armon Dadgar 2014-12-15 12:12:57 -08:00
commit f923368fc5
6 changed files with 348 additions and 0 deletions

View File

@ -30,6 +30,7 @@ func Provider() terraform.ResourceProvider {
"heroku_addon": resourceHerokuAddon(), "heroku_addon": resourceHerokuAddon(),
"heroku_domain": resourceHerokuDomain(), "heroku_domain": resourceHerokuDomain(),
"heroku_drain": resourceHerokuDrain(), "heroku_drain": resourceHerokuDrain(),
"heroku_cert": resourceHerokuCert(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

View File

@ -0,0 +1,134 @@
package heroku
import (
"fmt"
"log"
"github.com/cyberdelia/heroku-go/v3"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceHerokuCert() *schema.Resource {
return &schema.Resource{
Create: resourceHerokuCertCreate,
Read: resourceHerokuCertRead,
Update: resourceHerokuCertUpdate,
Delete: resourceHerokuCertDelete,
Schema: map[string]*schema.Schema{
"app": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"certificate_chain": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"private_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"cname": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceHerokuCertCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
app := d.Get("app").(string)
preprocess := true
opts := heroku.SSLEndpointCreateOpts{
CertificateChain: d.Get("certificate_chain").(string),
Preprocess: &preprocess,
PrivateKey: d.Get("private_key").(string)}
log.Printf("[DEBUG] SSL Certificate create configuration: %#v, %#v", app, opts)
a, err := client.SSLEndpointCreate(app, opts)
if err != nil {
panic(err)
}
d.SetId(a.ID)
log.Printf("[INFO] SSL Certificate ID: %s", d.Id())
return resourceHerokuCertRead(d, meta)
}
func resourceHerokuCertRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
cert, err := resourceHerokuSSLCertRetrieve(
d.Get("app").(string), d.Id(), client)
if err != nil {
return err
}
d.Set("certificate_chain", cert.CertificateChain)
d.Set("name", cert.Name)
d.Set("cname", cert.CName)
return nil
}
func resourceHerokuCertUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
app := d.Get("app").(string)
if d.HasChange("certificate_chain") {
preprocess := true
rollback := false
ad, err := client.SSLEndpointUpdate(
app, d.Id(), heroku.SSLEndpointUpdateOpts{
CertificateChain: d.Get("certificate_chain").(*string),
Preprocess: &preprocess,
PrivateKey: d.Get("private_key").(*string),
Rollback: &rollback})
if err != nil {
return err
}
// Store the new ID
d.SetId(ad.ID)
}
return resourceHerokuCertRead(d, meta)
}
func resourceHerokuCertDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
log.Printf("[INFO] Deleting SSL Cert: %s", d.Id())
// Destroy the app
err := client.SSLEndpointDelete(d.Get("app").(string), d.Id())
if err != nil {
return fmt.Errorf("Error deleting SSL Cert: %s", err)
}
d.SetId("")
return nil
}
func resourceHerokuSSLCertRetrieve(app string, id string, client *heroku.Service) (*heroku.SSLEndpoint, error) {
addon, err := client.SSLEndpointInfo(app, id)
if err != nil {
return nil, fmt.Errorf("Error retrieving SSL Cert: %s", err)
}
return addon, nil
}

View File

@ -0,0 +1,116 @@
package heroku
import (
"fmt"
"testing"
"os"
"io/ioutil"
"github.com/cyberdelia/heroku-go/v3"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccHerokuCert_Basic(t *testing.T) {
var endpoint heroku.SSLEndpoint
wd, _ := os.Getwd()
certificateChainFile := wd + "/test-fixtures/terraform.cert"
certificateChainBytes, _ := ioutil.ReadFile(certificateChainFile)
certificateChain := string(certificateChainBytes)
testAccCheckHerokuCertConfig_basic := `
resource "heroku_app" "foobar" {
name = "terraform-test-cert-app"
region = "eu"
}
resource "heroku_addon" "ssl" {
app = "${heroku_app.foobar.name}"
plan = "ssl:endpoint"
}
resource "heroku_cert" "ssl_certificate" {
app = "${heroku_app.foobar.name}"
depends_on = "heroku_addon.ssl"
certificate_chain="${file("` + certificateChainFile + `")}"
private_key="${file("` + wd + `/test-fixtures/terraform.key")}"
}
`
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuCertDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckHerokuCertConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuCertExists("heroku_cert.ssl_certificate", &endpoint),
testAccCheckHerokuCertificateChain(&endpoint, certificateChain),
resource.TestCheckResourceAttr(
"heroku_cert.ssl_certificate", "cname", "terraform-test-cert-app.herokuapp.com"),
),
},
},
})
}
func testAccCheckHerokuCertDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
for _, rs := range s.RootModule().Resources {
if rs.Type != "heroku_cert" {
continue
}
_, err := client.SSLEndpointInfo(rs.Primary.Attributes["app"], rs.Primary.ID)
if err == nil {
return fmt.Errorf("Cerfificate still exists")
}
}
return nil
}
func testAccCheckHerokuCertificateChain(endpoint *heroku.SSLEndpoint, chain string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if endpoint.CertificateChain != chain {
return fmt.Errorf("Bad certificate chain: %s", endpoint.CertificateChain)
}
return nil
}
}
func testAccCheckHerokuCertExists(n string, endpoint *heroku.SSLEndpoint) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No SSL endpoint ID is set")
}
client := testAccProvider.Meta().(*heroku.Service)
foundEndpoint, err := client.SSLEndpointInfo(rs.Primary.Attributes["app"], rs.Primary.ID)
if err != nil {
return err
}
if foundEndpoint.ID != rs.Primary.ID {
return fmt.Errorf("SSL endpoint not found")
}
*endpoint = *foundEndpoint
return nil
}
}

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIJAIUuu5XX/tCRMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNV
BAMMEXd3dy50ZXJyYWZvcm0ub3JnMB4XDTE0MTIxNDIwMzA0MFoXDTI0MTIxMTIw
MzA0MFowHDEaMBgGA1UEAwwRd3d3LnRlcnJhZm9ybS5vcmcwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC7sp6oJ6czdRpl5azB7jaLCwQ38eqV2TRFPVVj
PD7cWyhV2REFtqd7vEF/AUrp3+ACvc6mLdTjDuaGVVga4oA42Qgqz5Wzkl3tnBSB
DlxFXXg+p4UjJWZPLUiOMbvHWNGthO9G1dp5h9rhqV7wJhyAlTqlnV7aaeSWcJgY
fh7xMe50BlAmh6ywpnnlZzsy4eJiJwgbglG8OU0JK+1OxdOUDe/1eUOhFPPx1U/p
25t8Z6qaI8FDLPwTVZzrvOZ0vTQSKyeA0ZhBTH1GhUqroogDlPETgkne6YqvZoxl
o8+9Wdjln2bjYe/nRWYKR5BxC46PnNJMPPJFI/VNLPYanAu5AgMBAAGjUDBOMB0G
A1UdDgQWBBQaJeROghiSQGVn30ARllECycYsczAfBgNVHSMEGDAWgBQaJeROghiS
QGVn30ARllECycYsczAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCa
iRpZ0b4KVqDT3bqc9UZV491UdBVF9BN0CV4BLvg9KPyRcftujZu0lKFu+wGlAlYr
bV6DjqHgFXltBzIFM/y790EivkgePcFv+0HKy1O8ELLduQcigYT5AC7h34xxWBy7
96VW6qD7/OOjvexVdKmTfXO/njdmot38/uO9TdfJPQzCHrgpzjBCcI+eBFnvQwzb
gOpMlh04U4nDeITOTbraLur1zWQjzSA9DjaGGA+IQ556MUPAS85YmJ4Jf+f8UW3o
sZmzFojLFd8EhVRZDE4tZzqo/vN0plGv/Kh/oob5Cnp6EJ3BbG6y9tzATMWz/hRo
oeTtQe3gt6gPKBS+UeBf
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAu7KeqCenM3UaZeWswe42iwsEN/Hqldk0RT1VYzw+3FsoVdkR
Bbane7xBfwFK6d/gAr3Opi3U4w7mhlVYGuKAONkIKs+Vs5Jd7ZwUgQ5cRV14PqeF
IyVmTy1IjjG7x1jRrYTvRtXaeYfa4ale8CYcgJU6pZ1e2mnklnCYGH4e8THudAZQ
JoessKZ55Wc7MuHiYicIG4JRvDlNCSvtTsXTlA3v9XlDoRTz8dVP6dubfGeqmiPB
Qyz8E1Wc67zmdL00EisngNGYQUx9RoVKq6KIA5TxE4JJ3umKr2aMZaPPvVnY5Z9m
42Hv50VmCkeQcQuOj5zSTDzyRSP1TSz2GpwLuQIDAQABAoIBAQCethLiLWV8ZXDE
6MiD02HbgJ04kR7DRr6kLZCeMLsWqR4aOUnjgudsAWuAcR9fUyagKs8qRWbV+CuF
O3UchpnVd+8oBA+ZoBI8cNYFqpbrMHYUxKIXbfBs0uWfFv6pOblS+C07wGjUisPS
PN1CQ3emYokMsV0bYp8fdmWlkD+pwhHH3vsPm4sYwbabaURRxZYLNyYd/4Czafqz
UBWbAJ+xap6t/WLJCR7goHCVX0DNtpNfzoYK5/rKpQzw0H+L1pB5yghx5GbslthB
xtTb1LjMMl4AUuU5Bv1XLzDZlS/HUYQyefhljlqJPC678KNlegRLjn2YZeZF07X1
b/KSKDrhAoGBAPVST4JYGS9OoW4Fvu4SV7AJGW1ewHhAmzO1XFrYD8DvSjf3Cinn
ylTYwkQK9ayLLM405Sof2J55NbEakDs5sahN93mqGrk9bWPZrFHgDf0NiMoyI/0N
/ZBXhkBeg9LitBvmEWiBlGK55At0zePWVDcUtXg+d0tSJC4o0y1DoGo7AoGBAMPe
ML95QKabWsCRpGKVwhOFrEp68rZlugwJEjzubC8EXHX8dNy3IURl1j8tSM0kDGay
CDMpxOjqzVyLmLqfIghiG1nkQU7EnJdx86k3AaesHoJff3Ywi+9DwC5r/T3zg20U
Qkr1c4Yxv0Lk3IluHwjPaT/uMd4utlPB6EpAvY6bAoGAdcNBb6yiylbQn2Qat2YO
ue5kSmBFvHQnDLdu0h0N0uwLkLoCIwOl2P0EpG0uadmVdJdnusT203wUDiRWQFf9
tHFY7wp9MZcPP/NqCROpI2Sv2YAgToW8xuF9DMFSPpWdKBdVG/m4JXxewDEd9NUa
MCa8xjAWTA3uWEo4tW3VP6kCgYAKAYvT/EnFOSKFu+r97lCf1rBajbVghAnhG4WG
/1cff8WJcYA21lQovlsXlySk9jZ7+JRaqMOacoRTOf5vajm+2+Qxz2tWrsyhH/0m
o9y9yBk259IHI6vCaV+j/3hMdeg85lAMrEVekaQHstFhY/LJ7G6gCXcatqAx3zIS
uQP2CQKBgQDGx31nzcrYcJz0EtZPJ5n4JMKrx9S4ZQ2+C7p6oDYXJl2Woh0f+c3K
X1oW1MLFChq+5Or9IVh11cePtV3a6X10Q9xSLQnXdPC9X4QsZoWOUBh5fzw301g1
WB9ncXqqew3EPHm1G5j4hZ+Gjz/P53TLRYTEYW8fm1Rv9ga5wiJpiw==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,51 @@
---
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.
---
# heroku\_cert
Provides a Heroku SSL certificate resource. It allows to set a given certificate for a Heroku app.
## Example Usage
```
# Create a new heroku app
resource "heroku_app" "default" {
name = "test-app"
}
# Add-on SSL to application
resource "heroku_addon" "ssl" {
app = "${heroku_app.service.name}"
plan = "ssl"
}
# Establish certificate for a given application
resource "heroku_cert" "ssl_certificate" {
app = "${heroku_app.service.name}"
certificate_chain = "${file("server.crt")}"
private_key = "${file("server.key")}"
depends_on = "heroku_addon.ssl"
}
```
## Argument Reference
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
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the add-on
* `cname` - The CNAME of ssl endpoint
* `name` - The name of the SSL certificate