Skip to content

Commit 3c0e997

Browse files
author
Jakub Janczak
committed
Heroku SSL certificate support added
1 parent cbd9132 commit 3c0e997

6 files changed

Lines changed: 355 additions & 0 deletions

File tree

builtin/providers/heroku/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func Provider() terraform.ResourceProvider {
3030
"heroku_addon": resourceHerokuAddon(),
3131
"heroku_domain": resourceHerokuDomain(),
3232
"heroku_drain": resourceHerokuDrain(),
33+
"heroku_cert": resourceHerokuCert(),
3334
},
3435

3536
ConfigureFunc: providerConfigure,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package heroku
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/cyberdelia/heroku-go/v3"
8+
"github.com/hashicorp/terraform/helper/schema"
9+
)
10+
11+
func resourceHerokuCert() *schema.Resource {
12+
return &schema.Resource{
13+
Create: resourceHerokuCertCreate,
14+
Read: resourceHerokuCertRead,
15+
Update: resourceHerokuCertUpdate,
16+
Delete: resourceHerokuCertDelete,
17+
18+
Schema: map[string]*schema.Schema{
19+
"app": &schema.Schema{
20+
Type: schema.TypeString,
21+
Required: true,
22+
ForceNew: true,
23+
},
24+
25+
"certificate_chain": &schema.Schema{
26+
Type: schema.TypeString,
27+
Required: true,
28+
},
29+
30+
"private_key": &schema.Schema{
31+
Type: schema.TypeString,
32+
Required: true,
33+
},
34+
35+
"cname": &schema.Schema{
36+
Type: schema.TypeString,
37+
Computed: true,
38+
},
39+
40+
"name": &schema.Schema{
41+
Type: schema.TypeString,
42+
Computed: true,
43+
},
44+
},
45+
}
46+
}
47+
48+
func resourceHerokuCertCreate(d *schema.ResourceData, meta interface{}) error {
49+
client := meta.(*heroku.Service)
50+
51+
app := d.Get("app").(string)
52+
preprocess := true
53+
opts := heroku.SSLEndpointCreateOpts{
54+
CertificateChain: d.Get("certificate_chain").(string),
55+
Preprocess: &preprocess,
56+
PrivateKey: d.Get("private_key").(string)}
57+
58+
log.Printf("[DEBUG] SSL Certificate create configuration: %#v, %#v", app, opts)
59+
a, err := client.SSLEndpointCreate(app, opts)
60+
if err != nil {
61+
panic(err)
62+
}
63+
64+
d.SetId(a.ID)
65+
log.Printf("[INFO] SSL Certificate ID: %s", d.Id())
66+
67+
return resourceHerokuCertRead(d, meta)
68+
}
69+
70+
func resourceHerokuCertRead(d *schema.ResourceData, meta interface{}) error {
71+
client := meta.(*heroku.Service)
72+
73+
cert, err := resource_heroku_ssl_cert_retrieve(
74+
d.Get("app").(string), d.Id(), client)
75+
if err != nil {
76+
return err
77+
}
78+
79+
d.Set("certificate_chain", cert.CertificateChain)
80+
d.Set("name", cert.Name)
81+
d.Set("cname", cert.CName)
82+
d.Set("created_at", cert.CreatedAt)
83+
d.Set("updated_at", cert.UpdatedAt)
84+
85+
return nil
86+
}
87+
88+
func resourceHerokuCertUpdate(d *schema.ResourceData, meta interface{}) error {
89+
client := meta.(*heroku.Service)
90+
91+
app := d.Get("app").(string)
92+
93+
if d.HasChange("certificate_chain") {
94+
preprocess := true
95+
rollback := false
96+
ad, err := client.SSLEndpointUpdate(
97+
app, d.Id(), heroku.SSLEndpointUpdateOpts{
98+
CertificateChain: d.Get("certificate_chain").(*string),
99+
Preprocess: &preprocess,
100+
PrivateKey: d.Get("private_key").(*string),
101+
Rollback: &rollback})
102+
if err != nil {
103+
return err
104+
}
105+
106+
// Store the new ID
107+
d.SetId(ad.ID)
108+
}
109+
110+
return resourceHerokuCertRead(d, meta)
111+
}
112+
113+
func resourceHerokuCertDelete(d *schema.ResourceData, meta interface{}) error {
114+
client := meta.(*heroku.Service)
115+
116+
log.Printf("[INFO] Deleting SSL Cert: %s", d.Id())
117+
118+
// Destroy the app
119+
err := client.SSLEndpointDelete(d.Get("app").(string), d.Id())
120+
if err != nil {
121+
return fmt.Errorf("Error deleting SSL Cert: %s", err)
122+
}
123+
124+
d.SetId("")
125+
return nil
126+
}
127+
128+
func resource_heroku_ssl_cert_retrieve(app string, id string, client *heroku.Service) (*heroku.SSLEndpoint, error) {
129+
addon, err := client.SSLEndpointInfo(app, id)
130+
131+
if err != nil {
132+
return nil, fmt.Errorf("Error retrieving SSL Cert: %s", err)
133+
}
134+
135+
return addon, nil
136+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package heroku
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"os"
7+
"io/ioutil"
8+
9+
"github.com/cyberdelia/heroku-go/v3"
10+
"github.com/hashicorp/terraform/helper/resource"
11+
"github.com/hashicorp/terraform/terraform"
12+
)
13+
14+
func TestAccHerokuCert_Basic(t *testing.T) {
15+
var endpoint heroku.SSLEndpoint
16+
wd, _ := os.Getwd()
17+
certificateChainFile := wd + "/test-fixtures/terraform.cert"
18+
certificateChainBytes, _ := ioutil.ReadFile(certificateChainFile)
19+
certificateChain := string(certificateChainBytes)
20+
testAccCheckHerokuCertConfig_basic := `
21+
resource "heroku_app" "foobar" {
22+
name = "terraform-test-cert-app"
23+
region = "eu"
24+
organization = {
25+
name = "plan3-labs"
26+
locked = false
27+
private = false
28+
}
29+
}
30+
31+
resource "heroku_addon" "ssl" {
32+
app = "${heroku_app.foobar.name}"
33+
plan = "ssl:endpoint"
34+
}
35+
36+
resource "heroku_cert" "ssl_certificate" {
37+
app = "${heroku_app.foobar.name}"
38+
depends_on = "heroku_addon.ssl"
39+
certificate_chain="${file("` + certificateChainFile + `")}"
40+
private_key="${file("` + wd + `/test-fixtures/terraform.key")}"
41+
}
42+
`
43+
44+
resource.Test(t, resource.TestCase{
45+
PreCheck: func() { testAccPreCheck(t) },
46+
Providers: testAccProviders,
47+
CheckDestroy: testAccCheckHerokuCertDestroy,
48+
Steps: []resource.TestStep{
49+
resource.TestStep{
50+
Config: testAccCheckHerokuCertConfig_basic,
51+
Check: resource.ComposeTestCheckFunc(
52+
testAccCheckHerokuCertExists("heroku_cert.ssl_certificate", &endpoint),
53+
testAccCheckHerokuCertificateChain(&endpoint, certificateChain),
54+
resource.TestCheckResourceAttr(
55+
"heroku_cert.ssl_certificate", "cname", "terraform-test-cert-app.herokuapp.com"),
56+
),
57+
},
58+
},
59+
})
60+
}
61+
62+
func testAccCheckHerokuCertDestroy(s *terraform.State) error {
63+
client := testAccProvider.Meta().(*heroku.Service)
64+
65+
for _, rs := range s.RootModule().Resources {
66+
if rs.Type != "heroku_cert" {
67+
continue
68+
}
69+
70+
_, err := client.SSLEndpointInfo(rs.Primary.Attributes["app"], rs.Primary.ID)
71+
72+
if err == nil {
73+
return fmt.Errorf("Cerfificate still exists")
74+
}
75+
}
76+
77+
return nil
78+
}
79+
80+
func testAccCheckHerokuCertificateChain(endpoint *heroku.SSLEndpoint, chain string) resource.TestCheckFunc {
81+
return func(s *terraform.State) error {
82+
83+
if endpoint.CertificateChain != chain {
84+
return fmt.Errorf("Bad certificate chain: %s", endpoint.CertificateChain)
85+
}
86+
87+
return nil
88+
}
89+
}
90+
91+
func testAccCheckHerokuCertExists(n string, endpoint *heroku.SSLEndpoint) resource.TestCheckFunc {
92+
return func(s *terraform.State) error {
93+
rs, ok := s.RootModule().Resources[n]
94+
95+
if !ok {
96+
return fmt.Errorf("Not found: %s", n)
97+
}
98+
99+
if rs.Primary.ID == "" {
100+
return fmt.Errorf("No SSL endpoint ID is set")
101+
}
102+
103+
client := testAccProvider.Meta().(*heroku.Service)
104+
105+
foundEndpoint, err := client.SSLEndpointInfo(rs.Primary.Attributes["app"], rs.Primary.ID)
106+
107+
if err != nil {
108+
return err
109+
}
110+
111+
if foundEndpoint.ID != rs.Primary.ID {
112+
return fmt.Errorf("SSL endpoint not found")
113+
}
114+
115+
*endpoint = *foundEndpoint
116+
117+
return nil
118+
}
119+
}
120+
121+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDCzCCAfOgAwIBAgIJAIUuu5XX/tCRMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNV
3+
BAMMEXd3dy50ZXJyYWZvcm0ub3JnMB4XDTE0MTIxNDIwMzA0MFoXDTI0MTIxMTIw
4+
MzA0MFowHDEaMBgGA1UEAwwRd3d3LnRlcnJhZm9ybS5vcmcwggEiMA0GCSqGSIb3
5+
DQEBAQUAA4IBDwAwggEKAoIBAQC7sp6oJ6czdRpl5azB7jaLCwQ38eqV2TRFPVVj
6+
PD7cWyhV2REFtqd7vEF/AUrp3+ACvc6mLdTjDuaGVVga4oA42Qgqz5Wzkl3tnBSB
7+
DlxFXXg+p4UjJWZPLUiOMbvHWNGthO9G1dp5h9rhqV7wJhyAlTqlnV7aaeSWcJgY
8+
fh7xMe50BlAmh6ywpnnlZzsy4eJiJwgbglG8OU0JK+1OxdOUDe/1eUOhFPPx1U/p
9+
25t8Z6qaI8FDLPwTVZzrvOZ0vTQSKyeA0ZhBTH1GhUqroogDlPETgkne6YqvZoxl
10+
o8+9Wdjln2bjYe/nRWYKR5BxC46PnNJMPPJFI/VNLPYanAu5AgMBAAGjUDBOMB0G
11+
A1UdDgQWBBQaJeROghiSQGVn30ARllECycYsczAfBgNVHSMEGDAWgBQaJeROghiS
12+
QGVn30ARllECycYsczAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCa
13+
iRpZ0b4KVqDT3bqc9UZV491UdBVF9BN0CV4BLvg9KPyRcftujZu0lKFu+wGlAlYr
14+
bV6DjqHgFXltBzIFM/y790EivkgePcFv+0HKy1O8ELLduQcigYT5AC7h34xxWBy7
15+
96VW6qD7/OOjvexVdKmTfXO/njdmot38/uO9TdfJPQzCHrgpzjBCcI+eBFnvQwzb
16+
gOpMlh04U4nDeITOTbraLur1zWQjzSA9DjaGGA+IQ556MUPAS85YmJ4Jf+f8UW3o
17+
sZmzFojLFd8EhVRZDE4tZzqo/vN0plGv/Kh/oob5Cnp6EJ3BbG6y9tzATMWz/hRo
18+
oeTtQe3gt6gPKBS+UeBf
19+
-----END CERTIFICATE-----
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpAIBAAKCAQEAu7KeqCenM3UaZeWswe42iwsEN/Hqldk0RT1VYzw+3FsoVdkR
3+
Bbane7xBfwFK6d/gAr3Opi3U4w7mhlVYGuKAONkIKs+Vs5Jd7ZwUgQ5cRV14PqeF
4+
IyVmTy1IjjG7x1jRrYTvRtXaeYfa4ale8CYcgJU6pZ1e2mnklnCYGH4e8THudAZQ
5+
JoessKZ55Wc7MuHiYicIG4JRvDlNCSvtTsXTlA3v9XlDoRTz8dVP6dubfGeqmiPB
6+
Qyz8E1Wc67zmdL00EisngNGYQUx9RoVKq6KIA5TxE4JJ3umKr2aMZaPPvVnY5Z9m
7+
42Hv50VmCkeQcQuOj5zSTDzyRSP1TSz2GpwLuQIDAQABAoIBAQCethLiLWV8ZXDE
8+
6MiD02HbgJ04kR7DRr6kLZCeMLsWqR4aOUnjgudsAWuAcR9fUyagKs8qRWbV+CuF
9+
O3UchpnVd+8oBA+ZoBI8cNYFqpbrMHYUxKIXbfBs0uWfFv6pOblS+C07wGjUisPS
10+
PN1CQ3emYokMsV0bYp8fdmWlkD+pwhHH3vsPm4sYwbabaURRxZYLNyYd/4Czafqz
11+
UBWbAJ+xap6t/WLJCR7goHCVX0DNtpNfzoYK5/rKpQzw0H+L1pB5yghx5GbslthB
12+
xtTb1LjMMl4AUuU5Bv1XLzDZlS/HUYQyefhljlqJPC678KNlegRLjn2YZeZF07X1
13+
b/KSKDrhAoGBAPVST4JYGS9OoW4Fvu4SV7AJGW1ewHhAmzO1XFrYD8DvSjf3Cinn
14+
ylTYwkQK9ayLLM405Sof2J55NbEakDs5sahN93mqGrk9bWPZrFHgDf0NiMoyI/0N
15+
/ZBXhkBeg9LitBvmEWiBlGK55At0zePWVDcUtXg+d0tSJC4o0y1DoGo7AoGBAMPe
16+
ML95QKabWsCRpGKVwhOFrEp68rZlugwJEjzubC8EXHX8dNy3IURl1j8tSM0kDGay
17+
CDMpxOjqzVyLmLqfIghiG1nkQU7EnJdx86k3AaesHoJff3Ywi+9DwC5r/T3zg20U
18+
Qkr1c4Yxv0Lk3IluHwjPaT/uMd4utlPB6EpAvY6bAoGAdcNBb6yiylbQn2Qat2YO
19+
ue5kSmBFvHQnDLdu0h0N0uwLkLoCIwOl2P0EpG0uadmVdJdnusT203wUDiRWQFf9
20+
tHFY7wp9MZcPP/NqCROpI2Sv2YAgToW8xuF9DMFSPpWdKBdVG/m4JXxewDEd9NUa
21+
MCa8xjAWTA3uWEo4tW3VP6kCgYAKAYvT/EnFOSKFu+r97lCf1rBajbVghAnhG4WG
22+
/1cff8WJcYA21lQovlsXlySk9jZ7+JRaqMOacoRTOf5vajm+2+Qxz2tWrsyhH/0m
23+
o9y9yBk259IHI6vCaV+j/3hMdeg85lAMrEVekaQHstFhY/LJ7G6gCXcatqAx3zIS
24+
uQP2CQKBgQDGx31nzcrYcJz0EtZPJ5n4JMKrx9S4ZQ2+C7p6oDYXJl2Woh0f+c3K
25+
X1oW1MLFChq+5Or9IVh11cePtV3a6X10Q9xSLQnXdPC9X4QsZoWOUBh5fzw301g1
26+
WB9ncXqqew3EPHm1G5j4hZ+Gjz/P53TLRYTEYW8fm1Rv9ga5wiJpiw==
27+
-----END RSA PRIVATE KEY-----
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
layout: "heroku"
3+
page_title: "Heroku: heroku_cert"
4+
sidebar_current: "docs-heroku-resource-cert"
5+
description: |-
6+
Provides a Heroku SSL certificate resource. It allows to set a given certificate for a Heroku app.
7+
---
8+
9+
# heroku\_cert
10+
11+
Provides a Heroku SSL certificate resource. It allows to set a given certificate for a Heroku app.
12+
13+
## Example Usage
14+
15+
```
16+
# Create a new heroku app
17+
resource "heroku_app" "default" {
18+
name = "test-app"
19+
}
20+
21+
# Add-on SSL to application
22+
resource "heroku_addon" "ssl" {
23+
app = "${heroku_app.service.name}"
24+
plan = "ssl"
25+
}
26+
27+
# Establish certificate for a given application
28+
resource "heroku_cert" "ssl_certificate" {
29+
app = "${heroku_app.service.name}"
30+
certificate_chain = "${file("server.crt")}"
31+
private_key = "${file("server.key")}"
32+
depends_on = "heroku_addon.ssl"
33+
}
34+
```
35+
36+
## Argument Reference
37+
38+
The following arguments are supported:
39+
40+
* `app` - (Required) The Heroku app to add to.
41+
* `certificate_chain` - (Required) The certificate chain to add
42+
* `private_key` - (Required) The private key for a given certificate chain
43+
44+
## Attributes Reference
45+
46+
The following attributes are exported:
47+
48+
* `id` - The ID of the add-on
49+
* `cname` - The CNAME of ssl endpoint
50+
* `name` - The name of the SSL certificate
51+

0 commit comments

Comments
 (0)