Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to work correctly with V2 API #23

Merged
merged 10 commits into from Sep 4, 2016
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