diff --git a/ruleset.go b/ruleset.go new file mode 100644 index 00000000..0fb9dd75 --- /dev/null +++ b/ruleset.go @@ -0,0 +1,287 @@ +package pagerduty + +import ( + "fmt" + "net/http" +) + +// Ruleset represents a ruleset. +type Ruleset struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + RoutingKeys []string `json:"routing_keys,omitempty"` + Team *RulesetObject `json:"team,omitempty"` + Updater *RulesetObject `json:"updater,omitempty"` + Creator *RulesetObject `json:"creator,omitempty"` +} + +// RulesetObject represents a generic object that is common within a ruleset object +type RulesetObject struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` +} + +// RulesetPayload represents payload with a ruleset object +type RulesetPayload struct { + Ruleset *Ruleset `json:"ruleset,omitempty"` +} + +// ListRulesetsResponse represents a list response of rulesets. +type ListRulesetsResponse struct { + Total uint `json:"total,omitempty"` + Rulesets []*Ruleset `json:"rulesets,omitempty"` + Offset uint `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit uint `json:"limit,omitempty"` +} + +// RulesetRule represents a Ruleset rule +type RulesetRule struct { + ID string `json:"id,omitempty"` + Position int `json:"position,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Conditions *RuleConditions `json:"conditions,omitempty"` + Actions *RuleActions `json:"actions,omitempty"` + Ruleset *APIObject `json:"ruleset,omitempty"` + Self string `json:"self,omitempty"` + CatchAll bool `json:"catch_all,omitempty"` + TimeFrame *RuleTimeFrame `json:"time_frame,omitempty"` +} + +// RulesetRulePayload represents a payload for ruleset rules +type RulesetRulePayload struct { + Rule *RulesetRule `json:"rule,omitempty"` +} + +// RuleConditions represents the conditions field for a Ruleset +type RuleConditions struct { + Operator string `json:"operator,omitempty"` + RuleSubconditions []*RuleSubcondition `json:"subconditions,omitempty"` +} + +// RuleSubcondition represents a subcondition of a ruleset condition +type RuleSubcondition struct { + Operator string `json:"operator,omitempty"` + Parameters *ConditionParameter `json:"parameters,omitempty"` +} + +// ConditionParameter represents parameters in a rule condition +type ConditionParameter struct { + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` +} + +// RuleTimeFrame represents a time_frame object on the rule object +type RuleTimeFrame struct { + ScheduledWeekly *ScheduledWeekly `json:"scheduled_weekly,omitempty"` + ActiveBetween *ActiveBetween `json:"active_between,omitempty"` +} + +// ScheduledWeekly represents a time_frame object for scheduling rules weekly +type ScheduledWeekly struct { + Weekdays []int `json:"weekdays,omitempty"` + Timezone string `json:"timezone,omitempty"` + StartTime int `json:"start_time,omitempty"` + Duration int `json:"duration,omitempty"` +} + +// ActiveBetween represents an active_between object for setting a timeline for rules +type ActiveBetween struct { + StartTime int `json:"start_time,omitempty"` + EndTime int `json:"end_time,omitempty"` +} + +// ListRulesetRulesResponse represents a list of rules in a ruleset +type ListRulesetRulesResponse struct { + Total uint `json:"total,omitempty"` + Rules []*RulesetRule `json:"rules,omitempty"` + Offset uint `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit uint `json:"limit,omitempty"` +} + +// RuleActions represents a rule action +type RuleActions struct { + Suppress *RuleActionSuppress `json:"suppress,omitempty"` + Annotate *RuleActionParameter `json:"annotate,omitempty"` + Severity *RuleActionParameter `json:"severity,omitempty"` + Priority *RuleActionParameter `json:"priority,omitempty"` + Route *RuleActionParameter `json:"route,omitempty"` + EventAction *RuleActionParameter `json:"event_action,omitempty"` + Extractions []*RuleActionExtraction `json:"extractions,omitempty"` +} + +// RuleActionParameter represents a generic parameter object on a rule action +type RuleActionParameter struct { + Value string `json:"value,omitempty"` +} + +// RuleActionSuppress represents a rule suppress action object +type RuleActionSuppress struct { + Value bool `json:"value,omitempty"` + ThresholdValue int `json:"threshold_value,omitempty"` + ThresholdTimeUnit string `json:"threshold_time_unit,omitempty"` + ThresholdTimeAmount int `json:"threshold_time_amount,omitempty"` +} + +// RuleActionExtraction represents a rule extraction action object +type RuleActionExtraction struct { + Target string `json:"target,omitempty"` + Source string `json:"source,omitempty"` + Regex string `json:"regex,omitempty"` +} + +// ListRulesets gets all rulesets. +func (c *Client) ListRulesets() (*ListRulesetsResponse, error) { + rulesetResponse := new(ListRulesetsResponse) + rulesets := make([]*Ruleset, 0) + + // Create a handler closure capable of parsing data from the rulesets endpoint + // and appending resultant rulesets to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListRulesetsResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + rulesets = append(rulesets, result.Rulesets...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet("/rulesets/", responseHandler); err != nil { + return nil, err + } + rulesetResponse.Rulesets = rulesets + + return rulesetResponse, nil +} + +// CreateRuleset creates a new user. +func (c *Client) CreateRuleset(r *Ruleset) (*Ruleset, *http.Response, error) { + data := make(map[string]*Ruleset) + data["ruleset"] = r + resp, err := c.post("/rulesets", data, nil) + return getRulesetFromResponse(c, resp, err) +} + +// DeleteRuleset deletes a user. +func (c *Client) DeleteRuleset(id string) error { + _, err := c.delete("/rulesets/" + id) + return err +} + +// GetRuleset gets details about a ruleset. +func (c *Client) GetRuleset(id string) (*Ruleset, *http.Response, error) { + resp, err := c.get("/rulesets/" + id) + return getRulesetFromResponse(c, resp, err) +} + +// UpdateRuleset updates a ruleset. +func (c *Client) UpdateRuleset(r *Ruleset) (*Ruleset, *http.Response, error) { + v := make(map[string]*Ruleset) + v["ruleset"] = r + resp, err := c.put("/rulesets/"+r.ID, v, nil) + return getRulesetFromResponse(c, resp, err) +} + +func getRulesetFromResponse(c *Client, resp *http.Response, err error) (*Ruleset, *http.Response, error) { + if err != nil { + return nil, nil, err + } + var target map[string]Ruleset + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + t, nodeOK := target["ruleset"] + if !nodeOK { + return nil, nil, fmt.Errorf("JSON response does not have ruleset field") + } + return &t, resp, nil +} + +// ListRulesetRules gets all rules for a ruleset. +func (c *Client) ListRulesetRules(rulesetID string) (*ListRulesetRulesResponse, error) { + rulesResponse := new(ListRulesetRulesResponse) + rules := make([]*RulesetRule, 0) + + // Create a handler closure capable of parsing data from the ruleset rules endpoint + // and appending resultant ruleset rules to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListRulesetRulesResponse + + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + rules = append(rules, result.Rules...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet("/rulesets/"+rulesetID+"/rules", responseHandler); err != nil { + return nil, err + } + rulesResponse.Rules = rules + + return rulesResponse, nil +} + +// GetRulesetRule gets an event rule +func (c *Client) GetRulesetRule(rulesetID, ruleID string) (*RulesetRule, *http.Response, error) { + resp, err := c.get("/rulesets/" + rulesetID + "/rules/" + ruleID) + return getRuleFromResponse(c, resp, err) +} + +// DeleteRulesetRule deletes a rule. +func (c *Client) DeleteRulesetRule(rulesetID, ruleID string) error { + _, err := c.delete("/rulesets/" + rulesetID + "/rules/" + ruleID) + return err +} + +// CreateRulesetRule creates a new rule for a ruleset. +func (c *Client) CreateRulesetRule(rulesetID string, rule *RulesetRule) (*RulesetRule, *http.Response, error) { + data := make(map[string]*RulesetRule) + data["rule"] = rule + resp, err := c.post("/rulesets/"+rulesetID+"/rules/", data, nil) + return getRuleFromResponse(c, resp, err) +} + +// UpdateRulesetRule updates a rule. +func (c *Client) UpdateRulesetRule(rulesetID, ruleID string, r *RulesetRule) (*RulesetRule, *http.Response, error) { + v := make(map[string]*RulesetRule) + v["rule"] = r + resp, err := c.put("/rulesets/"+rulesetID+"/rules/"+ruleID, v, nil) + return getRuleFromResponse(c, resp, err) +} + +func getRuleFromResponse(c *Client, resp *http.Response, err error) (*RulesetRule, *http.Response, error) { + if err != nil { + return nil, nil, err + } + var target map[string]RulesetRule + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "rule" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, resp, nil +} diff --git a/ruleset_test.go b/ruleset_test.go new file mode 100644 index 00000000..90e2e6d3 --- /dev/null +++ b/ruleset_test.go @@ -0,0 +1,258 @@ +package pagerduty + +import ( + "net/http" + "testing" +) + +// List Rulesets +func TestRuleset_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte(`{"rulesets": [{"id": "1"}]}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + + res, err := client.ListRulesets() + if err != nil { + t.Fatal(err) + } + want := &ListRulesetsResponse{ + Rulesets: []*Ruleset{ + { + ID: "1", + }, + }, + } + + testEqual(t, want, res) +} + +// Create Ruleset +func TestUser_Ruleset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.Write([]byte(`{"ruleset": {"id": "1", "name": "foo"}}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + input := &Ruleset{ + Name: "foo", + } + res, _, err := client.CreateRuleset(input) + + want := &Ruleset{ + ID: "1", + Name: "foo", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +// Get Ruleset +func TestRuleset_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte(`{"ruleset": {"id": "1", "name":"foo"}}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + ruleSetID := "1" + + res, _, err := client.GetRuleset(ruleSetID) + + want := &Ruleset{ + ID: "1", + Name: "foo", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +// Update Ruleset +func TestRuleset_Update(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.Write([]byte(`{"ruleset": {"id": "1", "name":"foo"}}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + input := &Ruleset{ + ID: "1", + Name: "foo", + } + res, _, err := client.UpdateRuleset(input) + + want := &Ruleset{ + ID: "1", + Name: "foo", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +// Delete Ruleset +func TestRuleset_Delete(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + id := "1" + err := client.DeleteRuleset(id) + + if err != nil { + t.Fatal(err) + } +} + +// List Ruleset Rules +func TestRuleset_ListRules(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1/rules", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte(`{"rules": [{"id": "1"}]}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + + rulesetID := "1" + res, err := client.ListRulesetRules(rulesetID) + if err != nil { + t.Fatal(err) + } + + want := &ListRulesetRulesResponse{ + Rules: []*RulesetRule{ + { + ID: "1", + }, + }, + } + testEqual(t, want, res) +} + +// Get Ruleset Rule +func TestRuleset_GetRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1/rules/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte(`{"rule": {"id": "1"}}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + + rulesetID := "1" + ruleID := "1" + res, _, err := client.GetRulesetRule(rulesetID, ruleID) + if err != nil { + t.Fatal(err) + } + + want := &RulesetRule{ + ID: "1", + } + testEqual(t, want, res) +} + +// Create Ruleset Rule +func TestRuleset_CreateRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1/rules/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.Write([]byte(`{"rule": {"id": "1"}}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + + rulesetID := "1" + rule := &RulesetRule{} + + res, _, err := client.CreateRulesetRule(rulesetID, rule) + if err != nil { + t.Fatal(err) + } + + want := &RulesetRule{ + ID: "1", + } + testEqual(t, want, res) +} + +// Update Ruleset Rule +func TestRuleset_UpdateRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1/rules/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.Write([]byte(`{"rule": {"id": "1"}}`)) + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + + rulesetID := "1" + ruleID := "1" + rule := &RulesetRule{} + + res, _, err := client.UpdateRulesetRule(rulesetID, ruleID, rule) + if err != nil { + t.Fatal(err) + } + + want := &RulesetRule{ + ID: "1", + } + testEqual(t, want, res) +} + +// Delete Ruleset Rule +func TestRuleset_DeleteRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rulesets/1/rules/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + ruleID := "1" + rulesetID := "1" + + err := client.DeleteRulesetRule(rulesetID, ruleID) + + if err != nil { + t.Fatal(err) + } +}