├── .travis.yml ├── edgegrid ├── reader.go ├── gtm_client.go ├── papi_client.go ├── client_test.go ├── gtm_endpoints.go ├── gtm_endpoints_test.go ├── util_test.go ├── client.go ├── util.go ├── papi_json_fixtures_test.go ├── papi_endpoints_test.go ├── papi_endpoints.go ├── auth.go ├── auth_test.go ├── papi_client_api.go ├── papi_resources.go ├── gtm_client_api.go ├── gtm_resources.go ├── gtm_client_api_test.go ├── papi_client_api_test.go └── gtm_json_fixtures_test.go ├── NOTICE ├── CONTRIBUTING.md ├── LICENSE ├── Makefile └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8 5 | - 1.10.2 6 | - tip 7 | -------------------------------------------------------------------------------- /edgegrid/reader.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import "bytes" 4 | 5 | type reader struct { 6 | *bytes.Buffer 7 | } 8 | 9 | func (m reader) Close() error { return nil } 10 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | go-edgegrid 2 | 3 | Copyright 2015-2016 Comcast Cable Communications Management, LLC 4 | 5 | This product includes software developed at Comcast (http://www.comcast.com/). 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | If you would like to contribute code to this project you can do so through GitHub by forking the repository and sending a pull request. 2 | 3 | Before Comcast accepts your code into the project you must sign the Comcast Contributor License Agreement (CLA). 4 | 5 | If you haven't previously signed a Comcast CLA, we can email you a PDF that you can sign and scan back to us. Please send us an email or create a new GitHub issue to request a PDF version of the CLA. 6 | -------------------------------------------------------------------------------- /edgegrid/gtm_client.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import "net/http" 4 | 5 | // GTMClient is an Akamai GTM API client. 6 | // https://developer.akamai.com/api/luna/config-gtm/overview.html 7 | type GTMClient struct { 8 | Credentials *AuthCredentials 9 | HTTPClient *http.Client 10 | } 11 | 12 | // GetCredentials takes a GTMClient and returns its Credentials. 13 | func (c GTMClient) GetCredentials() *AuthCredentials { 14 | return c.Credentials 15 | } 16 | 17 | // GetHTTPClient takes a GTMClient and returns its HTTPClient. 18 | func (c GTMClient) GetHTTPClient() *http.Client { 19 | return c.HTTPClient 20 | } 21 | -------------------------------------------------------------------------------- /edgegrid/papi_client.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import "net/http" 4 | 5 | // PAPIClient is an Akamai PAPI API client. 6 | // https://developer.akamai.com/api/luna/papi/overview.html 7 | type PAPIClient struct { 8 | Credentials *AuthCredentials 9 | HTTPClient *http.Client 10 | } 11 | 12 | // GetCredentials takes a PAPIClient and returns its credentials. 13 | func (c PAPIClient) GetCredentials() *AuthCredentials { 14 | return c.Credentials 15 | } 16 | 17 | // GetHTTPClient takes a PAPIClient and returns its HTTPClient. 18 | func (c PAPIClient) GetHTTPClient() *http.Client { 19 | return c.HTTPClient 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Comcast Cable Communications Management, LLC 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /edgegrid/client_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestNewCredentials(t *testing.T) { 9 | os.Setenv("AKAMAI_EDGEGRID_HOST", "https://apiHost") 10 | os.Setenv("AKAMAI_EDGEGRID_ACCESS_TOKEN", "accessToken") 11 | os.Setenv("AKAMAI_EDGEGRID_CLIENT_TOKEN", "clientToken") 12 | os.Setenv("AKAMAI_EDGEGRID_CLIENT_SECRET", "clientSecret") 13 | 14 | creds := NewCredentials() 15 | 16 | if creds.APIHost != "https://apiHost" { 17 | t.Error("ClientWithCredsFromEnv should set an APIHost") 18 | } 19 | 20 | if creds.AccessToken != "accessToken" { 21 | t.Error("ClientWithCredsFromEnv should set an AccessToken") 22 | } 23 | 24 | if creds.ClientToken != "clientToken" { 25 | t.Error("ClientWithCredsFromEnv should set a ClientToken") 26 | } 27 | 28 | if creds.ClientSecret != "clientSecret" { 29 | t.Error("ClientWithCredsFromEnv should set a ClientSecret") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VETARGS?=-all 2 | SOURCE?=./... 3 | 4 | all: updatedeps lint vet test install 5 | 6 | updatedeps: 7 | @go tool cover 2>/dev/null; if [ $$? -eq 3 ]; then \ 8 | go get -u golang.org/x/tools/cmd/cover; \ 9 | fi 10 | go get github.com/gobs/pretty 11 | go get github.com/golang/lint/golint 12 | go get $(SOURCE) 13 | 14 | test: 15 | go test $(SOURCE) -cover 16 | 17 | cover: 18 | go test $(SOURCE) -coverprofile=coverage.out 19 | cd edgegrid; go tool cover -html=../coverage.out 20 | rm coverage.out 21 | 22 | install: 23 | go install $(SOURCE) 24 | 25 | lint: 26 | golint -set_exit_status edgegrid 27 | 28 | # vet runs the Go source code static analysis tool `vet` to find 29 | # any common errors. 30 | vet: 31 | @go tool vet 2>/dev/null ; if [ $$? -eq 3 ]; then \ 32 | go get golang.org/x/tools/cmd/vet; \ 33 | fi 34 | @echo "go tool vet $(VETARGS) edgegrid" 35 | @go tool vet $(VETARGS) edgegrid ; if [ $$? -eq 1 ]; then \ 36 | echo ""; \ 37 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 38 | echo "and fix them if necessary before submitting the code for review."; \ 39 | exit 1; \ 40 | fi 41 | -------------------------------------------------------------------------------- /edgegrid/gtm_endpoints.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const gtmPath = "/config-gtm/v1/" 8 | 9 | func gtmBase(c *AuthCredentials) string { 10 | return concat([]string{ 11 | c.APIHost, 12 | gtmPath, 13 | }) 14 | } 15 | 16 | func domainsEndpoint(c *AuthCredentials) string { 17 | return concat([]string{ 18 | gtmBase(c), 19 | "domains", 20 | }) 21 | } 22 | 23 | func domainEndpoint(c *AuthCredentials, domain string) string { 24 | return concat([]string{ 25 | domainsEndpoint(c), 26 | "/", 27 | domain, 28 | }) 29 | } 30 | 31 | func domainStatusEndpoint(c *AuthCredentials, domain string) string { 32 | return concat([]string{ 33 | domainsEndpoint(c), 34 | "/", 35 | domain, 36 | "/status/current", 37 | }) 38 | } 39 | 40 | func dcEndpoint(c *AuthCredentials, domain string, id int) string { 41 | return fmt.Sprintf("%s/%d", dcsEndpoint(c, domain), id) 42 | } 43 | 44 | func dcsEndpoint(c *AuthCredentials, domain string) string { 45 | return concat([]string{ 46 | domainEndpoint(c, domain), 47 | "/datacenters", 48 | }) 49 | } 50 | 51 | func propertiesEndpoint(c *AuthCredentials, domain string) string { 52 | return concat([]string{ 53 | domainEndpoint(c, domain), 54 | "/properties", 55 | }) 56 | } 57 | 58 | func propertyEndpoint(c *AuthCredentials, domain, property string) string { 59 | return concat([]string{ 60 | propertiesEndpoint(c, domain), 61 | "/", 62 | property, 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /edgegrid/gtm_endpoints_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import "testing" 4 | 5 | var c = &AuthCredentials{"accessToken", "clientToken", "clientSecret", "http://apibase.com"} 6 | 7 | func TestGtmBase(t *testing.T) { 8 | if gtmBase(c) != "http://apibase.com/config-gtm/v1/" { 9 | t.Error("gtmBase should return the proper endpoint") 10 | } 11 | } 12 | 13 | func TestDomainsEndpoint(t *testing.T) { 14 | if domainsEndpoint(c) != "http://apibase.com/config-gtm/v1/domains" { 15 | t.Error("domainsEndpoint should return the proper endpoint") 16 | } 17 | } 18 | 19 | func TestDomainEndpoint(t *testing.T) { 20 | if domainEndpoint(c, "domain") != "http://apibase.com/config-gtm/v1/domains/domain" { 21 | t.Error("domainEndpoint should return the proper endpoint") 22 | } 23 | } 24 | 25 | func TestDomainStatusEndpoint(t *testing.T) { 26 | if domainStatusEndpoint(c, "domain") != "http://apibase.com/config-gtm/v1/domains/domain/status/current" { 27 | t.Error("domainStatusEndpoint should return the proper endpoint") 28 | } 29 | } 30 | 31 | func TestDcsEndpoint(t *testing.T) { 32 | if dcsEndpoint(c, "domain") != "http://apibase.com/config-gtm/v1/domains/domain/datacenters" { 33 | t.Error("dcsEndpoint should return the proper endpoint") 34 | } 35 | } 36 | 37 | func TestDcEndpoint(t *testing.T) { 38 | if dcEndpoint(c, "domain", 4) != "http://apibase.com/config-gtm/v1/domains/domain/datacenters/4" { 39 | t.Error("dcEndpoint should return the proper endpoint") 40 | } 41 | } 42 | 43 | func TestPropertiesEndpoint(t *testing.T) { 44 | if propertiesEndpoint(c, "domain") != "http://apibase.com/config-gtm/v1/domains/domain/properties" { 45 | t.Error("propertiesEndpoint should return the proper endpoint") 46 | } 47 | } 48 | 49 | func TestPropertyEndpoint(t *testing.T) { 50 | if propertyEndpoint(c, "domain", "property") != "http://apibase.com/config-gtm/v1/domains/domain/properties/property" { 51 | t.Error("propertyEndpoint should return the proper endpoint") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /edgegrid/util_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "testing" 11 | ) 12 | 13 | type testClient struct { 14 | Credentials *AuthCredentials 15 | HTTPClient *http.Client 16 | } 17 | 18 | func (c testClient) GetCredentials() *AuthCredentials { 19 | return c.Credentials 20 | } 21 | 22 | func (c testClient) GetHTTPClient() *http.Client { 23 | return c.HTTPClient 24 | } 25 | 26 | func utilTestTools(code int, body string) (*httptest.Server, *testClient) { 27 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 28 | w.WriteHeader(code) 29 | w.Header().Set("Content-Type", "application/json") 30 | fmt.Fprintf(w, body) 31 | })) 32 | 33 | tr := &http.Transport{ 34 | Proxy: func(req *http.Request) (*url.URL, error) { 35 | return url.Parse(server.URL) 36 | }, 37 | } 38 | 39 | httpClient := &http.Client{Transport: tr} 40 | 41 | return server, &testClient{ 42 | &AuthCredentials{ 43 | "accessToken", 44 | "clientToken", 45 | "clientSecret", 46 | server.URL, 47 | }, 48 | httpClient, 49 | } 50 | } 51 | 52 | func TestAuthenticate(t *testing.T) { 53 | body := []byte("hello world") 54 | server, client := utilTestTools(200, "hello world") 55 | defer server.Close() 56 | 57 | req, _ := http.NewRequest("GET", client.GetCredentials().APIHost, bytes.NewBuffer(body)) 58 | 59 | authenticatedReq := authenticate(client, req) 60 | header := authenticatedReq.Header.Get("Authorization") 61 | 62 | if header == "" { 63 | t.Error("authenticate should set an Authorization header on the request") 64 | } 65 | } 66 | 67 | func TestConcat(t *testing.T) { 68 | res := concat([]string{"I ", "am ", "a ", "sentence"}) 69 | 70 | if res != "I am a sentence" { 71 | t.Error("concat failed") 72 | } 73 | } 74 | 75 | func TestUrlPathWithQuery(t *testing.T) { 76 | req, _ := http.NewRequest("GET", "http://foo.com/biz/bar?someKey=someVal", nil) 77 | 78 | if urlPathWithQuery(req) != "/biz/bar?someKey=someVal" { 79 | t.Error("urlPathWithQuery should return the request URL path & query") 80 | } 81 | } 82 | 83 | func TestDoClientReq(t *testing.T) { 84 | server, client := utilTestTools(200, "hello world") 85 | defer server.Close() 86 | 87 | resp, err := doClientReq(client, "GET", client.GetCredentials().APIHost, nil) 88 | if err != nil { 89 | t.Error("test doClientReq failed") 90 | } 91 | 92 | body, _ := ioutil.ReadAll(resp.Body) 93 | 94 | if string(body) != "hello world" { 95 | t.Error("test doClientReq failed") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /edgegrid/client.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | ) 7 | 8 | // Client is an interface for an Akamai API client. 9 | type Client interface { 10 | GetCredentials() *AuthCredentials 11 | GetHTTPClient() *http.Client 12 | } 13 | 14 | // AuthCredentials houses various Akamai-client-specific 15 | // data necessary to authenticate the Akamai API. 16 | type AuthCredentials struct { 17 | AccessToken string 18 | ClientToken string 19 | ClientSecret string 20 | APIHost string 21 | } 22 | 23 | // GTMClientWithCreds takes an accessToken, a clientToken, a clientSecret, 24 | // and an apiHost string and returns a GTMClient. 25 | func GTMClientWithCreds(accessToken, clientToken, clientSecret, apiHost string) *GTMClient { 26 | return >MClient{ 27 | &AuthCredentials{accessToken, clientToken, clientSecret, apiHost}, 28 | &http.Client{}, 29 | } 30 | } 31 | 32 | // NewGTMClient returns a GTMClient using the 33 | // AKAMAI_EDGEGRID_ACCESS_TOKEN, AKAMAI_EDGEGRID_CLIENT_TOKEN, 34 | // AKAMAI_EDGEGRID_CLIENT_SECRET, and AKAMAI_EDGEGRID_HOST environment 35 | // variables. 36 | func NewGTMClient() *GTMClient { 37 | return >MClient{ 38 | NewCredentials(), 39 | &http.Client{}, 40 | } 41 | } 42 | 43 | // NewPAPIClient returns a PAPIClient using the 44 | // AKAMAI_EDGEGRID_ACCESS_TOKEN, AKAMAI_EDGEGRID_CLIENT_TOKEN, 45 | // AKAMAI_EDGEGRID_CLIENT_SECRET, and AKAMAI_EDGEGRID_HOST environment 46 | // variables. 47 | func NewPAPIClient() *PAPIClient { 48 | return &PAPIClient{ 49 | NewCredentials(), 50 | &http.Client{}, 51 | } 52 | } 53 | 54 | // PAPIClientWithCreds takes an accessToken, a clientToken, a clientSecret, 55 | // and an apiHost and returns a PAPIClient. 56 | func PAPIClientWithCreds(accessToken, clientToken, clientSecret, apiHost string) *PAPIClient { 57 | return &PAPIClient{ 58 | &AuthCredentials{accessToken, clientToken, clientSecret, apiHost}, 59 | &http.Client{}, 60 | } 61 | } 62 | 63 | // NewCredentials returns an AuthCredentials 64 | // using the AKAMAI_EDGEGRID_ACCESS_TOKEN, AKAMAI_EDGEGRID_CLIENT_TOKEN, 65 | // AKAMAI_EDGEGRID_CLIENT_SECRET, and AKAMAI_EDGEGRID_HOST environment 66 | // variables. 67 | func NewCredentials() *AuthCredentials { 68 | return &AuthCredentials{ 69 | os.Getenv("AKAMAI_EDGEGRID_ACCESS_TOKEN"), 70 | os.Getenv("AKAMAI_EDGEGRID_CLIENT_TOKEN"), 71 | os.Getenv("AKAMAI_EDGEGRID_CLIENT_SECRET"), 72 | os.Getenv("AKAMAI_EDGEGRID_HOST"), 73 | } 74 | } 75 | 76 | // LogRequests returns true if the AK_LOG environment variable is set; 77 | // false if it is not. 78 | func LogRequests() bool { 79 | return os.Getenv("AK_LOG") != "" 80 | } 81 | -------------------------------------------------------------------------------- /edgegrid/util.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | func authenticate(c Client, req *http.Request) *http.Request { 12 | auth := Auth(NewAuthParams(req, c.GetCredentials().AccessToken, c.GetCredentials().ClientToken, c.GetCredentials().ClientSecret)) 13 | 14 | req.Header.Add("Authorization", auth) 15 | 16 | return req 17 | } 18 | 19 | func concat(arr []string) string { 20 | var buff bytes.Buffer 21 | 22 | for _, elem := range arr { 23 | buff.WriteString(elem) 24 | } 25 | 26 | return buff.String() 27 | } 28 | 29 | func urlPathWithQuery(req *http.Request) string { 30 | var query string 31 | 32 | if req.URL.RawQuery != "" { 33 | query = concat([]string{ 34 | "?", 35 | req.URL.RawQuery, 36 | }) 37 | } else { 38 | query = "" 39 | } 40 | 41 | return concat([]string{ 42 | req.URL.Path, 43 | query, 44 | }) 45 | } 46 | 47 | func resourceRequest(c Client, method string, url string, body []byte, responseStruct interface{}) error { 48 | if LogRequests() { 49 | fmt.Printf("Request url: \n\t%s\nrequest body: \n\t%s \n\n", url, string(body)) 50 | } 51 | req, err := http.NewRequest(method, url, bytes.NewReader(body)) 52 | 53 | if err != nil { 54 | return err 55 | } 56 | 57 | req.Header.Add("Content-Type", "application/json") 58 | 59 | authReq := authenticate(c, req) 60 | resp, err := c.GetHTTPClient().Do(authReq) 61 | 62 | if err != nil { 63 | return err 64 | } 65 | 66 | bodyContents, err := ioutil.ReadAll(resp.Body) 67 | if LogRequests() { 68 | fmt.Printf("Response status: \n\t%d\nresponse body: \n\t%s \n\n", resp.StatusCode, bodyContents) 69 | } 70 | if err != nil { 71 | return err 72 | } 73 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted { 74 | akError := &AkamaiError{} 75 | if err := json.Unmarshal(bodyContents, &akError); err != nil { 76 | return err 77 | } 78 | akError.RequestBody = string(body) 79 | akError.ResponseBody = string(bodyContents) 80 | return akError 81 | } 82 | if err := json.Unmarshal(bodyContents, responseStruct); err != nil { 83 | return err 84 | } 85 | return nil 86 | } 87 | 88 | func doClientReq(c Client, method string, url string, body []byte) (*http.Response, error) { 89 | if LogRequests() { 90 | fmt.Printf("Request url: \n\t%s\nrequest body: \n\t%s \n\n", url, string(body)) 91 | } 92 | req, err := http.NewRequest(method, url, bytes.NewReader(body)) 93 | 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | req.Header.Add("Content-Type", "application/json") 99 | 100 | authReq := authenticate(c, req) 101 | resp, err := c.GetHTTPClient().Do(authReq) 102 | 103 | return resp, err 104 | } 105 | 106 | func getXML(c Client, url string) (*http.Response, error) { 107 | if LogRequests() { 108 | fmt.Printf("Request url: \n\t%s\n", url) 109 | } 110 | req, err := http.NewRequest("GET", url, nil) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | req.Header.Add("Accept", "text/xml") 116 | authReq := authenticate(c, req) 117 | resp, err := c.GetHTTPClient().Do(authReq) 118 | 119 | return resp, err 120 | } 121 | -------------------------------------------------------------------------------- /edgegrid/papi_json_fixtures_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | const ( 4 | groupsJSON = `{ 5 | "groups": { 6 | "items": [{ 7 | "groupName": "group one", 8 | "groupId": "1", 9 | "parentGroupId": "parent1", 10 | "contractIds": ["contract1", "contract1-2"] 11 | }, { 12 | "groupName": "group two", 13 | "groupId": "2", 14 | "parentGroupId": "parent2", 15 | "contractIds": ["contract2", "contract2-2"] 16 | }] 17 | } 18 | }` 19 | 20 | productsJSON = `{ 21 | "products": { 22 | "items": [{ 23 | "productName": "prod one", 24 | "productId": "1" 25 | }, { 26 | "productName": "prod two", 27 | "productId": "2" 28 | }] 29 | } 30 | }` 31 | 32 | cpCodesJSON = `{ 33 | "accountId": "act_1-1TJZFB", 34 | "contractId": "ctr_1-1TJZFW", 35 | "groupId": "grp_15225", 36 | "cpcodes": { 37 | "items": [{ 38 | "cpcodeId": "someCodeId", 39 | "cpcodeName": "someName", 40 | "productIds": ["someId"], 41 | "createdDate":"someDate" 42 | }] 43 | } 44 | }` 45 | 46 | hostnamesJSON = `{ 47 | "edgeHostnames": { 48 | "items": [{ 49 | "edgeHostnameId": "hostId1", 50 | "domainPrefix": "foo.com", 51 | "domainSuffix": "edge.net", 52 | "ipVersionBehavior": "IPV4", 53 | "secure" : false, 54 | "edgeHostnameDomain": "foo.com.edge.net" 55 | }] 56 | } 57 | }` 58 | 59 | propertiesJSON = `{ 60 | "properties": { 61 | "items": [{ 62 | "accountId": "accountId", 63 | "contractId": "contractId", 64 | "groupId": "groupid", 65 | "propertyId": "propertyId", 66 | "propertyName": "m.example.com", 67 | "latestVersion": 1, 68 | "stagingVersion": 2, 69 | "productionVersion": null, 70 | "note": "a note" 71 | }] 72 | } 73 | }` 74 | 75 | propertyVersionsJSON = `{ 76 | "versions": { 77 | "items": [{ 78 | "propertyVersion": 2, 79 | "updatedByUser": "some user", 80 | "updatedDate": "updatedDate", 81 | "productionStatus": "INACTIVE", 82 | "stagingStatus": "ACTIVE", 83 | "etag": "123", 84 | "productId": "productId", 85 | "note": "some note" 86 | }] 87 | } 88 | }` 89 | 90 | propertyRulesJSON = `{ 91 | "rules": { 92 | "name": "default", 93 | "options": { 94 | "is_secure": false 95 | }, 96 | "uuid": "some-uuid", 97 | "behaviors": [ 98 | { 99 | "name": "origin", 100 | "options": { 101 | "type": "customer", 102 | "hostname": "example.com", 103 | "forwardhostheader": "requesthostheader", 104 | "cachekeyhostname": "originhostname", 105 | "compression": "on", 106 | "tcip_enabled": "off", 107 | "http_port": "80" 108 | } 109 | }, 110 | { 111 | "name": "cpCode", 112 | "options": { 113 | "cpcode": { 114 | "id": 12345, 115 | "name": "main site" 116 | } 117 | } 118 | } 119 | ] 120 | } 121 | }` 122 | 123 | propertyHostnamesJSON = `{ 124 | "accountId": "act_1-1TJZFB", 125 | "contractId": "ctr_1-1TJZH5", 126 | "groupId": "grp_15225", 127 | "propertyId": "prp_175780", 128 | "propertyVersion": 1, 129 | "etag": "6aed418629b4e5c0", 130 | "hostnames": { 131 | "items": [ 132 | { 133 | "cnameType": "EDGE_HOSTNAME", 134 | "edgeHostnameId": "ehn_895822", 135 | "cnameFrom": "example.com", 136 | "cnameTo": "example.com.edgesuite.net" 137 | } 138 | ] 139 | } 140 | }` 141 | 142 | activationsJSON = `{ 143 | "accountId": "act_1-1TJZFB", 144 | "contractId": "ctr_1-1TJZH5", 145 | "groupId": "grp_15225", 146 | "activations": { 147 | "items": [{ 148 | "activationId": "123", 149 | "propertyName": "example.com", 150 | "propertyId": "propId", 151 | "propertyVersion": 1, 152 | "network": "STAGING", 153 | "status": "ACTIVE", 154 | "activationType": "ACTIVATE", 155 | "submitDate": "2014-03-05T02:22:12Z", 156 | "updateDate": "2014-03-04T21:12:57Z", 157 | "note": "some note", 158 | "notifyEmails": [ 159 | "you@example.com", 160 | "them@example.com" 161 | ] 162 | }] 163 | } 164 | }` 165 | ) 166 | -------------------------------------------------------------------------------- /edgegrid/papi_endpoints_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import "testing" 4 | 5 | var a = &AuthCredentials{"accessToken", "clientToken", "clientSecret", "http://apibase.com"} 6 | 7 | func TestPapiBase(t *testing.T) { 8 | if papiBase(a) != "http://apibase.com/papi/v0/" { 9 | t.Error("papiBase should return the proper endpoint") 10 | } 11 | } 12 | 13 | func TestGroups(t *testing.T) { 14 | if papiGroupsEndpoint(a) != "http://apibase.com/papi/v0/groups/" { 15 | t.Error("groups should return the proper endpoint") 16 | } 17 | } 18 | 19 | func TestProducts(t *testing.T) { 20 | if papiProductsEndpoint(a, "someId") != "http://apibase.com/papi/v0/products?contractId=someId" { 21 | t.Error("products should return the proper endpoint") 22 | } 23 | } 24 | 25 | func TestCpCodes(t *testing.T) { 26 | if papiCpCodesEndpoint(a, "contractId", "groupId") != "http://apibase.com/papi/v0/cpcodes/?contractId=contractId&groupId=groupId" { 27 | t.Error("cpCodes should return the proper endpoint") 28 | } 29 | } 30 | 31 | func TestCpCode(t *testing.T) { 32 | if papiCpCodeEndpoint(a, "cpCodeId", "contractId", "groupId") != "http://apibase.com/papi/v0/cpcodes/cpCodeId?contractId=contractId&groupId=groupId" { 33 | t.Error("cpCode should return the proper endpoint") 34 | } 35 | } 36 | 37 | func TestHostnames(t *testing.T) { 38 | if papiHostnamesEndpoint(a, "contractId", "groupId") != "http://apibase.com/papi/v0/edgehostnames?contractId=contractId&groupId=groupId" { 39 | t.Error("hostnames should return the proper endpoint") 40 | } 41 | } 42 | 43 | func TestHostname(t *testing.T) { 44 | if papiHostnameEndpoint(a, "hostId", "contractId", "groupId") != "http://apibase.com/papi/v0/edgehostnames/hostId?contractId=contractId&groupId=groupId" { 45 | t.Error("hostname should return the proper endpoint") 46 | } 47 | } 48 | 49 | func TestPropertiesEp(t *testing.T) { 50 | if papiPropertiesEndpoint(a, "contractId", "groupId") != "http://apibase.com/papi/v0/properties/?contractId=contractId&groupId=groupId" { 51 | t.Error("papiProperties should return the proper endpoint") 52 | } 53 | } 54 | 55 | func TestPropertyEp(t *testing.T) { 56 | if papiPropertyEndpoint(a, "propId", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId?contractId=contractId&groupId=groupId" { 57 | t.Error("papiProperty should return the proper endpoint") 58 | } 59 | } 60 | 61 | func TestPropertyVersionsEp(t *testing.T) { 62 | if papiPropertyVersionsEndpoint(a, "propId", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId/versions?contractId=contractId&groupId=groupId" { 63 | t.Error("papiPropertyVersions should return the proper endpoint") 64 | } 65 | } 66 | 67 | func TestPropertyVersionEp(t *testing.T) { 68 | if papiPropertyVersionEndpoint(a, "2", "propId", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId/versions/2?contractId=contractId&groupId=groupId" { 69 | t.Error("papiPropertyVersion should return the proper endpoint") 70 | } 71 | } 72 | 73 | func TestPropertyLatestVersionEp(t *testing.T) { 74 | if papiPropertyLatestVersionEndpoint(a, "propId", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId/versions/latest?contractId=contractId&groupId=groupId" { 75 | t.Error("papiPropertyLatestVersion should return the proper endpoint") 76 | } 77 | } 78 | 79 | func TestPropertyRulesEp(t *testing.T) { 80 | if papiPropertyRulesEndpoint(a, "propId", "version", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId/versions/version/rules/?contractId=contractId&groupId=groupId" { 81 | t.Error("papiPropertyRules should return the proper endpoint") 82 | } 83 | } 84 | 85 | func TestPropertyHostnamesEp(t *testing.T) { 86 | if papiPropertyHostnamesEndpoint(a, "propId", "version", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId/versions/version/hostnames/?contractId=contractId&groupId=groupId" { 87 | t.Error(papiPropertyHostnamesEndpoint(a, "propId", "version", "contractId", "groupId")) 88 | } 89 | } 90 | 91 | func TestActivationsEp(t *testing.T) { 92 | if papiActivationsEndpoint(a, "propId", "contractId", "groupId") != "http://apibase.com/papi/v0/properties/propId/activations?contractId=contractId&groupId=groupId" { 93 | t.Error("papiActivations should return the proper endpoint") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /edgegrid/papi_endpoints.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | const papiPath = "/papi/v0/" 4 | 5 | func papiBase(c *AuthCredentials) string { 6 | return concat([]string{ 7 | c.APIHost, 8 | papiPath, 9 | }) 10 | } 11 | 12 | func papiGroupsEndpoint(c *AuthCredentials) string { 13 | return concat([]string{ 14 | papiBase(c), 15 | "groups/", 16 | }) 17 | } 18 | 19 | func papiProductsEndpoint(c *AuthCredentials, contractID string) string { 20 | return concat([]string{ 21 | papiBase(c), 22 | "products?contractId=", 23 | contractID, 24 | }) 25 | } 26 | 27 | func papiCpCodesEndpoint(c *AuthCredentials, contractID, groupID string) string { 28 | return concat([]string{ 29 | papiBase(c), 30 | "cpcodes/", 31 | papiQuery(contractID, groupID), 32 | }) 33 | } 34 | 35 | func papiCpCodeEndpoint(c *AuthCredentials, cpCodeID, contractID, groupID string) string { 36 | return concat([]string{ 37 | papiBase(c), 38 | "cpcodes/", 39 | cpCodeID, 40 | papiQuery(contractID, groupID), 41 | }) 42 | } 43 | 44 | func papiQuery(contractID, groupID string) string { 45 | return concat([]string{ 46 | "?contractId=", 47 | contractID, 48 | "&groupId=", 49 | groupID, 50 | }) 51 | } 52 | 53 | func papiHostnamesEndpoint(c *AuthCredentials, contractID, groupID string) string { 54 | return concat([]string{ 55 | papiBase(c), 56 | "edgehostnames", 57 | papiQuery(contractID, groupID), 58 | }) 59 | } 60 | 61 | func papiHostnameEndpoint(c *AuthCredentials, hostID, contractID, groupID string) string { 62 | return concat([]string{ 63 | papiBase(c), 64 | "edgehostnames/", 65 | hostID, 66 | papiQuery(contractID, groupID), 67 | }) 68 | } 69 | 70 | func papiPropertiesBase(c *AuthCredentials) string { 71 | return concat([]string{ 72 | papiBase(c), 73 | "properties/", 74 | }) 75 | } 76 | 77 | func papiPropertiesEndpoint(c *AuthCredentials, contractID, groupID string) string { 78 | return concat([]string{ 79 | papiPropertiesBase(c), 80 | papiQuery(contractID, groupID), 81 | }) 82 | } 83 | 84 | func papiPropertyBase(c *AuthCredentials, propID string) string { 85 | return concat([]string{ 86 | papiPropertiesBase(c), 87 | propID, 88 | }) 89 | } 90 | 91 | func papiPropertyEndpoint(c *AuthCredentials, propID, contractID, groupID string) string { 92 | return concat([]string{ 93 | papiPropertyBase(c, propID), 94 | papiQuery(contractID, groupID), 95 | }) 96 | } 97 | 98 | func papiPropertyVersionsBase(c *AuthCredentials, propID, contractID, groupID string) string { 99 | return concat([]string{ 100 | papiPropertyBase(c, propID), 101 | "/versions", 102 | }) 103 | } 104 | 105 | func papiPropertyVersionsEndpoint(c *AuthCredentials, propID, contractID, groupID string) string { 106 | return concat([]string{ 107 | papiPropertyVersionsBase(c, propID, contractID, groupID), 108 | papiQuery(contractID, groupID), 109 | }) 110 | } 111 | 112 | func papiPropertyVersionBase(c *AuthCredentials, version, propID, contractID, groupID string) string { 113 | return concat([]string{ 114 | papiPropertyVersionsBase(c, propID, contractID, groupID), 115 | "/", 116 | version, 117 | }) 118 | } 119 | 120 | func papiPropertyVersionEndpoint(c *AuthCredentials, version, propID, contractID, groupID string) string { 121 | return concat([]string{ 122 | papiPropertyVersionBase(c, version, propID, contractID, groupID), 123 | papiQuery(contractID, groupID), 124 | }) 125 | } 126 | 127 | func papiPropertyLatestVersionEndpoint(c *AuthCredentials, propID, contractID, groupID string) string { 128 | return concat([]string{ 129 | papiPropertyVersionsBase(c, propID, contractID, groupID), 130 | "/latest", 131 | papiQuery(contractID, groupID), 132 | }) 133 | } 134 | 135 | func papiPropertyHostnamesEndpoint(c *AuthCredentials, propID, version, contractID, groupID string) string { 136 | return concat([]string{ 137 | papiPropertyVersionBase(c, version, propID, contractID, groupID), 138 | "/hostnames/", 139 | papiQuery(contractID, groupID), 140 | }) 141 | } 142 | 143 | func papiPropertyRulesEndpoint(c *AuthCredentials, propID, version, contractID, groupID string) string { 144 | return concat([]string{ 145 | papiPropertyVersionBase(c, version, propID, contractID, groupID), 146 | "/rules/", 147 | papiQuery(contractID, groupID), 148 | }) 149 | } 150 | 151 | func papiActivationsEndpoint(c *AuthCredentials, propID, contractID, groupID string) string { 152 | return concat([]string{ 153 | papiPropertyBase(c, propID), 154 | "/activations", 155 | papiQuery(contractID, groupID), 156 | }) 157 | } 158 | -------------------------------------------------------------------------------- /edgegrid/auth.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/base64" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "time" 12 | 13 | "github.com/satori/go.uuid" 14 | ) 15 | 16 | // AuthParams is used to house various request details such that 17 | // the AuthParams can be passed to Auth to sign using the 18 | // Akamai {OPEN} EdgeGrid Authentication scheme. 19 | type AuthParams struct { 20 | req *http.Request 21 | clientToken string 22 | accessToken string 23 | clientSecret string 24 | timestamp string 25 | nonce string 26 | headersToSign []string 27 | } 28 | 29 | // NewAuthParams returns an AuthParams generated from req, accessToken, 30 | // clientToken, and clientSecret. 31 | func NewAuthParams(req *http.Request, accessToken, clientToken, clientSecret string) AuthParams { 32 | id, _ := uuid.NewV4() 33 | return AuthParams{ 34 | req, 35 | clientToken, 36 | accessToken, 37 | clientSecret, 38 | time.Now().UTC().Format("20060102T15:04:05+0000"), 39 | id.String(), 40 | []string{}, 41 | } 42 | } 43 | 44 | // Auth takes prm and returns a string that can be 45 | // used as the `Authorization` header in making Akamai API requests. 46 | // 47 | // The string returned by Auth conforms to the 48 | // Akamai {OPEN} EdgeGrid Authentication scheme. 49 | // https://developer.akamai.com/introduction/Client_Auth.html 50 | func Auth(prm AuthParams) string { 51 | var auth bytes.Buffer 52 | orderedKeys := []string{"client_token", "access_token", "timestamp", "nonce"} 53 | timestamp := prm.timestamp 54 | 55 | m := map[string]string{ 56 | orderedKeys[0]: prm.clientToken, 57 | orderedKeys[1]: prm.accessToken, 58 | orderedKeys[2]: timestamp, 59 | orderedKeys[3]: prm.nonce, 60 | } 61 | 62 | auth.WriteString("EG1-HMAC-SHA256 ") 63 | 64 | for _, each := range orderedKeys { 65 | auth.WriteString(concat([]string{ 66 | each, 67 | "=", 68 | m[each], 69 | ";", 70 | })) 71 | } 72 | 73 | auth.WriteString(signRequest(prm.req, timestamp, prm.clientSecret, auth.String(), prm.headersToSign)) 74 | 75 | return auth.String() 76 | } 77 | 78 | func signRequest(request *http.Request, timestamp, clientSecret, authHeader string, headersToSign []string) string { 79 | dataToSign := makeDataToSign(request, authHeader, headersToSign) 80 | signingKey := makeSigningKey(timestamp, clientSecret) 81 | 82 | return concat([]string{ 83 | "signature=", 84 | base64HmacSha256(dataToSign, signingKey), 85 | }) 86 | } 87 | 88 | func base64Sha256(str string) string { 89 | h := sha256.New() 90 | 91 | h.Write([]byte(str)) 92 | 93 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 94 | } 95 | 96 | func base64HmacSha256(message, secret string) string { 97 | h := hmac.New(sha256.New, []byte(secret)) 98 | 99 | h.Write([]byte(message)) 100 | 101 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 102 | } 103 | 104 | func makeDataToSign(request *http.Request, authHeader string, headersToSign []string) string { 105 | var data bytes.Buffer 106 | values := []string{ 107 | request.Method, 108 | request.URL.Scheme, 109 | request.Host, 110 | urlPathWithQuery(request), 111 | canonicalizeHeaders(request, headersToSign), 112 | makeContentHash(request), 113 | authHeader, 114 | } 115 | 116 | data.WriteString(strings.Join(values, "\t")) 117 | 118 | return data.String() 119 | } 120 | 121 | func canonicalizeHeaders(request *http.Request, headersToSign []string) string { 122 | var canonicalized bytes.Buffer 123 | 124 | for key, values := range request.Header { 125 | if stringInSlice(key, headersToSign) { 126 | canonicalized.WriteString(concat([]string{ 127 | strings.ToLower(key), 128 | ":", 129 | strings.Join(strings.Fields(values[0]), " "), 130 | "\t", 131 | })) 132 | } 133 | } 134 | 135 | return canonicalized.String() 136 | } 137 | 138 | func makeContentHash(req *http.Request) string { 139 | if req.Method == "POST" { 140 | buf, err := ioutil.ReadAll(req.Body) 141 | rdr := reader{bytes.NewBuffer(buf)} 142 | 143 | if err != nil { 144 | panic(err) 145 | } 146 | 147 | req.Body = rdr 148 | 149 | return base64Sha256(string(buf)) 150 | } 151 | 152 | return "" 153 | } 154 | 155 | func makeSigningKey(timestamp, clientSecret string) string { 156 | return base64HmacSha256(timestamp, clientSecret) 157 | } 158 | 159 | func stringInSlice(a string, list []string) bool { 160 | for _, b := range list { 161 | if strings.ToLower(b) == strings.ToLower(a) { 162 | return true 163 | } 164 | } 165 | 166 | return false 167 | } 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edgegrid 2 | 3 | [![Build Status](https://travis-ci.org/Comcast/go-edgegrid.svg?branch=master)](https://travis-ci.org/Comcast/go-edgegrid) 4 | 5 | A Golang [Akamai](https://developer.akamai.com/api/) API client. 6 | 7 | [Akamai {OPEN} EdgeGrid Authentication](https://developer.akamai.com/introduction/Client_Auth.html) for Golang. 8 | 9 | `edgegrid` implements the [Akamai {OPEN} EdgeGrid Authentication](https://developer.akamai.com/introduction/Client_Auth.html) scheme for the Golang `net/http` package, and also offers a convenience API for interacting with the Akamai [GTM](https://developer.akamai.com/api/luna/config-gtm/overview.html) and [PAPI](https://developer.akamai.com/api/luna/papi/overview.html) APIs. 10 | 11 | ## Installation 12 | 13 | Assuming Golang is properly installed: 14 | 15 | ```bash 16 | go get github.com/comcast/go-edgegrid/edgegrid 17 | ``` 18 | 19 | ## Basic Usage 20 | 21 | Basic usage assumes you've established up the following environment variables. 22 | 23 | ```bash 24 | AKAMAI_EDGEGRID_HOST= 25 | AKAMAI_EDGEGRID_CLIENT_TOKEN= 26 | AKAMAI_EDGEGRID_ACCESS_TOKEN= 27 | AKAMAI_EDGEGRID_CLIENT_SECRET= 28 | ``` 29 | 30 | To log request/response details: 31 | 32 | ```bash 33 | AK_LOG=true 34 | ``` 35 | 36 | ### GTMClient 37 | 38 | ```go 39 | import "github.com/comcast/go-edgegrid/edgegrid" 40 | 41 | client := edgegrid.NewGTMClient() 42 | 43 | // get all domains 44 | client.Domains() 45 | 46 | // get domain 47 | client.Domain("example.akadns.net") 48 | 49 | // create domain 50 | domain := &Domain{ 51 | // your data here; see resources.go 52 | } 53 | client.DomainCreate(domain) 54 | 55 | // update domain 56 | domain := &Domain{ 57 | // your data here; see gtm_resources.go 58 | } 59 | client.DomainUpdate(domain) 60 | 61 | // delete domain 62 | // NOTE: this may 403, as it appears Akamai professional services is needed to perform DELETEs 63 | client.DomainDelete("domain") 64 | 65 | // get datacenters info for domain 66 | client.DataCenters("domain") 67 | 68 | // get datacenter info by dc id 69 | client.DataCenter("domain", "123") 70 | 71 | // get datacenter info by dc nickname 72 | client.DataCenterByName("domain", "some_nickname") 73 | 74 | // create datacenter 75 | dc := &DataCenter{ 76 | // your data here; see gtm_resources.go 77 | } 78 | client.DataCenterCreate("domain", dc) 79 | 80 | // update datacenter 81 | dc := &DataCenter{ 82 | // your data here; see gtm_resources.go 83 | } 84 | client.DataCenterUpdate("domain", dc) 85 | 86 | // delete datacenter by id 87 | client.DataCenterDelete("domain", "123") 88 | 89 | // get property info 90 | client.Property("domain", "property") 91 | 92 | // create property 93 | prop := &Property{ 94 | // your data here; see gtm_resources.go 95 | } 96 | client.PropertyCreate("domain", prop) 97 | 98 | // adjust aspects of a property 99 | prop := &Property{ 100 | // your data here; see gtm_resources.go 101 | } 102 | client.PropertyUpdate("domain", prop) 103 | 104 | // delete a property 105 | client.PropertyDelete("domain", "property") 106 | ``` 107 | 108 | ### PAPIClient 109 | 110 | ```go 111 | import "github.com/comcast/go-edgegrid/edgegrid" 112 | 113 | client := edgegrid.NewPAPIClient() 114 | 115 | // get all PAPI groups 116 | client.Groups() 117 | 118 | // get all PAPI products 119 | client.Groups("someContractId") 120 | 121 | // get all PAPI CP codes for a contract and group 122 | client.CpCodes("someContractId", "someGroupID") 123 | 124 | // get a PAPI CP code 125 | client.CpCode("someCPCodeID", "someContractId", "someGroupID") 126 | 127 | // get all PAPI hostnames for a contract & group 128 | client.Hostnames("someContractId", "someGroupID") 129 | 130 | // get a PAPI hostname for a contract & group 131 | client.Hostnames("someHostID", "someContractId", "someGroupID") 132 | 133 | // get all PAPI properties for a contract & group 134 | client.Properties("someContractId", "someGroupID") 135 | 136 | // get a PAPI property for a contract & group 137 | client.Property("somePropertyID", "someContractId", "someGroupID") 138 | 139 | // get all PAPI property versions for a property, contract, & group 140 | client.Versions("somePropertyID", "someContractId", "someGroupID") 141 | 142 | // get a PAPI property version for a property, contract, & group 143 | client.Version("somePropertyVersion", "somePropertyID", "someContractId", "someGroupID") 144 | 145 | // get a PAPI property version XML for a property, contract, & group 146 | client.PropertyVersionXML("somePropertyVersion", "somePropertyID", "someContractId", "someGroupID") 147 | 148 | // get the latest PAPI property version for a property, contract, & group 149 | client.PropertyLatestVersion("somePropertyID", "someContractId", "someGroupID") 150 | 151 | // get the PAPI property rules for a property, contract, & group 152 | client.PropertyRules("somePropertyID", "someContractId", "someGroupID") 153 | 154 | // get the PAPI property activations for a property, contract, & group 155 | client.PropertyActivations("somePropertyID", "someContractId", "someGroupID") 156 | ``` 157 | 158 | ### Alternative Usage 159 | 160 | In addition to the `edgegrid.GTMClient` and `edgegrid.PAPIClient` convenience clients, `edgegrid.Auth` offers a standalone implementation of the [Akamai {OPEN} EdgeGrid Authentication](https://developer.akamai.com/introduction/Client_Auth.html) scheme for the Golang net/http library and can be used independent of the `GTM` and `PAPI` convenience clients. 161 | 162 | `edgegrid.Auth` offers a utility for establishing a valid `Authorization` header for use with authenticated requests to Akamai using your own HTTP client. 163 | 164 | Example: 165 | 166 | ```go 167 | package main 168 | 169 | import ( 170 | "fmt" 171 | "io/ioutil" 172 | "net/http" 173 | "os" 174 | 175 | "github.com/comcast/go-edgegrid/edgegrid" 176 | ) 177 | 178 | func main() { 179 | client := &http.Client{} 180 | req, _ := http.NewRequest("GET", "", nil) 181 | accessToken := os.Getenv("AKAMAI_EDGEGRID_ACCESS_TOKEN") 182 | clientToken := os.Getenv("AKAMAI_EDGEGRID_CLIENT_TOKEN") 183 | clientSecret := os.Getenv("AKAMAI_EDGEGRID_CLIENT_SECRET") 184 | params := edgegrid.NewAuthParams(req, accessToken, clientToken, clientSecret) 185 | auth := edgegrid.Auth(params) 186 | 187 | req.Header.Add("Authorization", auth) 188 | 189 | resp, _ := client.Do(req) 190 | contents, _ := ioutil.ReadAll(resp.Body) 191 | 192 | fmt.Printf("%s\n", string(contents)) 193 | } 194 | ``` 195 | 196 | ## Run tests 197 | 198 | Tests use the built-in `testing` package: 199 | 200 | ```bash 201 | make test 202 | ``` 203 | -------------------------------------------------------------------------------- /edgegrid/auth_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func newTestAuthParams() AuthParams { 10 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 11 | 12 | params := AuthParams{ 13 | clientToken: "clientToken", 14 | accessToken: "accessToken", 15 | clientSecret: "clientSecret", 16 | nonce: "uuid", 17 | timestamp: "timestamp", 18 | req: req, 19 | } 20 | 21 | return params 22 | } 23 | 24 | func TestNewAuthParams(t *testing.T) { 25 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 26 | params := NewAuthParams(req, "accessToken", "clientToken", "clientSecret") 27 | 28 | if params.req != req { 29 | t.Error("NewAuthParams failed: req") 30 | } 31 | 32 | if params.accessToken != "accessToken" { 33 | t.Error("NewAuthParams failed: accessToken") 34 | } 35 | 36 | if params.clientToken != "clientToken" { 37 | t.Error("NewAuthParams failed: clientToken") 38 | } 39 | 40 | if params.clientSecret != "clientSecret" { 41 | t.Error("NewAuthParams failed: clientSecret") 42 | } 43 | 44 | if params.timestamp == "" { 45 | t.Error("NewAuthParams failed: timestamp") 46 | } 47 | 48 | if params.nonce == "" { 49 | t.Error("NewAuthParams failed: nonce") 50 | } 51 | } 52 | 53 | func TestAuth(t *testing.T) { 54 | auth := Auth(newTestAuthParams()) 55 | 56 | if auth != "EG1-HMAC-SHA256 client_token=clientToken;access_token=accessToken;timestamp=timestamp;nonce=uuid;signature=1Vqh8JzaFhBbyzPoyjZ5EygbH86MNJxAa6QlGAEY85A=" { 57 | t.Error("Auth failed") 58 | } 59 | } 60 | 61 | func TestSignRequest(t *testing.T) { 62 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 63 | signed := signRequest(req, "timestamp", "secret", "auth", []string{}) 64 | 65 | if signed != "signature=JYgCcBLZV63pALRV/zsufws39UPzIJHjPRUa0Uayi/k=" { 66 | t.Error("signRequest failed") 67 | } 68 | } 69 | 70 | func TestBase64Sha256(t *testing.T) { 71 | if base64Sha256("foo") != "LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=" { 72 | t.Error("base64Sha256 should properly hash 'foo' string") 73 | } 74 | 75 | if base64Sha256("monarch") != "DyfVOp4Jx6moDmQCZl6cLbqaelHmTN/pKcYSE+OWn+Y=" { 76 | t.Error("base64Sha256 should properly hash 'monarch' string") 77 | } 78 | 79 | if base64Sha256("{}") != "RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=" { 80 | t.Error("base64Sha256 should properly hash a '{}' string") 81 | } 82 | 83 | /* TODO: this fails 84 | simpleJ := `{"foo:":"bar"}` 85 | 86 | if base64Sha256(simpleJ) != "eji/gfOD9pQzrW6QDTWz4jhVk/dqe3q11DVbi6Qe4ks=" { 87 | t.Error("base64Sha256 should properly hash a simple JSON string") 88 | } 89 | */ 90 | 91 | /* TODO: this fails 92 | j := `{ 93 | "city": "Downpatrick", 94 | "continent": "EU", 95 | "country": "GB", 96 | "latitude": 54.367, 97 | "longitude": -5.582, 98 | "nickname": "somedc" 99 | }` 100 | 101 | if base64Sha256(str) != "az4Agj4KfiS/eH+vIB6pG8eTe2a2B6aTuiKSGP4bfbY=" { 102 | t.Error("base64Sha256 should properly hash a complex JSON string") 103 | } 104 | */ 105 | } 106 | 107 | func TestBase64HmacSha256(t *testing.T) { 108 | hash := base64HmacSha256("message", "secret") 109 | 110 | if hash != "i19IcCmVwVmMVz2x4hhmqbgl1KeU0WnXBgoDYFeWNgs=" { 111 | t.Error("base64HmacSha256 failed") 112 | } 113 | } 114 | 115 | func TestMakeDataToSign(t *testing.T) { 116 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 117 | data := makeDataToSign(req, "authHeader", []string{}) 118 | 119 | if data != "GET\thttp\texample.com\t/foo\t\t\tauthHeader" { 120 | t.Error("makeDataToSign failed") 121 | } 122 | } 123 | 124 | func TestMakeDataToSignWithQuery(t *testing.T) { 125 | req, _ := http.NewRequest("GET", "http://example.com/foo?baz=bim", nil) 126 | data := makeDataToSign(req, "authHeader", []string{}) 127 | 128 | if data != "GET\thttp\texample.com\t/foo?baz=bim\t\t\tauthHeader" { 129 | t.Error("makeDataToSign failed for a request URL containing a query") 130 | } 131 | } 132 | 133 | func TestCanonicalizeHeaders(t *testing.T) { 134 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 135 | 136 | req.Header.Add("Foo", "bar") 137 | req.Header.Add("Baz", " baz zoo ") 138 | 139 | canonicalized := canonicalizeHeaders(req, []string{"foo", "baz"}) 140 | 141 | if canonicalized != "foo:bar\tbaz:baz zoo\t" && canonicalized != "baz:baz zoo\tfoo:bar\t" { 142 | t.Error("canonicalized failed") 143 | } 144 | } 145 | 146 | func TestCanonicalizeHeadersMultipleValues(t *testing.T) { 147 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 148 | 149 | req.Header.Add("Foo", "bar") 150 | req.Header.Add("Foo", "biz") 151 | 152 | canonicalized := canonicalizeHeaders(req, []string{"foo"}) 153 | 154 | if canonicalized != "foo:bar\t" { 155 | t.Error("canonicalized with multiple values failed") 156 | } 157 | } 158 | 159 | func TestCanonicalizeHeadersNoHeaders(t *testing.T) { 160 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 161 | 162 | canonicalized := canonicalizeHeaders(req, []string{"foo"}) 163 | 164 | if canonicalized != "" { 165 | t.Error("canonicalized with no header values failed") 166 | } 167 | } 168 | 169 | func TestCanonicalizeHeadersNoIncludedHeaders(t *testing.T) { 170 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 171 | 172 | req.Header.Add("Fiz", "biz") 173 | 174 | canonicalized := canonicalizeHeaders(req, []string{"foo"}) 175 | 176 | if canonicalized != "" { 177 | t.Error("canonicalized with no headers to include failed") 178 | } 179 | } 180 | 181 | func TestMakeContentHash(t *testing.T) { 182 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 183 | hash := makeContentHash(req) 184 | 185 | if hash != "" { 186 | t.Error("makeContentHash with GET request failed") 187 | } 188 | } 189 | 190 | func TestMakeContentHashPost(t *testing.T) { 191 | req, _ := http.NewRequest("POST", "http://example.com/foo", bytes.NewBufferString("foo")) 192 | hash := makeContentHash(req) 193 | 194 | if hash != "LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=" { 195 | t.Error("makeContentHash with GET request failed") 196 | } 197 | } 198 | 199 | func TestMakeSigningKey(t *testing.T) { 200 | key := makeSigningKey("timestamp", "secret") 201 | 202 | if key != "ydMIxJIPPypuUya3KZGJ0qCRwkYcKrFn68Nyvpkf1WY=" { 203 | t.Error("makeSigningKey failed") 204 | } 205 | } 206 | 207 | func TestStringInSlice(t *testing.T) { 208 | result := stringInSlice("a", []string{"a", "b", "c"}) 209 | resultTwo := stringInSlice("a", []string{"b", "c"}) 210 | 211 | if result != true { 212 | t.Error("stringInSlice:true failed") 213 | } 214 | 215 | if resultTwo != false { 216 | t.Error("stringInSlice:false failed") 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /edgegrid/papi_client_api.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import "io/ioutil" 4 | 5 | // Groups returns a []GroupSummary representing the groups associated with the PAPIClient. 6 | func (c *PAPIClient) Groups() ([]GroupSummary, error) { 7 | groups := &Groups{} 8 | err := resourceRequest(c, "GET", papiGroupsEndpoint(c.GetCredentials()), nil, groups) 9 | if err != nil { 10 | return []GroupSummary{}, err 11 | } 12 | return groups.Groups.Items, err 13 | } 14 | 15 | // Products takes a contractID returns the associated ProductSummary for each product 16 | // associated with the contractID. 17 | func (c *PAPIClient) Products(contractID string) ([]ProductSummary, error) { 18 | prods := &Products{} 19 | err := resourceRequest(c, "GET", papiProductsEndpoint(c.GetCredentials(), contractID), nil, prods) 20 | if err != nil { 21 | return []ProductSummary{}, err 22 | } 23 | return prods.Products.Items, err 24 | } 25 | 26 | // CpCodes takes a contractID and a groupID and returns the 27 | // associated []CpCodeSummary representing the CP code summaries associated 28 | // with the contractId and groupId. 29 | func (c *PAPIClient) CpCodes(contractID, groupID string) ([]CpCodeSummary, error) { 30 | cps := &CpCodes{} 31 | err := resourceRequest(c, "GET", papiCpCodesEndpoint(c.GetCredentials(), contractID, groupID), nil, cps) 32 | if err != nil { 33 | return []CpCodeSummary{}, err 34 | } 35 | return cps.CpCodes.Items, err 36 | } 37 | 38 | // CpCode takes a cpCodeID, a contractID, and a groupID 39 | // and returns the associated CpCodeSummary. 40 | func (c *PAPIClient) CpCode(cpCodeID, contractID, groupID string) (*CpCodeSummary, error) { 41 | cps := &CpCodes{} 42 | err := resourceRequest(c, "GET", papiCpCodeEndpoint(c.GetCredentials(), cpCodeID, contractID, groupID), nil, cps) 43 | if err != nil { 44 | return &CpCodeSummary{}, err 45 | } 46 | return &cps.CpCodes.Items[0], err 47 | } 48 | 49 | // Hostnames takes a contractID and a groupID and returns the 50 | // associated []HostnameSummary representing each HostnameSummary 51 | // for the hostnames associated with the contractID and groupID. 52 | func (c *PAPIClient) Hostnames(contractID, groupID string) ([]HostnameSummary, error) { 53 | hostnames := &Hostnames{} 54 | err := resourceRequest(c, "GET", papiHostnamesEndpoint(c.GetCredentials(), contractID, groupID), nil, hostnames) 55 | if err != nil { 56 | return []HostnameSummary{}, err 57 | } 58 | return hostnames.Hostnames.Items, err 59 | } 60 | 61 | // Hostname takes a hostID, a contractID, and a groupID and returns 62 | // the associated HostnameSummary. 63 | func (c *PAPIClient) Hostname(hostID, contractID, groupID string) (HostnameSummary, error) { 64 | hostnames := &Hostnames{} 65 | err := resourceRequest(c, "GET", papiHostnameEndpoint(c.GetCredentials(), hostID, contractID, groupID), nil, hostnames) 66 | if err != nil { 67 | return HostnameSummary{}, err 68 | } 69 | return hostnames.Hostnames.Items[0], err 70 | } 71 | 72 | // Properties takes a contractID and a groupID and returns the associated 73 | // []PapiPropertySummary for each property associated with the contractID and the groupID. 74 | func (c *PAPIClient) Properties(contractID, groupID string) ([]PapiPropertySummary, error) { 75 | props := &PapiProperties{} 76 | err := resourceRequest(c, "GET", papiPropertiesEndpoint(c.GetCredentials(), contractID, groupID), nil, props) 77 | if err != nil { 78 | return []PapiPropertySummary{}, err 79 | } 80 | return props.Properties.Items, err 81 | } 82 | 83 | // Property takes a propertyID, a contractID, and a groupID and returns 84 | // the PapiPropertySummary for the associated property. 85 | func (c *PAPIClient) Property(propID, contractID, groupID string) (PapiPropertySummary, error) { 86 | props := &PapiProperties{} 87 | err := resourceRequest(c, "GET", papiPropertyEndpoint(c.GetCredentials(), propID, contractID, groupID), nil, props) 88 | if err != nil { 89 | return PapiPropertySummary{}, err 90 | } 91 | return props.Properties.Items[0], err 92 | } 93 | 94 | // PropertyVersions takes a propertyID, a contractID, and a groupID and 95 | // returns the associated PapiPropertyVersionSummary for each property version. 96 | func (c *PAPIClient) PropertyVersions(propID, contractID, groupID string) ([]PapiPropertyVersionSummary, error) { 97 | versions := &PapiPropertyVersions{} 98 | err := resourceRequest(c, "GET", papiPropertyVersionsEndpoint(c.GetCredentials(), propID, contractID, groupID), nil, versions) 99 | if err != nil { 100 | return []PapiPropertyVersionSummary{}, err 101 | } 102 | return versions.Versions.Items, err 103 | } 104 | 105 | // PropertyVersion takes a version, a propertyID, a contractID, and a groupID 106 | // and returns the associated PapiPropertyVersionSummary. 107 | func (c *PAPIClient) PropertyVersion(version, propID, contractID, groupID string) (PapiPropertyVersionSummary, error) { 108 | versions := &PapiPropertyVersions{} 109 | err := resourceRequest(c, "GET", papiPropertyVersionEndpoint(c.GetCredentials(), version, propID, contractID, groupID), nil, versions) 110 | if err != nil { 111 | return PapiPropertyVersionSummary{}, err 112 | } 113 | return versions.Versions.Items[0], err 114 | } 115 | 116 | // PropertyVersionXML takes a version, a propertyID, a contractID, and a groupID 117 | // and returns the the associated property version XML string. 118 | func (c *PAPIClient) PropertyVersionXML(version, propID, contractID, groupID string) (string, error) { 119 | xml, err := getXML(c, papiPropertyVersionEndpoint(c.GetCredentials(), version, propID, contractID, groupID)) 120 | if err != nil { 121 | return "", err 122 | } 123 | 124 | body, err := ioutil.ReadAll(xml.Body) 125 | if err != nil { 126 | return "", err 127 | } 128 | 129 | return string(body), nil 130 | } 131 | 132 | // PropertyLatestVersion takes a propertyID, a contractID, and a groupID and returns 133 | // the PapiPropertyVersionSummary for the most recent property version. 134 | func (c *PAPIClient) PropertyLatestVersion(propID, contractID, groupID string) (PapiPropertyVersionSummary, error) { 135 | versions := &PapiPropertyVersions{} 136 | err := resourceRequest(c, "GET", papiPropertyLatestVersionEndpoint(c.GetCredentials(), propID, contractID, groupID), nil, versions) 137 | if err != nil { 138 | return PapiPropertyVersionSummary{}, err 139 | } 140 | return versions.Versions.Items[0], err 141 | } 142 | 143 | // PropertyRules takes a propertyID string, a version, and a groupID and returns a 144 | // the PapiPropertyRuleSummary for the associated property. 145 | func (c *PAPIClient) PropertyRules(propID, version, contractID, groupID string) (PapiPropertyRuleSummary, error) { 146 | rules := &PapiPropertyRules{} 147 | err := resourceRequest(c, "GET", papiPropertyRulesEndpoint(c.GetCredentials(), propID, version, contractID, groupID), nil, rules) 148 | if err != nil { 149 | return PapiPropertyRuleSummary{}, err 150 | } 151 | return rules.Rules, err 152 | } 153 | 154 | // PropertyHostnames takes a propertyID string, a version, and a groupID and returns 155 | // the PapiPropertyHostnames for the associated property. 156 | func (c *PAPIClient) PropertyHostnames(propID, version, contractID, groupID string) (PapiPropertyHostnames, error) { 157 | container := &papiPropertyHostnamesContainer{} 158 | err := resourceRequest(c, "GET", papiPropertyHostnamesEndpoint(c.GetCredentials(), propID, version, contractID, groupID), nil, container) 159 | if err != nil { 160 | return PapiPropertyHostnames{}, err 161 | } 162 | return container.Hostnames, err 163 | } 164 | 165 | // Activations takes a propertyID, a contractID, and a groupID and returns 166 | // the associated []PapiActivation representing the property activations. 167 | func (c *PAPIClient) Activations(propID, contractID, groupID string) ([]PapiActivation, error) { 168 | acts := &PapiActivations{} 169 | err := resourceRequest(c, "GET", papiActivationsEndpoint(c.GetCredentials(), propID, contractID, groupID), nil, acts) 170 | if err != nil { 171 | return []PapiActivation{}, err 172 | } 173 | return acts.Activations.Items, err 174 | } 175 | -------------------------------------------------------------------------------- /edgegrid/papi_resources.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | // Groups is a representation of the Akamai PAPI 4 | // Groups response available at: 5 | // http://apibase.com/papi/v0/groups/ 6 | type Groups struct { 7 | Groups struct { 8 | Items []GroupSummary `json:"items"` 9 | } `json:"groups"` 10 | } 11 | 12 | // GroupSummary is a representation of the Akamai PAPI 13 | // group summary associated with each group returned by 14 | // the groups response at: 15 | // http://apibase.com/papi/v0/groups/ 16 | type GroupSummary struct { 17 | GroupID string `json:"groupId"` 18 | Name string `json:"groupName"` 19 | ContractIDs []string `json:"contractIds"` 20 | ParentGroupID string `json:"parentGroupId"` 21 | } 22 | 23 | // Products is a representation of the Akamai PAPI 24 | // products response available at: 25 | // http://apibase.com/papi/v0/products?contractId=someId 26 | type Products struct { 27 | Products struct { 28 | Items []ProductSummary `json:"items"` 29 | } `json:"products"` 30 | } 31 | 32 | // ProductSummary is a representation of the Akamai PAPI 33 | // product summary associated with each product returned 34 | // by the products response at: 35 | // http://apibase.com/papi/v0/products?contractId=someId 36 | type ProductSummary struct { 37 | ProductID string `json:"productId"` 38 | Name string `json:"productName"` 39 | } 40 | 41 | // CpCodes is a representation of the Akamai PAPI 42 | // CP codes associated with the CP codes response available at: 43 | // http://apibase.com/papi/v0/cpcodes/?contractId=contractId&groupId=groupId 44 | type CpCodes struct { 45 | CpCodes struct { 46 | Items []CpCodeSummary `json:"items"` 47 | } `json:"cpcodes"` 48 | } 49 | 50 | // CpCodeSummary is a representation of the Akamai PAPI 51 | // CP code summary associated with each CP code returned 52 | // by the CP codes response at: 53 | // http://apibase.com/papi/v0/cpcodes/?contractId=contractId&groupId=groupId 54 | type CpCodeSummary struct { 55 | CPCodeID string `json:"cpcodeId"` 56 | Name string `json:"cpcodeName"` 57 | ProductIDs []string `json:"productIds"` 58 | CreatedDate string `json:"createdDate"` 59 | } 60 | 61 | // Hostnames is a representation of the Akamai PAPI 62 | // hostnames response available at: 63 | // http://apibase.com/papi/v0/edgehostnames?contractId=contractId&groupId=groupId 64 | type Hostnames struct { 65 | Hostnames struct { 66 | Items []HostnameSummary `json:"items"` 67 | } `json:"edgehostnames"` 68 | } 69 | 70 | // HostnameSummary is a representation of the Akamai PAPI 71 | // hostname summary associated with each hostname returned 72 | // by the hostnames response at: 73 | // http://apibase.com/papi/v0/edgehostnames?contractId=contractId&groupId=groupId 74 | type HostnameSummary struct { 75 | EdgeHostnameID string `json:"edgeHostnameId"` 76 | DomainPrefix string `json:"domainPrefix"` 77 | DomainSuffix string `json:"domainSuffix"` 78 | IPVersionBehavior string `json:"ipVersionBehavior"` 79 | Secure bool `json:"secure"` 80 | EdgeHostnameDomain string `json:"edgehostnameDomain"` 81 | } 82 | 83 | // PapiProperties is a representation of the Akamai PAPI 84 | // properties response available at: 85 | // http://apibase.com/papi/v0/properties/?contractId=contractId&groupId=groupId 86 | type PapiProperties struct { 87 | Properties struct { 88 | Items []PapiPropertySummary `json:"items"` 89 | } `json:"properties"` 90 | } 91 | 92 | // PapiPropertySummary is a representation of the Akamai PAPI 93 | // property summary associated with each property returned by 94 | // the properties response at: 95 | // http://apibase.com/papi/v0/properties/?contractId=contractId&groupId=groupId 96 | type PapiPropertySummary struct { 97 | AccountID string `json:"accountId"` 98 | ContractID string `json:"contractId"` 99 | GroupID string `json:"groupId"` 100 | PropertyID string `json:"propertyId"` 101 | Name string `json:"propertyName"` 102 | LatestVersion int `json:"latestVersion"` 103 | StagingVersion int `json:"stagingVersion"` 104 | ProductionVersion int `json:"productionVersion"` 105 | Note string `json:"note"` 106 | } 107 | 108 | // PapiPropertyVersions is a representation of the Akamai PAPI 109 | // property versions response available at: 110 | // http://apibase.com/papi/v0/properties/propId/versions?contractId=contractId&groupId=groupId 111 | type PapiPropertyVersions struct { 112 | Versions struct { 113 | Items []PapiPropertyVersionSummary `json:"items"` 114 | } `json:"versions"` 115 | } 116 | 117 | // PapiPropertyVersionSummary is a representation of the Akamai PAPI 118 | // property version summary associated with each property version at: 119 | // http://apibase.com/papi/v0/properties/propId/versions?contractId=contractId&groupId=groupId 120 | type PapiPropertyVersionSummary struct { 121 | PropertyVersion int `json:"propertyVersion"` 122 | UpdatedByUser string `json:"updatedByUser"` 123 | UpdatedDate string `json:"updatedDate"` 124 | ProductionStatus string `json:"productionStatus"` 125 | StagingStatus string `json:"stagingStatus"` 126 | Etag string `json:"etag"` 127 | ProductID string `json:"productId"` 128 | Note string `json:"note"` 129 | } 130 | 131 | // PapiPropertyHostnames is a representation of the Akamai PAPI 132 | // property hostnames response at: 133 | // http://apibase.com/papi/v0/properties/propId/versions/version/hostnames/?contractId=contractId&groupId=groupId 134 | type PapiPropertyHostnames struct { 135 | Hostnames []PapiPropertyHostname `json:"items"` 136 | } 137 | 138 | // papiPropertyHostnamesContainer is a helper struct to extract nesting in above struct 139 | type papiPropertyHostnamesContainer struct { 140 | Hostnames PapiPropertyHostnames `json:"hostnames"` 141 | } 142 | 143 | // PapiPropertyHostname is a representation of the Akamai PAPI 144 | // hostname associated with each property at: 145 | // http://apibase.com/papi/v0/properties/propId/versions/version/hostnames/?contractId=contractId&groupId=groupId 146 | type PapiPropertyHostname struct { 147 | ID string `json:"edgeHostnameId"` 148 | From string `json:"cnameFrom"` 149 | To string `json:"cnameTo"` 150 | } 151 | 152 | // PapiPropertyRules is a representation of the Akamai PAPI 153 | // property rules response at: 154 | // http://apibase.com/papi/v0/properties/propId/versions/version/rules/?contractId=contractId&groupId=groupId 155 | type PapiPropertyRules struct { 156 | Rules PapiPropertyRuleSummary `json:"rules"` 157 | } 158 | 159 | // PapiPropertyRuleSummary is a representation of the Akamai PAPI 160 | // rule summary associated with each property rule at: 161 | // http://apibase.com/papi/v0/properties/propId/versions/version/rules/?contractId=contractId&groupId=groupId 162 | type PapiPropertyRuleSummary struct { 163 | Name string `json:"name"` 164 | UUID string `json:"uuid"` 165 | Behaviors []PapiPropertyRuleBehavior `json:"behaviors"` 166 | } 167 | 168 | // PapiPropertyRuleBehavior is a representation of the Akamai PAPI 169 | // property rule behavior associated with each property rule. 170 | type PapiPropertyRuleBehavior struct { 171 | Name string `json:"name"` 172 | } 173 | 174 | // PapiActivations is a representation of the Akamai PAPI 175 | // activations response at: 176 | // http://apibase.com/papi/v0/properties/propId/activations?contractId=contractId&groupId=groupId 177 | type PapiActivations struct { 178 | Activations struct { 179 | Items []PapiActivation `json:"items"` 180 | } `json:"activations"` 181 | } 182 | 183 | // PapiActivation is a representation of each Akamai PAPI 184 | // activation available at: 185 | // http://apibase.com/papi/v0/properties/propId/activations?contractId=contractId&groupId=groupId 186 | type PapiActivation struct { 187 | ActivationID string `json:"activationId"` 188 | PropertyName string `json:"propertyName"` 189 | PropertyID string `json:"propertyId"` 190 | PropertyVersion int `json:"propertyVersion"` 191 | Network string `json:"network"` 192 | ActivationType string `json:"activationType"` 193 | Status string `json:"status"` 194 | SubmitDate string `json:"submitDate"` 195 | UpdateDate string `json:"updateDate"` 196 | Note string `json:"note"` 197 | } 198 | -------------------------------------------------------------------------------- /edgegrid/gtm_client_api.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "sort" 9 | ) 10 | 11 | // Domains returns the Akamai GTM DomainSummary for each domain the GTMClient 12 | // is authorized to view and modify 13 | func (c *GTMClient) Domains() ([]DomainSummary, error) { 14 | domains := &Domains{} 15 | err := resourceRequest(c, "GET", domainsEndpoint(c.GetCredentials()), nil, domains) 16 | if err != nil { 17 | return []DomainSummary{}, err 18 | } 19 | return domains.Domains, err 20 | } 21 | 22 | // Domain takes an Akamai GTM domain name and returns a Domain. 23 | func (c *GTMClient) Domain(name string) (*Domain, error) { 24 | domain := &Domain{} 25 | err := resourceRequest(c, "GET", domainEndpoint(c.GetCredentials(), name), nil, domain) 26 | return domain, err 27 | } 28 | 29 | // DomainStatus takes an Akamai GTM domain name and returns its ResourceStatus. 30 | func (c *GTMClient) DomainStatus(name string) (*ResourceStatus, error) { 31 | status := &ResourceStatus{} 32 | err := resourceRequest(c, "GET", domainStatusEndpoint(c.GetCredentials(), name), nil, status) 33 | return status, err 34 | } 35 | 36 | // DomainCreate issues a request to create a domain with the provided name and type. 37 | // The result is returned as a DomainResponse. 38 | func (c *GTMClient) DomainCreate(name string, domainType string) (*DomainResponse, error) { 39 | payload := map[string]string{ 40 | "name": name, 41 | "type": domainType, 42 | } 43 | 44 | jsonRequest, err := json.Marshal(payload) 45 | if err != nil { 46 | return nil, err 47 | } 48 | var createdDomain = &DomainResponse{} 49 | err = resourceRequest(c, "PUT", domainEndpoint(c.GetCredentials(), name), jsonRequest, createdDomain) 50 | return createdDomain, err 51 | } 52 | 53 | // DomainUpdate takes a domain and issues a request to update it accordingly. 54 | // The result is returned as a DomainResponse. 55 | func (c *GTMClient) DomainUpdate(domain *Domain) (*DomainResponse, error) { 56 | jsonRequest, err := json.Marshal(domain) 57 | if err != nil { 58 | return nil, err 59 | } 60 | var updatedDomain = &DomainResponse{} 61 | err = resourceRequest(c, "PUT", domainEndpoint(c.GetCredentials(), domain.Name), jsonRequest, updatedDomain) 62 | return updatedDomain, err 63 | } 64 | 65 | // DomainDelete takes a domain and issues a request to delete it. 66 | func (c *GTMClient) DomainDelete(name string) error { 67 | resp, err := doClientReq(c, "DELETE", domainEndpoint(c.GetCredentials(), name), nil) 68 | 69 | if err != nil { 70 | return err 71 | } 72 | if resp.StatusCode != http.StatusOK { 73 | return errors.New("HTTP status not OK") 74 | } 75 | return err 76 | } 77 | 78 | // DataCenters takes an Akamai GTM domain name and returns a []DataCenter 79 | // representing the datacenters associated with the domain. 80 | func (c *GTMClient) DataCenters(domain string) ([]DataCenter, error) { 81 | dcs := &DataCenters{} 82 | err := resourceRequest(c, "GET", dcsEndpoint(c.GetCredentials(), domain), nil, dcs) 83 | if err != nil { 84 | return []DataCenter{}, err 85 | } 86 | return dcs.Items, err 87 | } 88 | 89 | // DataCenterCreate takes an Akamai GTM domain name and a dc and 90 | // issues a request to create the DataCenter via the Akamai GTM API. 91 | // The result is returned as a DataCenterResponse. 92 | func (c *GTMClient) DataCenterCreate(domain string, dc *DataCenter) (*DataCenterResponse, error) { 93 | jsonRequest, err := json.Marshal(dc) 94 | if err != nil { 95 | return nil, err 96 | } 97 | var resource = &DataCenterResponse{} 98 | err = resourceRequest(c, "POST", dcsEndpoint(c.GetCredentials(), domain), jsonRequest, resource) 99 | return resource, err 100 | } 101 | 102 | // DataCenter takes an Akamai GTM domain name and a datacenter id and 103 | // returns the DataCenter for the given ID. 104 | func (c *GTMClient) DataCenter(domain string, id int) (*DataCenter, error) { 105 | dc := &DataCenter{} 106 | err := resourceRequest(c, "GET", dcEndpoint(c.GetCredentials(), domain, id), nil, dc) 107 | return dc, err 108 | } 109 | 110 | // DataCenterUpdate takes an Akamai GTM domain name and a dc and issues a request 111 | // to update the DataCenter details accordingly via the Akamai GTM API. 112 | // The result is returned as a DataCenterResponse. 113 | func (c *GTMClient) DataCenterUpdate(domain string, dc *DataCenter) (*DataCenterResponse, error) { 114 | jsonRequest, err := json.Marshal(dc) 115 | if err != nil { 116 | return nil, err 117 | } 118 | var resource = &DataCenterResponse{} 119 | err = resourceRequest(c, "PUT", dcEndpoint(c.GetCredentials(), domain, dc.DataCenterID), jsonRequest, resource) 120 | return resource, err 121 | } 122 | 123 | // DataCenterByName takes an Akamai GTM domain name and a datacenter name 124 | // and returns the matching DataCenter. 125 | func (c *GTMClient) DataCenterByName(domain string, name string) (*DataCenter, error) { 126 | dcs, err := c.DataCenters(domain) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | for _, each := range dcs { 132 | if each.Nickname == name { 133 | return &each, nil 134 | } 135 | } 136 | return &DataCenter{}, fmt.Errorf("DataCenter named: %s not found in domain: %s", name, domain) 137 | } 138 | 139 | // DataCenterDelete takes an Akamai GTM domain name and a datacenter id and 140 | // issues a request to delete the matching datacenter via the Akamai GTM API. 141 | func (c *GTMClient) DataCenterDelete(domain string, id int) error { 142 | resp, err := doClientReq(c, "DELETE", dcEndpoint(c.GetCredentials(), domain, id), nil) 143 | 144 | if err != nil { 145 | return err 146 | } 147 | if resp.StatusCode != http.StatusOK { 148 | return errors.New("HTTP status not OK") 149 | } 150 | return err 151 | } 152 | 153 | // Properties takes an Akamai GTM domain name and returns the Akamai GTM properties 154 | // associated with the domain. 155 | func (c *GTMClient) Properties(domain string) (*Properties, error) { 156 | props := &Properties{} 157 | err := resourceRequest(c, "GET", propertiesEndpoint(c.GetCredentials(), domain), nil, props) 158 | return props, err 159 | } 160 | 161 | // PropertiesSorted sorted takes an Akamai GTM domain name and returns the Akamai GTM 162 | // properties associated with the domain name, sorted by their names. 163 | func (c *GTMClient) PropertiesSorted(domain string) (*Properties, error) { 164 | props, err := c.Properties(domain) 165 | sort.Sort(props) 166 | return props, err 167 | } 168 | 169 | // Property takes an Akamai GTM domain name and a property name 170 | // and returns the matching Akamai GTM property. 171 | func (c *GTMClient) Property(domain, property string) (*Property, error) { 172 | prop := &Property{} 173 | err := resourceRequest(c, "GET", propertyEndpoint(c.GetCredentials(), domain, property), nil, prop) 174 | return prop, err 175 | } 176 | 177 | // PropertyCreate takes an Akamai GTM domain name and a property and issues a request 178 | // to create the Property via the Akamai GTM API. 179 | // The result is returned as a PropertyResponse. 180 | func (c *GTMClient) PropertyCreate(domain string, property *Property) (*PropertyResponse, error) { 181 | jsonRequest, err := json.Marshal(property) 182 | if err != nil { 183 | return nil, err 184 | } 185 | 186 | resource := &PropertyResponse{} 187 | err = resourceRequest(c, "PUT", propertyEndpoint(c.GetCredentials(), domain, property.Name), jsonRequest, resource) 188 | return resource, err 189 | } 190 | 191 | // PropertyUpdate takes an Akamai GTM domain name and a property and issues a request 192 | // to update the property accordingly via the Akamai GTM API. 193 | // The result is returned as a PropertyResponse. 194 | func (c *GTMClient) PropertyUpdate(domain string, property *Property) (*PropertyResponse, error) { 195 | return c.PropertyCreate(domain, property) 196 | } 197 | 198 | // PropertyDelete takes an Akamai GTM domain name and a property name and issues a request 199 | // to delete the matching Akamai GTM property via the Akamai GTM API. 200 | // It returns true if the action was successful and false if it was not successful. 201 | func (c *GTMClient) PropertyDelete(domain string, property string) (bool, error) { 202 | url := propertyEndpoint(c.GetCredentials(), domain, property) 203 | resp, err := doClientReq(c, "DELETE", url, nil) 204 | 205 | if err != nil { 206 | return false, err 207 | } 208 | if resp.StatusCode != http.StatusOK { 209 | return false, errors.New("HTTP status not OK") 210 | } 211 | return true, nil 212 | } 213 | -------------------------------------------------------------------------------- /edgegrid/gtm_resources.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Domains is a representation of the Akamai GTM 8 | // domains response available at: 9 | // http://apibase.com/config-gtm/v1/domains 10 | type Domains struct { 11 | Domains []DomainSummary `json:"items"` 12 | } 13 | 14 | // DomainSummary is a representation of the Akamai GTM 15 | // domain summary associated with each domain returned by 16 | // the domains response at: 17 | // http://apibase.com/config-gtm/v1/domains 18 | type DomainSummary struct { 19 | Name string `json:"name"` 20 | Status string `json:"status"` 21 | LastModified string `json:"lastModified"` 22 | } 23 | 24 | // DomainResponse is a representation of the Akamai GTM 25 | // response from a domain CREATEs and UPDATEs at: 26 | // http://apibase.com/config-gtm/v1/domains/domain 27 | type DomainResponse struct { 28 | Domain *Domain `json:"resource"` 29 | Status *ResourceStatus `json:"status"` 30 | } 31 | 32 | // Domain is a representation of an Akamai GTM domain. 33 | type Domain struct { 34 | Name string `json:"name"` 35 | Status *ResourceStatus `json:"status,omitempty"` 36 | Type string `json:"type"` 37 | LastModified string `json:"lastModified,omitempty"` 38 | LastModifiedBy string `json:"lastModifiedBy,omitempty"` 39 | ModificationComments string `json:"modificationComments,omitempty"` 40 | CIDRMaps []interface{} `json:"cidrMaps,omitempty"` 41 | Datacenters []DataCenter `json:"datacenters,omitempty"` 42 | Properties []Property `json:"properties,omitempty"` 43 | Links []Link `json:"links,omitempty"` 44 | GeographicMaps []interface{} `json:"geographicMaps,omitempty"` 45 | Resources []interface{} `json:"resources,omitempty"` 46 | } 47 | 48 | // ResourceStatus is a representation of an Akamai GTM status for 49 | // a given resource, such as a domain. 50 | type ResourceStatus struct { 51 | Message string `json:"message"` 52 | ChangeID string `json:"changeId"` 53 | PropagationStatus string `json:"propagationStatus"` 54 | PropagationStatusDate string `json:"propagationStatusDate"` 55 | PassingValidation bool `json:"passingValidation"` 56 | Links []Link `json:"links"` 57 | } 58 | 59 | // DataCenters is a representation of an Akamai GTM datacenters 60 | // response returned from: 61 | // http://apibase.com/config-gtm/v1/domains/domain/datacenters 62 | type DataCenters struct { 63 | Items []DataCenter `json:"items"` 64 | } 65 | 66 | // DataCenterResponse is a representation of an Akamai GTM datacenter 67 | // response returned from datacenter CREATEs and UPDATEs at: 68 | // http://apibase.com/config-gtm/v1/domains/domain/datacenters/4 69 | type DataCenterResponse struct { 70 | DataCenter *DataCenter `json:"resource"` 71 | Status *ResourceStatus `json:"status"` 72 | } 73 | 74 | // DataCenter represents an Akamai GTM datacenter. 75 | type DataCenter struct { 76 | City string `json:"city"` 77 | CloneOf int `json:"cloneOf,omitempty"` 78 | CloudServerTargeting bool `json:"cloudServerTargeting"` 79 | Continent string `json:"continent"` 80 | Country string `json:"country"` 81 | DataCenterID int `json:"datacenterId,omitempty"` 82 | DefaultLoadObject *DefaultLoadObject `json:"defaultLoadObject,omitempty"` 83 | Latitude float64 `json:"latitude"` 84 | Links []Link `json:"links,omitempty"` 85 | Longitude float64 `json:"longitude"` 86 | Nickname string `json:"nickname"` 87 | StateOrProvince string `json:"stateOrProvince"` 88 | Virtual bool `json:"virtual"` 89 | } 90 | 91 | // Link represents the link objects embedded in Akamai GTM API 92 | // response JSON. 93 | type Link struct { 94 | Href string `json:"href"` 95 | Rel string `json:"rel"` 96 | } 97 | 98 | // DefaultLoadObject represents the default load object associated 99 | // with an Akamai GTM datacenter. 100 | type DefaultLoadObject struct { 101 | LoadObject interface{} `json:"loadObject"` 102 | LoadObjectPort int64 `json:"loadObjectPort"` 103 | LoadServers interface{} `json:"loadServers"` 104 | } 105 | 106 | // LoadObject represents the load object associated with an Akamai 107 | // GTM datacenter. 108 | type LoadObject struct { 109 | LoadObject string `json:"loadObject"` 110 | LoadObjectPort string `json:"loadObjectPort"` 111 | LoadServers string `json:"loadServers"` 112 | Continent string `json:"continent"` 113 | CloudServerTargeting bool `json:"cloudServerTargeting"` 114 | } 115 | 116 | // PropertyResponse represents the Akamai GTM response returned 117 | // by Akamai GTM API CREATEs and DELETEs against: 118 | // http://apibase.com/config-gtm/v1/domains/domain/properties/property 119 | type PropertyResponse struct { 120 | Property *Property `json:"resource"` 121 | Status *ResourceStatus `json:"status"` 122 | } 123 | 124 | // Properties represents the properties returned from: 125 | // http://apibase.com/config-gtm/v1/domains/domain/properties/property 126 | type Properties struct { 127 | Properties []Property `json:"items"` 128 | } 129 | 130 | func (props Properties) Len() int { 131 | return len(props.Properties) 132 | } 133 | 134 | func (props Properties) Less(i, j int) bool { 135 | return props.Properties[i].Name < props.Properties[j].Name 136 | } 137 | 138 | func (props Properties) Swap(i, j int) { 139 | props.Properties[i], props.Properties[j] = props.Properties[j], props.Properties[i] 140 | } 141 | 142 | // Property represents an Akamai GTM property. 143 | type Property struct { 144 | BackupCname string `json:"backupCName,omitempty"` 145 | BackupIP string `json:"backupIp,omitempty"` 146 | BalanceByDownloadScore bool `json:"balanceByDownloadScore,omitempty"` 147 | Cname string `json:"cname,omitempty"` 148 | Comments string `json:"comments,omitempty"` 149 | DynamicTTL int `json:"dynamicTTL,omitempty"` 150 | FailbackDelay int `json:"failbackDelay"` 151 | FailoverDelay int `json:"failoverDelay"` 152 | HandoutMode string `json:"handoutMode,omitempty"` 153 | HealthMax float64 `json:"healthMax,omitempty"` 154 | HealthMultiplier float64 `json:"healthMultiplier,omitempty"` 155 | HealthThreshold float64 `json:"healthThreshold,omitempty"` 156 | Ipv6 bool `json:"ipv6,omitempty"` 157 | LastModified string `json:"lastModified,omitempty"` 158 | Links []Link `json:"links,omitempty"` 159 | LivenessTests []LivenessTest `json:"livenessTests,omitempty"` 160 | LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` 161 | MapName interface{} `json:"mapName,omitempty"` 162 | MaxUnreachablePenalty interface{} `json:"maxUnreachablePenalty,omitempty"` 163 | MxRecords []interface{} `json:"mxRecords,omitempty"` 164 | Name string `json:"name"` 165 | ScoreAggregationType string `json:"scoreAggregationType"` 166 | StaticTTL interface{} `json:"staticTTL,omitempty"` 167 | StickinessBonusConstant interface{} `json:"stickinessBonusConstant,omitempty"` 168 | StickinessBonusPercentage interface{} `json:"stickinessBonusPercentage,omitempty"` 169 | TrafficTargets []TrafficTarget `json:"trafficTargets"` 170 | Type string `json:"type"` 171 | UnreachableThreshold interface{} `json:"unreachableThreshold,omitempty"` 172 | UseComputedTargets bool `json:"useComputedTargets,omitempty"` 173 | } 174 | 175 | // LivenessTest represents a liveness test associated with an Akamai 176 | // GTM property. 177 | type LivenessTest struct { 178 | Name string `json:"name"` 179 | HTTPError3xx bool `json:"httpError3xx,omitempty"` 180 | HTTPError4xx bool `json:"httpError4xx,omitempty"` 181 | HTTPError5xx bool `json:"httpError5xx,omitempty"` 182 | TestInterval int64 `json:"testInterval,omitempty"` 183 | TestObject string `json:"testObject,omitempty"` 184 | TestObjectPort int64 `json:"testObjectPort,omitempty"` 185 | TestObjectProtocol string `json:"testObjectProtocol,omitempty"` 186 | TestObjectUsername string `json:"testObjectUsername,omitempty"` 187 | TestObjectPassword string `json:"testObjectPassword,omitempty"` 188 | TestTimeout float64 `json:"testTimeout,omitempty"` 189 | DisableNonstandardPortWarning bool `json:"disableNonstandardPortWarning,omitempty"` 190 | RequestString string `json:"requestString,omitempty"` 191 | ResponseString string `json:"responseString,omitempty"` 192 | SSLClientPrivateKey string `json:"sslClientPrivateKey,omitempty"` 193 | SSLCertificate string `json:"sslClientCertificate,omitempty"` 194 | HostHeader string `json:"hostHeader,omitempty"` 195 | } 196 | 197 | // TrafficTarget represents a traffic target associated with an Akamai 198 | // GTM property. 199 | type TrafficTarget struct { 200 | DataCenterID int `json:"datacenterId"` 201 | Enabled bool `json:"enabled"` 202 | HandoutCname interface{} `json:"handoutCName"` 203 | Name interface{} `json:"name"` 204 | Servers []string `json:"servers"` 205 | Weight float64 `json:"weight"` 206 | } 207 | 208 | // AkamaiError represents a non-successful HTTP response from the 209 | // Akamai API. 210 | type AkamaiError struct { 211 | Type string `json:"type"` 212 | Title string `json:"title"` 213 | Detail string `json:"detail"` 214 | RequestBody string `json:"-"` 215 | ResponseBody string `json:"-"` 216 | } 217 | 218 | func (a AkamaiError) Error() string { 219 | components := []string{a.Title, a.Detail, a.RequestBody, a.ResponseBody} 220 | return strings.Join(components, "\n") 221 | } 222 | -------------------------------------------------------------------------------- /edgegrid/gtm_client_api_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "testing" 9 | 10 | "github.com/gobs/pretty" 11 | ) 12 | 13 | func gtmTestTools(code int, body string) (*httptest.Server, *GTMClient) { 14 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | w.WriteHeader(code) 16 | w.Header().Set("Content-Type", "application/json") 17 | fmt.Fprintf(w, body) 18 | })) 19 | 20 | tr := &http.Transport{ 21 | Proxy: func(req *http.Request) (*url.URL, error) { 22 | return url.Parse(server.URL) 23 | }, 24 | } 25 | 26 | httpClient := &http.Client{Transport: tr} 27 | 28 | creds := &AuthCredentials{ 29 | "accessToken", 30 | "clientToken", 31 | "clientSecret", 32 | server.URL, 33 | } 34 | 35 | client := >MClient{creds, httpClient} 36 | 37 | return server, client 38 | } 39 | 40 | func TestDomains(t *testing.T) { 41 | server, client := gtmTestTools(200, domainsJSON) 42 | defer server.Close() 43 | 44 | domains, _ := client.Domains() 45 | t.Log(pretty.PrettyFormat(domains)) 46 | 47 | if len(domains) != 2 { 48 | t.Error("Expected 2 Domains") 49 | } 50 | for _, d := range domains { 51 | if d.Name == "" { 52 | t.Error("Expected Name to have a value") 53 | } 54 | if d.Status == "" { 55 | t.Error("Expected Status to have a value") 56 | } 57 | if d.LastModified == "" { 58 | t.Error("Expected LastModified to have a value") 59 | } 60 | } 61 | } 62 | 63 | func TestDomain(t *testing.T) { 64 | server, client := gtmTestTools(200, domainJSON) 65 | defer server.Close() 66 | 67 | domain, _ := client.Domain("example.akadns.net") 68 | t.Log(pretty.PrettyFormat(domain)) 69 | 70 | if "example.akadns.net" != domain.Name { 71 | t.Error("domain.Name should be example.akadns.net") 72 | } 73 | if domain.Status == nil { 74 | t.Error("domain.Status should not be nil") 75 | } 76 | } 77 | 78 | func TestDomainStatus(t *testing.T) { 79 | server, client := gtmTestTools(200, domainStatusJSON) 80 | defer server.Close() 81 | 82 | status, _ := client.DomainStatus("example.akadns.net") 83 | t.Log(pretty.PrettyFormat(status)) 84 | 85 | if "DENIED" != status.PropagationStatus { 86 | t.Error("status.PropagationStatus should be DENIED") 87 | } 88 | if "ERROR: Some error" != status.Message { 89 | t.Error("status.Message should be 'ERROR: Some error'") 90 | } 91 | } 92 | 93 | func TestDomainCreate(t *testing.T) { 94 | server, client := gtmTestTools(201, createdDomainJSON) 95 | defer server.Close() 96 | 97 | domain, _ := client.DomainCreate("example.akadns.net", "weighted") 98 | t.Log(pretty.PrettyFormat(domain)) 99 | 100 | if domain.Domain == nil { 101 | t.Error("domain creation response should contain the created Domain") 102 | } 103 | if domain.Status == nil { 104 | t.Error("domain creation response should contain the creation status") 105 | } 106 | } 107 | 108 | func TestDomainCreateFailure(t *testing.T) { 109 | server, client := gtmTestTools(500, errorResponseJSON) 110 | defer server.Close() 111 | 112 | _, err := client.DomainCreate("example.akadns.net", "weighted") 113 | t.Log(pretty.PrettyFormat(err)) 114 | if err == nil { 115 | t.Error("Expected an error back") 116 | } 117 | var theError = err.(*AkamaiError) 118 | if theError.RequestBody == "" || theError.ResponseBody == "" { 119 | t.Error("Expected AkamaiError to contain the HTTP request and response bodies") 120 | } 121 | } 122 | 123 | func TestDomainUpdate(t *testing.T) { 124 | server, client := gtmTestTools(200, updatedDomainJSON) 125 | defer server.Close() 126 | 127 | domain := &Domain{ 128 | Name: "example.akadns.net", 129 | Type: "full", 130 | } 131 | 132 | updatedDomain, _ := client.DomainUpdate(domain) 133 | t.Log(pretty.PrettyFormat(updatedDomain)) 134 | 135 | if updatedDomain.Domain == nil { 136 | t.Error("domain update response should contain the updated Domain") 137 | } 138 | if updatedDomain.Status == nil { 139 | t.Error("domain update response should contain the update status") 140 | } 141 | } 142 | 143 | func TestDataCenters(t *testing.T) { 144 | server, client := gtmTestTools(200, dataCentersJSON) 145 | defer server.Close() 146 | 147 | dcs, _ := client.DataCenters("foo.akadns.net") 148 | t.Log(pretty.PrettyFormat(dcs)) 149 | 150 | if len(dcs) != 2 { 151 | t.Error("Response should contain 2 DataCenters") 152 | } 153 | 154 | if dcs[0].Nickname != "dcOne" { 155 | t.Error("Response should return proper DataCenter item with a Nickname") 156 | } 157 | } 158 | 159 | func TestDataCenterCreate(t *testing.T) { 160 | server, client := gtmTestTools(201, createdDataCenterJSON) 161 | defer server.Close() 162 | 163 | dataCenter := &DataCenter{ 164 | Nickname: "Winterfell", 165 | City: "Doune", 166 | Country: "GB", 167 | Continent: "EU", 168 | Latitude: 56.185097, 169 | Longitude: -4.050264, 170 | } 171 | 172 | createdDataCenter, _ := client.DataCenterCreate("example.akadns.net", dataCenter) 173 | t.Log(pretty.PrettyFormat(createdDataCenter)) 174 | 175 | if createdDataCenter.DataCenter == nil { 176 | t.Error("Expected response to contain the DataCenter resource") 177 | } 178 | if createdDataCenter.DataCenter.DataCenterID != 3133 { 179 | t.Error("Expected response to contain the assigned DataCenterID") 180 | } 181 | if createdDataCenter.Status == nil { 182 | t.Error("Ecpected response to contain the creation status") 183 | } 184 | } 185 | 186 | func TestDataCenterByNameSuccess(t *testing.T) { 187 | server, client := gtmTestTools(200, dataCentersJSON) 188 | defer server.Close() 189 | 190 | dc, _ := client.DataCenterByName("foo.akadns.net", "dcOne") 191 | t.Log(pretty.PrettyFormat(dc)) 192 | 193 | if dc.Nickname != "dcOne" { 194 | t.Error("DataCenterByName should return a DataCenter with a 'Nickname'") 195 | } 196 | } 197 | 198 | func TestDataCenterByNameError(t *testing.T) { 199 | server, client := gtmTestTools(200, `{ "items": [] }`) 200 | defer server.Close() 201 | 202 | _, err := client.DataCenterByName("foo.akadns.net", "dcOne") 203 | if err == nil { 204 | t.Error("Expected an error since the DataCenter was not found") 205 | } 206 | } 207 | 208 | func TestDataCenter(t *testing.T) { 209 | server, client := gtmTestTools(200, dataCenterJSON) 210 | defer server.Close() 211 | 212 | dc, _ := client.DataCenter("foo.akadns.net", 1) 213 | t.Log(pretty.PrettyFormat(dc)) 214 | 215 | if dc.Nickname != "dcNickname" { 216 | t.Error("DataCenter should return the correct DataCenter for id 1") 217 | } 218 | } 219 | 220 | func TestDataCenterUpdate(t *testing.T) { 221 | server, client := gtmTestTools(200, createdDataCenterJSON) 222 | defer server.Close() 223 | 224 | dataCenter := &DataCenter{ 225 | Nickname: "Winterfell", 226 | City: "Doune", 227 | Country: "GB", 228 | Continent: "EU", 229 | Latitude: 56.185097, 230 | Longitude: -4.050264, 231 | } 232 | 233 | createdDataCenter, _ := client.DataCenterUpdate("example.akadns.net", dataCenter) 234 | t.Log(pretty.PrettyFormat(createdDataCenter)) 235 | 236 | if createdDataCenter.DataCenter == nil { 237 | t.Error("Expected response to contain the DataCenter resource") 238 | } 239 | if createdDataCenter.DataCenter.DataCenterID != 3133 { 240 | t.Error("Expected response to contain the assigned DataCenterID") 241 | } 242 | if createdDataCenter.Status == nil { 243 | t.Error("Ecpected response to contain the creation status") 244 | } 245 | } 246 | 247 | func TestDataCenterDeleteSuccess(t *testing.T) { 248 | server, client := gtmTestTools(200, "") 249 | defer server.Close() 250 | 251 | err := client.DataCenterDelete("domain", 123) 252 | if err != nil { 253 | t.Error("DataCenterDelete should return true if the HTTP request succeeds") 254 | } 255 | } 256 | 257 | func TestDataCenterDeleteFail(t *testing.T) { 258 | server, client := gtmTestTools(500, "") 259 | defer server.Close() 260 | 261 | err := client.DataCenterDelete("domain", 123) 262 | if err.Error() != "HTTP status not OK" { 263 | t.Error("DataCenterDelete should return an error if the HTTP request returns non-successfully") 264 | } 265 | } 266 | 267 | func TestProperties(t *testing.T) { 268 | server, client := gtmTestTools(200, propertiesResponseJSON) 269 | defer server.Close() 270 | 271 | props, _ := client.Properties("foo.akadns.net") 272 | t.Log(pretty.PrettyFormat(props)) 273 | 274 | if len(props.Properties) != 5 { 275 | t.Error("Response should contain 5 Properties") 276 | } 277 | 278 | if props.Properties[0].Name != "newprop" { 279 | t.Error("Response should return proper Property item with a Name") 280 | } 281 | } 282 | 283 | func TestPropertiesSorted(t *testing.T) { 284 | server, client := gtmTestTools(200, propertiesResponseJSON) 285 | defer server.Close() 286 | 287 | props, _ := client.PropertiesSorted("foo.akadns.net") 288 | t.Log(pretty.PrettyFormat(props)) 289 | 290 | if len(props.Properties) != 5 { 291 | t.Error("Response should contain 5 Properties") 292 | } 293 | 294 | if props.Properties[0].Name != "anotherprop" { 295 | t.Error("PropertiesSorted should return the correct Property in the first position") 296 | } 297 | 298 | if props.Properties[1].Name != "aprop" { 299 | t.Error("PropertiesSorted should return the correct Property in the second position") 300 | } 301 | 302 | if props.Properties[2].Name != "newprop" { 303 | t.Error("PropertiesSorted should return the correct Property in the third position") 304 | } 305 | 306 | if props.Properties[3].Name != "prop" { 307 | t.Error("PropertiesSorted should return the correct Property in the fourth position") 308 | } 309 | 310 | if props.Properties[4].Name != "someprop" { 311 | t.Error("PropertiesSorted should return the correct Property in the fifth position") 312 | } 313 | } 314 | 315 | func TestProperty(t *testing.T) { 316 | server, client := gtmTestTools(200, propJSON) 317 | defer server.Close() 318 | 319 | prop, _ := client.Property("foo.akadns.net", "someName") 320 | t.Log(pretty.PrettyFormat(prop)) 321 | 322 | if prop.Name != "someName" { 323 | t.Error("Property should return the correct Property for the name it's passed") 324 | } 325 | 326 | if prop.TrafficTargets[0].DataCenterID != 3131 { 327 | t.Error("Property should return the correct traffic targets for the property associated with the name it's passed") 328 | } 329 | 330 | if prop.LivenessTests[0].Name != "livenessTestOne" { 331 | t.Error("Property should return the correct liveness tests for the property associated with the name it's passed") 332 | } 333 | } 334 | 335 | func TestPropertyCreate(t *testing.T) { 336 | newProperty := &Property{ 337 | Name: "origin", 338 | HandoutMode: "normal", 339 | FailoverDelay: 0, 340 | FailbackDelay: 0, 341 | Type: "weighted-round-robin", 342 | ScoreAggregationType: "mean", 343 | TrafficTargets: []TrafficTarget{ 344 | TrafficTarget{ 345 | DataCenterID: 3131, 346 | Enabled: true, 347 | Servers: []string{"1.2.3.5"}, 348 | Weight: 50, 349 | }, 350 | TrafficTarget{ 351 | DataCenterID: 3132, 352 | Enabled: true, 353 | Servers: []string{"1.2.3.4"}, 354 | Weight: 50, 355 | }, 356 | }, 357 | } 358 | 359 | server, client := gtmTestTools(201, createdPropertyJSON) 360 | defer server.Close() 361 | 362 | createdProperty, err := client.PropertyCreate("foo.akadns.net", newProperty) 363 | if err != nil { 364 | t.Fail() 365 | } 366 | t.Log(pretty.PrettyFormat(createdProperty)) 367 | 368 | if createdProperty.Property == nil { 369 | t.Error("Property response should contain a Property resource") 370 | } 371 | if createdProperty.Status == nil { 372 | t.Error("Property response should contain the creation status") 373 | } 374 | } 375 | 376 | func TestPropertyCreateFailure(t *testing.T) { 377 | newProperty := &Property{} 378 | 379 | server, client := gtmTestTools(500, errorResponseJSON) 380 | defer server.Close() 381 | 382 | _, err := client.PropertyCreate("foo.akadns.net", newProperty) 383 | if err == nil { 384 | t.Error("Property creation failure should result in an error") 385 | } 386 | } 387 | 388 | func TestPropertyDeleteSuccess(t *testing.T) { 389 | server, client := gtmTestTools(200, "prop") 390 | defer server.Close() 391 | 392 | deleted, _ := client.PropertyDelete("domain", "prop") 393 | if deleted != true { 394 | t.Error("PropertyDelete should return true if the HTTP request succeeds") 395 | } 396 | } 397 | 398 | func TestPropertyDeleteFail(t *testing.T) { 399 | server, client := gtmTestTools(500, "prop") 400 | defer server.Close() 401 | 402 | deleted, err := client.PropertyDelete("domain", "prop") 403 | if deleted != false { 404 | t.Error("PropertyDelete should return false if the HTTP request returns non-successfully") 405 | } 406 | if err.Error() != "HTTP status not OK" { 407 | t.Error("PropertyDelete should return an error if the HTTP request returns non-successfully") 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /edgegrid/papi_client_api_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "testing" 9 | 10 | "github.com/gobs/pretty" 11 | ) 12 | 13 | func papiTestTools(code int, body string) (*httptest.Server, *PAPIClient) { 14 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | w.WriteHeader(code) 16 | w.Header().Set("Content-Type", "application/json") 17 | fmt.Fprintf(w, body) 18 | })) 19 | 20 | tr := &http.Transport{ 21 | Proxy: func(req *http.Request) (*url.URL, error) { 22 | return url.Parse(server.URL) 23 | }, 24 | } 25 | 26 | httpClient := &http.Client{Transport: tr} 27 | 28 | creds := &AuthCredentials{ 29 | "accessToken", 30 | "clientToken", 31 | "clientSecret", 32 | server.URL, 33 | } 34 | 35 | client := &PAPIClient{creds, httpClient} 36 | 37 | return server, client 38 | } 39 | 40 | func TestPAPIGroups(t *testing.T) { 41 | server, client := papiTestTools(200, groupsJSON) 42 | defer server.Close() 43 | 44 | groups, err := client.Groups() 45 | if err != nil { 46 | panic(err) 47 | } 48 | t.Log(pretty.PrettyFormat(groups)) 49 | 50 | if len(groups) != 2 { 51 | t.Error("Expected 2 Groups") 52 | } 53 | for _, g := range groups { 54 | if g.Name == "" { 55 | t.Error("Expected Group Name to have a value") 56 | } 57 | if g.GroupID == "" { 58 | t.Error("Expected Group ID to have a value") 59 | } 60 | } 61 | } 62 | 63 | func TestPAPIProducts(t *testing.T) { 64 | server, client := papiTestTools(200, productsJSON) 65 | defer server.Close() 66 | 67 | prods, err := client.Products("contractId") 68 | if err != nil { 69 | panic(err) 70 | } 71 | t.Log(pretty.PrettyFormat(prods)) 72 | 73 | if len(prods) != 2 { 74 | t.Error("Expected 2 Products") 75 | } 76 | for _, p := range prods { 77 | if p.Name == "" { 78 | t.Error("Expected Product Name to have a value") 79 | } 80 | if p.ProductID == "" { 81 | t.Error("Expected Product ID to have a value") 82 | } 83 | } 84 | } 85 | 86 | func TestPAPICpCodes(t *testing.T) { 87 | server, client := papiTestTools(200, cpCodesJSON) 88 | defer server.Close() 89 | 90 | cps, err := client.CpCodes("contractId", "groupId") 91 | if err != nil { 92 | panic(err) 93 | } 94 | t.Log(pretty.PrettyFormat(cps)) 95 | 96 | if len(cps) != 1 { 97 | t.Error("Expected 1 CPCodes") 98 | } 99 | 100 | c := cps[0] 101 | if c.Name != "someName" { 102 | t.Error("Expected CPCode Name to have a value") 103 | } 104 | if c.CPCodeID != "someCodeId" { 105 | t.Error("Expected CpCode ID to have a value") 106 | } 107 | if c.ProductIDs[0] != "someId" { 108 | t.Error("Expected CPCode ProductIDs to have a value") 109 | } 110 | if c.CreatedDate != "someDate" { 111 | t.Error("Expected CPCode CreatedDate to have a value") 112 | } 113 | } 114 | 115 | func TestPAPICpCode(t *testing.T) { 116 | server, client := papiTestTools(200, cpCodesJSON) 117 | defer server.Close() 118 | 119 | c, err := client.CpCode("cpCodeId", "contractId", "groupId") 120 | if err != nil { 121 | panic(err) 122 | } 123 | t.Log(pretty.PrettyFormat(c)) 124 | 125 | if c.Name != "someName" { 126 | t.Error("Expected CpCode Name to have a value") 127 | } 128 | if c.CPCodeID != "someCodeId" { 129 | t.Error("Expected CPCode ID to have a value") 130 | } 131 | if c.ProductIDs[0] != "someId" { 132 | t.Error("Expected CpCode ProductIDs to have a value") 133 | } 134 | if c.CreatedDate != "someDate" { 135 | t.Error("Expected CpCode CreatedDate to have a value") 136 | } 137 | } 138 | 139 | func TestPAPIHostnames(t *testing.T) { 140 | server, client := papiTestTools(200, hostnamesJSON) 141 | defer server.Close() 142 | 143 | hostnames, err := client.Hostnames("contractId", "groupId") 144 | if err != nil { 145 | panic(err) 146 | } 147 | t.Log(pretty.PrettyFormat(hostnames)) 148 | 149 | if len(hostnames) != 1 { 150 | t.Error("Expected 1 Hostnames") 151 | } 152 | 153 | h := hostnames[0] 154 | 155 | if h.EdgeHostnameID != "hostId1" { 156 | t.Error("Expected Hostname.ID to have correct value") 157 | } 158 | if h.DomainPrefix != "foo.com" { 159 | t.Error("Expected Hostname.DomainPrefix to have correct value") 160 | } 161 | if h.DomainSuffix != "edge.net" { 162 | t.Error("Expected Hostname.DomainSuffix to have correct value") 163 | } 164 | if h.IPVersionBehavior != "IPV4" { 165 | t.Error("Expected Hostname.IPVersionBehavior to have correct value") 166 | } 167 | if h.Secure != false { 168 | t.Error("Expected Hostname.Secure to have correct value") 169 | } 170 | if h.EdgeHostnameDomain != "foo.com.edge.net" { 171 | t.Error("Expected Hostname.EdgeHostnameDomain to have correct value") 172 | } 173 | } 174 | 175 | func TestPAPIHostname(t *testing.T) { 176 | server, client := papiTestTools(200, hostnamesJSON) 177 | defer server.Close() 178 | 179 | h, err := client.Hostname("hostId", "contractId", "groupId") 180 | if err != nil { 181 | panic(err) 182 | } 183 | t.Log(pretty.PrettyFormat(h)) 184 | 185 | if h.EdgeHostnameID != "hostId1" { 186 | t.Error("Expected Hostname.ID to have correct value") 187 | } 188 | if h.DomainPrefix != "foo.com" { 189 | t.Error("Expected Hostname.DomainPrefix to have correct value") 190 | } 191 | if h.DomainSuffix != "edge.net" { 192 | t.Error("Expected Hostname.DomainSuffix to have correct value") 193 | } 194 | if h.IPVersionBehavior != "IPV4" { 195 | t.Error("Expected Hostname.IPVersionBehavior to have correct value") 196 | } 197 | if h.Secure != false { 198 | t.Error("Expected Hostname.Secure to have correct value") 199 | } 200 | if h.EdgeHostnameDomain != "foo.com.edge.net" { 201 | t.Error("Expected Hostname.EdgeHostnameDomain to have correct value") 202 | } 203 | } 204 | 205 | func TestPAPIProperties(t *testing.T) { 206 | server, client := papiTestTools(200, propertiesJSON) 207 | defer server.Close() 208 | 209 | ps, err := client.Properties("contractId", "groupId") 210 | if err != nil { 211 | panic(err) 212 | } 213 | t.Log(pretty.PrettyFormat(ps)) 214 | 215 | p := ps[0] 216 | 217 | if p.PropertyID != "propertyId" { 218 | t.Error("Expected PropertySummary.PropertyID to have correct value") 219 | } 220 | if p.AccountID != "accountId" { 221 | t.Error("Expected PropertySummary.AccountId to have correct value") 222 | } 223 | if p.GroupID != "groupid" { 224 | t.Error("Expected PropertySummary.GroupID to have correct value") 225 | } 226 | if p.ContractID != "contractId" { 227 | t.Error("Expected PropertySummary.ContractId to have correct value") 228 | } 229 | if p.Name != "m.example.com" { 230 | t.Error("Expected PropertySummary.Name to have correct value") 231 | } 232 | if p.LatestVersion != 1 { 233 | t.Error("Expected PropertySummary.LatestVersion to have correct value") 234 | } 235 | if p.StagingVersion != 2 { 236 | t.Error("Expected PropertySummary.StagingVersion to have correct value") 237 | } 238 | if p.ProductionVersion != 0 { 239 | t.Error("Expected PropertySummary.ProductionVersion to have correct value") 240 | } 241 | if p.Note != "a note" { 242 | t.Error("Expected PropertySummary.Note to have correct value") 243 | } 244 | } 245 | 246 | func TestPAPIProperty(t *testing.T) { 247 | server, client := papiTestTools(200, propertiesJSON) 248 | defer server.Close() 249 | 250 | p, err := client.Property("propId", "contractId", "groupId") 251 | if err != nil { 252 | panic(err) 253 | } 254 | t.Log(pretty.PrettyFormat(p)) 255 | 256 | if p.PropertyID != "propertyId" { 257 | t.Error("Expected PropertySummary.PropertyID to have correct value") 258 | } 259 | if p.AccountID != "accountId" { 260 | t.Error("Expected PropertySummary.AccountID to have correct value") 261 | } 262 | if p.GroupID != "groupid" { 263 | t.Error("Expected PropertySummary.GroupID to have correct value") 264 | } 265 | if p.ContractID != "contractId" { 266 | t.Error("Expected PropertySummary.ContractID to have correct value") 267 | } 268 | if p.Name != "m.example.com" { 269 | t.Error("Expected PropertySummary.Name to have correct value") 270 | } 271 | if p.LatestVersion != 1 { 272 | t.Error("Expected PropertySummary.LatestVersion to have correct value") 273 | } 274 | if p.StagingVersion != 2 { 275 | t.Error("Expected PropertySummary.StagingVersion to have correct value") 276 | } 277 | if p.ProductionVersion != 0 { 278 | t.Error("Expected PropertySummary.ProductionVersion to have correct value") 279 | } 280 | if p.Note != "a note" { 281 | t.Error("Expected PropertySummary.Note to have correct value") 282 | } 283 | } 284 | 285 | func TestPAPIPropertyVersions(t *testing.T) { 286 | server, client := papiTestTools(200, propertyVersionsJSON) 287 | defer server.Close() 288 | 289 | vs, err := client.PropertyVersions("propId", "contractId", "groupId") 290 | if err != nil { 291 | panic(err) 292 | } 293 | t.Log(pretty.PrettyFormat(vs)) 294 | 295 | v := vs[0] 296 | 297 | if v.ProductID != "productId" { 298 | t.Error("Expected PropertyVersionSummary.ProductID to have correct value") 299 | } 300 | if v.PropertyVersion != 2 { 301 | t.Error("Expected PropertyVersionSummary.PropertyVersion to have correct value") 302 | } 303 | if v.UpdatedByUser != "some user" { 304 | t.Error("Expected PropertyVersionSummary.UpdatedByUser to have correct value") 305 | } 306 | if v.UpdatedDate != "updatedDate" { 307 | t.Error("Expected PropertyVersionSummary.UpdatedDate to have correct value") 308 | } 309 | if v.ProductionStatus != "INACTIVE" { 310 | t.Error("Expected PropertyVersionSummary.ProductionStatus to have correct value") 311 | } 312 | if v.StagingStatus != "ACTIVE" { 313 | t.Error("Expected PropertyVersionSummary.StagingStatus to have correct value") 314 | } 315 | if v.Etag != "123" { 316 | t.Error("Expected PropertyVersionSummary.Etag to have correct value") 317 | } 318 | if v.Note != "some note" { 319 | t.Error("Expected PropertyVersionSummary.Note to have correct value") 320 | } 321 | } 322 | 323 | func TestPAPIPropertyVersion(t *testing.T) { 324 | server, client := papiTestTools(200, propertyVersionsJSON) 325 | defer server.Close() 326 | 327 | v, err := client.PropertyVersion("2", "propId", "contractId", "groupId") 328 | if err != nil { 329 | panic(err) 330 | } 331 | t.Log(pretty.PrettyFormat(v)) 332 | 333 | if v.ProductID != "productId" { 334 | t.Error("Expected PropertyVersionSummary.ProductID to have correct value") 335 | } 336 | if v.PropertyVersion != 2 { 337 | t.Error("Expected PropertyVersionSummary.PropertyVersion to have correct value") 338 | } 339 | if v.UpdatedByUser != "some user" { 340 | t.Error("Expected PropertyVersionSummary.UpdatedByUser to have correct value") 341 | } 342 | if v.UpdatedDate != "updatedDate" { 343 | t.Error("Expected PropertyVersionSummary.UpdatedDate to have correct value") 344 | } 345 | if v.ProductionStatus != "INACTIVE" { 346 | t.Error("Expected PropertyVersionSummary.ProductionStatus to have correct value") 347 | } 348 | if v.StagingStatus != "ACTIVE" { 349 | t.Error("Expected PropertyVersionSummary.StagingStatus to have correct value") 350 | } 351 | if v.Etag != "123" { 352 | t.Error("Expected PropertyVersionSummary.Etag to have correct value") 353 | } 354 | if v.Note != "some note" { 355 | t.Error("Expected PropertyVersionSummary.Note to have correct value") 356 | } 357 | } 358 | 359 | func TestPAPIPropertyVersionXML(t *testing.T) { 360 | server, client := papiTestTools(200, "") 361 | defer server.Close() 362 | 363 | xml, err := client.PropertyVersionXML("2", "propId", "contractId", "groupId") 364 | if err != nil { 365 | panic(err) 366 | } 367 | t.Log(pretty.PrettyFormat(xml)) 368 | 369 | if xml != "" { 370 | t.Error("Expected PropertyVersionXML to return the correct value") 371 | } 372 | } 373 | 374 | func TestPAPIPropertyLatestVersion(t *testing.T) { 375 | server, client := papiTestTools(200, propertyVersionsJSON) 376 | defer server.Close() 377 | 378 | v, err := client.PropertyLatestVersion("propId", "contractId", "groupId") 379 | if err != nil { 380 | panic(err) 381 | } 382 | t.Log(pretty.PrettyFormat(v)) 383 | 384 | if v.ProductID != "productId" { 385 | t.Error("Expected PropertyVersionSummary.ProductID to have correct value") 386 | } 387 | if v.PropertyVersion != 2 { 388 | t.Error("Expected PropertyVersionSummary.PropertyVersion to have correct value") 389 | } 390 | if v.UpdatedByUser != "some user" { 391 | t.Error("Expected PropertyVersionSummary.UpdatedByUser to have correct value") 392 | } 393 | if v.UpdatedDate != "updatedDate" { 394 | t.Error("Expected PropertyVersionSummary.UpdatedDate to have correct value") 395 | } 396 | if v.ProductionStatus != "INACTIVE" { 397 | t.Error("Expected PropertyVersionSummary.ProductionStatus to have correct value") 398 | } 399 | if v.StagingStatus != "ACTIVE" { 400 | t.Error("Expected PropertyVersionSummary.StagingStatus to have correct value") 401 | } 402 | if v.Etag != "123" { 403 | t.Error("Expected PropertyVersionSummary.Etag to have correct value") 404 | } 405 | if v.Note != "some note" { 406 | t.Error("Expected PropertyVersionSummary.Note to have correct value") 407 | } 408 | } 409 | 410 | func TestPAPIPropertyRules(t *testing.T) { 411 | server, client := papiTestTools(200, propertyRulesJSON) 412 | defer server.Close() 413 | 414 | r, err := client.PropertyRules("propId", "version", "contractId", "groupId") 415 | if err != nil { 416 | panic(err) 417 | } 418 | t.Log(pretty.PrettyFormat(r)) 419 | 420 | if r.Name != "default" { 421 | t.Error("Expected PropertyRuleSummary.Name to have correct value") 422 | } 423 | if r.UUID != "some-uuid" { 424 | t.Error("Expected PropertyRuleSummary.UUID to have correct value") 425 | } 426 | } 427 | 428 | func TestPAPIPropertyHostnames(t *testing.T) { 429 | server, client := papiTestTools(200, propertyHostnamesJSON) 430 | defer server.Close() 431 | 432 | hostnames, err := client.PropertyHostnames("propertyId", "1", "contractId", "groupId") 433 | if err != nil { 434 | panic(err) 435 | } 436 | t.Log(pretty.PrettyFormat(hostnames)) 437 | 438 | hostname := hostnames.Hostnames[0] 439 | if hostname.From != "example.com" { 440 | t.Error("Expected Hostname.From to have correct value") 441 | } 442 | if hostname.ID != "ehn_895822" { 443 | t.Error("Expected Hostname.ID to have correct value") 444 | } 445 | if hostname.To != "example.com.edgesuite.net" { 446 | t.Error("Expected Hostname.To to have correct value") 447 | } 448 | } 449 | 450 | func TestPAPIActivations(t *testing.T) { 451 | server, client := papiTestTools(200, activationsJSON) 452 | defer server.Close() 453 | 454 | r, err := client.Activations("propId", "contractId", "groupId") 455 | if err != nil { 456 | panic(err) 457 | } 458 | t.Log(pretty.PrettyFormat(r)) 459 | 460 | act := r[0] 461 | 462 | if act.PropertyName != "example.com" { 463 | t.Error("Expected PapiActivation.PropertyName to have correct value") 464 | } 465 | if act.ActivationID != "123" { 466 | t.Error("Expected PapiActivation.ActivationID to have correct value") 467 | } 468 | if act.PropertyID != "propId" { 469 | t.Error("Expected PapiActivation.PropertyID to have correct value") 470 | } 471 | if act.PropertyVersion != 1 { 472 | t.Error("Expected PapiActivation.PropertyVersion to have correct value") 473 | } 474 | if act.PropertyVersion != 1 { 475 | t.Error("Expected PapiActivation.PropertyVersion to have correct value") 476 | } 477 | if act.Network != "STAGING" { 478 | t.Error("Expected PapiActivation.Network to have correct value") 479 | } 480 | if act.ActivationType != "ACTIVATE" { 481 | t.Error("Expected PapiActivation.ActivationType to have correct value") 482 | } 483 | if act.Status != "ACTIVE" { 484 | t.Error("Expected PapiActivation.Status to have correct value") 485 | } 486 | if act.SubmitDate != "2014-03-05T02:22:12Z" { 487 | t.Error("Expected PapiActivation.SubmitDate to have correct value") 488 | } 489 | if act.UpdateDate != "2014-03-04T21:12:57Z" { 490 | t.Error("Expected PapiActivation.UpdateDate to have correct value") 491 | } 492 | if act.Note != "some note" { 493 | t.Error("Expected PapiActivation.Note to have correct value") 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /edgegrid/gtm_json_fixtures_test.go: -------------------------------------------------------------------------------- 1 | package edgegrid 2 | 3 | const ( 4 | domainsJSON = `{ 5 | "items": [ 6 | { 7 | "acgId": "1-2345", 8 | "lastModified": "2014-03-03T16:02:45.000+0000", 9 | "links": [ 10 | { 11 | "href": "/config-gtm/v1/domains/example.akadns.net", 12 | "rel": "self" 13 | } 14 | ], 15 | "name": "example.akadns.net", 16 | "status": "2014-02-20 22:56 GMT: Current configuration has been propagated to all toplevels" 17 | }, 18 | { 19 | "acgId": "1-2345", 20 | "lastModified": "2013-11-09T12:04:45.000+0000", 21 | "links": [ 22 | { 23 | "href": "/config-gtm/v1/domains/demo.akadns.net", 24 | "rel": "self" 25 | } 26 | ], 27 | "name": "demo.akadns.net", 28 | "status": "2014-02-20 22:56 GMT: Current configuration has been propagated to all toplevels" 29 | } 30 | ] 31 | }` 32 | 33 | createdDomainJSON = `{ 34 | "resource": { 35 | "cidrMaps": [], 36 | "datacenters": [], 37 | "defaultErrorPenalty": 75, 38 | "defaultSslClientCertificate": null, 39 | "defaultSslClientPrivateKey": null, 40 | "defaultTimeoutPenalty": 25, 41 | "emailNotificationList": [ 42 | "our.admin@example.com" 43 | ], 44 | "geographicMaps": [], 45 | "lastModified": "2014-08-04T15:30:40.057+0000", 46 | "lastModifiedBy": "admin", 47 | "links": [ 48 | { 49 | "href": "/config-gtm/v1/domains/example.akadns.net", 50 | "rel": "self" 51 | } 52 | ], 53 | "loadFeedback": false, 54 | "loadImbalancePercentage": null, 55 | "modificationComments": "New Traffic Management domain.", 56 | "name": "example.akadns.net", 57 | "properties": [], 58 | "resources": [], 59 | "status": { 60 | "changeId": "abb94136-dd94-4779-bfb0-fcfac67fb843", 61 | "links": [ 62 | { 63 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 64 | "rel": "self" 65 | } 66 | ], 67 | "message": "Change Pending", 68 | "passingValidation": true, 69 | "propagationStatus": "PENDING", 70 | "propagationStatusDate": "2014-08-04T15:30:40.057+0000" 71 | }, 72 | "type": "weighted" 73 | }, 74 | "status": { 75 | "changeId": "abb94136-dd94-4779-bfb0-fcfac67fb843", 76 | "links": [ 77 | { 78 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 79 | "rel": "self" 80 | } 81 | ], 82 | "message": "Change Pending", 83 | "passingValidation": true, 84 | "propagationStatus": "PENDING", 85 | "propagationStatusDate": "2014-08-04T15:30:40.057+0000" 86 | } 87 | }` 88 | 89 | domainJSON = `{ 90 | "links": [ 91 | { 92 | "href": "/config-gtm/v1/domains/example.akadns.net", 93 | "rel": "self" 94 | }, 95 | { 96 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters", 97 | "rel": "datacenters" 98 | }, 99 | { 100 | "href": "/config-gtm/v1/domains/example.akadns.net/properties", 101 | "rel": "properties" 102 | }, 103 | { 104 | "href": "/config-gtm/v1/domains/example.akadns.net/geographic-maps", 105 | "rel": "geographic-maps" 106 | }, 107 | { 108 | "href": "/config-gtm/v1/domains/example.akadns.net/cidr-maps", 109 | "rel": "cidr-maps" 110 | }, 111 | { 112 | "href": "/config-gtm/v1/domains/example.akadns.net/resources", 113 | "rel": "resources" 114 | } 115 | ], 116 | "cidrMaps": [ 117 | { 118 | "assignments": [ 119 | { 120 | "blocks": [ 121 | "1.3.5.9", 122 | "1.2.3.0/24" 123 | ], 124 | "datacenterId": 3134, 125 | "nickname": "Frostfangs and the Fist of First Men" 126 | }, 127 | { 128 | "blocks": [ 129 | "1.2.4.0/24" 130 | ], 131 | "datacenterId": 3133, 132 | "nickname": "Winterfell" 133 | } 134 | ], 135 | "defaultDatacenter": { 136 | "datacenterId": 5400, 137 | "nickname": "All Other CIDR Blocks" 138 | }, 139 | "links": [ 140 | { 141 | "href": "/config-gtm/v1/domains/example.akadns.net/cidr-maps/The%20North", 142 | "rel": "self" 143 | } 144 | ], 145 | "name": "The North" 146 | } 147 | ], 148 | "datacenters": [ 149 | { 150 | "city": "Downpatrick", 151 | "cloneOf": 0, 152 | "continent": "EU", 153 | "country": "GB", 154 | "datacenterId": 3133, 155 | "defaultLoadObject": { 156 | "loadObject": null, 157 | "loadObjectPort": 0, 158 | "loadServers": null 159 | }, 160 | "latitude": 54.367, 161 | "links": [ 162 | { 163 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/3133", 164 | "rel": "self" 165 | } 166 | ], 167 | "longitude": -5.582, 168 | "nickname": "Winterfell", 169 | "stateOrProvince": null, 170 | "virtual": true 171 | }, 172 | { 173 | "city": "Doune", 174 | "cloneOf": 0, 175 | "continent": "EU", 176 | "country": "GB", 177 | "datacenterId": 3134, 178 | "defaultLoadObject": { 179 | "loadObject": null, 180 | "loadObjectPort": 0, 181 | "loadServers": null 182 | }, 183 | "latitude": 56.185097, 184 | "links": [ 185 | { 186 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/3134", 187 | "rel": "self" 188 | } 189 | ], 190 | "longitude": -4.050264, 191 | "nickname": "Winterfell", 192 | "stateOrProvince": "Perthshire", 193 | "virtual": true 194 | }, 195 | { 196 | "city": null, 197 | "cloneOf": 0, 198 | "continent": null, 199 | "country": null, 200 | "datacenterId": 5400, 201 | "defaultLoadObject": { 202 | "loadObject": null, 203 | "loadObjectPort": 0, 204 | "loadServers": null 205 | }, 206 | "latitude": 0.0, 207 | "links": [ 208 | { 209 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/5400", 210 | "rel": "self" 211 | } 212 | ], 213 | "longitude": 0.0, 214 | "nickname": "Default Datacenter", 215 | "stateOrProvince": null, 216 | "virtual": true 217 | } 218 | ], 219 | "defaultSslClientCertificate": null, 220 | "defaultSslClientPrivateKey": null, 221 | "defaultUnreachableThreshold": null, 222 | "emailNotificationList": [], 223 | "geographicMaps": [ 224 | { 225 | "assignments": [ 226 | { 227 | "countries": [ 228 | "GB" 229 | ], 230 | "datacenterId": 3133, 231 | "nickname": "UK users" 232 | } 233 | ], 234 | "defaultDatacenter": { 235 | "datacenterId": 5400, 236 | "nickname": "Default Mapping" 237 | }, 238 | "links": [ 239 | { 240 | "href": "/config-gtm/v1/domains/example.akadns.net/geographic-maps/UK%20Delivery", 241 | "rel": "self" 242 | } 243 | ], 244 | "name": "UK Delivery" 245 | } 246 | ], 247 | "lastModified": "2014-04-08T18:25:51.000+0000", 248 | "lastModifiedBy": "admin@example.com", 249 | "loadFeedback": true, 250 | "loadImbalancePercentage": 10.0, 251 | "minPingableRegionFraction": null, 252 | "modificationComments": "CIDRMap example", 253 | "name": "example.akadns.net", 254 | "pingInterval": null, 255 | "properties": [ 256 | { 257 | "backupCName": null, 258 | "backupIp": null, 259 | "balanceByDownloadScore": false, 260 | "cname": null, 261 | "comments": null, 262 | "dynamicTTL": 300, 263 | "failbackDelay": 0, 264 | "failoverDelay": 0, 265 | "handoutMode": "normal", 266 | "healthMax": null, 267 | "healthMultiplier": null, 268 | "healthThreshold": null, 269 | "ipv6": false, 270 | "lastModified": "2014-04-08T18:25:52.000+0000", 271 | "links": [ 272 | { 273 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/www", 274 | "rel": "self" 275 | } 276 | ], 277 | "livenessTests": [ 278 | { 279 | "disableNonstandardPortWarning": false, 280 | "hostHeader": "foo.example.com", 281 | "httpError3xx": true, 282 | "httpError4xx": true, 283 | "httpError5xx": true, 284 | "links": [], 285 | "name": "health-check", 286 | "requestString": null, 287 | "responseString": null, 288 | "sslClientCertificate": null, 289 | "sslClientPrivateKey": null, 290 | "testInterval": 60, 291 | "testObject": "/status", 292 | "testObjectPassword": null, 293 | "testObjectPort": 80, 294 | "testObjectProtocol": "HTTP", 295 | "testObjectUsername": null, 296 | "testTimeout": 25.0 297 | } 298 | ], 299 | "mapName": null, 300 | "maxUnreachablePenalty": null, 301 | "mxRecords": [], 302 | "name": "www", 303 | "scoreAggregationType": "mean", 304 | "staticTTL": 600, 305 | "stickinessBonusConstant": 0, 306 | "stickinessBonusPercentage": 0, 307 | "trafficTargets": [ 308 | { 309 | "datacenterId": 5400, 310 | "enabled": false, 311 | "handoutCName": null, 312 | "name": null, 313 | "servers": [], 314 | "weight": 0.0 315 | }, 316 | { 317 | "datacenterId": 3134, 318 | "enabled": true, 319 | "handoutCName": null, 320 | "name": null, 321 | "servers": [ 322 | "1.2.3.5" 323 | ], 324 | "weight": 0.0 325 | }, 326 | { 327 | "datacenterId": 3133, 328 | "enabled": true, 329 | "handoutCName": null, 330 | "name": null, 331 | "servers": [ 332 | "1.2.3.4" 333 | ], 334 | "weight": 1.0 335 | } 336 | ], 337 | "type": "failover", 338 | "unreachableThreshold": null, 339 | "useComputedTargets": false 340 | }, 341 | { 342 | "backupCName": null, 343 | "backupIp": null, 344 | "balanceByDownloadScore": false, 345 | "cname": null, 346 | "comments": null, 347 | "dynamicTTL": 300, 348 | "failbackDelay": 0, 349 | "failoverDelay": 0, 350 | "handoutMode": "normal", 351 | "healthMax": null, 352 | "healthMultiplier": null, 353 | "healthThreshold": null, 354 | "ipv6": true, 355 | "lastModified": "2014-04-08T18:25:52.000+0000", 356 | "links": [ 357 | { 358 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/mail", 359 | "rel": "self" 360 | } 361 | ], 362 | "livenessTests": [], 363 | "loadImbalancePercentage": null, 364 | "mapName": null, 365 | "maxUnreachablePenalty": null, 366 | "mxRecords": [], 367 | "name": "mail", 368 | "scoreAggregationType": "mean", 369 | "staticTTL": 600, 370 | "stickinessBonusConstant": 0, 371 | "stickinessBonusPercentage": 0, 372 | "trafficTargets": [ 373 | { 374 | "datacenterId": 5400, 375 | "enabled": false, 376 | "handoutCName": null, 377 | "name": null, 378 | "servers": [], 379 | "weight": 0.0 380 | }, 381 | { 382 | "datacenterId": 3134, 383 | "enabled": true, 384 | "handoutCName": null, 385 | "name": null, 386 | "servers": [ 387 | "2001:4878::5043:4078" 388 | ], 389 | "weight": 1.0 390 | }, 391 | { 392 | "datacenterId": 3133, 393 | "enabled": true, 394 | "handoutCName": null, 395 | "name": null, 396 | "servers": [ 397 | "2001:4878::5043:4072", 398 | "2001:4878::5043:4071" 399 | ], 400 | "weight": 1.0 401 | } 402 | ], 403 | "type": "weighted-round-robin", 404 | "unreachableThreshold": null, 405 | "useComputedTargets": false 406 | }, 407 | { 408 | "backupCName": null, 409 | "backupIp": null, 410 | "balanceByDownloadScore": false, 411 | "cname": null, 412 | "comments": null, 413 | "dynamicTTL": 300, 414 | "failbackDelay": 0, 415 | "failoverDelay": 0, 416 | "handoutMode": "normal", 417 | "healthMax": null, 418 | "healthMultiplier": null, 419 | "healthThreshold": null, 420 | "ipv6": false, 421 | "lastModified": "2014-04-08T18:25:52.000+0000", 422 | "links": [ 423 | { 424 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/supplies", 425 | "rel": "self" 426 | } 427 | ], 428 | "livenessTests": [], 429 | "loadImbalancePercentage": null, 430 | "mapName": null, 431 | "maxUnreachablePenalty": null, 432 | "mxRecords": [], 433 | "name": "supplies", 434 | "scoreAggregationType": "mean", 435 | "staticTTL": 600, 436 | "stickinessBonusConstant": 0, 437 | "stickinessBonusPercentage": 0, 438 | "trafficTargets": [ 439 | { 440 | "datacenterId": 5400, 441 | "enabled": false, 442 | "handoutCName": "supplies.example.com", 443 | "name": null, 444 | "servers": [], 445 | "weight": 0.0 446 | }, 447 | { 448 | "datacenterId": 3134, 449 | "enabled": true, 450 | "handoutCName": "winter.supplies.example.com", 451 | "name": null, 452 | "servers": [], 453 | "weight": 0.0 454 | }, 455 | { 456 | "datacenterId": 3133, 457 | "enabled": true, 458 | "handoutCName": "redcross.org", 459 | "name": null, 460 | "servers": [], 461 | "weight": 0.0 462 | } 463 | ], 464 | "type": "failover", 465 | "unreachableThreshold": null, 466 | "useComputedTargets": false 467 | }, 468 | { 469 | "backupCName": null, 470 | "backupIp": null, 471 | "balanceByDownloadScore": false, 472 | "cname": null, 473 | "comments": null, 474 | "dynamicTTL": 300, 475 | "failbackDelay": 0, 476 | "failoverDelay": 0, 477 | "handoutMode": "normal", 478 | "healthMax": null, 479 | "healthMultiplier": null, 480 | "healthThreshold": null, 481 | "ipv6": false, 482 | "lastModified": "2014-04-08T18:25:52.000+0000", 483 | "links": [ 484 | { 485 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/shop", 486 | "rel": "self" 487 | } 488 | ], 489 | "livenessTests": [], 490 | "loadImbalancePercentage": null, 491 | "mapName": "UK Delivery", 492 | "maxUnreachablePenalty": null, 493 | "mxRecords": [], 494 | "name": "shop", 495 | "scoreAggregationType": "mean", 496 | "staticTTL": 600, 497 | "stickinessBonusConstant": 0, 498 | "stickinessBonusPercentage": 0, 499 | "trafficTargets": [ 500 | { 501 | "datacenterId": 5400, 502 | "enabled": true, 503 | "handoutCName": "shop.example.com", 504 | "name": null, 505 | "servers": [], 506 | "weight": 1.0 507 | }, 508 | { 509 | "datacenterId": 3134, 510 | "enabled": false, 511 | "handoutCName": null, 512 | "name": null, 513 | "servers": [], 514 | "weight": 1.0 515 | }, 516 | { 517 | "datacenterId": 3133, 518 | "enabled": true, 519 | "handoutCName": "uk.shop.example.com", 520 | "name": null, 521 | "servers": [], 522 | "weight": 1.0 523 | } 524 | ], 525 | "type": "geographic", 526 | "unreachableThreshold": null, 527 | "useComputedTargets": false 528 | } 529 | ], 530 | "resources": [ 531 | { 532 | "aggregationType": "latest", 533 | "constrainedProperty": "mail", 534 | "decayRate": null, 535 | "description": "CPU utilization", 536 | "hostHeader": null, 537 | "leaderString": null, 538 | "leastSquaresDecay": null, 539 | "links": [ 540 | { 541 | "href": "/config-gtm/v1/domains/example.akadns.net/resources/cpu", 542 | "rel": "self" 543 | } 544 | ], 545 | "loadImbalancePercentage": null, 546 | "maxUMultiplicativeIncrement": null, 547 | "name": "cpu", 548 | "resourceInstances": [ 549 | { 550 | "datacenterId": 3134, 551 | "loadObject": "/cpu", 552 | "loadObjectPort": 80, 553 | "loadServers": [ 554 | "1.2.3.8" 555 | ], 556 | "useDefaultLoadObject": false 557 | }, 558 | { 559 | "datacenterId": 3133, 560 | "loadObject": "/cpu", 561 | "loadObjectPort": 80, 562 | "loadServers": [ 563 | "1.2.3.7" 564 | ], 565 | "useDefaultLoadObject": false 566 | }, 567 | { 568 | "datacenterId": 5400, 569 | "loadObject": null, 570 | "loadObjectPort": 0, 571 | "loadServers": [], 572 | "useDefaultLoadObject": false 573 | } 574 | ], 575 | "type": "XML load object via HTTP", 576 | "upperBound": 0 577 | }, 578 | { 579 | "aggregationType": "latest", 580 | "constrainedProperty": "**", 581 | "decayRate": null, 582 | "description": "Supply levels of Arbor Gold", 583 | "hostHeader": null, 584 | "leaderString": null, 585 | "leastSquaresDecay": null, 586 | "links": [ 587 | { 588 | "href": "/config-gtm/v1/domains/example.akadns.net/resources/arbor-gold", 589 | "rel": "self" 590 | } 591 | ], 592 | "loadImbalancePercentage": null, 593 | "maxUMultiplicativeIncrement": null, 594 | "name": "arbor-gold", 595 | "resourceInstances": [ 596 | { 597 | "datacenterId": 3134, 598 | "loadObject": "/cups", 599 | "loadObjectPort": 80, 600 | "loadServers": [ 601 | "1.2.3.8" 602 | ], 603 | "useDefaultLoadObject": false 604 | }, 605 | { 606 | "datacenterId": 3133, 607 | "loadObject": "/cups", 608 | "loadObjectPort": 80, 609 | "loadServers": [ 610 | "1.2.3.7" 611 | ], 612 | "useDefaultLoadObject": false 613 | }, 614 | { 615 | "datacenterId": 5400, 616 | "loadObject": null, 617 | "loadObjectPort": 0, 618 | "loadServers": [], 619 | "useDefaultLoadObject": false 620 | } 621 | ], 622 | "type": "Non-XML load object via HTTP", 623 | "upperBound": 0 624 | } 625 | ], 626 | "roundRobinPrefix": null, 627 | "servermonitorLivenessCount": null, 628 | "servermonitorLoadCount": null, 629 | "status": { 630 | "changeId": "5beb11ae-8908-4bfe-8459-e88efc4d2fdc", 631 | "links": [ 632 | { 633 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 634 | "rel": "self" 635 | } 636 | ], 637 | "message": "Change Pending", 638 | "passingValidation": true, 639 | "propagationStatus": "PENDING", 640 | "propagationStatusDate": "2014-04-08T18:25:51.000+0000" 641 | }, 642 | "type": "full" 643 | } 644 | ` 645 | domainStatusJSON = `{ 646 | "message": "ERROR: Some error", 647 | "changeId": "123", 648 | "propagationStatus": "DENIED", 649 | "propagationStatusDate": "2015-07-31T23:08:00.000+0000", 650 | "passingValidation": false, 651 | "links": [] 652 | }` 653 | 654 | updatedDomainJSON = `{ 655 | "resource": { 656 | "links": [ 657 | { 658 | "href": "/config-gtm/v1/domains/example.akadns.net", 659 | "rel": "self" 660 | }, 661 | { 662 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters", 663 | "rel": "datacenters" 664 | }, 665 | { 666 | "href": "/config-gtm/v1/domains/example.akadns.net/properties", 667 | "rel": "properties" 668 | }, 669 | { 670 | "href": "/config-gtm/v1/domains/example.akadns.net/geographic-maps", 671 | "rel": "geographic-maps" 672 | }, 673 | { 674 | "href": "/config-gtm/v1/domains/example.akadns.net/cidr-maps", 675 | "rel": "cidr-maps" 676 | }, 677 | { 678 | "href": "/config-gtm/v1/domains/example.akadns.net/resources", 679 | "rel": "resources" 680 | } 681 | ], 682 | "cidrMaps": [ 683 | { 684 | "assignments": [ 685 | { 686 | "blocks": [ 687 | "1.3.5.9", 688 | "1.2.3.0/24" 689 | ], 690 | "datacenterId": 3134, 691 | "nickname": "Frostfangs and the Fist of First Men" 692 | }, 693 | { 694 | "blocks": [ 695 | "1.2.4.0/24" 696 | ], 697 | "datacenterId": 3133, 698 | "nickname": "Winterfell" 699 | } 700 | ], 701 | "defaultDatacenter": { 702 | "datacenterId": 5400, 703 | "nickname": "All Other CIDR Blocks" 704 | }, 705 | "links": [ 706 | { 707 | "href": "/config-gtm/v1/domains/example.akadns.net/cidr-maps/The%20North", 708 | "rel": "self" 709 | } 710 | ], 711 | "name": "The North" 712 | } 713 | ], 714 | "datacenters": [ 715 | { 716 | "city": "Doune", 717 | "cloneOf": 0, 718 | "continent": "EU", 719 | "country": "GB", 720 | "datacenterId": 3133, 721 | "defaultLoadObject": { 722 | "loadObject": null, 723 | "loadObjectPort": 0, 724 | "loadServers": null 725 | }, 726 | "latitude": 56.185097, 727 | "links": [ 728 | { 729 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/3133", 730 | "rel": "self" 731 | } 732 | ], 733 | "longitude": -4.050264, 734 | "nickname": "Winterfell", 735 | "stateOrProvince": "Perthshire", 736 | "virtual": true 737 | }, 738 | { 739 | "city": "Snæfellsjökull", 740 | "cloneOf": 0, 741 | "continent": "EU", 742 | "country": "IS", 743 | "datacenterId": 3134, 744 | "defaultLoadObject": { 745 | "loadObject": null, 746 | "loadObjectPort": 0, 747 | "loadServers": null 748 | }, 749 | "latitude": 64.808, 750 | "links": [ 751 | { 752 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/3134", 753 | "rel": "self" 754 | } 755 | ], 756 | "longitude": -23.776, 757 | "nickname": "Frostfangs", 758 | "stateOrProvince": null, 759 | "virtual": true 760 | }, 761 | { 762 | "city": null, 763 | "cloneOf": 0, 764 | "continent": null, 765 | "country": null, 766 | "datacenterId": 5400, 767 | "defaultLoadObject": { 768 | "loadObject": null, 769 | "loadObjectPort": 0, 770 | "loadServers": null 771 | }, 772 | "latitude": 0.0, 773 | "links": [ 774 | { 775 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/5400", 776 | "rel": "self" 777 | } 778 | ], 779 | "longitude": 0.0, 780 | "nickname": "Default Datacenter", 781 | "stateOrProvince": null, 782 | "virtual": true 783 | } 784 | ], 785 | "defaultSslClientCertificate": null, 786 | "defaultSslClientPrivateKey": null, 787 | "defaultUnreachableThreshold": null, 788 | "emailNotificationList": [], 789 | "geographicMaps": [ 790 | { 791 | "assignments": [ 792 | { 793 | "countries": [ 794 | "GB" 795 | ], 796 | "datacenterId": 3133, 797 | "nickname": "UK users" 798 | } 799 | ], 800 | "defaultDatacenter": { 801 | "datacenterId": 5400, 802 | "nickname": "Default Mapping" 803 | }, 804 | "links": [ 805 | { 806 | "href": "/config-gtm/v1/domains/example.akadns.net/geographic-maps/UK%20Delivery", 807 | "rel": "self" 808 | } 809 | ], 810 | "name": "UK Delivery" 811 | } 812 | ], 813 | "lastModified": "2014-04-08T18:25:51.000+0000", 814 | "lastModifiedBy": "admin@example.com", 815 | "loadFeedback": true, 816 | "loadImbalancePercentage": 10.0, 817 | "minPingableRegionFraction": null, 818 | "modificationComments": "CIDRMap example", 819 | "name": "example.akadns.net", 820 | "pingInterval": null, 821 | "properties": [ 822 | { 823 | "backupCName": null, 824 | "backupIp": null, 825 | "balanceByDownloadScore": false, 826 | "cname": null, 827 | "comments": null, 828 | "dynamicTTL": 300, 829 | "failbackDelay": 0, 830 | "failoverDelay": 0, 831 | "handoutMode": "normal", 832 | "healthMax": null, 833 | "healthMultiplier": null, 834 | "healthThreshold": null, 835 | "ipv6": false, 836 | "lastModified": "2014-04-08T18:25:52.000+0000", 837 | "links": [ 838 | { 839 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/www", 840 | "rel": "self" 841 | } 842 | ], 843 | "livenessTests": [ 844 | { 845 | "disableNonstandardPortWarning": false, 846 | "hostHeader": "foo.example.com", 847 | "httpError3xx": true, 848 | "httpError4xx": true, 849 | "httpError5xx": true, 850 | "links": [], 851 | "name": "health-check", 852 | "requestString": null, 853 | "responseString": null, 854 | "sslClientCertificate": null, 855 | "sslClientPrivateKey": null, 856 | "testInterval": 60, 857 | "testObject": "/status", 858 | "testObjectPassword": null, 859 | "testObjectPort": 80, 860 | "testObjectProtocol": "HTTP", 861 | "testObjectUsername": null, 862 | "testTimeout": 25.0 863 | } 864 | ], 865 | "mapName": null, 866 | "maxUnreachablePenalty": null, 867 | "mxRecords": [], 868 | "name": "www", 869 | "scoreAggregationType": "mean", 870 | "staticTTL": 600, 871 | "stickinessBonusConstant": 0, 872 | "stickinessBonusPercentage": 0, 873 | "trafficTargets": [ 874 | { 875 | "datacenterId": 5400, 876 | "enabled": false, 877 | "handoutCName": null, 878 | "name": null, 879 | "servers": [], 880 | "weight": 0.0 881 | }, 882 | { 883 | "datacenterId": 3134, 884 | "enabled": true, 885 | "handoutCName": null, 886 | "name": null, 887 | "servers": [ 888 | "1.2.3.5" 889 | ], 890 | "weight": 0.0 891 | }, 892 | { 893 | "datacenterId": 3133, 894 | "enabled": true, 895 | "handoutCName": null, 896 | "name": null, 897 | "servers": [ 898 | "1.2.3.4" 899 | ], 900 | "weight": 1.0 901 | } 902 | ], 903 | "type": "failover", 904 | "unreachableThreshold": null, 905 | "useComputedTargets": false 906 | }, 907 | { 908 | "backupCName": null, 909 | "backupIp": null, 910 | "balanceByDownloadScore": false, 911 | "cname": null, 912 | "comments": null, 913 | "dynamicTTL": 300, 914 | "failbackDelay": 0, 915 | "failoverDelay": 0, 916 | "handoutMode": "normal", 917 | "healthMax": null, 918 | "healthMultiplier": null, 919 | "healthThreshold": null, 920 | "ipv6": true, 921 | "lastModified": "2014-04-08T18:25:52.000+0000", 922 | "links": [ 923 | { 924 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/mail", 925 | "rel": "self" 926 | } 927 | ], 928 | "livenessTests": [], 929 | "mapName": null, 930 | "maxUnreachablePenalty": null, 931 | "mxRecords": [], 932 | "name": "mail", 933 | "scoreAggregationType": "mean", 934 | "staticTTL": 600, 935 | "stickinessBonusConstant": 0, 936 | "stickinessBonusPercentage": 0, 937 | "trafficTargets": [ 938 | { 939 | "datacenterId": 5400, 940 | "enabled": false, 941 | "handoutCName": null, 942 | "name": null, 943 | "servers": [], 944 | "weight": 1.0 945 | }, 946 | { 947 | "datacenterId": 3134, 948 | "enabled": true, 949 | "handoutCName": null, 950 | "name": null, 951 | "servers": [ 952 | "2001:4878::5043:4078" 953 | ], 954 | "weight": 1.0 955 | }, 956 | { 957 | "datacenterId": 3133, 958 | "enabled": true, 959 | "handoutCName": null, 960 | "name": null, 961 | "servers": [ 962 | "2001:4878::5043:4072", 963 | "2001:4878::5043:4071" 964 | ], 965 | "weight": 1.0 966 | } 967 | ], 968 | "type": "weighted-round-robin", 969 | "unreachableThreshold": null, 970 | "useComputedTargets": false 971 | }, 972 | { 973 | "backupCName": null, 974 | "backupIp": null, 975 | "balanceByDownloadScore": false, 976 | "cname": null, 977 | "comments": null, 978 | "dynamicTTL": 300, 979 | "failbackDelay": 0, 980 | "failoverDelay": 0, 981 | "handoutMode": "normal", 982 | "healthMax": null, 983 | "healthMultiplier": null, 984 | "healthThreshold": null, 985 | "ipv6": false, 986 | "lastModified": "2014-04-08T18:25:52.000+0000", 987 | "links": [ 988 | { 989 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/supplies", 990 | "rel": "self" 991 | } 992 | ], 993 | "livenessTests": [], 994 | "loadImbalancePercentage": null, 995 | "mapName": null, 996 | "maxUnreachablePenalty": null, 997 | "mxRecords": [], 998 | "name": "supplies", 999 | "scoreAggregationType": "mean", 1000 | "staticTTL": 600, 1001 | "stickinessBonusConstant": 0, 1002 | "stickinessBonusPercentage": 0, 1003 | "trafficTargets": [ 1004 | { 1005 | "datacenterId": 5400, 1006 | "enabled": true, 1007 | "handoutCName": "supplies.example.com", 1008 | "name": null, 1009 | "servers": [], 1010 | "weight": 1.0 1011 | }, 1012 | { 1013 | "datacenterId": 3134, 1014 | "enabled": true, 1015 | "handoutCName": "winter.supplies.example.com", 1016 | "name": null, 1017 | "servers": [], 1018 | "weight": 0.0 1019 | }, 1020 | { 1021 | "datacenterId": 3133, 1022 | "enabled": true, 1023 | "handoutCName": "redcross.org", 1024 | "name": null, 1025 | "servers": [], 1026 | "weight": 0.0 1027 | } 1028 | ], 1029 | "type": "failover", 1030 | "unreachableThreshold": null, 1031 | "useComputedTargets": false 1032 | }, 1033 | { 1034 | "backupCName": null, 1035 | "backupIp": null, 1036 | "balanceByDownloadScore": false, 1037 | "cname": null, 1038 | "comments": null, 1039 | "dynamicTTL": 300, 1040 | "failbackDelay": 0, 1041 | "failoverDelay": 0, 1042 | "handoutMode": "normal", 1043 | "healthMax": null, 1044 | "healthMultiplier": null, 1045 | "healthThreshold": null, 1046 | "ipv6": false, 1047 | "lastModified": "2014-04-08T18:25:52.000+0000", 1048 | "links": [ 1049 | { 1050 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/shop", 1051 | "rel": "self" 1052 | } 1053 | ], 1054 | "livenessTests": [], 1055 | "loadImbalancePercentage": null, 1056 | "mapName": "UK Delivery", 1057 | "maxUnreachablePenalty": null, 1058 | "mxRecords": [], 1059 | "name": "shop", 1060 | "scoreAggregationType": "mean", 1061 | "staticTTL": 600, 1062 | "stickinessBonusConstant": 0, 1063 | "stickinessBonusPercentage": 0, 1064 | "trafficTargets": [ 1065 | { 1066 | "datacenterId": 5400, 1067 | "enabled": true, 1068 | "handoutCName": "shop.example.com", 1069 | "name": null, 1070 | "servers": [], 1071 | "weight": 1.0 1072 | }, 1073 | { 1074 | "datacenterId": 3134, 1075 | "enabled": false, 1076 | "handoutCName": null, 1077 | "name": null, 1078 | "servers": [], 1079 | "weight": 1.0 1080 | }, 1081 | { 1082 | "datacenterId": 3133, 1083 | "enabled": true, 1084 | "handoutCName": "uk.shop.example.com", 1085 | "name": null, 1086 | "servers": [], 1087 | "weight": 1.0 1088 | } 1089 | ], 1090 | "type": "geographic", 1091 | "unreachableThreshold": null, 1092 | "useComputedTargets": false 1093 | } 1094 | ], 1095 | "resources": [ 1096 | { 1097 | "aggregationType": "latest", 1098 | "constrainedProperty": "mail", 1099 | "decayRate": null, 1100 | "description": "CPU utilization", 1101 | "hostHeader": null, 1102 | "leaderString": null, 1103 | "leastSquaresDecay": null, 1104 | "links": [ 1105 | { 1106 | "href": "/config-gtm/v1/domains/example.akadns.net/resources/cpu", 1107 | "rel": "self" 1108 | } 1109 | ], 1110 | "loadImbalancePercentage": null, 1111 | "maxUMultiplicativeIncrement": null, 1112 | "name": "cpu", 1113 | "resourceInstances": [ 1114 | { 1115 | "datacenterId": 3134, 1116 | "loadObject": "/cpu", 1117 | "loadObjectPort": 80, 1118 | "loadServers": [ 1119 | "1.2.3.8" 1120 | ], 1121 | "useDefaultLoadObject": false 1122 | }, 1123 | { 1124 | "datacenterId": 3133, 1125 | "loadObject": "/cpu", 1126 | "loadObjectPort": 80, 1127 | "loadServers": [ 1128 | "1.2.3.7" 1129 | ], 1130 | "useDefaultLoadObject": false 1131 | }, 1132 | { 1133 | "datacenterId": 5400, 1134 | "loadObject": null, 1135 | "loadObjectPort": 0, 1136 | "loadServers": [], 1137 | "useDefaultLoadObject": false 1138 | } 1139 | ], 1140 | "type": "XML load object via HTTP", 1141 | "upperBound": 0 1142 | }, 1143 | { 1144 | "aggregationType": "latest", 1145 | "constrainedProperty": "**", 1146 | "decayRate": null, 1147 | "description": "Supply levels of Arbor Gold", 1148 | "hostHeader": null, 1149 | "leaderString": null, 1150 | "leastSquaresDecay": null, 1151 | "links": [ 1152 | { 1153 | "href": "/config-gtm/v1/domains/example.akadns.net/resources/arbor-gold", 1154 | "rel": "self" 1155 | } 1156 | ], 1157 | "loadImbalancePercentage": null, 1158 | "maxUMultiplicativeIncrement": null, 1159 | "name": "arbor-gold", 1160 | "resourceInstances": [ 1161 | { 1162 | "datacenterId": 3134, 1163 | "loadObject": "/cups", 1164 | "loadObjectPort": 80, 1165 | "loadServers": [ 1166 | "1.2.3.8" 1167 | ], 1168 | "useDefaultLoadObject": false 1169 | }, 1170 | { 1171 | "datacenterId": 3133, 1172 | "loadObject": "/cups", 1173 | "loadObjectPort": 80, 1174 | "loadServers": [ 1175 | "1.2.3.7" 1176 | ], 1177 | "useDefaultLoadObject": false 1178 | }, 1179 | { 1180 | "datacenterId": 5400, 1181 | "loadObject": null, 1182 | "loadObjectPort": 0, 1183 | "loadServers": [], 1184 | "useDefaultLoadObject": false 1185 | } 1186 | ], 1187 | "type": "Non-XML load object via HTTP", 1188 | "upperBound": 0 1189 | } 1190 | ], 1191 | "roundRobinPrefix": null, 1192 | "servermonitorLivenessCount": null, 1193 | "servermonitorLoadCount": null, 1194 | "status": { 1195 | "changeId": "5beb11ae-8908-4bfe-8459-e88efc4d2fdc", 1196 | "links": [ 1197 | { 1198 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 1199 | "rel": "self" 1200 | } 1201 | ], 1202 | "message": "Change Pending", 1203 | "passingValidation": true, 1204 | "propagationStatus": "PENDING", 1205 | "propagationStatusDate": "2014-04-08T18:25:51.000+0000" 1206 | }, 1207 | "type": "full" 1208 | }, 1209 | "status": { 1210 | "changeId": "5beb11ae-8908-4bfe-8459-e88efc4d2fdc", 1211 | "links": [ 1212 | { 1213 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 1214 | "rel": "self" 1215 | } 1216 | ], 1217 | "message": "Change Pending", 1218 | "passingValidation": true, 1219 | "propagationStatus": "PENDING", 1220 | "propagationStatusDate": "2014-04-08T18:25:51.000+0000" 1221 | } 1222 | }` 1223 | 1224 | dataCenterJSON = `{ 1225 | "nickname":"dcNickname", 1226 | "city":"city", 1227 | "stateOrProvince":"state", 1228 | "country":"country", 1229 | "latitude":0.0, 1230 | "longitude":0.0, 1231 | "cloneOf":null, 1232 | "virtual":false, 1233 | "defaultLoadObject":{ 1234 | "loadObject":null, 1235 | "loadObjectPort":0, 1236 | "loadServers":null 1237 | }, 1238 | "continent":null, 1239 | "cloudServerTargeting":false, 1240 | "links":[{ 1241 | "rel":"self", 1242 | "href":"https://some-url.com" 1243 | }], 1244 | "datacenterId":1 1245 | }` 1246 | 1247 | createdDataCenterJSON = `{ 1248 | "resource": { 1249 | "city": "Doune", 1250 | "cloneOf": 0, 1251 | "continent": "EU", 1252 | "country": "GB", 1253 | "datacenterId": 3133, 1254 | "defaultLoadObject": { 1255 | "loadObject": null, 1256 | "loadObjectPort": 0, 1257 | "loadServers": null 1258 | }, 1259 | "latitude": 56.185097, 1260 | "links": [ 1261 | { 1262 | "href": "/config-gtm/v1/domains/example.akadns.net/datacenters/3133", 1263 | "rel": "self" 1264 | } 1265 | ], 1266 | "longitude": -4.050264, 1267 | "nickname": "Winterfell", 1268 | "stateOrProvince": "Perthshire", 1269 | "virtual": true 1270 | }, 1271 | "status": { 1272 | "changeId": "f0c51967-d119-4665-9403-364a57ea5530", 1273 | "links": [ 1274 | { 1275 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 1276 | "rel": "self" 1277 | } 1278 | ], 1279 | "message": "Change Pending", 1280 | "passingValidation": true, 1281 | "propagationStatus": "PENDING", 1282 | "propagationStatusDate": "2014-04-15T11:30:27.000+0000" 1283 | } 1284 | }` 1285 | 1286 | dataCentersJSON = `{ 1287 | "items":[{ 1288 | "nickname":"dcOne", 1289 | "city":"Some City", 1290 | "stateOrProvince":"someState", 1291 | "country":"someCountry", 1292 | "latitude":0, 1293 | "longitude":0, 1294 | "cloneOf":null, 1295 | "virtual":false, 1296 | "defaultLoadObject":{ 1297 | "loadObject":null, 1298 | "loadObjectPort":0, 1299 | "loadServers":null 1300 | }, 1301 | "continent":null, 1302 | "cloudServerTargeting":false, 1303 | "links":[{ 1304 | "rel":"self", 1305 | "href":"https://url.com" 1306 | }], 1307 | "datacenterId":123 1308 | }, { 1309 | "nickname":"dc_two", 1310 | "city":"Another City", 1311 | "stateOrProvince":"anotherState", 1312 | "country":"anotherCountry", 1313 | "latitude":0, 1314 | "longitude":0, 1315 | "cloneOf":null, 1316 | "virtual":false, 1317 | "defaultLoadObject":{ 1318 | "loadObject":null, 1319 | "loadObjectPort":0, 1320 | "loadServers":null 1321 | }, 1322 | "continent":null, 1323 | "cloudServerTargeting":false, 1324 | "links":[{ 1325 | "rel":"self", 1326 | "href":"https://another-url.com" 1327 | }], 1328 | "datacenterId":1234 1329 | }] 1330 | }` 1331 | 1332 | createdPropertyJSON = `{ 1333 | "resource": { 1334 | "backupCName": null, 1335 | "backupIp": null, 1336 | "balanceByDownloadScore": false, 1337 | "cname": null, 1338 | "comments": null, 1339 | "dynamicTTL": 300, 1340 | "failbackDelay": 0, 1341 | "failoverDelay": 0, 1342 | "handoutMode": "normal", 1343 | "healthMax": null, 1344 | "healthMultiplier": null, 1345 | "healthThreshold": null, 1346 | "ipv6": false, 1347 | "lastModified": null, 1348 | "links": [ 1349 | { 1350 | "href": "/config-gtm/v1/domains/example.akadns.net/properties/origin", 1351 | "rel": "self" 1352 | } 1353 | ], 1354 | "livenessTests": [ 1355 | { 1356 | "disableNonstandardPortWarning": false, 1357 | "hostHeader": "foo.example.com", 1358 | "httpError3xx": true, 1359 | "httpError4xx": true, 1360 | "httpError5xx": true, 1361 | "name": "health-check", 1362 | "requestString": null, 1363 | "responseString": null, 1364 | "sslClientCertificate": null, 1365 | "sslClientPrivateKey": null, 1366 | "testInterval": 60, 1367 | "testObject": "/status", 1368 | "testObjectPassword": null, 1369 | "testObjectPort": 80, 1370 | "testObjectProtocol": "HTTP", 1371 | "testObjectUsername": null, 1372 | "testTimeout": 25 1373 | } 1374 | ], 1375 | "loadImbalancePercentage": null, 1376 | "mapName": null, 1377 | "maxUnreachablePenalty": null, 1378 | "mxRecords": [], 1379 | "name": "origin", 1380 | "scoreAggregationType": "mean", 1381 | "staticTTL": 600, 1382 | "stickinessBonusConstant": 0, 1383 | "stickinessBonusPercentage": 0, 1384 | "trafficTargets": [ 1385 | { 1386 | "datacenterId": 3134, 1387 | "enabled": true, 1388 | "handoutCName": null, 1389 | "name": null, 1390 | "servers": [ 1391 | "1.2.3.5" 1392 | ], 1393 | "weight": 50 1394 | }, 1395 | { 1396 | "datacenterId": 3133, 1397 | "enabled": true, 1398 | "handoutCName": null, 1399 | "name": null, 1400 | "servers": [ 1401 | "1.2.3.4" 1402 | ], 1403 | "weight": 50 1404 | } 1405 | ], 1406 | "type": "weighted-round-robin", 1407 | "unreachableThreshold": null, 1408 | "useComputedTargets": false 1409 | }, 1410 | "status": { 1411 | "changeId": "eee0c3b4-0e45-4f4b-822c-7dbc60764d18", 1412 | "links": [ 1413 | { 1414 | "href": "/config-gtm/v1/domains/example.akadns.net/status/current", 1415 | "rel": "self" 1416 | } 1417 | ], 1418 | "message": "Change Pending", 1419 | "passingValidation": true, 1420 | "propagationStatus": "PENDING", 1421 | "propagationStatusDate": "2014-04-15T11:30:27.000+0000" 1422 | } 1423 | }` 1424 | 1425 | propJSON = `{ 1426 | "backupCName":null, 1427 | "backupIp":null, 1428 | "balanceByDownloadScore":false, 1429 | "cname":null, 1430 | "comments":null, 1431 | "dynamicTTL":300, 1432 | "failoverDelay":0, 1433 | "failbackDelay":0, 1434 | "handoutMode":"normal", 1435 | "healthMax":null, 1436 | "healthMultiplier":null, 1437 | "healthThreshold":null, 1438 | "lastModified":null, 1439 | "livenessTests":[{ 1440 | "disableNonstandardPortWarning":false, 1441 | "hostHeader":null, 1442 | "httpError3xx":true, 1443 | "httpError4xx":true, 1444 | "httpError5xx":true, 1445 | "name":"livenessTestOne", 1446 | "requestString":null, 1447 | "responseString":null, 1448 | "testInterval":60, 1449 | "testObject":"/", 1450 | "testObjectPort":443, 1451 | "testObjectProtocol":"HTTPS", 1452 | "testObjectUsername":null, 1453 | "testObjectPassword":null, 1454 | "testTimeout":25.0, 1455 | "sslClientCertificate":null, 1456 | "sslClientPrivateKey":null 1457 | }], 1458 | "loadImbalancePercentage":null, 1459 | "mapName":null, 1460 | "maxUnreachablePenalty":null, 1461 | "mxRecords":[], 1462 | "scoreAggregationType":"mean", 1463 | "stickinessBonusConstant":null, 1464 | "stickinessBonusPercentage":null, 1465 | "staticTTL":null, 1466 | "trafficTargets":[{ 1467 | "datacenterId":3131, 1468 | "enabled":true, 1469 | "weight":50.0, 1470 | "handoutCName":null, 1471 | "name":null, 1472 | "servers":["1.2.3.5"] 1473 | },{ 1474 | "datacenterId":3132, 1475 | "enabled":true, 1476 | "weight":50.0, 1477 | "handoutCName":null, 1478 | "name":null, 1479 | "servers":["1.2.3.4"] 1480 | }], 1481 | "type":"weighted-round-robin", 1482 | "unreachableThreshold":null, 1483 | "useComputedTargets":false, 1484 | "ipv6":false, 1485 | "links":[{ 1486 | "rel":"self", 1487 | "href":"https://url.com" 1488 | }], 1489 | "name":"someName" 1490 | }` 1491 | 1492 | errorResponseJSON = `{ 1493 | "type": "https://problems.luna.akamaiapis.net/config-gtm/v1/serverError", 1494 | "title": "Server Error", 1495 | "status": 500, 1496 | "detail": "Could not read JSON: parse exception" 1497 | }` 1498 | 1499 | propertiesResponseJSON = `{ 1500 | "items":[ 1501 | { 1502 | "backupCName":null, 1503 | "backupIp":null, 1504 | "balanceByDownloadScore":false, 1505 | "cname":null, 1506 | "comments":null, 1507 | "dynamicTTL":300, 1508 | "failoverDelay":0, 1509 | "failbackDelay":0, 1510 | "handoutMode":"normal", 1511 | "healthMax":null, 1512 | "healthMultiplier":null, 1513 | "healthThreshold":null, 1514 | "lastModified":null, 1515 | "livenessTests":[ 1516 | 1517 | ], 1518 | "loadImbalancePercentage":null, 1519 | "mapName":null, 1520 | "maxUnreachablePenalty":null, 1521 | "mxRecords":[ 1522 | 1523 | ], 1524 | "scoreAggregationType":"mean", 1525 | "stickinessBonusConstant":0, 1526 | "stickinessBonusPercentage":0, 1527 | "staticTTL":600, 1528 | "trafficTargets":[ 1529 | { 1530 | "datacenterId":3131, 1531 | "enabled":true, 1532 | "weight":70.0, 1533 | "handoutCName":null, 1534 | "name":null, 1535 | "servers":[ 1536 | "1.2.3.5" 1537 | ] 1538 | }, 1539 | { 1540 | "datacenterId":3132, 1541 | "enabled":true, 1542 | "weight":30.0, 1543 | "handoutCName":null, 1544 | "name":null, 1545 | "servers":[ 1546 | "1.2.3.4" 1547 | ] 1548 | } 1549 | ], 1550 | "type":"weighted-round-robin", 1551 | "unreachableThreshold":null, 1552 | "useComputedTargets":false, 1553 | "ipv6":false, 1554 | "links":[ 1555 | { 1556 | "rel":"self", 1557 | "href":"https://akab-7zefyjeiytziublw-o4tcc67vhvt7ewwd.luna.akamaiapis.net/config-gtm/v1/domains/example.akadns.net/properties/newprop" 1558 | } 1559 | ], 1560 | "name":"newprop" 1561 | }, 1562 | { 1563 | "backupCName":null, 1564 | "backupIp":null, 1565 | "balanceByDownloadScore":false, 1566 | "cname":null, 1567 | "comments":null, 1568 | "dynamicTTL":300, 1569 | "failoverDelay":0, 1570 | "failbackDelay":0, 1571 | "handoutMode":"normal", 1572 | "healthMax":null, 1573 | "healthMultiplier":null, 1574 | "healthThreshold":null, 1575 | "lastModified":null, 1576 | "livenessTests":[ 1577 | 1578 | ], 1579 | "loadImbalancePercentage":null, 1580 | "mapName":null, 1581 | "maxUnreachablePenalty":null, 1582 | "mxRecords":[ 1583 | 1584 | ], 1585 | "scoreAggregationType":"mean", 1586 | "stickinessBonusConstant":0, 1587 | "stickinessBonusPercentage":0, 1588 | "staticTTL":600, 1589 | "trafficTargets":[ 1590 | { 1591 | "datacenterId":3131, 1592 | "enabled":true, 1593 | "weight":70.0, 1594 | "handoutCName":null, 1595 | "name":null, 1596 | "servers":[ 1597 | "1.2.3.5" 1598 | ] 1599 | }, 1600 | { 1601 | "datacenterId":3132, 1602 | "enabled":true, 1603 | "weight":30.0, 1604 | "handoutCName":null, 1605 | "name":null, 1606 | "servers":[ 1607 | "1.2.3.4" 1608 | ] 1609 | } 1610 | ], 1611 | "type":"weighted-round-robin", 1612 | "unreachableThreshold":null, 1613 | "useComputedTargets":false, 1614 | "ipv6":false, 1615 | "links":[ 1616 | { 1617 | "rel":"self", 1618 | "href":"https://akab-7zefyjeiytziublw-o4tcc67vhvt7ewwd.luna.akamaiapis.net/config-gtm/v1/domains/example.akadns.net/properties/someprop" 1619 | } 1620 | ], 1621 | "name":"someprop" 1622 | }, 1623 | { 1624 | "backupCName":null, 1625 | "backupIp":null, 1626 | "balanceByDownloadScore":false, 1627 | "cname":null, 1628 | "comments":null, 1629 | "dynamicTTL":300, 1630 | "failoverDelay":0, 1631 | "failbackDelay":0, 1632 | "handoutMode":"normal", 1633 | "healthMax":0.0, 1634 | "healthMultiplier":0.0, 1635 | "healthThreshold":0.0, 1636 | "lastModified":null, 1637 | "livenessTests":[ 1638 | 1639 | ], 1640 | "loadImbalancePercentage":null, 1641 | "mapName":null, 1642 | "maxUnreachablePenalty":null, 1643 | "mxRecords":[ 1644 | 1645 | ], 1646 | "scoreAggregationType":"mean", 1647 | "stickinessBonusConstant":0, 1648 | "stickinessBonusPercentage":0, 1649 | "staticTTL":600, 1650 | "trafficTargets":[ 1651 | { 1652 | "datacenterId":3131, 1653 | "enabled":true, 1654 | "weight":50.0, 1655 | "handoutCName":null, 1656 | "name":null, 1657 | "servers":[ 1658 | "1.2.3.5" 1659 | ] 1660 | }, 1661 | { 1662 | "datacenterId":3132, 1663 | "enabled":true, 1664 | "weight":50.0, 1665 | "handoutCName":null, 1666 | "name":null, 1667 | "servers":[ 1668 | "1.2.3.4" 1669 | ] 1670 | } 1671 | ], 1672 | "type":"weighted-round-robin", 1673 | "unreachableThreshold":null, 1674 | "useComputedTargets":false, 1675 | "ipv6":false, 1676 | "links":[ 1677 | { 1678 | "rel":"self", 1679 | "href":"https://akab-7zefyjeiytziublw-o4tcc67vhvt7ewwd.luna.akamaiapis.net/config-gtm/v1/domains/example.akadns.net/properties/prop" 1680 | } 1681 | ], 1682 | "name":"prop" 1683 | }, 1684 | { 1685 | "backupCName":null, 1686 | "backupIp":null, 1687 | "balanceByDownloadScore":false, 1688 | "cname":null, 1689 | "comments":null, 1690 | "dynamicTTL":300, 1691 | "failoverDelay":0, 1692 | "failbackDelay":0, 1693 | "handoutMode":"normal", 1694 | "healthMax":null, 1695 | "healthMultiplier":null, 1696 | "healthThreshold":null, 1697 | "lastModified":null, 1698 | "livenessTests":[ 1699 | 1700 | ], 1701 | "loadImbalancePercentage":null, 1702 | "mapName":null, 1703 | "maxUnreachablePenalty":null, 1704 | "mxRecords":[ 1705 | 1706 | ], 1707 | "scoreAggregationType":"mean", 1708 | "stickinessBonusConstant":0, 1709 | "stickinessBonusPercentage":0, 1710 | "staticTTL":600, 1711 | "trafficTargets":[ 1712 | { 1713 | "datacenterId":3131, 1714 | "enabled":true, 1715 | "weight":70.0, 1716 | "handoutCName":null, 1717 | "name":null, 1718 | "servers":[ 1719 | "1.2.3.5" 1720 | ] 1721 | }, 1722 | { 1723 | "datacenterId":3132, 1724 | "enabled":true, 1725 | "weight":30.0, 1726 | "handoutCName":null, 1727 | "name":null, 1728 | "servers":[ 1729 | "1.2.3.4" 1730 | ] 1731 | } 1732 | ], 1733 | "type":"weighted-round-robin", 1734 | "unreachableThreshold":null, 1735 | "useComputedTargets":false, 1736 | "ipv6":false, 1737 | "links":[ 1738 | { 1739 | "rel":"self", 1740 | "href":"https://akab-7zefyjeiytziublw-o4tcc67vhvt7ewwd.luna.akamaiapis.net/config-gtm/v1/domains/example.akadns.net/properties/anotherprop" 1741 | } 1742 | ], 1743 | "name":"anotherprop" 1744 | }, 1745 | { 1746 | "backupCName":null, 1747 | "backupIp":null, 1748 | "balanceByDownloadScore":false, 1749 | "cname":null, 1750 | "comments":null, 1751 | "dynamicTTL":300, 1752 | "failoverDelay":0, 1753 | "failbackDelay":0, 1754 | "handoutMode":"normal", 1755 | "healthMax":0.0, 1756 | "healthMultiplier":0.0, 1757 | "healthThreshold":0.0, 1758 | "lastModified":null, 1759 | "livenessTests":[ 1760 | 1761 | ], 1762 | "loadImbalancePercentage":null, 1763 | "mapName":null, 1764 | "maxUnreachablePenalty":null, 1765 | "mxRecords":[ 1766 | 1767 | ], 1768 | "scoreAggregationType":"mean", 1769 | "stickinessBonusConstant":0, 1770 | "stickinessBonusPercentage":0, 1771 | "staticTTL":600, 1772 | "trafficTargets":[ 1773 | { 1774 | "datacenterId":3131, 1775 | "enabled":true, 1776 | "weight":50.0, 1777 | "handoutCName":null, 1778 | "name":null, 1779 | "servers":[ 1780 | "1.2.3.5" 1781 | ] 1782 | }, 1783 | { 1784 | "datacenterId":3132, 1785 | "enabled":true, 1786 | "weight":50.0, 1787 | "handoutCName":null, 1788 | "name":null, 1789 | "servers":[ 1790 | "1.2.3.4" 1791 | ] 1792 | } 1793 | ], 1794 | "type":"weighted-round-robin", 1795 | "unreachableThreshold":null, 1796 | "useComputedTargets":false, 1797 | "ipv6":false, 1798 | "links":[ 1799 | { 1800 | "rel":"self", 1801 | "href":"https://akab-7zefyjeiytziublw-o4tcc67vhvt7ewwd.luna.akamaiapis.net/config-gtm/v1/domains/example.akadns.net/properties/prop" 1802 | } 1803 | ], 1804 | "name":"aprop" 1805 | } 1806 | ] 1807 | }` 1808 | ) 1809 | --------------------------------------------------------------------------------