diff --git a/command/addon_install.go b/command/addon_install.go index 9cb1bc85..f7d3df98 100644 --- a/command/addon_install.go +++ b/command/addon_install.go @@ -3,11 +3,12 @@ package main import ( "encoding/json" "fmt" - "github.com/PagerDuty/go-pagerduty" - log "github.com/sirupsen/logrus" - "github.com/mitchellh/cli" "os" "strings" + + "github.com/mitchellh/cli" + "github.com/PagerDuty/go-pagerduty" + log "github.com/sirupsen/logrus" ) type AddonInstall struct { diff --git a/command/main.go b/command/main.go index 78965fe3..376e693f 100644 --- a/command/main.go +++ b/command/main.go @@ -2,8 +2,9 @@ package main import ( "fmt" - "github.com/mitchellh/cli" "os" + + "github.com/mitchellh/cli" ) const ( @@ -69,6 +70,11 @@ func loadCommands() map[string]cli.CommandFactory { "service integration create": ServiceIntegrationCreateCommand, "service integration show": ServiceIntegrationShowCommand, "service integration update": ServiceIntegrationUpdateCommand, + "service rule create": ServiceRuleCreateCommand, + "service rule delete": ServiceRuleDeleteCommand, + "service rule list": ServiceRuleListCommand, + "service rule show": ServiceRuleShowCommand, + "service rule update": ServiceRuleUpdateCommand, "team list": TeamListCommand, "team create": TeamShowCommand, diff --git a/command/meta.go b/command/meta.go index 28051443..4842c001 100644 --- a/command/meta.go +++ b/command/meta.go @@ -3,14 +3,15 @@ package main import ( "flag" "fmt" - "github.com/PagerDuty/go-pagerduty" - log "github.com/sirupsen/logrus" - "github.com/mitchellh/go-homedir" - "gopkg.in/yaml.v2" "io/ioutil" "os" "path/filepath" "strings" + + "github.com/mitchellh/go-homedir" + "github.com/PagerDuty/go-pagerduty" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" ) type ArrayFlags []string diff --git a/command/service_integration_create.go b/command/service_integration_create.go index d8f4e1c4..b64253e6 100644 --- a/command/service_integration_create.go +++ b/command/service_integration_create.go @@ -3,11 +3,12 @@ package main import ( "encoding/json" "fmt" - "github.com/PagerDuty/go-pagerduty" - log "github.com/sirupsen/logrus" - "github.com/mitchellh/cli" "os" "strings" + + "github.com/mitchellh/cli" + "github.com/PagerDuty/go-pagerduty" + log "github.com/sirupsen/logrus" ) type ServiceIntegrationCreate struct { diff --git a/command/service_list.go b/command/service_list.go index 1be0240c..08b36ffb 100644 --- a/command/service_list.go +++ b/command/service_list.go @@ -2,11 +2,12 @@ package main import ( "fmt" + "strings" + "github.com/PagerDuty/go-pagerduty" - log "github.com/sirupsen/logrus" "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" - "strings" ) type ServiceList struct { diff --git a/command/service_rule_create.go b/command/service_rule_create.go new file mode 100644 index 00000000..93e4cc2b --- /dev/null +++ b/command/service_rule_create.go @@ -0,0 +1,28 @@ +package main + +import ( + "strings" + + "github.com/mitchellh/cli" +) + +type ServiceRuleCreate struct { +} + +func ServiceRuleCreateCommand() (cli.Command, error) { + return &ServiceRuleCreate{}, nil +} + +func (c *ServiceRuleCreate) Help() string { + helpText := ` + ` + return strings.TrimSpace(helpText) +} + +func (c *ServiceRuleCreate) Synopsis() string { + return "Get details about an integration belonging to a service" +} + +func (c *ServiceRuleCreate) Run(args []string) int { + return 0 +} diff --git a/command/service_rule_delete.go b/command/service_rule_delete.go new file mode 100644 index 00000000..d7cedd04 --- /dev/null +++ b/command/service_rule_delete.go @@ -0,0 +1,28 @@ +package main + +import ( + "strings" + + "github.com/mitchellh/cli" +) + +type ServiceRuleDelete struct { +} + +func ServiceRuleDeleteCommand() (cli.Command, error) { + return &ServiceRuleDelete{}, nil +} + +func (c *ServiceRuleDelete) Help() string { + helpText := ` + ` + return strings.TrimSpace(helpText) +} + +func (c *ServiceRuleDelete) Synopsis() string { + return "Get details about an integration belonging to a service" +} + +func (c *ServiceRuleDelete) Run(args []string) int { + return 0 +} diff --git a/command/service_rule_list.go b/command/service_rule_list.go new file mode 100644 index 00000000..e65a73c8 --- /dev/null +++ b/command/service_rule_list.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type ServiceRuleList struct { + Meta +} + +func ServiceRuleListCommand() (cli.Command, error) { + return &ServiceRuleList{}, nil +} + +func (c *ServiceRuleList) Help() string { + helpText := ` + pd service rules list List rules for service + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *ServiceRuleList) Synopsis() string { + return "List existing Rules for a service" +} + +func (c *ServiceRuleList) Run(args []string) int { + flags := c.Meta.FlagSet("service rules list") + flags.Usage = func() { fmt.Println(c.Help()) } + if err := flags.Parse(args); err != nil { + log.Error(err) + return -1 + } + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + if len(flags.Args()) != 1 { + log.Error("Please specify service id") + return -1 + } + log.Info("Service id is:", flags.Arg(0)) + + client := c.Meta.Client() + if rulesList, err := client.ListServiceRulesPaginated(context.Background(), flags.Arg(0)); err != nil { + log.Error(err) + return -1 + } else { + for i, rule := range rulesList { + fmt.Println("Entry: ", i+1) + data, err := yaml.Marshal(rule) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + } + } + return 0 +} diff --git a/command/service_rule_show.go b/command/service_rule_show.go new file mode 100644 index 00000000..a4dc6739 --- /dev/null +++ b/command/service_rule_show.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type ServiceRuleShow struct { + Meta +} + +func ServiceRuleShowCommand() (cli.Command, error) { + return &ServiceRuleShow{}, nil +} + +func (c *ServiceRuleShow) Help() string { + helpText := ` + pd service rules show Show specific service + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *ServiceRuleShow) Synopsis() string { + return "Get details about an existing service rule" +} + +func (c *ServiceRuleShow) Run(args []string) int { + flags := c.Meta.FlagSet("service rules show") + flags.Usage = func() { fmt.Println(c.Help()) } + if err := flags.Parse(args); err != nil { + log.Error(err) + return -1 + } + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + if len(flags.Args()) != 2 { + log.Error("Please specify service id and rule id") + return -1 + } + log.Info("Service id is:", flags.Arg(0)) + log.Info("Rule id is:", flags.Arg(1)) + + client := c.Meta.Client() + rule, err := client.GetServiceRule(context.Background(), flags.Arg(0), flags.Arg(1)) + if err != nil { + log.Error(err) + return -1 + } + data, err := yaml.Marshal(rule) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + return 0 +} diff --git a/command/service_rule_update.go b/command/service_rule_update.go new file mode 100644 index 00000000..117d50cd --- /dev/null +++ b/command/service_rule_update.go @@ -0,0 +1,28 @@ +package main + +import ( + "strings" + + "github.com/mitchellh/cli" +) + +type ServiceRuleUpdate struct { +} + +func ServiceRuleUpdateCommand() (cli.Command, error) { + return &ServiceRuleUpdate{}, nil +} + +func (c *ServiceRuleUpdate) Help() string { + helpText := ` + ` + return strings.TrimSpace(helpText) +} + +func (c *ServiceRuleUpdate) Synopsis() string { + return "Get details about an integration belonging to a service" +} + +func (c *ServiceRuleUpdate) Run(args []string) int { + return 0 +} diff --git a/ruleset.go b/ruleset.go index d1f96f6c..acce74ee 100644 --- a/ruleset.go +++ b/ruleset.go @@ -104,13 +104,14 @@ type ListRulesetRulesResponse struct { // 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"` EventAction *RuleActionParameter `json:"event_action,omitempty"` Extractions []*RuleActionExtraction `json:"extractions,omitempty"` + Priority *RuleActionParameter `json:"priority,omitempty"` + Severity *RuleActionParameter `json:"severity,omitempty"` + Suppress *RuleActionSuppress `json:"suppress,omitempty"` + Suspend *RuleActionSuspend `json:"suspend,omitempty"` + Route *RuleActionParameter `json:"route"` } // RuleActionParameter represents a generic parameter object on a rule action @@ -126,6 +127,11 @@ type RuleActionSuppress struct { ThresholdTimeAmount int `json:"threshold_time_amount,omitempty"` } +// RuleActionSuspend represents a rule suspend action object +type RuleActionSuspend struct { + Value *bool `json:"value,omitempty"` +} + // RuleActionExtraction represents a rule extraction action object type RuleActionExtraction struct { Target string `json:"target,omitempty"` diff --git a/service.go b/service.go index 8f6f838e..02074f2b 100644 --- a/service.go +++ b/service.go @@ -56,6 +56,37 @@ type IncidentUrgencyRule struct { OutsideSupportHours *IncidentUrgencyType `json:"outside_support_hours,omitempty"` } +// ListServiceRulesResponse represents a list of rules in a service +type ListServiceRulesResponse struct { + Offset uint `json:"offset,omitempty"` + Limit uint `json:"limit,omitempty"` + More bool `json:"more,omitempty"` + Total uint `json:"total,omitempty"` + Rules []ServiceRule `json:"rules,omitempty"` +} + +// ServiceRule represents a Service rule +type ServiceRule struct { + ID string `json:"id,omitempty"` + Self string `json:"self,omitempty"` + Disabled *bool `json:"disabled,omitempty"` + Conditions *RuleConditions `json:"conditions,omitempty"` + TimeFrame *RuleTimeFrame `json:"time_frame,omitempty"` + Position *int `json:"position,omitempty"` + Actions *ServiceRuleActions `json:"actions,omitempty"` +} + +// ServiceRuleActions represents a rule action +type ServiceRuleActions struct { + Annotate *RuleActionParameter `json:"annotate,omitempty"` + EventAction *RuleActionParameter `json:"event_action,omitempty"` + Extractions []RuleActionExtraction `json:"extractions,omitempty"` + Priority *RuleActionParameter `json:"priority,omitempty"` + Severity *RuleActionParameter `json:"severity,omitempty"` + Suppress *RuleActionSuppress `json:"suppress,omitempty"` + Suspend *RuleActionSuspend `json:"suspend,omitempty"` +} + // Service represents something you monitor (like a web service, email service, or database service). type Service struct { APIObject @@ -220,6 +251,88 @@ func (c *Client) DeleteIntegration(serviceID string, integrationID string) error return err } +// ListServiceRules gets all rules for a service. +func (c *Client) ListServiceRulesPaginated(ctx context.Context, serviceID string) ([]ServiceRule, error) { + var rules []ServiceRule + + // Create a handler closure capable of parsing data from the Service rules endpoint + // and appending resultant Service rules to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListServiceRulesResponse + + 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(ctx, "/services/"+serviceID+"/rules", responseHandler); err != nil { + return nil, err + } + + return rules, nil +} + +// GetServiceRule gets a service rule. +func (c *Client) GetServiceRule(ctx context.Context, serviceID, ruleID string) (ServiceRule, error) { + resp, err := c.get(ctx, "/services/"+serviceID+"/rules/"+ruleID) + return getServiceRuleFromResponse(c, resp, err) +} + +// DeleteServiceRule deletes a service rule. +func (c *Client) DeleteServiceRule(ctx context.Context, serviceID, ruleID string) error { + _, err := c.delete(ctx, "/services/"+serviceID+"/rules/"+ruleID) + return err +} + +// CreateServiceRule creates a service rule. +func (c *Client) CreateServiceRule(ctx context.Context, serviceID string, rule ServiceRule) (ServiceRule, error) { + d := map[string]ServiceRule{ + "rule": rule, + } + resp, err := c.post(ctx, "/services/"+serviceID+"/rules/", d, nil) + return getServiceRuleFromResponse(c, resp, err) +} + +// UpdateServiceRule updates a service rule. +func (c *Client) UpdateServiceRule(ctx context.Context, serviceID, ruleID string, rule ServiceRule) (ServiceRule, error) { + d := map[string]ServiceRule{ + "rule": rule, + } + resp, err := c.put(ctx, "/services/"+serviceID+"/rules/"+ruleID, d, nil) + return getServiceRuleFromResponse(c, resp, err) +} + +func getServiceRuleFromResponse(c *Client, resp *http.Response, err error) (ServiceRule, error) { + if err != nil { + return ServiceRule{}, err + } + + var target map[string]ServiceRule + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return ServiceRule{}, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "rule" + + t, nodeOK := target[rootNode] + if !nodeOK { + return ServiceRule{}, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return t, nil +} + func getServiceFromResponse(c *Client, resp *http.Response, err error) (*Service, error) { if err != nil { return nil, err diff --git a/service_test.go b/service_test.go index 6a482e15..445bacbb 100644 --- a/service_test.go +++ b/service_test.go @@ -429,3 +429,123 @@ func TestService_DeleteIntegration(t *testing.T) { t.Fatal(err) } } + +// List Service Rules +func TestService_ListRules(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/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} + + serviceID := "1" + res, err := client.ListServiceRulesPaginated(context.Background(), serviceID) + if err != nil { + t.Fatal(err) + } + + want := []ServiceRule{{ID: "1"}} + testEqual(t, want, res) +} + +// Create Service Rule +func TestService_CreateServiceRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/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} + + serviceID := "1" + rule := ServiceRule{} + + res, err := client.CreateServiceRule(context.Background(), serviceID, rule) + if err != nil { + t.Fatal(err) + } + + want := ServiceRule{ + ID: "1", + } + testEqual(t, want, res) +} + +// Get Service Rule +func TestService_GetServiceRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/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} + + serviceID := "1" + ruleID := "1" + res, err := client.GetServiceRule(context.Background(), serviceID, ruleID) + if err != nil { + t.Fatal(err) + } + + want := ServiceRule{ + ID: "1", + } + testEqual(t, want, res) +} + +// Update Service Rule +func TestService_UpdateServiceRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/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} + + serviceID := "1" + ruleID := "1" + rule := ServiceRule{} + + res, err := client.UpdateServiceRule(context.Background(), serviceID, ruleID, rule) + if err != nil { + t.Fatal(err) + } + + want := ServiceRule{ + ID: "1", + } + testEqual(t, want, res) +} + +// Delete Service Rule +func TestService_DeleteServiceRule(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/services/1/rules/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + var client = &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient} + serviceID := "1" + ruleID := "1" + + err := client.DeleteServiceRule(context.Background(), serviceID, ruleID) + + if err != nil { + t.Fatal(err) + } +}