Skip to content

Commit

Permalink
implement interface to manage custom hostnames (cloudflare#123)
Browse files Browse the repository at this point in the history
* implement interfcae to manage custom hostnames

* place holder UpdateCustomHostnameSSL function

* improve documentation
  • Loading branch information
ElvinEfendi authored and elithrar committed May 13, 2017
1 parent 7a012bf commit 86147da
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The current feature list includes:
- [x] Cloudflare IPs
- [x] User Administration (partial)
- [x] Virtual DNS Management
- [x] Custom hostnames
- [ ] Organization Administration
- [ ] [Railgun](https://www.cloudflare.com/railgun/) administration
- [ ] [Keyless SSL](https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/)
Expand Down
149 changes: 149 additions & 0 deletions custom_hostname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package cloudflare

import (
"encoding/json"
"net/url"
"strconv"

"github.com/pkg/errors"
)

// CustomHostnameSSL represents the SSL section in a given custom hostname.
type CustomHostnameSSL struct {
Status string `json:"status,omitempty"`
Method string `json:"method,omitempty"`
Type string `json:"type,omitempty"`
CnameTarget string `json:"cname_target,omitempty"`
CnameName string `json:"cname_name,omitempty"`
}

// CustomMetadata defines custom metadata for the hostname. This requires logic to be implemented by Cloudflare to act on the data provided.
type CustomMetadata map[string]interface{}

// CustomHostname represents a custom hostname in a zone.
type CustomHostname struct {
ID string `json:"id,omitempty"`
Hostname string `json:"hostname,omitempty"`
SSL CustomHostnameSSL `json:"ssl,omitempty"`
CustomMetadata CustomMetadata `json:"custom_metadata,omitempty"`
}

// CustomHostNameResponse represents a response from the Custom Hostnames endpoints.
type CustomHostnameResponse struct {
Result CustomHostname `json:"result"`
Response
}

// CustomHostnameListResponse represents a response from the Custom Hostnames endpoints.
type CustomHostnameListResponse struct {
Result []CustomHostname `json:"result"`
Response
ResultInfo `json:"result_info"`
}

// Modify SSL configuration for the given custom hostname in the given zone.
//
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-update-custom-hostname-configuration
func (api *API) UpdateCustomHostnameSSL(zoneID string, customHostnameID string, ssl CustomHostnameSSL) (CustomHostname, error) {
return CustomHostname{}, errors.New("Not implemented")
}

// Delete a custom hostname (and any issued SSL certificates)
//
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-delete-a-custom-hostname-and-any-issued-ssl-certificates-
func (api *API) DeleteCustomHostname(zoneID string, customHostnameID string) error {
uri := "/zones/" + zoneID + "/custom_hostnames/" + customHostnameID
res, err := api.makeRequest("DELETE", uri, nil)
if err != nil {
return errors.Wrap(err, errMakeRequestError)
}

var response *CustomHostnameResponse
err = json.Unmarshal(res, &response)
if err != nil {
return errors.Wrap(err, errUnmarshalError)
}

return nil
}

// CreateCustomHostname creates a new custom hostname and requests that an SSL certificate be issued for it.
//
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-create-custom-hostname
func (api *API) CreateCustomHostname(zoneID string, ch CustomHostname) (*CustomHostnameResponse, error) {
uri := "/zones/" + zoneID + "/custom_hostnames"
res, err := api.makeRequest("POST", uri, ch)
if err != nil {
return nil, errors.Wrap(err, errMakeRequestError)
}

var response *CustomHostnameResponse
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}

return response, nil
}

// CustomHostnames fetches custom hostnames for the given zone,
// by applying filter.Hostname if not empty and scoping the result to page'th 50 items.
//
// The returned ResultInfo can be used to implement pagination.
//
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-list-custom-hostnames
func (api *API) CustomHostnames(zoneID string, page int, filter CustomHostname) ([]CustomHostname, ResultInfo, error) {
v := url.Values{}
v.Set("per_page", "50")
v.Set("page", strconv.Itoa(page))
if filter.Hostname != "" {
v.Set("hostname", filter.Hostname)
}
query := "?" + v.Encode()

uri := "/zones/" + zoneID + "/custom_hostnames" + query
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return []CustomHostname{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
}
var customHostnameListResponse CustomHostnameListResponse
err = json.Unmarshal(res, &customHostnameListResponse)
if err != nil {
return []CustomHostname{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
}

return customHostnameListResponse.Result, customHostnameListResponse.ResultInfo, nil
}

// CustomHostname inspects the given custom hostname in the given zone.
//
// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-custom-hostname-configuration-details
func (api *API) CustomHostname(zoneID string, customHostnameID string) (CustomHostname, error) {
uri := "/zones/" + zoneID + "/custom_hostnames/" + customHostnameID
res, err := api.makeRequest("GET", uri, nil)
if err != nil {
return CustomHostname{}, errors.Wrap(err, errMakeRequestError)
}

var response CustomHostnameResponse
err = json.Unmarshal(res, &response)
if err != nil {
return CustomHostname{}, errors.Wrap(err, errUnmarshalError)
}

return response.Result, nil
}

// CustomHostnameIDByName retrieves the ID for the given hostname in the given zone.
func (api *API) CustomHostnameIDByName(zoneID string, hostname string) (string, error) {
customHostnames, _, err := api.CustomHostnames(zoneID, 1, CustomHostname{Hostname: hostname})
if err != nil {
return "", errors.Wrap(err, "CustomHostnames command failed")
}
for _, ch := range customHostnames {
if ch.Hostname == hostname {
return ch.ID, nil
}
}
return "", errors.New("CustomHostname could not be found")
}
177 changes: 177 additions & 0 deletions custom_hostname_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package cloudflare

import (
"fmt"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCustomHostname_DeleteCustomHostname(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/zones/foo/custom_hostnames/bar", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "DELETE", r.Method, "Expected method 'DELETE', got %s", r.Method)

w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `
{
"id": "bar"
}`)
})

err := client.DeleteCustomHostname("foo", "bar")

assert.NoError(t, err)
}

func TestCustomHostname_CreateCustomHostname(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/zones/foo/custom_hostnames", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method, "Expected method 'POST', got %s", r.Method)

w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `
{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "0d89c70d-ad9f-4843-b99f-6cc0252067e9",
"hostname": "app.example.com",
"ssl": {
"status": "pending_validation",
"method": "cname",
"type": "dv",
"cname_target": "dcv.digicert.com",
"cname_name": "810b7d5f01154524b961ba0cd578acc2.app.example.com"
}
}
}`)
})

response, err := client.CreateCustomHostname("foo", CustomHostname{Hostname: "app.example.com", SSL: CustomHostnameSSL{Method: "cname", Type: "dv"}})

want := &CustomHostnameResponse{
Result: CustomHostname{
ID: "0d89c70d-ad9f-4843-b99f-6cc0252067e9",
Hostname: "app.example.com",
SSL: CustomHostnameSSL{
Type: "dv",
Method: "cname",
Status: "pending_validation",
CnameTarget: "dcv.digicert.com",
CnameName: "810b7d5f01154524b961ba0cd578acc2.app.example.com",
},
},
Response: Response{Success: true, Errors: []ResponseInfo{}, Messages: []ResponseInfo{}},
}

if assert.NoError(t, err) {
assert.Equal(t, want, response)
}
}

func TestCustomHostname_CustomHostnames(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/zones/foo/custom_hostnames", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)

w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"result": [
{
"id": "custom_host_1",
"hostname": "custom.host.one",
"ssl": {
"type": "dv",
"method": "cname",
"status": "pending_validation",
"cname_target": "dcv.digicert.com",
"cname_name": "810b7d5f01154524b961ba0cd578acc2.app.example.com"
},
"custom_metadata": {
"a_random_field": "random field value"
}
}
],
"result_info": {
"page": 1,
"per_page": 20,
"count": 5,
"total_count": 5
}
}`)
})

customHostnames, _, err := client.CustomHostnames("foo", 1, CustomHostname{})

want := []CustomHostname{
{
ID: "custom_host_1",
Hostname: "custom.host.one",
SSL: CustomHostnameSSL{
Type: "dv",
Method: "cname",
Status: "pending_validation",
CnameTarget: "dcv.digicert.com",
CnameName: "810b7d5f01154524b961ba0cd578acc2.app.example.com",
},
CustomMetadata: CustomMetadata{"a_random_field": "random field value"},
},
}

if assert.NoError(t, err) {
assert.Equal(t, want, customHostnames)
}
}

func TestCustomHostname_CustomHostname(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/zones/foo/custom_hostnames/bar", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Expected method 'GET', got %s", r.Method)

w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"result": {
"id": "bar",
"hostname": "foo.bar.com",
"ssl": {
"type": "dv",
"method": "http",
"status": "active"
},
"custom_metadata": {
"origin": "a.custom.origin"
}
}
}`)
})

customHostname, err := client.CustomHostname("foo", "bar")

want := CustomHostname{
ID: "bar",
Hostname: "foo.bar.com",
SSL: CustomHostnameSSL{
Status: "active",
Method: "http",
Type: "dv",
},
CustomMetadata: CustomMetadata{"origin": "a.custom.origin"},
}

if assert.NoError(t, err) {
assert.Equal(t, want, customHostname)
}
}

0 comments on commit 86147da

Please sign in to comment.