Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/digitalocean: Add support for certificates #14578

Merged
merged 6 commits into from
May 26, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion builtin/providers/digitalocean/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package digitalocean

import (
"context"
"log"
"net/http"
"net/http/httputil"
Expand Down Expand Up @@ -55,7 +56,7 @@ func waitForAction(client *godo.Client, action *godo.Action) error {
pending = "in-progress"
target = "completed"
refreshfn = func() (result interface{}, state string, err error) {
a, _, err := client.Actions.Get(action.ID)
a, _, err := client.Actions.Get(context.Background(), action.ID)
if err != nil {
return nil, "", err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package digitalocean

import (
"context"
"fmt"
"strconv"

Expand Down Expand Up @@ -54,7 +55,7 @@ func dataSourceDigitalOceanImageRead(d *schema.ResourceData, meta interface{}) e

opts := &godo.ListOptions{}

images, _, err := client.Images.ListUser(opts)
images, _, err := client.Images.ListUser(context.Background(), opts)
if err != nil {
d.SetId("")
return err
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package digitalocean

import (
"context"
"fmt"
"log"
"regexp"
Expand Down Expand Up @@ -70,7 +71,7 @@ func takeSnapshotsOfDroplet(rInt int, droplet *godo.Droplet, snapshotsId *[]int)
return err
}
}
retrieveDroplet, _, err := client.Droplets.Get((*droplet).ID)
retrieveDroplet, _, err := client.Droplets.Get(context.Background(), (*droplet).ID)
if err != nil {
return err
}
Expand All @@ -81,7 +82,7 @@ func takeSnapshotsOfDroplet(rInt int, droplet *godo.Droplet, snapshotsId *[]int)

func takeSnapshotOfDroplet(rInt, sInt int, droplet *godo.Droplet) error {
client := testAccProvider.Meta().(*godo.Client)
action, _, err := client.DropletActions.Snapshot((*droplet).ID, fmt.Sprintf("snap-%d-%d", rInt, sInt))
action, _, err := client.DropletActions.Snapshot(context.Background(), (*droplet).ID, fmt.Sprintf("snap-%d-%d", rInt, sInt))
if err != nil {
return err
}
Expand All @@ -96,7 +97,7 @@ func deleteSnapshots(snapshotsId *[]int) resource.TestCheckFunc {
snapshots := *snapshotsId
for _, value := range snapshots {
log.Printf("XXX Deleting %d", value)
_, err := client.Images.Delete(value)
_, err := client.Images.Delete(context.Background(), value)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion builtin/providers/digitalocean/loadbalancer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package digitalocean

import (
"context"
"fmt"

"github.com/digitalocean/godo"
Expand All @@ -9,7 +10,7 @@ import (

func loadbalancerStateRefreshFunc(client *godo.Client, loadbalancerId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
lb, _, err := client.LoadBalancers.Get(loadbalancerId)
lb, _, err := client.LoadBalancers.Get(context.Background(), loadbalancerId)
if err != nil {
return nil, "", fmt.Errorf("Error issuing read request in LoadbalancerStateRefreshFunc to DigitalOcean for Load Balancer '%s': %s", loadbalancerId, err)
}
Expand Down
1 change: 1 addition & 0 deletions builtin/providers/digitalocean/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"digitalocean_certificate": resourceDigitalOceanCertificate(),
"digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(),
"digitalocean_floating_ip": resourceDigitalOceanFloatingIp(),
Expand Down
118 changes: 118 additions & 0 deletions builtin/providers/digitalocean/resource_digitalocean_certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package digitalocean

import (
"context"
"fmt"
"log"

"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceDigitalOceanCertificate() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanCertificateCreate,
Read: resourceDigitalOceanCertificateRead,
Delete: resourceDigitalOceanCertificateDelete,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"private_key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"leaf_certificate": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"certificate_chain": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"not_after": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"sha1_fingerprint": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
}
}

func buildCertificateRequest(d *schema.ResourceData) (*godo.CertificateRequest, error) {
req := &godo.CertificateRequest{
Name: d.Get("name").(string),
PrivateKey: d.Get("private_key").(string),
LeafCertificate: d.Get("leaf_certificate").(string),
CertificateChain: d.Get("certificate_chain").(string),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any undesired effects to certificate_chain being nil in the CertificateRequest struct? As certificate_chain is an optional parameter, might be worthwhile to verify it's populated with d.GetOk(). If it doesn't cause any undesired effects, looks good as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grubernaut AFAIK, there are no undesired effects to certificate_chain being nil. But I'd rather play safe here, so I'm gonna add a check around it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I just checked the docs for Get on ResourceData and it seems to be safe for TypeString (it just returns the zero value, or ""). I think it's safe to leave it as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct about Get returning the nil type for the native golang string type, however, does that have any adverse effect during the Create request in the DO API?
IE: will setting certificate_chain to "", cause the API request to attempt to set certificate_chain to ""?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grubernaut It doesn't seem to have any adverse effects.

}

return req, nil
}

func resourceDigitalOceanCertificateCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

log.Printf("[INFO] Create a Certificate Request")

certReq, err := buildCertificateRequest(d)
if err != nil {
return err
}

log.Printf("[DEBUG] Certificate Create: %#v", certReq)
cert, _, err := client.Certificates.Create(context.Background(), certReq)
if err != nil {
return fmt.Errorf("Error creating Certificate: %s", err)
}

d.SetId(cert.ID)

return resourceDigitalOceanCertificateRead(d, meta)
}

func resourceDigitalOceanCertificateRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

log.Printf("[INFO] Reading the details of the Certificate %s", d.Id())
cert, _, err := client.Certificates.Get(context.Background(), d.Id())
if err != nil {
return fmt.Errorf("Error retrieving Certificate: %s", err)
}

d.Set("name", cert.Name)
d.Set("not_after", cert.NotAfter)
d.Set("sha1_fingerprint", cert.SHA1Fingerprint)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the other attributes listed in the schema not available in the cert object? These will need to be set as well so Terraform can detect any configuration drift between config/state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grubernaut Correct, they're available only for creation, but not returned by the API (for security reasons, I assume).

This is the example response from the API docs:

{
  "certificate": {
    "id": "892071a0-bb95-49bc-8021-3afd67a210bf",
    "name": "web-cert-01",
    "not_after": "2017-02-22T00:23:00Z",
    "sha1_fingerprint": "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
    "created_at": "2017-02-08T16:02:37Z"
  }
}


return nil

}

func resourceDigitalOceanCertificateDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

log.Printf("[INFO] Deleting Certificate: %s", d.Id())
_, err := client.Certificates.Delete(context.Background(), d.Id())
if err != nil {
return fmt.Errorf("Error deleting Certificate: %s", err)
}

return nil

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package digitalocean

import (
"context"
"fmt"
"strings"
"testing"

"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccDigitalOceanCertificate_Basic(t *testing.T) {
var cert godo.Certificate
rInt := acctest.RandInt()
leafCertMaterial, privateKeyMaterial, err := acctest.RandTLSCert("Acme Co")
if err != nil {
t.Fatalf("Cannot generate test TLS certificate: %s", err)
}
rootCertMaterial, _, err := acctest.RandTLSCert("Acme Go")
if err != nil {
t.Fatalf("Cannot generate test TLS certificate: %s", err)
}
certChainMaterial := fmt.Sprintf("%s\n%s", strings.TrimSpace(rootCertMaterial), leafCertMaterial)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanCertificateDestroy,
Steps: []resource.TestStep{
{
ExpectNonEmptyPlan: true,
Config: testAccCheckDigitalOceanCertificateConfig_basic(rInt, privateKeyMaterial, leafCertMaterial, certChainMaterial),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanCertificateExists("digitalocean_certificate.foobar", &cert),
resource.TestCheckResourceAttr(
"digitalocean_certificate.foobar", "name", fmt.Sprintf("certificate-%d", rInt)),
resource.TestCheckResourceAttr(
"digitalocean_certificate.foobar", "private_key", fmt.Sprintf("%s\n", privateKeyMaterial)),
resource.TestCheckResourceAttr(
"digitalocean_certificate.foobar", "leaf_certificate", fmt.Sprintf("%s\n", leafCertMaterial)),
resource.TestCheckResourceAttr(
"digitalocean_certificate.foobar", "certificate_chain", fmt.Sprintf("%s\n", certChainMaterial)),
),
},
},
})
}

func testAccCheckDigitalOceanCertificateDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*godo.Client)

for _, rs := range s.RootModule().Resources {
if rs.Type != "digitalocean_certificate" {
continue
}

_, _, err := client.Certificates.Get(context.Background(), rs.Primary.ID)

if err != nil && !strings.Contains(err.Error(), "404") {
return fmt.Errorf(
"Error waiting for certificate (%s) to be destroyed: %s",
rs.Primary.ID, err)
}
}

return nil
}

func testAccCheckDigitalOceanCertificateExists(n string, cert *godo.Certificate) 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 Certificate ID is set")
}

client := testAccProvider.Meta().(*godo.Client)

c, _, err := client.Certificates.Get(context.Background(), rs.Primary.ID)

if err != nil {
return err
}

if c.ID != rs.Primary.ID {
return fmt.Errorf("Certificate not found")
}

*cert = *c

return nil
}
}

func testAccCheckDigitalOceanCertificateConfig_basic(rInt int, privateKeyMaterial, leafCert, certChain string) string {
return fmt.Sprintf(`
resource "digitalocean_certificate" "foobar" {
name = "certificate-%d"
private_key = <<EOF
%s
EOF

leaf_certificate = <<EOF
%s
EOF

certificate_chain = <<EOF
%s
EOF
}`, rInt, privateKeyMaterial, leafCert, certChain)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package digitalocean

import (
"context"
"fmt"
"log"

Expand Down Expand Up @@ -44,7 +45,7 @@ func resourceDigitalOceanDomainCreate(d *schema.ResourceData, meta interface{})
}

log.Printf("[DEBUG] Domain create configuration: %#v", opts)
domain, _, err := client.Domains.Create(opts)
domain, _, err := client.Domains.Create(context.Background(), opts)
if err != nil {
return fmt.Errorf("Error creating Domain: %s", err)
}
Expand All @@ -58,7 +59,7 @@ func resourceDigitalOceanDomainCreate(d *schema.ResourceData, meta interface{})
func resourceDigitalOceanDomainRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

domain, resp, err := client.Domains.Get(d.Id())
domain, resp, err := client.Domains.Get(context.Background(), d.Id())
if err != nil {
// If the domain is somehow already destroyed, mark as
// successfully gone
Expand All @@ -79,7 +80,7 @@ func resourceDigitalOceanDomainDelete(d *schema.ResourceData, meta interface{})
client := meta.(*godo.Client)

log.Printf("[INFO] Deleting Domain: %s", d.Id())
_, err := client.Domains.Delete(d.Id())
_, err := client.Domains.Delete(context.Background(), d.Id())
if err != nil {
return fmt.Errorf("Error deleting Domain: %s", err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package digitalocean

import (
"context"
"fmt"
"testing"

Expand Down Expand Up @@ -43,7 +44,7 @@ func testAccCheckDigitalOceanDomainDestroy(s *terraform.State) error {
}

// Try to find the domain
_, _, err := client.Domains.Get(rs.Primary.ID)
_, _, err := client.Domains.Get(context.Background(), rs.Primary.ID)

if err == nil {
return fmt.Errorf("Domain still exists")
Expand Down Expand Up @@ -78,7 +79,7 @@ func testAccCheckDigitalOceanDomainExists(n string, domain *godo.Domain) resourc

client := testAccProvider.Meta().(*godo.Client)

foundDomain, _, err := client.Domains.Get(rs.Primary.ID)
foundDomain, _, err := client.Domains.Get(context.Background(), rs.Primary.ID)

if err != nil {
return err
Expand Down
Loading