diff --git a/command/event_orchestration_create.go b/command/event_orchestration_create.go new file mode 100644 index 00000000..6a155519 --- /dev/null +++ b/command/event_orchestration_create.go @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/PagerDuty/go-pagerduty" + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" +) + +type EventOrchestrationCreate struct { + Meta +} + +func EventOrchestrationCreateCommand() (cli.Command, error) { + return &EventOrchestrationCreate{}, nil +} + +func (c *EventOrchestrationCreate) Help() string { + helpText := ` + pd event-orchestration create Create a new event orchestration + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *EventOrchestrationCreate) Synopsis() string { + return "Creates a new event orchestration" +} + +func (c *EventOrchestrationCreate) Run(args []string) int { + flags := c.Meta.FlagSet("event-orchestration create") + 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 + } + client := c.Meta.Client() + var eo pagerduty.Orchestration + if len(flags.Args()) != 1 { + log.Error("Please specify input json file") + return -1 + } + log.Info("Input file is:", flags.Arg(0)) + f, err := os.Open(flags.Arg(0)) + if err != nil { + log.Error(err) + return -1 + } + defer f.Close() + decoder := json.NewDecoder(f) + if err := decoder.Decode(&eo); err != nil { + log.Errorln("Failed to decode json. Error:", err) + return -1 + } + log.Debugf("%#v", eo) + if _, err := client.CreateOrchestrationWithContext(context.Background(), eo); err != nil { + log.Error(err) + return -1 + } + return 0 +} diff --git a/command/event_orchestration_delete.go b/command/event_orchestration_delete.go new file mode 100644 index 00000000..0f63cf41 --- /dev/null +++ b/command/event_orchestration_delete.go @@ -0,0 +1,62 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" +) + +type EventOrchestrationDelete struct { + Meta +} + +func EventOrchestrationDeleteCommand() (cli.Command, error) { + return &EventOrchestrationDelete{}, nil +} + +func (c *EventOrchestrationDelete) Help() string { + helpText := ` + pd event-orchestration delete Delete an event orchestration + + Options: + -id The event orchestration ID + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *EventOrchestrationDelete) Synopsis() string { + return "Delete an existing event orchestration and its rules" +} + +func (c *EventOrchestrationDelete) Run(args []string) int { + var eoID string + flags := c.Meta.FlagSet("event-orchestration delete") + flags.Usage = func() { fmt.Println(c.Help()) } + flags.StringVar(&eoID, "id", "", "Event orchestration ID") + + if err := flags.Parse(args); err != nil { + log.Error(err) + return -1 + } + + if eoID == "" { + log.Error("You must provide an event orchestration ID") + return -1 + } + + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + + client := c.Meta.Client() + if err := client.DeleteOrchestrationWithContext(context.Background(), eoID); err != nil { + log.Error(err) + return -1 + } + + return 0 +} diff --git a/command/event_orchestration_list.go b/command/event_orchestration_list.go new file mode 100644 index 00000000..9eff4b01 --- /dev/null +++ b/command/event_orchestration_list.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/PagerDuty/go-pagerduty" + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type EventOrchestrationList struct { + Meta +} + +func (c *EventOrchestrationList) Help() string { + helpText := ` + pd event-orchestration list List all of the existing event orchestrations + + Options: + + -sort Sort results by property (name:asc, name:desc, routes:asc, routes:desc, created_at:asc, created_at:desc) + + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *EventOrchestrationList) Synopsis() string { + return "List all of the existing event orchestrations" +} + +func EventOrchestrationListCommand() (cli.Command, error) { + return &EventOrchestrationList{}, nil +} + +func (c *EventOrchestrationList) Run(args []string) int { + var sortBy string + + flags := c.Meta.FlagSet("event-orchestration list") + flags.Usage = func() { fmt.Println(c.Help()) } + flags.StringVar(&sortBy, "sort", "", "Sort results by name, routes, or created_at (ascending or descending)") + + if err := flags.Parse(args); err != nil { + log.Error(err) + return -1 + } + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + client := c.Meta.Client() + opts := pagerduty.ListOrchestrationsOptions{ + SortBy: sortBy, + } + if eps, err := client.ListOrchestrationsWithContext(context.Background(), opts); err != nil { + log.Error(err) + return -1 + } else { + for i, p := range eps.Orchestrations { + if i > 0 { + fmt.Println("---") + } + data, err := yaml.Marshal(p) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + } + } + return 0 +} diff --git a/command/event_orchestration_show.go b/command/event_orchestration_show.go new file mode 100644 index 00000000..374b8e84 --- /dev/null +++ b/command/event_orchestration_show.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/PagerDuty/go-pagerduty" + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type EventOrchestrationShow struct { + Meta +} + +func (c *EventOrchestrationShow) Help() string { + helpText := ` + pd event-orchestration show Show event orchestrations + + Options: + + -id + + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *EventOrchestrationShow) Synopsis() string { + return "Show information about an existing event orchestration and its rules" +} + +func EventOrchestrationShowCommand() (cli.Command, error) { + return &EventOrchestrationShow{}, nil +} + +func (c *EventOrchestrationShow) Run(args []string) int { + var eoID string + flags := c.Meta.FlagSet("event-orchestration show") + flags.Usage = func() { fmt.Println(c.Help()) } + flags.StringVar(&eoID, "id", "", "Event orchestration id") + if err := flags.Parse(args); err != nil { + log.Errorln(err) + return -1 + } + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + if eoID == "" { + log.Error("You must provide event orchestration id") + return -1 + } + client := c.Meta.Client() + o := &pagerduty.GetOrchestrationOptions{} + ep, err := client.GetOrchestrationWithContext(context.Background(), eoID, o) + if err != nil { + log.Error(err) + return -1 + } + data, err := yaml.Marshal(ep) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + fmt.Println("---") + + ro := &pagerduty.GetOrchestrationRouterOptions{} + rules, err := client.GetOrchestrationRouterWithContext(context.Background(), eoID, ro) + if err != nil { + log.Error(err) + return -1 + } + data, err = yaml.Marshal(rules) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + + return 0 +} diff --git a/command/event_orchestration_update.go b/command/event_orchestration_update.go new file mode 100644 index 00000000..ac2d47e8 --- /dev/null +++ b/command/event_orchestration_update.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/PagerDuty/go-pagerduty" + log "github.com/sirupsen/logrus" + + "github.com/mitchellh/cli" +) + +type EventOrchestrationUpdate struct { + Meta +} + +func EventOrchestrationUpdateCommand() (cli.Command, error) { + return &EventOrchestrationUpdate{}, nil +} + +func (c *EventOrchestrationUpdate) Help() string { + helpText := ` + pd event-orchestration update Update an event orchestration from json file + Options: + + -id + + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *EventOrchestrationUpdate) Synopsis() string { + return "Update an existing event orchestration" +} + +func (c *EventOrchestrationUpdate) Run(args []string) int { + var eoID string + flags := c.Meta.FlagSet("event-orchestration update") + flags.Usage = func() { fmt.Println(c.Help()) } + flags.StringVar(&eoID, "id", "", "Event orchestration id") + 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 eoID == "" { + log.Error("You must provide event orchestration id") + return -1 + } + + client := c.Meta.Client() + var eo pagerduty.Orchestration + if len(flags.Args()) != 1 { + log.Error("Please specify input json file") + return -1 + } + + log.Info("Input file is:", flags.Arg(0)) + f, err := ioutil.ReadFile(flags.Arg(0)) + if err != nil { + log.Error(err) + return -1 + } + + if err := json.Unmarshal(f, &eo); err != nil { + log.Errorln("Failed to decode json. Error:", err) + return -1 + } + + log.Debugf("%#v", eo) + if _, err := client.UpdateOrchestrationWithContext(context.Background(), eoID, eo); err != nil { + log.Error(err) + return -1 + } + + return 0 +} diff --git a/command/main.go b/command/main.go index 27be30ad..ce6215b9 100644 --- a/command/main.go +++ b/command/main.go @@ -27,6 +27,12 @@ func loadCommands() map[string]cli.CommandFactory { "escalation-policy show": EscalationPolicyShowCommand, "escalation-policy update": EscalationPolicyUpdateCommand, + "event-orchestration list": EventOrchestrationListCommand, + "event-orchestration create": EventOrchestrationCreateCommand, + "event-orchestration delete": EventOrchestrationDeleteCommand, + "event-orchestration show": EventOrchestrationShowCommand, + "event-orchestration update": EventOrchestrationUpdateCommand, + "event-v2 manage": EventV2ManageCommand, "incident list": IncidentListCommand, diff --git a/event_orchestration.go b/event_orchestration.go new file mode 100644 index 00000000..da2f931e --- /dev/null +++ b/event_orchestration.go @@ -0,0 +1,486 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +const ( + eoPath = "/event_orchestrations" +) + +// Orchestration defines a global orchestration to route events the same source +// to different services. +type Orchestration struct { + APIObject + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Team *APIReference `json:"team,omitempty"` + Integrations []*OrchestrationIntegration `json:"integrations,omitempty"` + Routes uint `json:"routes,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +// OrchestrationIntegration is a route into an orchestration. +type OrchestrationIntegration struct { + ID string `json:"id,omitempty"` + Parameters *OrchestrationIntegrationParameters `json:"parameters,omitempty"` +} + +type OrchestrationIntegrationParameters struct { + RoutingKey string `json:"routing_key,omitempty"` + Type string `json:"type,omitempty"` +} + +// ListOrchestrationsResponse is the data structure returned from calling the ListOrchestrations API endpoint. +type ListOrchestrationsResponse struct { + APIListObject + Orchestrations []Orchestration `json:"orchestrations"` +} + +// ListOrchestrationsOptions is the data structure used when calling the ListOrchestrations API endpoint. +type ListOrchestrationsOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + + SortBy string `url:"sort_by,omitempty"` +} + +// ListOrchestrationsWithContext lists all the existing event orchestrations. +func (c *Client) ListOrchestrationsWithContext(ctx context.Context, o ListOrchestrationsOptions) (*ListOrchestrationsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"?"+v.Encode()) + if err != nil { + return nil, err + } + + var result ListOrchestrationsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// CreateOrchestrationWithContext creates a new event orchestration. +func (c *Client) CreateOrchestrationWithContext(ctx context.Context, e Orchestration) (*Orchestration, error) { + d := map[string]Orchestration{ + "orchestration": e, + } + + resp, err := c.post(ctx, eoPath, d, nil) + return getOrchestrationFromResponse(c, resp, err) +} + +// DeleteOrchestrationWithContext deletes an existing event orchestration. +func (c *Client) DeleteOrchestrationWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, eoPath+"/"+id) + return err +} + +// GetOrchestrationOptions is the data structure used when calling the GetOrchestration API endpoint. +type GetOrchestrationOptions struct { +} + +// GetOrchestrationWithContext gets information about an event orchestration. +func (c *Client) GetOrchestrationWithContext(ctx context.Context, id string, o *GetOrchestrationOptions) (*Orchestration, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/"+id+"?"+v.Encode()) + return getOrchestrationFromResponse(c, resp, err) +} + +// UpdateOrchestrationWithContext updates an existing event orchestration. +func (c *Client) UpdateOrchestrationWithContext(ctx context.Context, id string, e Orchestration) (*Orchestration, error) { + d := map[string]Orchestration{ + "orchestration": e, + } + + resp, err := c.put(ctx, eoPath+"/"+id, d, nil) + return getOrchestrationFromResponse(c, resp, err) +} + +func getOrchestrationFromResponse(c *Client, resp *http.Response, err error) (*Orchestration, error) { + if err != nil { + return nil, err + } + + var target map[string]Orchestration + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +// OrchestrationRouter is an event router. +type OrchestrationRouter struct { + Type string `json:"type,omitempty"` + Parent *APIReference `json:"parent,omitempty"` + Sets []*OrchestrationRouterRuleSet `json:"sets,omitempty"` + CatchAll *OrchestrationRouterCatchAllRule `json:"catch_all,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +type OrchestrationRouterRuleSet struct { + ID string `json:"id,omitempty"` + Rules []*OrchestrationRouterRule `json:"rules,omitempty"` +} + +type OrchestrationRouterRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*OrchestrationRouterRuleCondition `json:"conditions,omitempty"` + Actions *OrchestrationRouterActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type OrchestrationRouterRuleCondition struct { + Expression string `json:"expression,omitempty"` +} + +// OrchestrationRouterCatchAllRule routes an event when none of the rules match an event. +type OrchestrationRouterCatchAllRule struct { + Actions *OrchestrationRouterActions `json:"actions,omitempty"` +} + +// OrchestrationRouterActions are the actions that will be taken to change the resulting alert and incident. +type OrchestrationRouterActions struct { + RouteTo string `json:"route_to,omitempty"` +} + +// GetOrchestrationRouterOptions is the data structure used when calling the GetOrchestrationRouter API endpoint. +type GetOrchestrationRouterOptions struct { +} + +// GetOrchestrationRouterWithContext gets information about an event orchestration. +func (c *Client) GetOrchestrationRouterWithContext(ctx context.Context, id string, o *GetOrchestrationRouterOptions) (*OrchestrationRouter, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/"+id+"/router"+"?"+v.Encode()) + return getOrchestrationRouterFromResponse(c, resp, err) +} + +// UpdateOrchestrationRouterWithContext updates the routing rules of an existing event orchestration. +func (c *Client) UpdateOrchestrationRouterWithContext(ctx context.Context, id string, e OrchestrationRouter) (*OrchestrationRouter, error) { + d := map[string]OrchestrationRouter{ + "orchestration_path": e, + } + + resp, err := c.put(ctx, eoPath+"/"+id+"/router", d, nil) + return getOrchestrationRouterFromResponse(c, resp, err) +} + +func getOrchestrationRouterFromResponse(c *Client, resp *http.Response, err error) (*OrchestrationRouter, error) { + if err != nil { + return nil, err + } + + var target map[string]OrchestrationRouter + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration_path" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +// ServiceOrchestration defines sets of rules belonging to a service. +type ServiceOrchestration struct { + Type string `json:"type,omitempty"` + Parent *APIReference `json:"parent,omitempty"` + Sets []*ServiceOrchestrationRuleSet `json:"sets,omitempty"` + CatchAll *ServiceOrchestrationCatchAllRule `json:"catch_all,omitempty"` + + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +type ServiceOrchestrationCatchAllRule struct { + Actions *ServiceOrchestrationRuleActions `json:"actions,omitempty"` +} + +type ServiceOrchestrationRuleSet struct { + ID string `json:"id,omitempty"` + Rules []*ServiceOrchestrationRule `json:"rules,omitempty"` +} + +type ServiceOrchestrationRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*ServiceOrchestrationRuleCondition `json:"conditions,omitempty"` + Actions *ServiceOrchestrationRuleActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type ServiceOrchestrationRuleCondition struct { + Expression string `json:"expression,omitempty"` +} + +// ServiceOrchestrationRuleActions are the actions that will be taken to change the resulting alert and incident. +type ServiceOrchestrationRuleActions struct { + RouteTo string `json:"route_to,omitempty"` + Suppress bool `json:"suppress,omitempty"` + Suspend uint `json:"suspend,omitempty"` + Priority string `json:"priority,omitempty"` + Annotate string `json:"annotate,omitempty"` + PagerDutyAutomationActions []*PagerDutyAutomationAction `json:"pagerduty_automation_actions,omitempty"` + AutomationActions []*AutomationAction `json:"automation_actions,omitempty"` + Severity string `json:"severity,omitempty"` + EventAction string `json:"event_action,omitempty"` + Variables []*OrchestrationVariable `json:"variables,omitempty"` + Extractions []*OrchestrationExtraction `json:"extractions,omitempty"` +} + +type ServiceOrchestrationActive struct { + Active bool `json:"active,omitempty"` +} + +// GetServiceOrchestrationOptions is the data structure used when calling the GetServiceOrchestration API endpoint. +type GetServiceOrchestrationOptions struct { +} + +// GetServiceOrchestrationWithContext gets information about a service orchestration. +func (c *Client) GetServiceOrchestrationWithContext(ctx context.Context, id string, o *GetServiceOrchestrationOptions) (*ServiceOrchestration, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/services/"+id+"?"+v.Encode()) + return getServiceOrchestrationFromResponse(c, resp, err) +} + +// UpdateServiceOrchestrationWithContext updates the routing rules of a service orchestration. +func (c *Client) UpdateServiceOrchestrationWithContext(ctx context.Context, id string, e ServiceOrchestration) (*ServiceOrchestration, error) { + d := map[string]ServiceOrchestration{ + "orchestration_path": e, + } + + resp, err := c.put(ctx, eoPath+"/services/"+id, d, nil) + return getServiceOrchestrationFromResponse(c, resp, err) +} + +// GetServiceOrchestrationActiveWithContext gets a service orchestration's active status. +func (c *Client) GetServiceOrchestrationActiveWithContext(ctx context.Context, id string) (*ServiceOrchestrationActive, error) { + resp, err := c.get(ctx, eoPath+"/services/"+id+"/active") + return getServiceOrchestrationActiveFromResponse(c, resp, err) +} + +// UpdateServiceOrchestrationActiveWithContext updates a service orchestration's active status. +func (c *Client) UpdateServiceOrchestrationActiveWithContext(ctx context.Context, id string, e ServiceOrchestrationActive) (*ServiceOrchestrationActive, error) { + resp, err := c.put(ctx, eoPath+"/services/"+id+"/active", e, nil) + return getServiceOrchestrationActiveFromResponse(c, resp, err) +} + +func getServiceOrchestrationFromResponse(c *Client, resp *http.Response, err error) (*ServiceOrchestration, error) { + if err != nil { + return nil, err + } + + var target map[string]ServiceOrchestration + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration_path" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +func getServiceOrchestrationActiveFromResponse(c *Client, resp *http.Response, err error) (*ServiceOrchestrationActive, error) { + if err != nil { + return nil, err + } + + var target ServiceOrchestrationActive + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + return &target, nil +} + +// OrchestrationUnrouted defines sets of rules to be applied to unrouted events. +type OrchestrationUnrouted struct { + Type string `json:"type,omitempty"` + Parent *APIReference `json:"parent,omitempty"` + Sets []*ServiceOrchestrationRuleSet `json:"sets,omitempty"` + CatchAll *OrchestrationUnroutedCatchAllRule `json:"catch_all,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +type OrchestrationUnroutedCatchAllRule struct { + Actions *OrchestrationUnroutedRuleActions `json:"actions,omitempty"` +} + +type OrchestrationUnroutedRuleSet struct { + ID string `json:"id,omitempty"` + Rules []*OrchestrationUnroutedRule `json:"rules,omitempty"` +} + +type OrchestrationUnroutedRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*OrchestrationUnroutedRuleCondition `json:"conditions,omitempty"` + Actions *OrchestrationUnroutedRuleActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type OrchestrationUnroutedRuleCondition struct { + Expression string `json:"expression,omitempty"` +} + +// OrchestrationUnroutedRuleActions are the actions that will be taken to change the resulting alert and incident. +type OrchestrationUnroutedRuleActions struct { + RouteTo string `json:"route_to,omitempty"` + Severity string `json:"severity,omitempty"` + EventAction string `json:"event_action,omitempty"` + Variables []*OrchestrationVariable `json:"variables,omitempty"` + Extractions []*OrchestrationExtraction `json:"extractions,omitempty"` +} + +// GetOrchestrationUnroutedOptions is the data structure used when calling the GetOrchestrationUnrouted API endpoint. +type GetOrchestrationUnroutedOptions struct { +} + +// GetOrchestrationUnroutedWithContext gets the routing rules for unrouted events. +func (c *Client) GetOrchestrationUnroutedWithContext(ctx context.Context, id string, o *GetOrchestrationUnroutedOptions) (*OrchestrationUnrouted, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/"+id+"/unrouted"+"?"+v.Encode()) + return getOrchestrationUnroutedFromResponse(c, resp, err) +} + +// UpdateOrchestrationUnroutedWithContext updates the routing rules for unrouted events. +func (c *Client) UpdateOrchestrationUnroutedWithContext(ctx context.Context, id string, e OrchestrationUnrouted) (*OrchestrationUnrouted, error) { + d := map[string]OrchestrationUnrouted{ + "orchestration_path": e, + } + + resp, err := c.put(ctx, eoPath+"/"+id+"/unrouted", d, nil) + return getOrchestrationUnroutedFromResponse(c, resp, err) +} + +func getOrchestrationUnroutedFromResponse(c *Client, resp *http.Response, err error) (*OrchestrationUnrouted, error) { + if err != nil { + return nil, err + } + + var target map[string]OrchestrationUnrouted + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration_path" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +type PagerDutyAutomationAction struct { + ActionID string `json:"action_id,omitempty"` +} + +type AutomationAction struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + AutoSend bool `json:"auto_send,omitempty"` + Headers []*OrchestrationHeader `json:"headers,omitempty"` + Parameters []*OrchestrationParameter `json:"parameters,omitempty"` +} + +type OrchestrationHeader struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type OrchestrationParameter struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +// OrchestrationExtraction defines a value extraction in an orchestration rule. +type OrchestrationExtraction struct { + Target string `json:"target,omitempty"` + Regex string `json:"regex,omitempty"` + Source string `json:"source,omitempty"` + Template string `json:"template,omitempty"` +} + +// OrchestrationVariable defines a variable in an orchestration rule. +type OrchestrationVariable struct { + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` +} diff --git a/event_orchestration_test.go b/event_orchestration_test.go new file mode 100644 index 00000000..5466e7bf --- /dev/null +++ b/event_orchestration_test.go @@ -0,0 +1,313 @@ +package pagerduty + +import ( + "context" + "net/http" + "testing" +) + +func TestOrchestration_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"orchestrations": [{"id": "1"}]}`)) + }) + + listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0} + var opts ListOrchestrationsOptions + client := defaultTestClient(server.URL, "foo") + + res, err := client.ListOrchestrationsWithContext(context.Background(), opts) + + want := &ListOrchestrationsResponse{ + APIListObject: listObj, + Orchestrations: []Orchestration{ + { + APIObject: APIObject{ + ID: "1", + }, + }, + }, + } + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestration_Create(t *testing.T) { + setup() + defer teardown() + + input := Orchestration{Name: "foo"} + + mux.HandleFunc("/event_orchestrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, _ = w.Write([]byte(`{"orchestration": {"name": "foo", "id": "1"}}`)) + }) + client := defaultTestClient(server.URL, "foo") + res, err := client.CreateOrchestrationWithContext(context.Background(), input) + + want := &Orchestration{ + Name: "foo", + APIObject: APIObject{ + ID: "1", + }, + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestration_Delete(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + client := defaultTestClient(server.URL, "foo") + err := client.DeleteOrchestrationWithContext(context.Background(), "1") + if err != nil { + t.Fatal(err) + } +} + +func TestOrchestration_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"orchestration": {"id": "1"}}`)) + }) + client := defaultTestClient(server.URL, "foo") + var opts *GetOrchestrationOptions + res, err := client.GetOrchestrationWithContext(context.Background(), "1", opts) + + want := &Orchestration{ + APIObject: APIObject{ + ID: "1", + }, + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestration_Update(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, _ = w.Write([]byte(`{"orchestration": {"name": "foo", "id": "1"}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := &Orchestration{Name: "foo"} + want := &Orchestration{ + APIObject: APIObject{ + ID: "1", + }, + Name: "foo", + } + res, err := client.UpdateOrchestrationWithContext(context.Background(), "1", *input) + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestrationRouter_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1/router", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"orchestration_path": {"type": "router"}}`)) + }) + client := defaultTestClient(server.URL, "foo") + var opts *GetOrchestrationRouterOptions + res, err := client.GetOrchestrationRouterWithContext(context.Background(), "1", opts) + + want := &OrchestrationRouter{ + Type: "router", + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestrationRouter_Update(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1/router", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, _ = w.Write([]byte(`{"orchestration_path": {"type": "router", "parent": {"id": "1"}}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := &OrchestrationRouter{Type: "router"} + want := &OrchestrationRouter{ + Type: "router", + Parent: &APIReference{ + ID: "1", + }, + } + res, err := client.UpdateOrchestrationRouterWithContext(context.Background(), "1", *input) + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestrationUnrouted_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1/unrouted", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"orchestration_path": {"type": "unrouted", "parent": {"id": "1"}}}`)) + }) + client := defaultTestClient(server.URL, "foo") + var opts *GetOrchestrationUnroutedOptions + res, err := client.GetOrchestrationUnroutedWithContext(context.Background(), "1", opts) + + want := &OrchestrationUnrouted{ + Type: "unrouted", + Parent: &APIReference{ + ID: "1", + }, + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestOrchestrationUnrouted_Update(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/1/unrouted", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, _ = w.Write([]byte(`{"orchestration_path": {"type": "unrouted", "parent": {"id": "1"}}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := &OrchestrationUnrouted{Type: "unrouted"} + want := &OrchestrationUnrouted{ + Type: "unrouted", + Parent: &APIReference{ + ID: "1", + }, + } + res, err := client.UpdateOrchestrationUnroutedWithContext(context.Background(), "1", *input) + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestServiceOrchestration_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/services/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"orchestration_path": {"type": "service", "parent": {"id": "1"}}}`)) + }) + client := defaultTestClient(server.URL, "foo") + var opts *GetServiceOrchestrationOptions + res, err := client.GetServiceOrchestrationWithContext(context.Background(), "1", opts) + + want := &ServiceOrchestration{ + Type: "service", + Parent: &APIReference{ + ID: "1", + }, + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestServiceOrchestration_Update(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/services/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, _ = w.Write([]byte(`{"orchestration_path": {"type": "service", "parent": {"id": "1"}}}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := &ServiceOrchestration{Type: "service"} + want := &ServiceOrchestration{ + Type: "service", + Parent: &APIReference{ + ID: "1", + }, + } + res, err := client.UpdateServiceOrchestrationWithContext(context.Background(), "1", *input) + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestServiceOrchestrationActive_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/services/1/active", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = w.Write([]byte(`{"active": true}`)) + }) + client := defaultTestClient(server.URL, "foo") + res, err := client.GetServiceOrchestrationActiveWithContext(context.Background(), "1") + + want := &ServiceOrchestrationActive{ + Active: true, + } + + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestServiceOrchestrationActive_Update(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/event_orchestrations/services/1/active", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, _ = w.Write([]byte(`{"active": true}`)) + }) + + client := defaultTestClient(server.URL, "foo") + input := &ServiceOrchestrationActive{Active: true} + want := &ServiceOrchestrationActive{ + Active: true, + } + res, err := client.UpdateServiceOrchestrationActiveWithContext(context.Background(), "1", *input) + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +}