From befaf62ebbdd4548508481d3de0aba5b58cbab88 Mon Sep 17 00:00:00 2001 From: Viola Holownia Date: Wed, 15 Feb 2017 17:59:31 -0500 Subject: [PATCH 1/2] Add CertificatesService. --- certificates.go | 119 ++++++++++++++++++++++++++++++ certificates_test.go | 170 +++++++++++++++++++++++++++++++++++++++++++ godo.go | 2 + 3 files changed, 291 insertions(+) create mode 100644 certificates.go create mode 100644 certificates_test.go diff --git a/certificates.go b/certificates.go new file mode 100644 index 00000000..6b18e883 --- /dev/null +++ b/certificates.go @@ -0,0 +1,119 @@ +package godo + +import ( + "fmt" +) + +const certificatesBasePath = "/v2/certificates" + +// CertificatesService is an interface for managing certificates with the DigitalOcean API. +// See: https://developers.digitalocean.com/documentation/v2/#certificates +type CertificatesService interface { + Get(cID string) (*Certificate, *Response, error) + List(opt *ListOptions) ([]Certificate, *Response, error) + Create(cr *CertificateRequest) (*Certificate, *Response, error) + Delete(cID string) (*Response, error) +} + +// Certificate represents a DigitalOcean certificate configuration. +type Certificate struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + NotAfter string `json:"not_after,omitempty"` + SHA1Fingerprint string `json:"sha1_fingerprint,omitempty"` + Created string `json:"created_at,omitempty"` +} + +// CertificateRequest represents configuration for a new certificate. +type CertificateRequest struct { + Name string `json:"name,omitempty"` + PrivateKey string `json:"private_key,omitempty"` + LeafCertificate string `json:"leaf_certificate,omitempty"` + CertificateChain string `json:"certificate_chain,omitempty"` +} + +type certificateRoot struct { + Certificate *Certificate `json:"certificate"` +} + +type certificatesRoot struct { + Certificates []Certificate `json:"certificates"` + Links *Links `json:"links"` +} + +// CertificatesServiceOp handles communication with certificates methods of the DigitalOcean API. +type CertificatesServiceOp struct { + client *Client +} + +var _ CertificatesService = &CertificatesServiceOp{} + +// Get an existing certificate by its identifier. +func (c *CertificatesServiceOp) Get(cID string) (*Certificate, *Response, error) { + path := fmt.Sprintf("%s/%s", certificatesBasePath, cID) + + req, err := c.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + root := new(certificateRoot) + resp, err := c.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Certificate, resp, nil +} + +// List all certificates. +func (c *CertificatesServiceOp) List(opt *ListOptions) ([]Certificate, *Response, error) { + path, err := addOptions(certificatesBasePath, opt) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + root := new(certificatesRoot) + resp, err := c.client.Do(req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + + return root.Certificates, resp, nil +} + +// Create a new certificate with provided configuration. +func (c *CertificatesServiceOp) Create(cr *CertificateRequest) (*Certificate, *Response, error) { + req, err := c.client.NewRequest("POST", certificatesBasePath, cr) + if err != nil { + return nil, nil, err + } + + root := new(certificateRoot) + resp, err := c.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Certificate, resp, nil +} + +// Delete a certificate by its identifier. +func (c *CertificatesServiceOp) Delete(cID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", certificatesBasePath, cID) + + req, err := c.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + return c.client.Do(req, nil) +} diff --git a/certificates_test.go b/certificates_test.go new file mode 100644 index 00000000..819af39c --- /dev/null +++ b/certificates_test.go @@ -0,0 +1,170 @@ +package godo + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +var certJSONResponse = ` +{ + "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" + } +} +` + +var certsJSONResponse = ` +{ + "certificates": [ + { + "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" + }, + { + "id": "992071a0-bb95-49bc-8021-3afd67a210bf", + "name": "web-cert-02", + "not_after": "2017-02-22T00:23:00Z", + "sha1_fingerprint": "cfcc9f57d86bf58e321c2c6c31c7a971be244ac7", + "created_at": "2017-02-08T16:02:37Z" + } + ], + "links": {}, + "meta": { + "total": 1 + } +} +` + +func TestCertificates_Get(t *testing.T) { + setup() + defer teardown() + + path := "/v2/certificates" + cID := "892071a0-bb95-49bc-8021-3afd67a210bf" + path = fmt.Sprintf("%s/%s", path, cID) + mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, certJSONResponse) + }) + + certificate, _, err := client.Certificates.Get(cID) + if err != nil { + t.Errorf("Certificates.Get returned error: %v", err) + } + + expected := &Certificate{ + ID: "892071a0-bb95-49bc-8021-3afd67a210bf", + Name: "web-cert-01", + NotAfter: "2017-02-22T00:23:00Z", + SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7", + Created: "2017-02-08T16:02:37Z", + } + + assert.Equal(t, expected, certificate) +} + +func TestCertificates_List(t *testing.T) { + setup() + defer teardown() + + path := "/v2/certificates" + mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, certsJSONResponse) + }) + + certificates, _, err := client.Certificates.List(nil) + + if err != nil { + t.Errorf("Certificates.List returned error: %v", err) + } + + expected := []Certificate{ + { + ID: "892071a0-bb95-49bc-8021-3afd67a210bf", + Name: "web-cert-01", + NotAfter: "2017-02-22T00:23:00Z", + SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7", + Created: "2017-02-08T16:02:37Z", + }, + { + ID: "992071a0-bb95-49bc-8021-3afd67a210bf", + Name: "web-cert-02", + NotAfter: "2017-02-22T00:23:00Z", + SHA1Fingerprint: "cfcc9f57d86bf58e321c2c6c31c7a971be244ac7", + Created: "2017-02-08T16:02:37Z", + }, + } + + assert.Equal(t, expected, certificates) +} + +func TestCertificates_Create(t *testing.T) { + setup() + defer teardown() + + createRequest := &CertificateRequest{ + Name: "web-cert-01", + PrivateKey: "-----BEGIN PRIVATE KEY-----", + LeafCertificate: "-----BEGIN CERTIFICATE-----", + CertificateChain: "-----BEGIN CERTIFICATE-----", + } + + path := "/v2/certificates" + mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + v := new(CertificateRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "POST") + assert.Equal(t, createRequest, v) + + fmt.Fprint(w, certJSONResponse) + }) + + certificate, _, err := client.Certificates.Create(createRequest) + if err != nil { + t.Errorf("Certificates.Create returned error: %v", err) + } + + expected := &Certificate{ + ID: "892071a0-bb95-49bc-8021-3afd67a210bf", + Name: "web-cert-01", + NotAfter: "2017-02-22T00:23:00Z", + SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7", + Created: "2017-02-08T16:02:37Z", + } + + assert.Equal(t, expected, certificate) +} + +func TestCertificates_Delete(t *testing.T) { + setup() + defer teardown() + + cID := "892071a0-bb95-49bc-8021-3afd67a210bf" + path := "/v2/certificates" + path = fmt.Sprintf("%s/%s", path, cID) + mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Certificates.Delete(cID) + + if err != nil { + t.Errorf("Certificates.Delete returned error: %v", err) + } +} diff --git a/godo.go b/godo.go index 205f1ff9..bbca9af8 100644 --- a/godo.go +++ b/godo.go @@ -60,6 +60,7 @@ type Client struct { StorageActions StorageActionsService Tags TagsService LoadBalancers LoadBalancersService + Certificates CertificatesService // Optional function called after every successful request made to the DO APIs onRequestCompleted RequestCompletionCallback @@ -169,6 +170,7 @@ func NewClient(httpClient *http.Client) *Client { c.StorageActions = &StorageActionsServiceOp{client: c} c.Tags = &TagsServiceOp{client: c} c.LoadBalancers = &LoadBalancersServiceOp{client: c} + c.Certificates = &CertificatesServiceOp{client: c} return c } From 566f70276aa88a24bfbb36797cf31a2666faca82 Mon Sep 17 00:00:00 2001 From: Viola Holownia Date: Fri, 17 Feb 2017 10:47:14 -0500 Subject: [PATCH 2/2] Code review changes. --- certificates.go | 16 +++++++--------- certificates_test.go | 21 +++++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/certificates.go b/certificates.go index 6b18e883..edd0b90b 100644 --- a/certificates.go +++ b/certificates.go @@ -1,8 +1,6 @@ package godo -import ( - "fmt" -) +import "path" const certificatesBasePath = "/v2/certificates" @@ -50,9 +48,9 @@ var _ CertificatesService = &CertificatesServiceOp{} // Get an existing certificate by its identifier. func (c *CertificatesServiceOp) Get(cID string) (*Certificate, *Response, error) { - path := fmt.Sprintf("%s/%s", certificatesBasePath, cID) + urlStr := path.Join(certificatesBasePath, cID) - req, err := c.client.NewRequest("GET", path, nil) + req, err := c.client.NewRequest("GET", urlStr, nil) if err != nil { return nil, nil, err } @@ -68,12 +66,12 @@ func (c *CertificatesServiceOp) Get(cID string) (*Certificate, *Response, error) // List all certificates. func (c *CertificatesServiceOp) List(opt *ListOptions) ([]Certificate, *Response, error) { - path, err := addOptions(certificatesBasePath, opt) + urlStr, err := addOptions(certificatesBasePath, opt) if err != nil { return nil, nil, err } - req, err := c.client.NewRequest("GET", path, nil) + req, err := c.client.NewRequest("GET", urlStr, nil) if err != nil { return nil, nil, err } @@ -108,9 +106,9 @@ func (c *CertificatesServiceOp) Create(cr *CertificateRequest) (*Certificate, *R // Delete a certificate by its identifier. func (c *CertificatesServiceOp) Delete(cID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", certificatesBasePath, cID) + urlStr := path.Join(certificatesBasePath, cID) - req, err := c.client.NewRequest("DELETE", path, nil) + req, err := c.client.NewRequest("DELETE", urlStr, nil) if err != nil { return nil, err } diff --git a/certificates_test.go b/certificates_test.go index 819af39c..f93e9a3b 100644 --- a/certificates_test.go +++ b/certificates_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "path" "testing" "github.com/stretchr/testify/assert" @@ -50,10 +51,10 @@ func TestCertificates_Get(t *testing.T) { setup() defer teardown() - path := "/v2/certificates" + urlStr := "/v2/certificates" cID := "892071a0-bb95-49bc-8021-3afd67a210bf" - path = fmt.Sprintf("%s/%s", path, cID) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + urlStr = path.Join(urlStr, cID) + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") fmt.Fprint(w, certJSONResponse) }) @@ -78,8 +79,8 @@ func TestCertificates_List(t *testing.T) { setup() defer teardown() - path := "/v2/certificates" - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + urlStr := "/v2/certificates" + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") fmt.Fprint(w, certsJSONResponse) }) @@ -121,8 +122,8 @@ func TestCertificates_Create(t *testing.T) { CertificateChain: "-----BEGIN CERTIFICATE-----", } - path := "/v2/certificates" - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + urlStr := "/v2/certificates" + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { v := new(CertificateRequest) err := json.NewDecoder(r.Body).Decode(v) if err != nil { @@ -156,9 +157,9 @@ func TestCertificates_Delete(t *testing.T) { defer teardown() cID := "892071a0-bb95-49bc-8021-3afd67a210bf" - path := "/v2/certificates" - path = fmt.Sprintf("%s/%s", path, cID) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + urlStr := "/v2/certificates" + urlStr = path.Join(urlStr, cID) + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "DELETE") })