1package pagerduty 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "runtime" 11 "time" 12) 13 14const ( 15 apiEndpoint = "https://api.pagerduty.com" 16) 17 18// APIObject represents generic api json response that is shared by most 19// domain object (like escalation 20type APIObject struct { 21 ID string `json:"id,omitempty"` 22 Type string `json:"type,omitempty"` 23 Summary string `json:"summary,omitempty"` 24 Self string `json:"self,omitempty"` 25 HTMLURL string `json:"html_url,omitempty"` 26} 27 28// APIListObject are the fields used to control pagination when listing objects. 29type APIListObject struct { 30 Limit uint `url:"limit,omitempty"` 31 Offset uint `url:"offset,omitempty"` 32 More bool `url:"more,omitempty"` 33 Total uint `url:"total,omitempty"` 34} 35 36// APIReference are the fields required to reference another API object. 37type APIReference struct { 38 ID string `json:"id,omitempty"` 39 Type string `json:"type,omitempty"` 40} 41 42type APIDetails struct { 43 Type string `json:"type,omitempty"` 44 Details string `json:"details,omitempty"` 45} 46 47type errorObject struct { 48 Code int `json:"code,omitempty"` 49 Message string `json:"message,omitempty"` 50 Errors interface{} `json:"errors,omitempty"` 51} 52 53func newDefaultHTTPClient() *http.Client { 54 return &http.Client{ 55 Transport: &http.Transport{ 56 Proxy: http.ProxyFromEnvironment, 57 DialContext: (&net.Dialer{ 58 Timeout: 30 * time.Second, 59 KeepAlive: 30 * time.Second, 60 }).DialContext, 61 MaxIdleConns: 10, 62 IdleConnTimeout: 60 * time.Second, 63 TLSHandshakeTimeout: 10 * time.Second, 64 ExpectContinueTimeout: 1 * time.Second, 65 MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, 66 }, 67 } 68} 69 70// HTTPClient is an interface which declares the functionality we need from an 71// HTTP client. This is to allow consumers to provide their own HTTP client as 72// needed, without restricting them to only using *http.Client. 73type HTTPClient interface { 74 Do(*http.Request) (*http.Response, error) 75} 76 77// defaultHTTPClient is our own default HTTP client. We use this, instead of 78// http.DefaultClient, to avoid other packages tweaks to http.DefaultClient 79// causing issues with our HTTP calls. This also allows us to tweak the 80// transport values to be more resilient without making changes to the 81// http.DefaultClient. 82// 83// Keep this unexported so consumers of the package can't make changes to it. 84var defaultHTTPClient HTTPClient = newDefaultHTTPClient() 85 86// Client wraps http client 87type Client struct { 88 authToken string 89 90 // HTTPClient is the HTTP client used for making requests against the 91 // PagerDuty API. You can use either *http.Client here, or your own 92 // implementation. 93 HTTPClient HTTPClient 94} 95 96// NewClient creates an API client 97func NewClient(authToken string) *Client { 98 return &Client{ 99 authToken: authToken, 100 HTTPClient: defaultHTTPClient, 101 } 102} 103 104func (c *Client) delete(path string) (*http.Response, error) { 105 return c.do("DELETE", path, nil, nil) 106} 107 108func (c *Client) put(path string, payload interface{}, headers *map[string]string) (*http.Response, error) { 109 110 if payload != nil { 111 data, err := json.Marshal(payload) 112 if err != nil { 113 return nil, err 114 } 115 return c.do("PUT", path, bytes.NewBuffer(data), headers) 116 } 117 return c.do("PUT", path, nil, headers) 118} 119 120func (c *Client) post(path string, payload interface{}, headers *map[string]string) (*http.Response, error) { 121 data, err := json.Marshal(payload) 122 if err != nil { 123 return nil, err 124 } 125 return c.do("POST", path, bytes.NewBuffer(data), headers) 126} 127 128func (c *Client) get(path string) (*http.Response, error) { 129 return c.do("GET", path, nil, nil) 130} 131 132func (c *Client) do(method, path string, body io.Reader, headers *map[string]string) (*http.Response, error) { 133 endpoint := apiEndpoint + path 134 req, _ := http.NewRequest(method, endpoint, body) 135 req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2") 136 if headers != nil { 137 for k, v := range *headers { 138 req.Header.Set(k, v) 139 } 140 } 141 req.Header.Set("Content-Type", "application/json") 142 req.Header.Set("Authorization", "Token token="+c.authToken) 143 144 resp, err := c.HTTPClient.Do(req) 145 return c.checkResponse(resp, err) 146} 147 148func (c *Client) decodeJSON(resp *http.Response, payload interface{}) error { 149 defer resp.Body.Close() 150 decoder := json.NewDecoder(resp.Body) 151 return decoder.Decode(payload) 152} 153 154func (c *Client) checkResponse(resp *http.Response, err error) (*http.Response, error) { 155 if err != nil { 156 return resp, fmt.Errorf("Error calling the API endpoint: %v", err) 157 } 158 if 199 >= resp.StatusCode || 300 <= resp.StatusCode { 159 var eo *errorObject 160 var getErr error 161 if eo, getErr = c.getErrorFromResponse(resp); getErr != nil { 162 return resp, fmt.Errorf("Response did not contain formatted error: %s. HTTP response code: %v. Raw response: %+v", getErr, resp.StatusCode, resp) 163 } 164 return resp, fmt.Errorf("Failed call API endpoint. HTTP response code: %v. Error: %v", resp.StatusCode, eo) 165 } 166 return resp, nil 167} 168 169func (c *Client) getErrorFromResponse(resp *http.Response) (*errorObject, error) { 170 var result map[string]errorObject 171 if err := c.decodeJSON(resp, &result); err != nil { 172 return nil, fmt.Errorf("Could not decode JSON response: %v", err) 173 } 174 s, ok := result["error"] 175 if !ok { 176 return nil, fmt.Errorf("JSON response does not have error field") 177 } 178 return &s, nil 179} 180