Skip to content

Commit

Permalink
Refactor to work correctly with V2 API (#23)
Browse files Browse the repository at this point in the history
* Revamped the PagerDuty API.

* Added documentation to all exported types.
* Hid some internal functions to client.
* Updated Team methods to return Team interface on get/set operations.
* Fixed JSON conversion for Team object.
* Removed unused fields from Integration object.
* Updated field names to match official documentation.
* Fixed JSON conversion for OnCall object.
* implemented UpdateEscalationPolicy.
* Vendored third-party libraries.

* Updated service.CreateService to return a Service interface.

* Fixed typo in error message.

* Fixed typo in error message.

* Updated functionality for client, escalation_policy, schedule, and service.

* Added ability to parse and return the error object from PagerDuty API.
* Added missing RepeatEnabled to escalation_policy.
* Added return of Schedule for create/update.
* Added return of Service for create/update.

* Enhanced error response when creating,updating,getting resources.

* Fixed problem with error response handling.

* Updated MaintenanceWindow and User to return objects on create/update.

* Removed vendored libraries per Luzifer's suggestion.

* Fixed typo in README.md.
  • Loading branch information
dthagard authored and ranjib committed Sep 4, 2016
1 parent 11b4be1 commit a1989ee
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 303 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# go-pgerduty
# go-pagerduty

go-pagerduty is a CLI and [go](https://golang.org/) client library for [PagerDuty v2 API](https://v2.developer.pagerduty.com/v2/page/api-reference).
[godoc](http://godoc.org/github.com/PagerDuty/go-pagerduty)
Expand Down
49 changes: 32 additions & 17 deletions addon.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,68 +9,90 @@ import (
"github.com/google/go-querystring/query"
)

// Addon is a third-party add-on to PagerDuty's UI.
type Addon struct {
APIObject
Name string `json:"name,omitempty"`
Src string `json:"src,omitempty"`
Services []APIObject `json:"services,omitempty"`
}

// ListAddonOptions are the options available when calling the ListAddons API endpoint.
type ListAddonOptions struct {
APIListObject
Includes []string `url:"include,omitempty,brackets"`
ServiceIDs []string `url:"service_ids,omitempty,brackets"`
Filter string `url:"filter,omitempty"`
}

// ListAddonResponse is the response when calling the ListAddons API endpoint.
type ListAddonResponse struct {
APIListObject
Addons []Addon `json:"addons"`
}

// ListAddons lists all of the add-ons installed on your account.
func (c *Client) ListAddons(o ListAddonOptions) (*ListAddonResponse, error) {
v, err := query.Values(o)
if err != nil {
return nil, err
}
resp, err := c.Get("/addons?" + v.Encode())
resp, err := c.get("/addons?" + v.Encode())
if err != nil {
return nil, err
}
var result ListAddonResponse
return &result, c.decodeJson(resp, &result)
return &result, c.decodeJSON(resp, &result)
}

func (c *Client) InstallAddon(a Addon) error {
// InstallAddon installs an add-on for your account.
func (c *Client) InstallAddon(a Addon) (*Addon, error) {
data := make(map[string]Addon)
data["addon"] = a
resp, err := c.Post("/addons", data)
resp, err := c.post("/addons", data)
defer resp.Body.Close()
if err != nil {
return err
return nil, err
}
if resp.StatusCode != http.StatusCreated {
ct, rErr := ioutil.ReadAll(resp.Body)
if rErr == nil {
log.Debug(string(ct))
}
return fmt.Errorf("Failed to create. HTTP Status code: %d", resp.StatusCode)
return nil, fmt.Errorf("Failed to create. HTTP Status code: %d", resp.StatusCode)
}
return nil
return getAddonFromResponse(c, resp)
}

// DeleteAddon deletes an add-on from your account.
func (c *Client) DeleteAddon(id string) error {
_, err := c.Delete("/addons/" + id)
_, err := c.delete("/addons/" + id)
return err
}

// GetAddon gets details about an existing add-on.
func (c *Client) GetAddon(id string) (*Addon, error) {
resp, err := c.Get("/addons/" + id)
resp, err := c.get("/addons/" + id)
if err != nil {
return nil, err
}
return getAddonFromResponse(c, resp)
}

// UpdateAddon updates an existing add-on.
func (c *Client) UpdateAddon(id string, a Addon) (*Addon, error) {
v := make(map[string]Addon)
v["addon"] = a
resp, err := c.put("/addons/"+id, v)
if err != nil {
return nil, err
}
return getAddonFromResponse(c, resp)
}

func getAddonFromResponse(c *Client, resp *http.Response) (*Addon, error) {
var result map[string]Addon
if err := c.decodeJson(resp, &result); err != nil {
if err := c.decodeJSON(resp, &result); err != nil {
return nil, err
}
a, ok := result["addon"]
Expand All @@ -79,10 +101,3 @@ func (c *Client) GetAddon(id string) (*Addon, error) {
}
return &a, nil
}

func (c *Client) UpdateAddon(id string, a Addon) error {
v := make(map[string]Addon)
v["addon"] = a
_, err := c.Put("/addons/"+id, v)
return err
}
72 changes: 53 additions & 19 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

const (
APIEndpoint = "https://api.pagerduty.com"
apiEndpoint = "https://api.pagerduty.com"
)

// APIObject represents generic api json response that is shared by most
Expand All @@ -21,16 +21,29 @@ type APIObject struct {
Type string `json:"type,omitempty"`
Summary string `json:"summary,omitempty"`
Self string `json:"self,omitempty"`
HtmlUrl string `json:"html_url,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
}

// APIListObject are the fields used to control pagination when listing objects.
type APIListObject struct {
Limit uint `url:"limit,omitempty"`
Offset uint `url:"offset,omitempty"`
More bool `url:"more,omitempty"`
Total uint `url:"total,omitempty"`
}

// APIReference are the fields required to reference another API object.
type APIReference struct {
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"`
}

type errorObject struct {
Code int `json:"code,omitempty"`
Mesage string `json:"message,omitempty"`
Errors []string `json:"errors,omitempty"`
}

// Client wraps http client
type Client struct {
authToken string
Expand All @@ -43,50 +56,71 @@ func NewClient(authToken string) *Client {
}
}

func (c *Client) Delete(path string) (*http.Response, error) {
return c.Do("DELETE", path, nil)
func (c *Client) delete(path string) (*http.Response, error) {
return c.do("DELETE", path, nil)
}

func (c *Client) Put(path string, payload interface{}) (*http.Response, error) {
func (c *Client) put(path string, payload interface{}) (*http.Response, error) {
data, err := json.Marshal(payload)
if err != nil {
return nil, err
}
return c.Do("PUT", path, bytes.NewBuffer(data))
return c.do("PUT", path, bytes.NewBuffer(data))
}

func (c *Client) Post(path string, payload interface{}) (*http.Response, error) {
func (c *Client) post(path string, payload interface{}) (*http.Response, error) {
data, err := json.Marshal(payload)
if err != nil {
return nil, err
}
log.Debugln(string(data))
return c.Do("POST", path, bytes.NewBuffer(data))
return c.do("POST", path, bytes.NewBuffer(data))
}

func (c *Client) Get(path string) (*http.Response, error) {
return c.Do("GET", path, nil)
func (c *Client) get(path string) (*http.Response, error) {
return c.do("GET", path, nil)
}

func (c *Client) Do(method, path string, body io.Reader) (*http.Response, error) {
endpoint := APIEndpoint + path
func (c *Client) do(method, path string, body io.Reader) (*http.Response, error) {
endpoint := apiEndpoint + path
log.Debugln("Endpoint:", endpoint)
req, _ := http.NewRequest(method, endpoint, body)
req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Token token="+c.authToken)
resp, err := http.DefaultClient.Do(req)
return c.checkResponse(resp, err)
}

func (c *Client) decodeJSON(resp *http.Response, payload interface{}) error {
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(payload)
}

func (c *Client) checkResponse(resp *http.Response, err error) (*http.Response, error) {
if err != nil {
return nil, err
return resp, fmt.Errorf("Error calling the API endpoint: %v", err)
}
if resp.StatusCode != http.StatusOK {
return resp, fmt.Errorf("HTTP Status Code: %d", resp.StatusCode)
if 199 >= resp.StatusCode || 300 <= resp.StatusCode {
var eo *errorObject
var getErr error
if eo, getErr = c.getErrorFromResponse(resp); getErr != nil {
return resp, fmt.Errorf("Response did not contain formatted error: %s. HTTP response code: %v. Raw response: %+v", getErr, resp.StatusCode, resp)
}
return resp, fmt.Errorf("Failed call API endpoint. HTTP response code: %v. Error: %v", resp.StatusCode, eo)
}
return resp, nil
}

func (c *Client) decodeJson(resp *http.Response, payload interface{}) error {
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(payload)
func (c *Client) getErrorFromResponse(resp *http.Response) (*errorObject, error) {
var result map[string]errorObject
if err := c.decodeJSON(resp, &result); err != nil {
return nil, fmt.Errorf("Could not decode JSON response: %v", err)
}
s, ok := result["error"]
if !ok {
return nil, fmt.Errorf("JSON response does not have error field")
}
return &s, nil
}
86 changes: 54 additions & 32 deletions escalation_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,41 @@ package pagerduty

import (
"fmt"
"net/http"

"github.com/google/go-querystring/query"
)

const (
escPath = "/escalation_policies"
)

// EscalationRule is a rule for an escalation policy to trigger.
type EscalationRule struct {
Id string `json:"id"`
Delay uint `json:"escalation_delay_in_minutes"`
Targets []APIObject
ID string `json:"id,omitempty"`
Delay uint `json:"escalation_delay_in_minutes,omitempty"`
Targets []APIObject `json:"targets"`
}

// EscalationPolicy is a collection of escalation rules.
type EscalationPolicy struct {
APIObject
Name string `json:"name,omitempty"`
EscalationRules []APIObject `json:"escalation_rules,omitempty"`
Services []APIObject `json:"services,omitempty"`
NumLoops uint `json:"num_loops,omitempty"`
Teams []APIObject `json:"teams,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
EscalationRules []EscalationRule `json:"escalation_rules,omitempty"`
Services []APIReference `json:"services,omitempty"`
NumLoops uint `json:"num_loops,omitempty"`
Teams []APIReference `json:"teams,omitempty"`
Description string `json:"description,omitempty"`
RepeatEnabled bool `json:"repeat_enabled,omitempty"`
}

type ListEscalationPolicyResponse struct {
// ListEscalationPoliciesResponse is the data structure returned from calling the ListEscalationPolicies API endpoint.
type ListEscalationPoliciesResponse struct {
APIListObject
EscalationPolicies []EscalationPolicy `json:"escalation_policies"`
}

// ListEscalationPoliciesOptions is the data structure used when calling the ListEscalationPolicies API endpoint.
type ListEscalationPoliciesOptions struct {
APIListObject
Query string `url:"query,omitempty"`
Expand All @@ -36,56 +46,68 @@ type ListEscalationPoliciesOptions struct {
SortBy string `url:"sort_by,omitempty"`
}

func (c *Client) ListEscalationPolicies(o ListEscalationPoliciesOptions) (*ListEscalationPolicyResponse, error) {
// ListEscalationPolicies lists all of the existing escalation policies.
func (c *Client) ListEscalationPolicies(o ListEscalationPoliciesOptions) (*ListEscalationPoliciesResponse, error) {
v, err := query.Values(o)
if err != nil {
return nil, err
}
resp, err := c.Get("/escalation_policies?" + v.Encode())
resp, err := c.get(escPath + "?" + v.Encode())
if err != nil {
return nil, err
}
var result ListEscalationPolicyResponse
return &result, c.decodeJson(resp, &result)
var result ListEscalationPoliciesResponse
return &result, c.decodeJSON(resp, &result)
}

func (c *Client) CreateEscalationPolicy(ep EscalationPolicy) error {
// CreateEscalationPolicy creates a new escalation policy.
func (c *Client) CreateEscalationPolicy(e EscalationPolicy) (*EscalationPolicy, error) {
data := make(map[string]EscalationPolicy)
data["escalation_policy"] = ep
_, err := c.Post("/escalation_policies", data)
return err
data["escalation_policy"] = e
resp, err := c.post(escPath, data)
return getEscalationPolicyFromResponse(c, resp, err)
}

// DeleteEscalationPolicy deletes an existing escalation policy and rules.
func (c *Client) DeleteEscalationPolicy(id string) error {
_, err := c.Delete("/escalation_policies/" + id)
_, err := c.delete(escPath + "/" + id)
return err
}

// GetEscalationPolicyOptions is the data structure used when calling the GetEscalationPolicy API endpoint.
type GetEscalationPolicyOptions struct {
Includes []string `url:"include,omitempty,brackets"`
}

// GetEscalationPolicy gets information about an existing escalation policy and its rules.
func (c *Client) GetEscalationPolicy(id string, o *GetEscalationPolicyOptions) (*EscalationPolicy, error) {
v, err := query.Values(o)
if err != nil {
return nil, err
}
resp, err := c.Get("/escalation_policies/" + id + "?" + v.Encode())
resp, err := c.get(escPath + "/" + id + "?" + v.Encode())
return getEscalationPolicyFromResponse(c, resp, err)
}

// UpdateEscalationPolicy updates an existing escalation policy and its rules.
func (c *Client) UpdateEscalationPolicy(id string, e *EscalationPolicy) (*EscalationPolicy, error) {
resp, err := c.put(escPath+"/"+id, e)
return getEscalationPolicyFromResponse(c, resp, err)
}

func getEscalationPolicyFromResponse(c *Client, resp *http.Response, err error) (*EscalationPolicy, error) {
defer resp.Body.Close()
if err != nil {
return nil, err
}
var result map[string]EscalationPolicy
if err := c.decodeJson(resp, &result); err != nil {
return nil, err
var target map[string]EscalationPolicy
if dErr := c.decodeJSON(resp, &target); dErr != nil {
return nil, fmt.Errorf("Could not decode JSON response: %v", dErr)
}
ep, ok := result["escalation_policy"]
if !ok {
return nil, fmt.Errorf("JSON responsde does not have escalation_policy field")
rootNode := "escalation_policy"
t, nodeOK := target[rootNode]
if !nodeOK {
return nil, fmt.Errorf("JSON response does not have %s field", rootNode)
}
return &ep, nil
}

func (c *Client) UpdateEscalationPolicy(e *EscalationPolicy) error {
//TODO
return nil
return &t, nil
}
Loading

0 comments on commit a1989ee

Please sign in to comment.