Merge pull request #672 from plan3-labs/heroku_cert_support
Heroku SSL certificate support
This commit is contained in:
commit
f923368fc5
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue