diff --git a/README.md b/README.md index 06ab3f3d..bc7cd50c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ API represented by their own sub commands. For an exhaustive list of sub-command __Install:__ ``` -cd $GOPATH/github.com/PagerDuty/go-pagerduty +cd $GOPATH/src/github.com/PagerDuty/go-pagerduty go build -o $GOPATH/bin/pd command/* ``` diff --git a/command/event_v2_manage.go b/command/event_v2_manage.go new file mode 100644 index 00000000..e6f86c01 --- /dev/null +++ b/command/event_v2_manage.go @@ -0,0 +1,67 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + pagerduty "github.com/PagerDuty/go-pagerduty" + log "github.com/Sirupsen/logrus" + "github.com/mitchellh/cli" +) + +type EventV2Manage struct { + Meta +} + +func EventV2ManageCommand() (cli.Command, error) { + return &EventV2Manage{}, nil +} + +func (c *EventV2Manage) Help() string { + helpText := ` + pd event-v2 manage Manage Events from json file + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +func (c *EventV2Manage) Synopsis() string { + return "Create a New V2 Event" +} + +func (c *EventV2Manage) Run(args []string) int { + flags := c.Meta.FlagSet("event-v2 manage") + 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 + } + var e pagerduty.V2Event + 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(&e); err != nil { + log.Errorln("Failed to decode json. Error:", err) + return -1 + } + log.Debugf("%#v", e) + if _, err := pagerduty.ManageEvent(e); err != nil { + log.Error(err) + return -1 + } + return 0 +} diff --git a/command/main.go b/command/main.go index 7b72ea12..78965fe3 100644 --- a/command/main.go +++ b/command/main.go @@ -26,6 +26,8 @@ func loadCommands() map[string]cli.CommandFactory { "escalation-policy show": EscalationPolicyShowCommand, "escalation-policy update": EscalationPolicyUpdateCommand, + "event-v2 manage": EventV2ManageCommand, + "incident list": IncidentListCommand, "incident manage": IncidentManageCommand, "incident show": IncidentShowCommand, @@ -92,6 +94,9 @@ func loadCommands() map[string]cli.CommandFactory { "user notification-rule delete": UserNotificationRuleDeleteCommand, "user notification-rule show": UserNotificationRuleShowCommand, "user notification-rule update": UserNotificationRuleUpdateCommand, + + "vendor list": VendorListCommand, + "vendor show": VendorShowCommand, } } diff --git a/command/vendor_list.go b/command/vendor_list.go new file mode 100644 index 00000000..8d5648f2 --- /dev/null +++ b/command/vendor_list.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "github.com/PagerDuty/go-pagerduty" + log "github.com/Sirupsen/logrus" + "github.com/mitchellh/cli" + "gopkg.in/yaml.v2" + "strings" +) + +type VendorList struct { + Meta +} + +func VendorListCommand() (cli.Command, error) { + return &VendorList{}, nil +} + +func (c *VendorList) Help() string { + helpText := ` + pd vendor list List vendors + + Options: + + -query Filter vendors with certain name + + ` + return strings.TrimSpace(helpText) +} + +func (c *VendorList) Synopsis() string { + return "List vendors within PagerDuty, optionally filtered by a search query" +} + +func (c *VendorList) Run(args []string) int { + var query string + + flags := c.Meta.FlagSet("user list") + flags.Usage = func() { fmt.Println(c.Help()) } + flags.StringVar(&query, "query", "", "Show vendors whose names contain the query") + + 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.ListVendorOptions{ + Query: query, + } + if resp, err := client.ListVendors(opts); err != nil { + log.Error(err) + return -1 + } else { + for i, p := range resp.Vendors { + fmt.Println("Entry: ", i) + data, err := yaml.Marshal(p) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + } + } + + return 0 +} diff --git a/command/vendor_show.go b/command/vendor_show.go new file mode 100644 index 00000000..f42a753b --- /dev/null +++ b/command/vendor_show.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + log "github.com/Sirupsen/logrus" + "github.com/mitchellh/cli" + "gopkg.in/yaml.v2" + "strings" +) + +type VendorShow struct { + Meta +} + +func VendorShowCommand() (cli.Command, error) { + return &VendorShow{}, nil +} + +func (c *VendorShow) Help() string { + helpText := ` + vendor show Show details of an individual vendor + + Options: + -id Vendor ID + ` + return strings.TrimSpace(helpText) +} + +func (c *VendorShow) Synopsis() string { return "Get details about a vendor" } + +func (c *VendorShow) Run(args []string) int { + log.Println(args) + var id string + flags := c.Meta.FlagSet("vendor show") + flags.Usage = func() { fmt.Println(c.Help()) } + flags.StringVar(&id, "id", "", "Vendor 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 id == "" { + log.Error("You must specify the vendor id using -id flag") + return -1 + } + client := c.Meta.Client() + result, err := client.GetVendor(id) + if err != nil { + log.Error(err) + return -1 + } + data, err := yaml.Marshal(result) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(data)) + return 0 +} diff --git a/contact_method.go b/contact_method.go new file mode 100644 index 00000000..0c7d37ef --- /dev/null +++ b/contact_method.go @@ -0,0 +1,62 @@ +package pagerduty + +import ( + "fmt" + "net/http" +) + +// ContactMethod is a way of contacting the user. +type ContactMethod struct { + ID string `json:"id"` + Type string `json:"type"` + Summary string `json:"summary"` + Self string `json:"self"` + Label string `json:"label"` + Address string `json:"address"` + SendShortEmail bool `json:"send_short_email,omitempty"` + SendHTMLEmail bool `json:"send_html_email,omitempty"` + Blacklisted bool `json:"blacklisted,omitempty"` + CountryCode int `json:"country_code,omitempty"` + Enabled bool `json:"enabled,omitempty"` + HTMLUrl string `json:"html_url"` +} + + +// ListContactMethodsResponse is the data structure returned from calling the ListContactMethods API endpoint. +type ListContactMethodsResponse struct { + APIListObject + ContactMethods []ContactMethod +} + +// ListContactMethods lists all contact methods for a user. +// TODO: Unify with `ListUserContactMethods`. +func (c *Client) ListContactMethods(userID string) (*ListContactMethodsResponse, error) { + resp, err := c.get("/users/" + userID + "/contact_methods") + if err != nil { + return nil, err + } + var result ListContactMethodsResponse + return &result, c.decodeJSON(resp, &result) +} + +// GetContactMethod gets details about a contact method. +func (c *Client) GetContactMethod(userID, id string) (*ContactMethod, error) { + resp, err := c.get("/users/" + userID + "/contact_methods/" + id) + return getContactMethodFromResponse(c, resp, err) +} + +func getContactMethodFromResponse(c *Client, resp *http.Response, err error) (*ContactMethod, error) { + if err != nil { + return nil, err + } + var target map[string]ContactMethod + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "contact_method" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} diff --git a/escalation_policy.go b/escalation_policy.go index c8b50fe0..723ccda1 100644 --- a/escalation_policy.go +++ b/escalation_policy.go @@ -23,7 +23,7 @@ type EscalationPolicy struct { APIObject Name string `json:"name,omitempty"` EscalationRules []EscalationRule `json:"escalation_rules,omitempty"` - Services []APIReference `json:"services,omitempty"` + Services []APIObject `json:"services,omitempty"` NumLoops uint `json:"num_loops,omitempty"` Teams []APIReference `json:"teams,omitempty"` Description string `json:"description,omitempty"` diff --git a/incident.go b/incident.go index c172bb09..04825301 100644 --- a/incident.go +++ b/incident.go @@ -7,28 +7,56 @@ import ( "github.com/google/go-querystring/query" ) -// Acknowledgement is the data structure of an acknoledgement of an incident. +// Acknowledgement is the data structure of an acknowledgement of an incident. type Acknowledgement struct { - At string - Acknowledger APIObject + At string `json:"at,omitempty"` + Acknowledger APIObject `json:"acknowledger,omitempty"` } // PendingAction is the data structure for any pending actions on an incident. type PendingAction struct { - Type string - At string + Type string `json:"type,omitempty"` + At string `json:"at,omitempty"` } // Assignment is the data structure for an assignment of an incident type Assignment struct { - At string - Assignee APIObject + At string `json:"at,omitempty"` + Assignee APIObject `json:"assignee,omitempty"` +} + +// AlertCounts is the data structure holding a summary of the number of alerts by status of an incident. +type AlertCounts struct { + Triggered uint `json:"triggered,omitempty"` + Resolved uint `json:"resolved,omitempty"` + All uint `json:"all,omitempty"` +} + +// Priority is the data structure describing the priority of an incident. +type Priority struct { + APIObject + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +// Resolve reason is the data structure describing the reason an incident was resolved +type ResolveReason struct { + Type string `json:"type,omitempty"` + Incident APIObject `json:"incident"` +} + +// IncidentBody is the datastructure containing data describing the incident. +type IncidentBody struct { + Type string `json:"type,omitempty"` + Details string `json:"details,omitempty"` } // Incident is a normalized, de-duplicated event generated by a PagerDuty integration. type Incident struct { APIObject IncidentNumber uint `json:"incident_number,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` CreatedAt string `json:"created_at,omitempty"` PendingActions []PendingAction `json:"pending_actions,omitempty"` IncidentKey string `json:"incident_key,omitempty"` @@ -40,10 +68,14 @@ type Incident struct { FirstTriggerLogEntry APIObject `json:"first_trigger_log_entry,omitempty"` EscalationPolicy APIObject `json:"escalation_policy,omitempty"` Teams []APIObject `json:"teams,omitempty"` + Priority Priority `json:"priority,omitempty"` Urgency string `json:"urgency,omitempty"` Status string `json:"status,omitempty"` Id string `json:"id,omitempty"` - Priority APIObject `json:"priority,omitempty"` + ResolveReason ResolveReason `json:"resolve_reason,omitempty"` + AlertCounts AlertCounts `json:"alert_counts,omitempty"` + Body IncidentBody `json:"body,omitempty"` + IsMergeable bool `json:"is_mergeable,omitempty"` } // ListIncidentsResponse is the response structure when calling the ListIncident API endpoint. @@ -90,25 +122,25 @@ type CreateIncident struct { // createIncidentResponse is returned from the API when creating a response. type createIncidentResponse struct { - Incident Incident `json:incident` + Incident Incident `json:"incident"` } // CreateIncidentOptions is the structure used when POSTing to the CreateIncident API endpoint. type CreateIncidentOptions struct { - Type string `json:"type"` - Title string `json:"title"` - Service APIReference `json:"service"` - Priority APIReference `json:"priority"` - IncidentKey string `json:"incident_key"` - Body APIDetails `json:"body"` - EscalationPolicy APIReference `json:"escalation_policy"` + Type string `json:"type"` + Title string `json:"title"` + Service *APIReference `json:"service"` + Priority *APIReference `json:"priority,omitempty"` + IncidentKey string `json:"incident_key,omitempty"` + Body *APIDetails `json:"body,omitempty"` + EscalationPolicy *APIReference `json:"escalation_policy,omitempty"` } // CreateIncident creates an incident synchronously without a corresponding event from a monitoring service. -func (c *Client) CreateIncident(from string, i *CreateIncident) (*Incident, error) { +func (c *Client) CreateIncident(from string, o *CreateIncidentOptions) (*Incident, error) { headers := make(map[string]string) headers["From"] = from - resp, e := c.post("/incidents", i, &headers) + resp, e := c.post("/incidents", o, &headers) if e != nil { return nil, e } @@ -132,6 +164,16 @@ func (c *Client) ManageIncidents(from string, incidents []Incident) error { return e } +// MergeIncidents a list of source incidents into a specified incident. +func (c *Client) MergeIncidents(from string, id string, incidents []Incident) error { + r := make(map[string][]Incident) + r["source_incidents"] = incidents + headers := make(map[string]string) + headers["From"] = from + _, e := c.put("/incidents/"+id+"/merge", r, &headers) + return e +} + // GetIncident shows detailed information about an incident. func (c *Client) GetIncident(id string) (*Incident, error) { resp, err := c.get("/incidents/" + id) @@ -176,15 +218,15 @@ func (c *Client) ListIncidentNotes(id string) ([]IncidentNote, error) { // IncidentAlert is a alert for the specified incident. type IncidentAlert struct { - ID string `json:"id,omitempty"` - Summary string `json:"summary,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - AlertKey string `json:"alert_key,omitempty"` + ID string `json:"id,omitempty"` + Summary string `json:"summary,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + AlertKey string `json:"alert_key,omitempty"` } // ListIncidentAlerts lists existing alerts for the specified incident. func (c *Client) ListIncidentAlerts(id string) ([]IncidentAlert, error) { - resp, err := c.get("/incidents/"+id+"/alerts") + resp, err := c.get("/incidents/" + id + "/alerts") if err != nil { return nil, err } @@ -202,8 +244,11 @@ func (c *Client) ListIncidentAlerts(id string) ([]IncidentAlert, error) { // CreateIncidentNote creates a new note for the specified incident. func (c *Client) CreateIncidentNote(id string, note IncidentNote) error { data := make(map[string]IncidentNote) + headers := make(map[string]string) + headers["From"] = note.User.Summary + data["note"] = note - _, err := c.post("/incidents/"+id+"/notes", data, nil) + _, err := c.post("/incidents/"+id+"/notes", data, &headers) return err } @@ -242,3 +287,18 @@ func (c *Client) ListIncidentLogEntries(id string, o ListIncidentLogEntriesOptio var result ListIncidentLogEntriesResponse return &result, c.decodeJSON(resp, &result) } + +// Alert is a list of all of the alerts that happened to an incident. +type Alert struct { + APIObject + Service APIObject `json:"service,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + Status string `json:"status,omitempty"` + AlertKey string `json:"alert_key,omitempty"` + Incident APIObject `json:"incident,omitempty"` +} + +type ListAlertResponse struct { + APIListObject + Alerts []Alert `json:"alerts,omitempty"` +} diff --git a/log_entry.go b/log_entry.go index e1fecc43..1b5e2fc0 100644 --- a/log_entry.go +++ b/log_entry.go @@ -68,7 +68,7 @@ func (c *Client) ListLogEntries(o ListLogEntriesOptions) (*ListLogEntryResponse, // GetLogEntryOptions is the data structure used when calling the GetLogEntry API endpoint. type GetLogEntryOptions struct { - TimeZone string `url:"timezone,omitempty"` + TimeZone string `url:"time_zone,omitempty"` Includes []string `url:"include,omitempty,brackets"` } diff --git a/maintenance_window.go b/maintenance_window.go index 2316f0fd..d1985138 100644 --- a/maintenance_window.go +++ b/maintenance_window.go @@ -7,6 +7,15 @@ import ( "github.com/google/go-querystring/query" ) +// CreateMaintenanceWindowOptions is used to create a MaintenanceWindow +type CreateMaintenanceWindowOptions struct { + APIObject + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + Description string `json:"description"` + Services []APIObject `json:"services"` +} + // MaintenanceWindow is used to temporarily disable one or more services for a set period of time. type MaintenanceWindow struct { APIObject @@ -49,7 +58,20 @@ func (c *Client) ListMaintenanceWindows(o ListMaintenanceWindowsOptions) (*ListM return &result, c.decodeJSON(resp, &result) } +// CreateMaintenanceWindow creates a new maintenance window for the specified services. +// TODO: Unify with `CreateMaintenanceWindows`. +func (c *Client) CreateMaintenanceWindow(from string, o CreateMaintenanceWindowOptions) (*MaintenanceWindow, error) { + data := make(map[string]CreateMaintenanceWindowOptions) + o.Type = "maintenance_window" + data["maintenance_window"] = o + headers := make(map[string]string) + headers["From"] = from + resp, err := c.post("/maintenance_windows", data, &headers) + return getMaintenanceWindowFromResponse(c, resp, err) +} + // CreateMaintenanceWindows creates a new maintenance window for the specified services. +// TODO: Unify with `CreateMaintenanceWindow`. func (c *Client) CreateMaintenanceWindows(m MaintenanceWindow) (*MaintenanceWindow, error) { data := make(map[string]MaintenanceWindow) data["maintenance_window"] = m diff --git a/on_call.go b/on_call.go index 6c7c3caf..537ef102 100644 --- a/on_call.go +++ b/on_call.go @@ -6,12 +6,12 @@ import ( // OnCall represents a contiguous unit of time for which a user will be on call for a given escalation policy and escalation rule. type OnCall struct { - User APIObject `json:"user,omitempty"` - Schedule APIObject `json:"schedule,omitempty"` - EscalationPolicy APIObject `json:"escalation_policy,omitempty"` - EscalationLevel uint `json:"escalation_level,omitempty"` - Start string `json:"start,omitempty"` - End string `json:"end,omitempty"` + User User `json:"user,omitempty"` + Schedule Schedule `json:"schedule,omitempty"` + EscalationPolicy EscalationPolicy `json:"escalation_policy,omitempty"` + EscalationLevel uint `json:"escalation_level,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` } // ListOnCallsResponse is the data structure returned from calling the ListOnCalls API endpoint. diff --git a/user.go b/user.go index 92b170ed..08305766 100644 --- a/user.go +++ b/user.go @@ -7,42 +7,41 @@ import ( "github.com/google/go-querystring/query" ) -// ContactMethod is a way of contacting the user. -type ContactMethod struct { - ID string - Label string - Address string - Type string - SendShortEmail bool `json:"send_short_email"` -} - // NotificationRule is a rule for notifying the user. type NotificationRule struct { - ID string + ID string `json:"id"` StartDelayInMinutes uint `json:"start_delay_in_minutes"` CreatedAt string `json:"created_at"` ContactMethod ContactMethod `json:"contact_method"` - Urgency string - Type string + Urgency string `json:"urgency"` + Type string `json:"type"` } // User is a member of a PagerDuty account that has the ability to interact with incidents and other data on the account. type User struct { APIObject - Name string `json:"name"` - Email string `json:"email"` - Timezone string `json:"timezone,omitempty"` - Color string `json:"color,omitempty"` - Role string `json:"role,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - Description string `json:"description,omitempty"` - InvitationSent bool + Type string `json:"type"` + Name string `json:"name"` + Summary string `json:"summary"` + Email string `json:"email"` + Timezone string `json:"time_zone,omitempty"` + Color string `json:"color,omitempty"` + Role string `json:"role,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Description string `json:"description,omitempty"` + InvitationSent bool `json:"invitation_sent,omitempty"` ContactMethods []ContactMethod `json:"contact_methods"` NotificationRules []NotificationRule `json:"notification_rules"` JobTitle string `json:"job_title,omitempty"` Teams []Team } +// ContactMethodResponse is the data structure returned from calling the GetUserContactMethod API endpoint. +type ContactMethodResponse struct { + ContactMethods []ContactMethod `json:"contact_methods"` + Total int +} + // ListUsersResponse is the data structure returned from calling the ListUsers API endpoint. type ListUsersResponse struct { APIListObject @@ -62,6 +61,12 @@ type GetUserOptions struct { Includes []string `url:"include,omitempty,brackets"` } +// ListUserContactMethodsResponse is the data structure returned from calling the ListUserContactMethods API endpoint. +type ListUserContactMethodsResponse struct { + APIListObject + ContactMethods []ContactMethod `json:"contact_methods"` +} + // ListUsers lists users of your PagerDuty account, optionally filtered by a search query. func (c *Client) ListUsers(o ListUsersOptions) (*ListUsersResponse, error) { v, err := query.Values(o) @@ -100,6 +105,17 @@ func (c *Client) GetUser(id string, o GetUserOptions) (*User, error) { return getUserFromResponse(c, resp, err) } +// GetUserContactMethod fetches contact methods of the existing user. +func (c *Client) GetUserContactMethod(id string) (*ContactMethodResponse, error) { + resp, err := c.get("/users/" + id + "/contact_methods") + if err != nil { + return nil, err + } + + var result ContactMethodResponse + return &result, c.decodeJSON(resp, &result) +} + // UpdateUser updates an existing user. func (c *Client) UpdateUser(u User) (*User, error) { v := make(map[string]User) @@ -123,3 +139,14 @@ func getUserFromResponse(c *Client, resp *http.Response, err error) (*User, erro } return &t, nil } + +// List a user's contact methods +// TODO: Unify with `ListContactMethods`. +func (c *Client) ListUserContactMethods(id string) (*ListUserContactMethodsResponse, error) { + resp, err := c.get("/users/" + id + "/contact_methods") + if err != nil { + return nil, err + } + var result ListUserContactMethodsResponse + return &result, c.decodeJSON(resp, &result) +} diff --git a/vendor.go b/vendor.go index 7a5bdd81..cb971602 100644 --- a/vendor.go +++ b/vendor.go @@ -10,15 +10,18 @@ import ( // Vendor represents a specific type of integration. AWS Cloudwatch, Splunk, Datadog, etc are all examples of vendors that can be integrated in PagerDuty by making an integration. type Vendor struct { APIObject - Name string `json:"name,omitempty"` - LogoURL string `json:"logo_url,omitempty"` - LongName string `json:"long_name,omitempty"` - WebsiteURL string `json:"website_url,omitempty"` - Description string `json:"description,omitempty"` - Connectable bool `json:"connectable,omitempty"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - GenericServiceType string `json:"generic_service_type,omitempty"` - IntegrationGuideURL string `json:"integration_guide_url,omitempty"` + Name string `json:"name,omitempty"` + LogoURL string `json:"logo_url,omitempty"` + LongName string `json:"long_name,omitempty"` + WebsiteURL string `json:"website_url,omitempty"` + Description string `json:"description,omitempty"` + Connectable bool `json:"connectable,omitempty"` + ThumbnailURL string `json:"thumbnail_url,omitempty"` + GenericServiceType string `json:"generic_service_type,omitempty"` + IntegrationGuideURL string `json:"integration_guide_url,omitempty"` + AlertCreationDefault string `json:"alert_creation_default,omitempty"` + AlertCreationEditable bool `json:"alert_creation_editable,omitempty"` + IsPDCEF bool `json:"is_pd_cef,omitempty"` } // ListVendorResponse is the data structure returned from calling the ListVendors API endpoint.