-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
Changes from 4 commits
6681a86
45ad54c
a565001
32d724e
2b12ddf
c4bbb6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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), | ||
} | ||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the other attributes listed in the schema not available in the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
There was a problem hiding this comment.
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
beingnil
in theCertificateRequest
struct? Ascertificate_chain
is an optional parameter, might be worthwhile to verify it's populated withd.GetOk()
. If it doesn't cause any undesired effects, looks good as-is.There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
onResourceData
and it seems to be safe forTypeString
(it just returns the zero value, or""
). I think it's safe to leave it as is.There was a problem hiding this comment.
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 thenil
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 setcertificate_chain
to""
?There was a problem hiding this comment.
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.