From 6b419e65944b12d9bf679b5cb407f0182c3138d1 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 26 Sep 2016 22:14:08 +0200 Subject: [PATCH 01/26] Add documentation --- website/source/assets/stylesheets/_docs.scss | 1 + .../providers/pagerduty/index.html.markdown | 41 +++++++++++ .../r/escalation_policy.html.markdown | 62 +++++++++++++++++ .../pagerduty/r/service.html.markdown | 68 +++++++++++++++++++ .../providers/pagerduty/r/team.html.markdown | 37 ++++++++++ .../providers/pagerduty/r/user.html.markdown | 55 +++++++++++++++ website/source/layouts/pagerduty.erb | 35 ++++++++++ 7 files changed, 299 insertions(+) create mode 100644 website/source/docs/providers/pagerduty/index.html.markdown create mode 100644 website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown create mode 100644 website/source/docs/providers/pagerduty/r/service.html.markdown create mode 100644 website/source/docs/providers/pagerduty/r/team.html.markdown create mode 100644 website/source/docs/providers/pagerduty/r/user.html.markdown create mode 100644 website/source/layouts/pagerduty.erb diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index ed6ec5cda..af6776ad4 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -37,6 +37,7 @@ body.layout-mailgun, body.layout-mysql, body.layout-openstack, body.layout-packet, +body.layout-pagerduty, body.layout-postgresql, body.layout-powerdns, body.layout-rabbitmq, diff --git a/website/source/docs/providers/pagerduty/index.html.markdown b/website/source/docs/providers/pagerduty/index.html.markdown new file mode 100644 index 000000000..c7504095c --- /dev/null +++ b/website/source/docs/providers/pagerduty/index.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "pagerduty" +page_title: "Provider: PagerDuty" +sidebar_current: "docs-pagerduty-index" +description: |- + PagerDuty is an alarm aggregation and dispatching service +--- + +# PagerDuty Provider + +[PagerDuty](https://www.pagerduty.com/) is an alarm aggregation and dispatching service for system administrators and support teams. It collects alerts from your monitoring tools, gives you an overall view of all of your monitoring alarms, and alerts an on duty engineer if there’s a problem. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the PagerDuty provider +provider "pagerduty" { + token = "${var.pagerduty_token}" +} + +# Create a PagerDuty team +resource "pagerduty_team" "engineering" { + name = "Engineering" + description = "All engineering" +} + +# Create a PagerDuty user +resource "pagerduty_user" "earline" { + name = "Earline Greenholt" + email = "125.greenholt.earline@graham.name" + teams = ["${pagerduty_team.engineering.id}"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `token` - (Required) The v2 authorization token. See [API Documentation](https://v2.developer.pagerduty.com/docs/authentication) for more information. diff --git a/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown b/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown new file mode 100644 index 000000000..1128ca196 --- /dev/null +++ b/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown @@ -0,0 +1,62 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_escalation_policy" +sidebar_current: "docs-pagerduty-resource-escalation_policy" +description: |- + Creates and manages an escalation policy in PagerDuty. +--- + +# pagerduty\_escalation_policy + +An [escalation policy](https://v2.developer.pagerduty.com/v2/page/api-reference#!/Escalation_Policies/get_escalation_policies) determines what user or schedule will be notified first, second, and so on when an incident is triggered. Escalation policies are used by one or more services. + + +## Example Usage + +``` +resource "pagerduty_user" "example" { + name = "Earline Greenholt" + email = "125.greenholt.earline@graham.name" + teams = ["${pagerduty_team.example.id}"] +} + +resource "pagerduty_escalation_policy" "example" { + name = "Engineering" + description = "Engineering Escalation Policy" + num_loops = 2 + escalation_rules = < + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %> From b1bf972d4da4a0bc562c538864c96716b733ba17 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 26 Sep 2016 11:36:26 +0200 Subject: [PATCH 02/26] Vendoring go-pagerduty --- .../PagerDuty/go-pagerduty/Dockerfile | 4 + .../PagerDuty/go-pagerduty/LICENSE.txt | 14 + .../PagerDuty/go-pagerduty/Makefile | 19 ++ .../PagerDuty/go-pagerduty/README.md | 73 +++++ .../PagerDuty/go-pagerduty/ability.go | 22 ++ .../PagerDuty/go-pagerduty/addon.go | 96 +++++++ .../PagerDuty/go-pagerduty/client.go | 132 +++++++++ .../go-pagerduty/escalation_policy.go | 114 ++++++++ .../PagerDuty/go-pagerduty/event.go | 52 ++++ .../PagerDuty/go-pagerduty/incident.go | 147 ++++++++++ .../PagerDuty/go-pagerduty/log_entry.go | 83 ++++++ .../go-pagerduty/maintenance_window.go | 100 +++++++ .../PagerDuty/go-pagerduty/notification.go | 44 +++ .../PagerDuty/go-pagerduty/on_call.go | 47 ++++ .../PagerDuty/go-pagerduty/schedule.go | 262 ++++++++++++++++++ .../PagerDuty/go-pagerduty/service.go | 202 ++++++++++++++ .../github.com/PagerDuty/go-pagerduty/team.go | 105 +++++++ .../github.com/PagerDuty/go-pagerduty/user.go | 124 +++++++++ .../PagerDuty/go-pagerduty/webhook.go | 37 +++ vendor/vendor.json | 6 + 20 files changed, 1683 insertions(+) create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/Dockerfile create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/LICENSE.txt create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/Makefile create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/README.md create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/ability.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/addon.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/client.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/event.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/incident.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/log_entry.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/notification.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/on_call.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/schedule.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/service.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/team.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/user.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/webhook.go diff --git a/vendor/github.com/PagerDuty/go-pagerduty/Dockerfile b/vendor/github.com/PagerDuty/go-pagerduty/Dockerfile new file mode 100644 index 000000000..650a69214 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/Dockerfile @@ -0,0 +1,4 @@ +FROM golang +ADD . /go/src/github.com/PagerDuty/go-pagerduty +WORKDIR /go/src/github.com/PagerDuty/go-pagerduty +RUN go get ./... && go test -v -race -cover ./... diff --git a/vendor/github.com/PagerDuty/go-pagerduty/LICENSE.txt b/vendor/github.com/PagerDuty/go-pagerduty/LICENSE.txt new file mode 100644 index 000000000..0c80aa6ce --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/LICENSE.txt @@ -0,0 +1,14 @@ +Copyright:: Copyright (c) 2016 PagerDuty, Inc. +License:: Apache License, Version 2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/PagerDuty/go-pagerduty/Makefile b/vendor/github.com/PagerDuty/go-pagerduty/Makefile new file mode 100644 index 000000000..108ccfa84 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/Makefile @@ -0,0 +1,19 @@ +SOURCEDIR=. +SOURCES = $(shell find $(SOURCEDIR) -name '*.go') +VERSION=$(git describe --always --tags) +BINARY=bin/pd + +bin: $(BINARY) + +$(BINARY): $(SOURCES) + go build -o $(BINARY) command/* + +.PHONY: build +build: + go get ./... + go test ./... + go vet ./... + +.PHONY: test +test: + go test ./... diff --git a/vendor/github.com/PagerDuty/go-pagerduty/README.md b/vendor/github.com/PagerDuty/go-pagerduty/README.md new file mode 100644 index 000000000..b1878806e --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/README.md @@ -0,0 +1,73 @@ +# 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) + +## Installation + +``` +go get github.com/PagerDuty/go-pagerduty +``` + +## Usage + +### CLI + +The CLI requires authentication token, which can be sepcified in `.pd.yml` +file in home directory of the user, or passed as command line argument. +Example of config file: + +```yaml +--- +authtoken: fooBar +``` + +`pd` command provides a single entrypoint for all the API endpoints, with individual +API represented by their own sub commands. For an exhaustive list of sub-commands, try: + +``` +pd --help +``` + +An example of the `service` sub-command + +``` +pd service list +``` + + +### From golang libraries + +```go +package main + +import ( + "fmt" + "github.com/PagerDuty/go-pagerduty" +) + +var authtoken = "" // Set your auth token here + +func main() { + var opts pagerduty.ListEscalationPoliciesOptions + client := pagerduty.NewClient(authtoken) + if eps, err := client.ListEscalationPolicies(opts); err != nil { + panic(err) + } else { + for _, p := range eps.EscalationPolicies { + fmt.Println(p.Name) + } + } +} +``` + +## License +[Apache 2](http://www.apache.org/licenses/LICENSE-2.0) + +## Contributing + +1. Fork it ( https://github.com/PagerDuty/go-pagerduty/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/vendor/github.com/PagerDuty/go-pagerduty/ability.go b/vendor/github.com/PagerDuty/go-pagerduty/ability.go new file mode 100644 index 000000000..26fe90968 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/ability.go @@ -0,0 +1,22 @@ +package pagerduty + +// ListAbilityResponse is the response when calling the ListAbility API endpoint. +type ListAbilityResponse struct { + Abilities []string `json:"abilities"` +} + +// ListAbilities lists all abilities on your account. +func (c *Client) ListAbilities() (*ListAbilityResponse, error) { + resp, err := c.get("/abilities") + if err != nil { + return nil, err + } + var result ListAbilityResponse + return &result, c.decodeJSON(resp, &result) +} + +// TestAbility Check if your account has the given ability. +func (c *Client) TestAbility(ability string) error { + _, err := c.get("/abilities/" + ability) + return err +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/addon.go b/vendor/github.com/PagerDuty/go-pagerduty/addon.go new file mode 100644 index 000000000..ae9d4e572 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/addon.go @@ -0,0 +1,96 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +// 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()) + if err != nil { + return nil, err + } + var result ListAddonResponse + return &result, c.decodeJSON(resp, &result) +} + +// 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) + defer resp.Body.Close() + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("Failed to create. HTTP Status code: %d", resp.StatusCode) + } + return getAddonFromResponse(c, resp) +} + +// DeleteAddon deletes an add-on from your account. +func (c *Client) DeleteAddon(id string) error { + _, 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) + 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, nil) + 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 { + return nil, err + } + a, ok := result["addon"] + if !ok { + return nil, fmt.Errorf("JSON response does not have 'addon' field") + } + return &a, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/client.go b/vendor/github.com/PagerDuty/go-pagerduty/client.go new file mode 100644 index 000000000..3cc17e00d --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/client.go @@ -0,0 +1,132 @@ +package pagerduty + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +const ( + apiEndpoint = "https://api.pagerduty.com" +) + +// APIObject represents generic api json response that is shared by most +// domain object (like escalation +type APIObject struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Summary string `json:"summary,omitempty"` + Self string `json:"self,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 +} + +// NewClient creates an API client +func NewClient(authToken string) *Client { + return &Client{ + authToken: authToken, + } +} + +func (c *Client) delete(path string) (*http.Response, error) { + return c.do("DELETE", path, nil, nil) +} + +func (c *Client) put(path string, payload interface{}, headers *map[string]string) (*http.Response, error) { + + if payload != nil { + data, err := json.Marshal(payload) + if err != nil { + return nil, err + } + return c.do("PUT", path, bytes.NewBuffer(data), headers) + } + return c.do("PUT", path, nil, headers) +} + +func (c *Client) post(path string, payload interface{}) (*http.Response, error) { + data, err := json.Marshal(payload) + if err != nil { + return nil, err + } + return c.do("POST", path, bytes.NewBuffer(data), nil) +} + +func (c *Client) get(path string) (*http.Response, error) { + return c.do("GET", path, nil, nil) +} + +func (c *Client) do(method, path string, body io.Reader, headers *map[string]string) (*http.Response, error) { + endpoint := apiEndpoint + path + req, _ := http.NewRequest(method, endpoint, body) + req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2") + if headers != nil { + for k, v := range *headers { + req.Header.Set(k, v) + } + } + 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 resp, fmt.Errorf("Error calling the API endpoint: %v", err) + } + 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) 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 +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go b/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go new file mode 100644 index 000000000..849d1db28 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go @@ -0,0 +1,114 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +const ( + escPath = "/escalation_policies" +) + +// EscalationRule is a rule for an escalation policy to trigger. +type EscalationRule struct { + 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 []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"` +} + +// 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"` + UserIDs []string `url:"user_ids,omitempty,brackets"` + TeamIDs []string `url:"team_ids,omitempty,brackets"` + Includes []string `url:"include,omitempty,brackets"` + SortBy string `url:"sort_by,omitempty"` +} + +// 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(escPath + "?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListEscalationPoliciesResponse + return &result, c.decodeJSON(resp, &result) +} + +// CreateEscalationPolicy creates a new escalation policy. +func (c *Client) CreateEscalationPolicy(e EscalationPolicy) (*EscalationPolicy, error) { + data := make(map[string]EscalationPolicy) + 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(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(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) { + data := make(map[string]EscalationPolicy) + data["escalation_policy"] = *e + resp, err := c.put(escPath+"/"+id, data, nil) + 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 target map[string]EscalationPolicy + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "escalation_policy" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/event.go b/vendor/github.com/PagerDuty/go-pagerduty/event.go new file mode 100644 index 000000000..5a7b33a24 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/event.go @@ -0,0 +1,52 @@ +package pagerduty + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +const eventEndPoint = "https://events.pagerduty.com/generic/2010-04-15/create_event.json" + +// Event stores data for problem reporting, acknowledgement, and resolution. +type Event struct { + ServiceKey string `json:"service_key"` + Type string `json:"event_type"` + IncidentKey string `json:"incident_key,omitempty"` + Description string `json:"description"` + Client string `json:"client,omitempty"` + ClientURL string `json:"client_url,omitempty"` + Details interface{} `json:"details,omitempty"` + Contexts []interface{} `json:"contexts,omitempty"` +} + +// EventResponse is the data returned from the CreateEvent API endpoint. +type EventResponse struct { + Status string `json:"status"` + Message string `json:"message"` + IncidentKey string `json:"incident_key"` +} + +// CreateEvent sends PagerDuty an event to report, acknowledge, or resolve a problem. +func CreateEvent(e Event) (*EventResponse, error) { + data, err := json.Marshal(e) + if err != nil { + return nil, err + } + req, _ := http.NewRequest("POST", eventEndPoint, bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP Status Code: %d", resp.StatusCode) + } + var eventResponse EventResponse + defer resp.Body.Close() + if err := json.NewDecoder(resp.Body).Decode(&eventResponse); err != nil { + return nil, err + } + return &eventResponse, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/incident.go b/vendor/github.com/PagerDuty/go-pagerduty/incident.go new file mode 100644 index 000000000..da55e5f5d --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/incident.go @@ -0,0 +1,147 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" +) + +// Acknowledgement is the data structure of an acknoledgement of an incident. +type Acknowledgement struct { + At string + Acknowledger APIObject +} + +// PendingAction is the data structure for any pending actions on an incident. +type PendingAction struct { + Type string + At string +} + +// Assignment is the data structure for an assignment of an incident +type Assignment struct { + At string + Assignee APIObject +} + +// Incident is a normalized, de-duplicated event generated by a PagerDuty integration. +type Incident struct { + APIObject + IncidentNumber uint `json:"incident_number,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + PendingActions []PendingAction `json:"pending_actions,omitempty"` + IncidentKey string `json:"incident_key,omitempty"` + Service APIObject `json:"service,omitempty"` + Assignments []Assignment `json:"assignments,omitempty"` + Acknowledgements []Acknowledgement `json:"acknowledgements,omitempty"` + LastStatusChangeAt string `json:"last_status_change_at,omitempty"` + LastStatusChangeBy APIObject `json:"last_status_change_by,omitempty"` + FirstTriggerLogEntry APIObject `json:"last_trigger_log_entry,omitempty"` + EscalationPolicy APIObject `json:"escalation_policy,omitempty"` + Teams []APIObject `json:"teams,omitempty"` + Urgency string `json:"urgency,omitempty"` +} + +// ListIncidentsResponse is the response structure when calling the ListIncident API endpoint. +type ListIncidentsResponse struct { + APIListObject + Incidents []Incident `json:"incidents,omitempty"` +} + +// ListIncidentsOptions is the structure used when passing parameters to the ListIncident API endpoint. +type ListIncidentsOptions struct { + APIListObject + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + DateRange string `url:"date_range,omitempty"` + Statuses []string `url:"statuses,omitempty,brackets"` + IncidentKey string `url:"incident_key,omitempty"` + ServiceIDs []string `url:"service_ids,omitempty,brackets"` + TeamIDs []string `url:"team_ids,omitempty,brackets"` + UserIDs []string `url:"user_ids,omitempty,brackets"` + Urgencies []string `url:"urgencies,omitempty,brackets"` + TimeZone string `url:"time_zone,omitempty"` + SortBy string `url:"sort_by,omitempty"` + Includes []string `url:"include,omitempty,brackets"` +} + +// ListIncidents lists existing incidents. +func (c *Client) ListIncidents(o ListIncidentsOptions) (*ListIncidentsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/incidents?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListIncidentsResponse + return &result, c.decodeJSON(resp, &result) +} + +// ManageIncidents acknowledges, resolves, escalates, or reassigns one or more incidents. +func (c *Client) ManageIncidents(from string, incidents []Incident) error { + r := make(map[string][]Incident) + headers := make(map[string]string) + headers["From"] = from + r["incidents"] = incidents + _, e := c.put("/incidents", 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) + if err != nil { + return nil, err + } + var result map[string]Incident + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + i, ok := result["incident"] + if !ok { + return nil, fmt.Errorf("JSON response does not have incident field") + } + return &i, nil +} + +// IncidentNote is a note for the specified incident. +type IncidentNote struct { + ID string `json:"id,omitempty"` + User APIObject `json:"user,omitempty"` + Content string `json:"content,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +// ListIncidentNotes lists existing notes for the specified incident. +func (c *Client) ListIncidentNotes(id string) ([]IncidentNote, error) { + resp, err := c.get("/incidents/" + id + "/notes") + if err != nil { + return nil, err + } + var result map[string][]IncidentNote + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + notes, ok := result["notes"] + if !ok { + return nil, fmt.Errorf("JSON response does not have notes field") + } + return notes, nil +} + +// CreateIncidentNote creates a new note for the specified incident. +func (c *Client) CreateIncidentNote(id string, note IncidentNote) error { + data := make(map[string]IncidentNote) + data["note"] = note + _, err := c.post("/incidents/"+id+"/notes", data) + return err +} + +// SnoozeIncident sets an incident to not alert for a specified period of time. +func (c *Client) SnoozeIncident(id string, duration uint) error { + data := make(map[string]uint) + data["duration"] = duration + _, err := c.post("/incidents/"+id+"/snooze", data) + return err +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go b/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go new file mode 100644 index 000000000..7e26df7ac --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go @@ -0,0 +1,83 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" +) + +// Agent is the actor who carried out the action. +type Agent APIObject + +// Channel is the means by which the action was carried out. +type Channel struct { + Type string +} + +// LogEntry is a list of all of the events that happened to an incident. +type LogEntry struct { + APIObject + CreatedAt string `json:"created_at"` + Agent Agent + Channel Channel + Incident Incident + Teams []Team + Contexts []string + EventDetails map[string]string +} + +// ListLogEntryResponse is the response data when calling the ListLogEntry API endpoint. +type ListLogEntryResponse struct { + APIListObject + LogEntries []LogEntry `json:"log_entries"` +} + +// ListLogEntriesOptions is the data structure used when calling the ListLogEntry API endpoint. +type ListLogEntriesOptions struct { + APIListObject + TimeZone string `url:"time_zone"` + Since string `url:"omitempty"` + Until string `url:"omitempty"` + IsOverview bool `url:"is_overview,omitempty"` + Includes []string `url:"include,omitempty,brackets"` +} + +// ListLogEntries lists all of the incident log entries across the entire account. +func (c *Client) ListLogEntries(o ListLogEntriesOptions) (*ListLogEntryResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/log_entries?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListLogEntryResponse + return &result, c.decodeJSON(resp, &result) +} + +// GetLogEntryOptions is the data structure used when calling the GetLogEntry API endpoint. +type GetLogEntryOptions struct { + TimeZone string `url:"timezone,omitempty"` + Includes []string `url:"include,omitempty,brackets"` +} + +// GetLogEntry list log entries for the specified incident. +func (c *Client) GetLogEntry(id string, o GetLogEntryOptions) (*LogEntry, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/log_entries/" + id + "?" + v.Encode()) + if err != nil { + return nil, err + } + var result map[string]LogEntry + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + le, ok := result["log_entry"] + if !ok { + return nil, fmt.Errorf("JSON response does not have log_entry field") + } + return &le, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go b/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go new file mode 100644 index 000000000..feb807788 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go @@ -0,0 +1,100 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +// MaintenanceWindow is used to temporarily disable one or more services for a set period of time. +type MaintenanceWindow struct { + APIObject + SequenceNumber uint `json:"sequence_number,omitempty"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + Description string + Services []APIObject + Teams []APIListObject + CreatedBy APIListObject `json:"created_by"` +} + +// ListMaintenanceWindowsResponse is the data structur returned from calling the ListMaintenanceWindows API endpoint. +type ListMaintenanceWindowsResponse struct { + APIListObject + MaintenanceWindows []MaintenanceWindow `json:"maintenance_windows"` +} + +// ListMaintenanceWindowsOptions is the data structure used when calling the ListMaintenanceWindows API endpoint. +type ListMaintenanceWindowsOptions struct { + APIListObject + Query string `url:"query,omitempty"` + Includes []string `url:"include,omitempty,brackets"` + TeamIDs []string `url:"team_ids,omitempty,brackets"` + ServiceIDs []string `url:"service_ids,omitempty,brackets"` + Filter string `url:"filter,omitempty,brackets"` +} + +// ListMaintenanceWindows lists existing maintenance windows, optionally filtered by service and/or team, or whether they are from the past, present or future. +func (c *Client) ListMaintenanceWindows(o ListMaintenanceWindowsOptions) (*ListMaintenanceWindowsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/maintenance_windows?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListMaintenanceWindowsResponse + return &result, c.decodeJSON(resp, &result) +} + +// CreateMaintaienanceWindows creates a new maintenance window for the specified services. +func (c *Client) CreateMaintaienanceWindows(m MaintenanceWindow) (*MaintenanceWindow, error) { + data := make(map[string]MaintenanceWindow) + data["maintenance_window"] = m + resp, err := c.post("/mainteance_windows", data) + return getMaintenanceWindowFromResponse(c, resp, err) +} + +// DeleteMaintenanceWindow deletes an existing maintenance window if it's in the future, or ends it if it's currently on-going. +func (c *Client) DeleteMaintenanceWindow(id string) error { + _, err := c.delete("/mainteance_windows/" + id) + return err +} + +// GetMaintenanceWindowOptions is the data structure used when calling the GetMaintenanceWindow API endpoint. +type GetMaintenanceWindowOptions struct { + Includes []string `url:"include,omitempty,brackets"` +} + +// GetMaintenanceWindow gets an existing maintenance window. +func (c *Client) GetMaintenanceWindow(id string, o GetMaintenanceWindowOptions) (*MaintenanceWindow, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/mainteance_windows/" + id + "?" + v.Encode()) + return getMaintenanceWindowFromResponse(c, resp, err) +} + +// UpdateMaintenanceWindow updates an existing maintenance window. +func (c *Client) UpdateMaintenanceWindow(m MaintenanceWindow) (*MaintenanceWindow, error) { + resp, err := c.put("/maintenance_windows/"+m.ID, m, nil) + return getMaintenanceWindowFromResponse(c, resp, err) +} + +func getMaintenanceWindowFromResponse(c *Client, resp *http.Response, err error) (*MaintenanceWindow, error) { + if err != nil { + return nil, err + } + var target map[string]MaintenanceWindow + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "maintenance_window" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/notification.go b/vendor/github.com/PagerDuty/go-pagerduty/notification.go new file mode 100644 index 000000000..cdd87c12f --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/notification.go @@ -0,0 +1,44 @@ +package pagerduty + +import ( + "github.com/google/go-querystring/query" +) + +// Notification is a message containing the details of the incident. +type Notification struct { + ID string `json:"id"` + Type string + StartedAt string `json:"started_at"` + Address string + User APIObject +} + +// ListNotificationOptions is the data structure used when calling the ListNotifications API endpoint. +type ListNotificationOptions struct { + APIListObject + TimeZone string `url:"time_zone,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + Filter string `url:"filter,omitempty"` + Includes []string `url:"include,omitempty"` +} + +// ListNotificationsResponse is the data structure returned from the ListNotifications API endpoint. +type ListNotificationsResponse struct { + APIListObject + Notifications []Notification +} + +// ListNotifications lists notifications for a given time range, optionally filtered by type (sms_notification, email_notification, phone_notification, or push_notification). +func (c *Client) ListNotifications(o ListNotificationOptions) (*ListNotificationsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/notifications?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListNotificationsResponse + return &result, c.decodeJSON(resp, &result) +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/on_call.go b/vendor/github.com/PagerDuty/go-pagerduty/on_call.go new file mode 100644 index 000000000..b05f88626 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/on_call.go @@ -0,0 +1,47 @@ +package pagerduty + +import ( + "github.com/google/go-querystring/query" +) + +// 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"` +} + +// ListOnCallsResponse is the data structure returned from calling the ListOnCalls API endpoint. +type ListOnCallsResponse struct { + OnCalls []OnCall `json:"oncalls"` +} + +// ListOnCallOptions is the data structure used when calling the ListOnCalls API endpoint. +type ListOnCallOptions struct { + APIListObject + TimeZone string `url:"time_zone,omitempty"` + Includes []string `url:"include,omitempty,brackets"` + UserIDs []string `url:"user_ids,omitempty,brackets"` + EscalationPolicyIDs []string `url:"escalation_policy_ids,omitempty,brackets"` + ScheduleIDs []string `url:"schedule_ids,omitempty,brackets"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + Earliest bool `url:"earliest,omitempty"` +} + +// ListOnCalls list the on-call entries during a given time range. +func (c *Client) ListOnCalls(o ListOnCallOptions) (*ListOnCallsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/oncalls?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListOnCallsResponse + return &result, c.decodeJSON(resp, &result) +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/schedule.go b/vendor/github.com/PagerDuty/go-pagerduty/schedule.go new file mode 100644 index 000000000..78dc21732 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/schedule.go @@ -0,0 +1,262 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +// Restriction limits on-call responsibility for a layer to certain times of the day or week. +type Restriction struct { + Type string `json:"type,omitempty"` + StartTimeOfDay string `json:"start_time_of_day,omitempty"` + DurationSeconds uint `json:"duration_seconds,omitempty"` +} + +// RenderedScheduleEntry represents the computed set of schedule layer entries that put users on call for a schedule, and cannot be modified directly. +type RenderedScheduleEntry struct { + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + User APIObject `json:"user,omitempty"` +} + +// ScheduleLayer is an entry that puts users on call for a schedule. +type ScheduleLayer struct { + APIObject + Name string `json:"name,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + RotationVirtualStart string `json:"rotation_virtual_start,omitempty"` + RotationTurnLengthSeconds uint `json:"rotation_turn_length_seconds,omitempty"` + Users []UserReference `json:"users,omitempty"` + Restrictions []Restriction `json:"restrictions,omitempty"` + RenderedScheduleEntries []RenderedScheduleEntry `json:"rendered_schedule_entries,omitempty"` + RenderedCoveragePercentage float64 `json:"rendered_coverage_percentage,omitempty"` +} + +// Schedule determines the time periods that users are on call. +type Schedule struct { + APIObject + Name string `json:"name,omitempty"` + TimeZone string `json:"time_zone,omitempty"` + Description string `json:"description,omitempty"` + EscalationPolicies []APIObject `json:"escalation_policies,omitempty"` + Users []APIObject `json:"users,omitempty"` + ScheduleLayers []ScheduleLayer `json:"schedule_layers,omitempty"` + OverrideSubschedule ScheduleLayer `json:"override_subschedule,omitempty"` + FinalSchedule ScheduleLayer `json:"final_schedule,omitempty"` +} + +// ListSchedulesOptions is the data structure used when calling the ListSchedules API endpoint. +type ListSchedulesOptions struct { + APIListObject + Query string `url:"query,omitempty"` +} + +// ListSchedulesResponse is the data structure returned from calling the ListSchedules API endpoint. +type ListSchedulesResponse struct { + APIListObject + Schedules []Schedule +} + +// UserReference is a reference to an authorized PagerDuty user. +type UserReference struct { + User APIObject `json:"user"` +} + +// ListSchedules lists the on-call schedules. +func (c *Client) ListSchedules(o ListSchedulesOptions) (*ListSchedulesResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/schedules?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListSchedulesResponse + return &result, c.decodeJSON(resp, &result) +} + +// CreateSchedule creates a new on-call schedule. +func (c *Client) CreateSchedule(s Schedule) (*Schedule, error) { + data := make(map[string]Schedule) + data["schedule"] = s + resp, err := c.post("/schedules", data) + if err != nil { + return nil, err + } + return getScheduleFromResponse(c, resp) +} + +// PreviewScheduleOptions is the data structure used when calling the PreviewSchedule API endpoint. +type PreviewScheduleOptions struct { + APIListObject + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + Overflow bool `url:"overflow,omitempty"` +} + +// PreviewSchedule previews what an on-call schedule would look like without saving it. +func (c *Client) PreviewSchedule(s Schedule, o PreviewScheduleOptions) error { + v, err := query.Values(o) + if err != nil { + return err + } + var data map[string]Schedule + data["schedule"] = s + _, e := c.post("/schedules/preview?"+v.Encode(), data) + return e +} + +// DeleteSchedule deletes an on-call schedule. +func (c *Client) DeleteSchedule(id string) error { + _, err := c.delete("/schedules/" + id) + return err +} + +// GetScheduleOptions is the data structure used when calling the GetSchedule API endpoint. +type GetScheduleOptions struct { + APIListObject + TimeZone string `url:"time_zone,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` +} + +// GetSchedule shows detailed information about a schedule, including entries for each layer and sub-schedule. +func (c *Client) GetSchedule(id string, o GetScheduleOptions) (*Schedule, error) { + v, err := query.Values(o) + if err != nil { + return nil, fmt.Errorf("Could not parse values for query: %v", err) + } + resp, err := c.get("/schedules/" + id + "?" + v.Encode()) + if err != nil { + return nil, err + } + return getScheduleFromResponse(c, resp) +} + +// UpdateScheduleOptions is the data structure used when calling the UpdateSchedule API endpoint. +type UpdateScheduleOptions struct { + Overflow bool `url:"overflow,omitempty"` +} + +// UpdateSchedule updates an existing on-call schedule. +func (c *Client) UpdateSchedule(id string, s Schedule) (*Schedule, error) { + v := make(map[string]Schedule) + v["schedule"] = s + resp, err := c.put("/schedules/"+id, v, nil) + if err != nil { + return nil, err + } + return getScheduleFromResponse(c, resp) +} + +// ListOverridesOptions is the data structure used when calling the ListOverrides API endpoint. +type ListOverridesOptions struct { + APIListObject + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + Editable bool `url:"editable,omitempty"` + Overflow bool `url:"overflow,omitempty"` +} + +// Overrides are any schedule layers from the override layer. +type Override struct { + ID string `json:"id,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + User APIObject `json:"user,omitempty"` +} + +// ListOverrides lists overrides for a given time range. +func (c *Client) ListOverrides(id string, o ListOverridesOptions) ([]Override, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/schedules/" + id + "/overrides?" + v.Encode()) + if err != nil { + return nil, err + } + var result map[string][]Override + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + overrides, ok := result["overrides"] + if !ok { + return nil, fmt.Errorf("JSON response does not have overrides field") + } + return overrides, nil +} + +// CreateOverride creates an override for a specific user covering the specified time range. +func (c *Client) CreateOverride(id string, o Override) (*Override, error) { + data := make(map[string]Override) + data["override"] = o + resp, err := c.post("/schedules/"+id+"/overrides", data) + if err != nil { + return nil, err + } + return getOverrideFromResponse(c, resp) +} + +// DeleteOverride removes an override. +func (c *Client) DeleteOverride(scheduleID, overrideID string) error { + _, err := c.delete("/schedules/" + scheduleID + "/overrides/" + overrideID) + return err +} + +// ListOnCallUsersOptions is the data structure used when calling the ListOnCallUsers API endpoint. +type ListOnCallUsersOptions struct { + APIListObject + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` +} + +// ListOnCallUsers lists all of the users on call in a given schedule for a given time range. +func (c *Client) ListOnCallUsers(id string, o ListOnCallUsersOptions) ([]User, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/schedules/" + id + "/users?" + v.Encode()) + if err != nil { + return nil, err + } + var result map[string][]User + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + u, ok := result["users"] + if !ok { + return nil, fmt.Errorf("JSON response does not have users field") + } + return u, nil +} + +func getScheduleFromResponse(c *Client, resp *http.Response) (*Schedule, error) { + var target map[string]Schedule + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "schedule" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} + +func getOverrideFromResponse(c *Client, resp *http.Response) (*Override, error) { + var target map[string]Override + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "override" + o, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &o, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/service.go b/vendor/github.com/PagerDuty/go-pagerduty/service.go new file mode 100644 index 000000000..4525bfd3d --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/service.go @@ -0,0 +1,202 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +// Integration is an endpoint (like Nagios, email, or an API call) that generates events, which are normalized and de-duplicated by PagerDuty to create incidents. +type Integration struct { + APIObject + Name string `json:"name,omitempty"` + Service *APIObject `json:"service,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + Vendor *APIObject `json:"vendor,omitempty"` + Type string `json:"type,omitempty"` + IntegrationKey string `json:"integration_key,omitempty"` + IntegrationEmail string `json:"integration_email,omitempty"` +} + +// InlineModel represents when a scheduled action will occur. +type InlineModel struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` +} + +// ScheduledAction contains scheduled actions for the service. +type ScheduledAction struct { + Type string `json:"type,omitempty"` + At InlineModel `json:"at,omitempty"` + ToUrgency string `json:"to_urgency"` +} + +// IncidentUrgencyType are the incidents urgency during or outside support hours. +type IncidentUrgencyType struct { + Type string `json:"type,omitempty"` + Urgency string `json:"urgency,omitempty"` +} + +// SupportHours are the support hours for the service. +type SupportHours struct { + Type string `json:"type,omitempty"` + Timezone string `json:"time_zone,omitempty"` + StartTime string `json:"start_time,omitempty"` + EndTime string `json:"end_time,omitempty"` + DaysOfWeek []uint `json:"days_of_week,omitempty"` +} + +// IncidentUrgencyRule is the default urgency for new incidents. +type IncidentUrgencyRule struct { + Type string `json:"type,omitempty"` + Urgency string `json:"urgency,omitempty"` + DuringSupportHours *IncidentUrgencyType `json:"during_support_hours,omitempty"` + OutsideSupportHours *IncidentUrgencyType `json:"outside_support_hours,omitempty"` +} + +// Service represents something you monitor (like a web service, email service, or database service). +type Service struct { + APIObject + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AutoResolveTimeout *uint `json:"auto_resolve_timeout,omitempty"` + AcknowledgementTimeout *uint `json:"acknowledgement_timeout,omitempty"` + CreateAt string `json:"created_at,omitempty"` + Status string `json:"status,omitempty"` + LastIncidentTimestamp string `json:"last_incident_timestamp,omitempty"` + Integrations []Integration `json:"integrations,omitempty"` + EscalationPolicy EscalationPolicy `json:"escalation_policy,omitempty"` + Teams []Team `json:"teams,omitempty"` + IncidentUrgencyRule *IncidentUrgencyRule `json:"incident_urgency_rule,omitempty"` + SupportHours *SupportHours `json:"support_hours,omitempty"` + ScheduledActions []ScheduledAction `json:"scheduled_actions,omitempty"` +} + +// ListServiceOptions is the data structure used when calling the ListServices API endpoint. +type ListServiceOptions struct { + APIListObject + TeamIDs []string `url:"team_ids,omitempty,brackets"` + TimeZone string `url:"time_zone,omitempty"` + SortBy string `url:"sort_by,omitempty"` + Query string `url:"query,omitempty"` + Includes []string `url:"include,omitempty,brackets"` +} + +// ListServiceResponse is the data structure returned from calling the ListServices API endpoint. +type ListServiceResponse struct { + APIListObject + Services []Service +} + +// ListServices lists existing services. +func (c *Client) ListServices(o ListServiceOptions) (*ListServiceResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/services?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListServiceResponse + return &result, c.decodeJSON(resp, &result) +} + +// GetServiceOptions is the data structure used when calling the GetService API endpoint. +type GetServiceOptions struct { + Includes []string `url:"include,brackets,omitempty"` +} + +// GetService gets details about an existing service. +func (c *Client) GetService(id string, o GetServiceOptions) (*Service, error) { + v, err := query.Values(o) + resp, err := c.get("/services/" + id + "?" + v.Encode()) + return getServiceFromResponse(c, resp, err) +} + +// CreateService creates a new service. +func (c *Client) CreateService(s Service) (*Service, error) { + data := make(map[string]Service) + data["service"] = s + resp, err := c.post("/services", data) + return getServiceFromResponse(c, resp, err) +} + +// UpdateService updates an existing service. +func (c *Client) UpdateService(s Service) (*Service, error) { + resp, err := c.put("/services/"+s.ID, s, nil) + return getServiceFromResponse(c, resp, err) +} + +// DeleteService deletes an existing service. +func (c *Client) DeleteService(id string) error { + _, err := c.delete("/services/" + id) + return err +} + +// CreateIntegration creates a new integration belonging to a service. +func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) { + data := make(map[string]Integration) + data["integration"] = i + resp, err := c.post("/services/"+id+"/integrations", data) + return getIntegrationFromResponse(c, resp, err) +} + +// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint. +type GetIntegrationOptions struct { + Includes []string `url:"include,omitempty,brackets"` +} + +// GetIntegration gets details about an integration belonging to a service. +func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { + v, queryErr := query.Values(o) + if queryErr != nil { + return nil, queryErr + } + resp, err := c.get("/services/" + serviceID + "/integrations/" + integrationID + "?" + v.Encode()) + return getIntegrationFromResponse(c, resp, err) +} + +// UpdateIntegration updates an integration belonging to a service. +func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) { + resp, err := c.put("/services/"+serviceID+"/integrations/"+i.ID, i, nil) + return getIntegrationFromResponse(c, resp, err) +} + +// DeleteIntegration deletes an existing integration. +func (c *Client) DeleteIntegration(serviceID string, integrationID string) error { + _, err := c.delete("/services/" + serviceID + "/integrations" + integrationID) + return err +} + +func getServiceFromResponse(c *Client, resp *http.Response, err error) (*Service, error) { + if err != nil { + return nil, err + } + var target map[string]Service + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "service" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} + +func getIntegrationFromResponse(c *Client, resp *http.Response, err error) (*Integration, error) { + if err != nil { + return nil, err + } + var target map[string]Integration + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", err) + } + rootNode := "integration" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/team.go b/vendor/github.com/PagerDuty/go-pagerduty/team.go new file mode 100644 index 000000000..096d4e8bd --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/team.go @@ -0,0 +1,105 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +// Team is a collection of users and escalation policies that represent a group of people within an organization. +type Team struct { + APIObject + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +// ListTeamResponse is the structure used when calling the ListTeams API endpoint. +type ListTeamResponse struct { + APIListObject + Teams []Team +} + +// ListTeamOptions are the input parameters used when calling the ListTeams API endpoint. +type ListTeamOptions struct { + APIListObject + Query string `url:"query,omitempty"` +} + +// ListTeams lists teams of your PagerDuty account, optionally filtered by a search query. +func (c *Client) ListTeams(o ListTeamOptions) (*ListTeamResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get("/teams?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListTeamResponse + return &result, c.decodeJSON(resp, &result) +} + +// CreateTeam creates a new team. +func (c *Client) CreateTeam(t *Team) (*Team, error) { + resp, err := c.post("/teams", t) + return getTeamFromResponse(c, resp, err) +} + +// DeleteTeam removes an existing team. +func (c *Client) DeleteTeam(id string) error { + _, err := c.delete("/teams/" + id) + return err +} + +// GetTeam gets details about an existing team. +func (c *Client) GetTeam(id string) (*Team, error) { + resp, err := c.get("/teams/" + id) + return getTeamFromResponse(c, resp, err) +} + +// UpdateTeam updates an existing team. +func (c *Client) UpdateTeam(id string, t *Team) (*Team, error) { + resp, err := c.put("/teams/"+id, t, nil) + return getTeamFromResponse(c, resp, err) +} + +// RemoveEscalationPolicyFromTeam removes an escalation policy from a team. +func (c *Client) RemoveEscalationPolicyFromTeam(teamID, epID string) error { + _, err := c.delete("/teams/" + teamID + "/escalation_policies/" + epID) + return err +} + +// AddEscalationPolicyToTeam adds an escalation policy to a team. +func (c *Client) AddEscalationPolicyToTeam(teamID, epID string) error { + _, err := c.put("/teams/"+teamID+"/escalation_policies/"+epID, nil, nil) + return err +} + +// RemoveUserFromTeam removes a user from a team. +func (c *Client) RemoveUserFromTeam(teamID, userID string) error { + _, err := c.delete("/teams/" + teamID + "/users/" + userID) + return err +} + +// AddUserToTeam adds a user to a team. +func (c *Client) AddUserToTeam(teamID, userID string) error { + _, err := c.put("/teams/"+teamID+"/users/"+userID, nil, nil) + return err +} + +func getTeamFromResponse(c *Client, resp *http.Response, err error) (*Team, error) { + if err != nil { + return nil, err + } + var target map[string]Team + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "team" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/user.go b/vendor/github.com/PagerDuty/go-pagerduty/user.go new file mode 100644 index 000000000..2f7bad4cb --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/user.go @@ -0,0 +1,124 @@ +package pagerduty + +import ( + "fmt" + "github.com/google/go-querystring/query" + "net/http" +) + +// 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 + StartDelayInMinutes uint `json:"start_delay_in_minutes"` + CreatedAt string `json:"created_at"` + ContactMethod ContactMethod `json:"contact_method"` + Urgency string + Type string +} + +// 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 + ContactMethods []ContactMethod `json:"contact_methods"` + NotificationRules []NotificationRule `json:"notification_rules"` + JobTitle string `json:"job_title,omitempty"` + Teams []Team +} + +// ListUsersResponse is the data structure returned from calling the ListUsers API endpoint. +type ListUsersResponse struct { + APIListObject + Users []User +} + +// ListUsersOptions is the data structure used when calling the ListUsers API endpoint. +type ListUsersOptions struct { + APIListObject + Query string `url:"query,omitempty"` + TeamIDs []string `url:"team_ids,omitempty,brackets"` + Includes []string `url:"include,omitempty,brackets"` +} + +// GetUserOptions is the data structure used when calling the GetUser API endpoint. +type GetUserOptions struct { + Includes []string `url:"include,omitempty,brackets"` +} + +// 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) + if err != nil { + return nil, err + } + resp, err := c.get("/users?" + v.Encode()) + if err != nil { + return nil, err + } + var result ListUsersResponse + return &result, c.decodeJSON(resp, &result) +} + +// CreateUser creates a new user. +func (c *Client) CreateUser(u User) (*User, error) { + data := make(map[string]User) + data["user"] = u + resp, err := c.post("/users", data) + return getUserFromResponse(c, resp, err) +} + +// DeleteUser deletes a user. +func (c *Client) DeleteUser(id string) error { + _, err := c.delete("/users/" + id) + return err +} + +// GetUser gets details about an existing user. +func (c *Client) GetUser(id string, o GetUserOptions) (*User, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + resp, err := c.get("/users/" + id + "?" + v.Encode()) + return getUserFromResponse(c, resp, err) +} + +// UpdateUser updates an existing user. +func (c *Client) UpdateUser(u User) (*User, error) { + v := make(map[string]User) + v["user"] = u + resp, err := c.put("/users/"+u.ID, v, nil) + return getUserFromResponse(c, resp, err) +} + +func getUserFromResponse(c *Client, resp *http.Response, err error) (*User, error) { + if err != nil { + return nil, err + } + var target map[string]User + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + rootNode := "user" + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/webhook.go b/vendor/github.com/PagerDuty/go-pagerduty/webhook.go new file mode 100644 index 000000000..a1435ac26 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/webhook.go @@ -0,0 +1,37 @@ +package pagerduty + +import ( + "encoding/json" + "io" +) + +// IncidentDetail contains a representation of the incident associated with the action that caused this webhook message. +type IncidentDetail struct { + ID string `json:"id"` + IncidentNumber uint `json:"incident_number"` + CreatedOn string `json:"created_on"` + Status string `json:"status"` + HTMLUrl string `json:"html_url"` + Service string `json:"service"` + AssignedToUser *json.RawMessage `json:"assigned_to_user"` + AssignedTo []string `json:"assigned_to"` + TriggerSummaryData *json.RawMessage `json:"trigger_summary_data"` + TriggerDeatilsHTMLUrl string `json:"trigger_details_html_url"` +} + +// WebhookPayload is a single message array for a webhook. +type WebhookPayload struct { + ID string `json:"id"` + Type string `json:"type"` + CreatedOn string `json:"created_on"` + Data *json.RawMessage `json:"data"` +} + +// DecodeWebhook decodes a webhook from a response object. +func DecodeWebhook(r io.Reader) (*WebhookPayload, error) { + var payload WebhookPayload + if err := json.NewDecoder(r).Decode(&payload); err != nil { + return nil, err + } + return &payload, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 244d35b59..d82ac5939 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -295,6 +295,12 @@ "path": "github.com/Ensighten/udnssdk", "revision": "0290933f5e8afd933f2823fce32bf2847e6ea603" }, + { + "checksumSHA1": "FQc+WPOoRAGM6UYOS4UjeEEAse4=", + "path": "github.com/PagerDuty/go-pagerduty", + "revision": "45ef5164a846163bdeea4e743dc4f5535ed023ca", + "revisionTime": "2016-10-13T06:26:24Z" + }, { "path": "github.com/Unknwon/com", "revision": "28b053d5a2923b87ce8c5a08f3af779894a72758" From d02067a75ed48fadf6abd1bda1aad1fdee76cbb6 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Fri, 23 Sep 2016 21:43:06 +0200 Subject: [PATCH 03/26] Add PagerDuty provider --- builtin/bins/provider-pagerduty/main.go | 12 + builtin/providers/pagerduty/config.go | 21 ++ ...import_pagerduty_escalation_policy_test.go | 38 +++ .../import_pagerduty_service_test.go | 35 +++ .../pagerduty/import_pagerduty_team_test.go | 32 ++ .../pagerduty/import_pagerduty_user_test.go | 35 +++ builtin/providers/pagerduty/provider.go | 40 +++ builtin/providers/pagerduty/provider_test.go | 48 +++ .../resource_pagerduty_escalation_policy.go | 187 ++++++++++++ ...source_pagerduty_escalation_policy_test.go | 137 +++++++++ .../pagerduty/resource_pagerduty_schedule.go | 280 ++++++++++++++++++ .../pagerduty/resource_pagerduty_service.go | 148 +++++++++ .../resource_pagerduty_service_test.go | 105 +++++++ .../pagerduty/resource_pagerduty_team.go | 118 ++++++++ .../pagerduty/resource_pagerduty_team_test.go | 83 ++++++ .../pagerduty/resource_pagerduty_user.go | 236 +++++++++++++++ .../pagerduty/resource_pagerduty_user_test.go | 110 +++++++ builtin/providers/pagerduty/structure.go | 22 ++ command/internal_plugin_list.go | 2 + 19 files changed, 1689 insertions(+) create mode 100644 builtin/bins/provider-pagerduty/main.go create mode 100644 builtin/providers/pagerduty/config.go create mode 100644 builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go create mode 100644 builtin/providers/pagerduty/import_pagerduty_service_test.go create mode 100644 builtin/providers/pagerduty/import_pagerduty_team_test.go create mode 100644 builtin/providers/pagerduty/import_pagerduty_user_test.go create mode 100644 builtin/providers/pagerduty/provider.go create mode 100644 builtin/providers/pagerduty/provider_test.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_schedule.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_service.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_service_test.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_team.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_team_test.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_user.go create mode 100644 builtin/providers/pagerduty/resource_pagerduty_user_test.go create mode 100644 builtin/providers/pagerduty/structure.go diff --git a/builtin/bins/provider-pagerduty/main.go b/builtin/bins/provider-pagerduty/main.go new file mode 100644 index 000000000..551efe182 --- /dev/null +++ b/builtin/bins/provider-pagerduty/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/pagerduty" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: pagerduty.Provider, + }) +} diff --git a/builtin/providers/pagerduty/config.go b/builtin/providers/pagerduty/config.go new file mode 100644 index 000000000..00f55e71c --- /dev/null +++ b/builtin/providers/pagerduty/config.go @@ -0,0 +1,21 @@ +package pagerduty + +import ( + "log" + + "github.com/PagerDuty/go-pagerduty" +) + +// Config defines the configuration options for the PagerDuty client +type Config struct { + Token string +} + +// Client returns a new PagerDuty client +func (c *Config) Client() (*pagerduty.Client, error) { + client := pagerduty.NewClient(c.Token) + + log.Printf("[INFO] PagerDuty client configured") + + return client, nil +} diff --git a/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go b/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go new file mode 100644 index 000000000..f263fb871 --- /dev/null +++ b/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go @@ -0,0 +1,38 @@ +package pagerduty + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPagerDutyEscalationPolicy_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEscalationPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPagerDutyEscalationPolicyConfigImported, + }, + resource.TestStep{ + ResourceName: "pagerduty_escalation_policy.foo", + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +const testAccPagerDutyEscalationPolicyConfigImported = ` +resource "pagerduty_escalation_policy" "foo" { + name = "foo" + escalation_rule { + escalation_delay_in_minutes = 10 + target { + id = "PLBP04G" + type = "user" + } + } +} +` diff --git a/builtin/providers/pagerduty/import_pagerduty_service_test.go b/builtin/providers/pagerduty/import_pagerduty_service_test.go new file mode 100644 index 000000000..84a9bbb51 --- /dev/null +++ b/builtin/providers/pagerduty/import_pagerduty_service_test.go @@ -0,0 +1,35 @@ +package pagerduty + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPagerDutyService_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPagerDutyServiceConfigImported, + }, + resource.TestStep{ + ResourceName: "pagerduty_service.foo", + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +const testAccPagerDutyServiceConfigImported = ` +resource "pagerduty_service" "foo" { + name = "foo" + description = "foo" + acknowledgement_timeout = "1800" + auto_resolve_timeout = "14400" + escalation_policy = "PGOMBUU" +} +` diff --git a/builtin/providers/pagerduty/import_pagerduty_team_test.go b/builtin/providers/pagerduty/import_pagerduty_team_test.go new file mode 100644 index 000000000..78f677dcf --- /dev/null +++ b/builtin/providers/pagerduty/import_pagerduty_team_test.go @@ -0,0 +1,32 @@ +package pagerduty + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPagerDutyTeam_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyTeamDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPagerDutyTeamConfigImported, + }, + resource.TestStep{ + ResourceName: "pagerduty_team.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +const testAccPagerDutyTeamConfigImported = ` +resource "pagerduty_team" "foo" { + name = "foo" + description = "foo" +} +` diff --git a/builtin/providers/pagerduty/import_pagerduty_user_test.go b/builtin/providers/pagerduty/import_pagerduty_user_test.go new file mode 100644 index 000000000..c39e8f2b8 --- /dev/null +++ b/builtin/providers/pagerduty/import_pagerduty_user_test.go @@ -0,0 +1,35 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPagerDutyUser_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyUserDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPagerDutyUserConfigImported(importUserID), + }, + resource.TestStep{ + ResourceName: "pagerduty_user.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccPagerDutyUserConfigImported(id string) string { + return fmt.Sprintf(` + resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + } + `) +} diff --git a/builtin/providers/pagerduty/provider.go b/builtin/providers/pagerduty/provider.go new file mode 100644 index 000000000..2be5bc633 --- /dev/null +++ b/builtin/providers/pagerduty/provider.go @@ -0,0 +1,40 @@ +package pagerduty + +import ( + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Provider represents a resource provider in Terraform +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "token": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("PAGERDUTY_TOKEN", nil), + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "pagerduty_user": resourcePagerDutyUser(), + "pagerduty_team": resourcePagerDutyTeam(), + "pagerduty_service": resourcePagerDutyService(), + "pagerduty_schedule": resourcePagerDutySchedule(), + "pagerduty_escalation_policy": resourcePagerDutyEscalationPolicy(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(data *schema.ResourceData) (interface{}, error) { + config := Config{ + Token: data.Get("token").(string), + } + + log.Println("[INFO] Initializing PagerDuty client") + return config.Client() +} diff --git a/builtin/providers/pagerduty/provider_test.go b/builtin/providers/pagerduty/provider_test.go new file mode 100644 index 000000000..4057873b0 --- /dev/null +++ b/builtin/providers/pagerduty/provider_test.go @@ -0,0 +1,48 @@ +package pagerduty + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "pagerduty": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProviderImpl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("PAGERDUTY_TOKEN"); v == "" { + t.Fatal("PAGERDUTY_TOKEN must be set for acceptance tests") + } + + if v := os.Getenv("PAGERDUTY_USER_ID"); v == "" { + t.Fatal("PAGERDUTY_USER_ID must be set for acceptance tests") + } + + if v := os.Getenv("PAGERDUTY_ESCALATION_POLICY_ID"); v == "" { + t.Fatal("PAGERDUTY_ESCALATION_POLICY_ID must be set for acceptance tests") + } +} + +var importEscalationPolicyID = os.Getenv("PAGERDUTY_ESCALATION_POLICY_ID") +var importUserID = os.Getenv("PAGERDUTY_USER_ID") +var userID = os.Getenv("PAGERDUTY_USER_ID") +var escalationPolicyID = os.Getenv("PAGERDUTY_ESCALATION_POLICY_ID") diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go new file mode 100644 index 000000000..76cf4d709 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go @@ -0,0 +1,187 @@ +package pagerduty + +import ( + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePagerDutyEscalationPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyEscalationPolicyCreate, + Read: resourcePagerDutyEscalationPolicyRead, + Update: resourcePagerDutyEscalationPolicyUpdate, + Delete: resourcePagerDutyEscalationPolicyDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyEscalationPolicyImport, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Managed by Terraform", + }, + "num_loops": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "escalation_rule": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "escalation_delay_in_minutes": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "target": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "user_reference", + }, + "id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func buildEscalationPolicyRules(escalationRules *[]interface{}) *[]pagerduty.EscalationRule { + + rules := make([]pagerduty.EscalationRule, len(*escalationRules)) + + for i, l := range *escalationRules { + rule := l.(map[string]interface{}) + + escalationPolicyRule := pagerduty.EscalationRule{ + Delay: uint(rule["escalation_delay_in_minutes"].(int)), + } + + for _, t := range rule["target"].([]interface{}) { + target := t.(map[string]interface{}) + escalationPolicyRule.Targets = append( + escalationPolicyRule.Targets, + pagerduty.APIObject{ + Type: target["type"].(string), + ID: target["id"].(string), + }, + ) + } + + rules[i] = escalationPolicyRule + } + + return &rules +} + +func buildEscalationPolicyStruct(d *schema.ResourceData) *pagerduty.EscalationPolicy { + escalationRules := d.Get("escalation_rule").([]interface{}) + + policy := pagerduty.EscalationPolicy{ + Name: d.Get("name").(string), + EscalationRules: *buildEscalationPolicyRules(&escalationRules), + } + + if attr, ok := d.GetOk("description"); ok { + policy.Description = attr.(string) + } + + if attr, ok := d.GetOk("num_loops"); ok { + policy.NumLoops = uint(attr.(int)) + } + + return &policy +} + +func resourcePagerDutyEscalationPolicyCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + e := buildEscalationPolicyStruct(d) + + log.Printf("[INFO] Creating PagerDuty escalation policy: %s", e.Name) + + e, err := client.CreateEscalationPolicy(*e) + + if err != nil { + return err + } + + d.SetId(e.ID) + + return resourcePagerDutyEscalationPolicyRead(d, meta) +} + +func resourcePagerDutyEscalationPolicyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty escalation policy: %s", d.Id()) + + e, err := client.GetEscalationPolicy(d.Id(), &pagerduty.GetEscalationPolicyOptions{}) + + if err != nil { + return err + } + + d.Set("name", e.Name) + d.Set("description", e.Description) + d.Set("num_loops", e.NumLoops) + d.Set("escalation_rules", e.EscalationRules) + + return nil +} + +func resourcePagerDutyEscalationPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + e := buildEscalationPolicyStruct(d) + + log.Printf("[INFO] Updating PagerDuty escalation policy: %s", d.Id()) + + e, err := client.UpdateEscalationPolicy(d.Id(), e) + + if err != nil { + return err + } + + return nil +} + +func resourcePagerDutyEscalationPolicyDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Deleting PagerDuty escalation policy: %s", d.Id()) + + err := client.DeleteEscalationPolicy(d.Id()) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} + +func resourcePagerDutyEscalationPolicyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if err := resourcePagerDutyEscalationPolicyRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go new file mode 100644 index 000000000..8e07df040 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go @@ -0,0 +1,137 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutyEscalationPolicy_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEscalationPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyEscalationPolicyConfig(userID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEscalationPolicyExists("pagerduty_escalation_policy.foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "num_loops", "1"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.escalation_delay_in_minutes", "10"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.target.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.id", userID), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.type", "user"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyEscalationPolicyConfigUpdated(userID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEscalationPolicyExists("pagerduty_escalation_policy.foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "num_loops", "2"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.#", "2"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.escalation_delay_in_minutes", "10"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.target.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.id", userID), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.type", "user"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyEscalationPolicyDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_escalation_policy" { + continue + } + + _, err := client.GetEscalationPolicy(r.Primary.ID, &pagerduty.GetEscalationPolicyOptions{}) + + if err == nil { + return fmt.Errorf("Escalation Policy still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyEscalationPolicyExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if _, err := client.GetEscalationPolicy(r.Primary.ID, &pagerduty.GetEscalationPolicyOptions{}); err != nil { + return fmt.Errorf("Received an error retrieving escalation_policy %s ID: %s", err, r.Primary.ID) + } + } + return nil + } +} + +func testAccCheckPagerDutyEscalationPolicyConfig(id string) string { + return fmt.Sprintf(` +resource "pagerduty_escalation_policy" "foo" { + name = "foo" + description = "foo" + num_loops = 1 + + escalation_rule { + escalation_delay_in_minutes = 10 + target { + type = "user" + id = "%s" + } + } +} + `, id) +} + +func testAccCheckPagerDutyEscalationPolicyConfigUpdated(id string) string { + return fmt.Sprintf(` +resource "pagerduty_escalation_policy" "foo" { + name = "bar" + description = "bar" + num_loops = 2 + + escalation_rule { + escalation_delay_in_minutes = 10 + target { + type = "user" + id = "%[1]v" + } + } + + escalation_rule { + escalation_delay_in_minutes = 20 + target { + type = "user" + id = "%[1]v" + } + } +} +`, userID) +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_schedule.go b/builtin/providers/pagerduty/resource_pagerduty_schedule.go new file mode 100644 index 000000000..acfb7ab47 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_schedule.go @@ -0,0 +1,280 @@ +package pagerduty + +import ( + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePagerDutySchedule() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyScheduleCreate, + Read: resourcePagerDutyScheduleRead, + Update: resourcePagerDutyScheduleUpdate, + Delete: resourcePagerDutyScheduleDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyScheduleImport, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "time_zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Managed by Terraform", + }, + "schedule_layer": &schema.Schema{ + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "start": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "" { + return false + } + return true + }, + }, + "end": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "rotation_virtual_start": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "rotation_turn_length_seconds": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "users": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "restriction": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "start_time_of_day": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "duration_seconds": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func buildScheduleLayers(d *schema.ResourceData, scheduleLayers *[]interface{}) *[]pagerduty.ScheduleLayer { + + pagerdutyLayers := make([]pagerduty.ScheduleLayer, len(*scheduleLayers)) + + for i, l := range *scheduleLayers { + layer := l.(map[string]interface{}) + + scheduleLayer := pagerduty.ScheduleLayer{ + Name: layer["name"].(string), + Start: layer["start"].(string), + End: layer["end"].(string), + RotationVirtualStart: layer["rotation_virtual_start"].(string), + RotationTurnLengthSeconds: uint(layer["rotation_turn_length_seconds"].(int)), + } + + if layer["id"] != nil || layer["id"] != "" { + scheduleLayer.ID = layer["id"].(string) + } + + for _, u := range layer["users"].([]interface{}) { + scheduleLayer.Users = append( + scheduleLayer.Users, + pagerduty.UserReference{ + User: pagerduty.APIObject{ + ID: u.(string), + Type: "user_reference"}, + }, + ) + } + + restrictions := layer["restriction"].([]interface{}) + + if len(restrictions) > 0 { + for _, r := range restrictions { + restriction := r.(map[string]interface{}) + scheduleLayer.Restrictions = append( + scheduleLayer.Restrictions, + pagerduty.Restriction{ + Type: restriction["type"].(string), + StartTimeOfDay: restriction["start_time_of_day"].(string), + DurationSeconds: uint(restriction["duration_seconds"].(int)), + }, + ) + } + } + + pagerdutyLayers[i] = scheduleLayer + + } + + return &pagerdutyLayers +} + +func buildScheduleStruct(d *schema.ResourceData) (*pagerduty.Schedule, error) { + pagerdutyLayers := d.Get("schedule_layer").([]interface{}) + + schedule := pagerduty.Schedule{ + Name: d.Get("name").(string), + TimeZone: d.Get("time_zone").(string), + } + + schedule.ScheduleLayers = *buildScheduleLayers(d, &pagerdutyLayers) + + if attr, ok := d.GetOk("description"); ok { + schedule.Description = attr.(string) + } + + return &schedule, nil +} + +func resourcePagerDutyScheduleCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + s, _ := buildScheduleStruct(d) + + log.Printf("[INFO] Creating PagerDuty schedule: %s", s.Name) + + e, err := client.CreateSchedule(*s) + + if err != nil { + return err + } + + d.SetId(e.ID) + + return resourcePagerDutyScheduleRead(d, meta) +} + +func resourcePagerDutyScheduleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty schedule: %s", d.Id()) + + s, err := client.GetSchedule(d.Id(), pagerduty.GetScheduleOptions{}) + + if err != nil { + return err + } + + d.Set("name", s.Name) + d.Set("description", s.Description) + + scheduleLayers := make([]map[string]interface{}, 0, len(s.ScheduleLayers)) + + for _, layer := range s.ScheduleLayers { + restrictions := make([]map[string]interface{}, 0, len(layer.Restrictions)) + + for _, r := range layer.Restrictions { + restrictions = append(restrictions, map[string]interface{}{ + "duration_seconds": r.DurationSeconds, + "start_time_of_day": r.StartTimeOfDay, + "type": r.Type, + }) + } + + users := make([]string, 0, len(layer.Users)) + + for _, u := range layer.Users { + users = append(users, u.User.ID) + } + + scheduleLayers = append(scheduleLayers, map[string]interface{}{ + "id": layer.ID, + "name": layer.Name, + "start": layer.Start, + "end": layer.End, + "users": users, + "rotation_turn_length_seconds": layer.RotationTurnLengthSeconds, + "rotation_virtual_start": layer.RotationVirtualStart, + "restriction": restrictions, + }) + } + + d.Set("schedule_layer", scheduleLayers) + + return nil +} + +func resourcePagerDutyScheduleUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + e, _ := buildScheduleStruct(d) + + d.MarkNewResource() + + log.Printf("[INFO] Updating PagerDuty schedule: %s", d.Id()) + + e, err := client.UpdateSchedule(d.Id(), *e) + + if err != nil { + return err + } + + return nil +} + +func resourcePagerDutyScheduleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Deleting PagerDuty schedule: %s", d.Id()) + + err := client.DeleteSchedule(d.Id()) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} + +func resourcePagerDutyScheduleImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if err := resourcePagerDutyScheduleRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_service.go b/builtin/providers/pagerduty/resource_pagerduty_service.go new file mode 100644 index 000000000..eb68f83c5 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_service.go @@ -0,0 +1,148 @@ +package pagerduty + +import ( + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePagerDutyService() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyServiceCreate, + Read: resourcePagerDutyServiceRead, + Update: resourcePagerDutyServiceUpdate, + Delete: resourcePagerDutyServiceDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyServiceImport, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Managed by Terraform", + }, + "auto_resolve_timeout": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "acknowledgement_timeout": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "escalation_policy": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func buildServiceStruct(d *schema.ResourceData) *pagerduty.Service { + service := pagerduty.Service{ + Name: d.Get("name").(string), + } + + if attr, ok := d.GetOk("description"); ok { + service.Description = attr.(string) + } + + if attr, ok := d.GetOk("auto_resolve_timeout"); ok { + autoResolveTimeout := uint(attr.(int)) + service.AutoResolveTimeout = &autoResolveTimeout + } + + if attr, ok := d.GetOk("acknowledgement_timeout"); ok { + acknowledgementTimeout := uint(attr.(int)) + service.AcknowledgementTimeout = &acknowledgementTimeout + } + + policy := &pagerduty.EscalationPolicy{} + policy.ID = d.Get("escalation_policy").(string) + policy.Type = "escalation_policy" + service.EscalationPolicy = *policy + + return &service +} + +func resourcePagerDutyServiceCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + s := buildServiceStruct(d) + + log.Printf("[INFO] Creating PagerDuty service %s", s.Name) + + s, err := client.CreateService(*s) + + if err != nil { + return err + } + + d.SetId(s.ID) + + return nil +} + +func resourcePagerDutyServiceRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty service %s", d.Id()) + + s, err := client.GetService(d.Id(), pagerduty.GetServiceOptions{}) + + if err != nil { + return err + } + + d.Set("name", s.Name) + d.Set("escalation_policy", s.EscalationPolicy) + d.Set("description", s.Description) + d.Set("auto_resolve_timeout", s.AutoResolveTimeout) + d.Set("acknowledgement_timeout", s.AcknowledgementTimeout) + + return nil +} + +func resourcePagerDutyServiceUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + s := buildServiceStruct(d) + s.ID = d.Id() + + log.Printf("[INFO] Updating PagerDuty service %s", d.Id()) + + s, err := client.UpdateService(*s) + + if err != nil { + return err + } + + return nil +} + +func resourcePagerDutyServiceDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Deleting PagerDuty service %s", d.Id()) + + err := client.DeleteService(d.Id()) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} + +func resourcePagerDutyServiceImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if err := resourcePagerDutyServiceRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_test.go b/builtin/providers/pagerduty/resource_pagerduty_service_test.go new file mode 100644 index 000000000..d270c7872 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_service_test.go @@ -0,0 +1,105 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutyService_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyServiceConfig(escalationPolicyID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "escalation_policy", escalationPolicyID), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyServiceConfigUpdated(escalationPolicyID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "escalation_policy", escalationPolicyID), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyServiceDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_service" { + continue + } + + _, err := client.GetService(r.Primary.ID, pagerduty.GetServiceOptions{}) + + if err == nil { + return fmt.Errorf("Service still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyServiceExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if _, err := client.GetService(r.Primary.ID, pagerduty.GetServiceOptions{}); err != nil { + return fmt.Errorf("Received an error retrieving service %s ID: %s", err, r.Primary.ID) + } + } + return nil + } +} + +func testAccCheckPagerDutyServiceConfig(id string) string { + return fmt.Sprintf(` + resource "pagerduty_service" "foo" { + name = "foo" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = "%s" + } + `, escalationPolicyID) +} + +func testAccCheckPagerDutyServiceConfigUpdated(id string) string { + return fmt.Sprintf(` +resource "pagerduty_service" "foo" { + name = "bar" + description = "bar" + auto_resolve_timeout = 3600 + acknowledgement_timeout = 3600 + escalation_policy = "%s" +} +`, escalationPolicyID) +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_team.go b/builtin/providers/pagerduty/resource_pagerduty_team.go new file mode 100644 index 000000000..a42cd9005 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_team.go @@ -0,0 +1,118 @@ +package pagerduty + +import ( + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePagerDutyTeam() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyTeamCreate, + Read: resourcePagerDutyTeamRead, + Update: resourcePagerDutyTeamUpdate, + Delete: resourcePagerDutyTeamDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyTeamImport, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Managed by Terraform", + }, + }, + } +} + +func buildTeamStruct(d *schema.ResourceData) *pagerduty.Team { + team := pagerduty.Team{ + Name: d.Get("name").(string), + } + + if attr, ok := d.GetOk("description"); ok { + team.Description = attr.(string) + } + + return &team +} + +func resourcePagerDutyTeamCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + t := buildTeamStruct(d) + + log.Printf("[INFO] Creating PagerDuty team %s", t.Name) + + t, err := client.CreateTeam(t) + + if err != nil { + return err + } + + d.SetId(t.ID) + + return nil + +} + +func resourcePagerDutyTeamRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty team %s", d.Id()) + + t, err := client.GetTeam(d.Id()) + + if err != nil { + return err + } + + d.Set("name", t.Name) + d.Set("description", t.Description) + + return nil +} + +func resourcePagerDutyTeamUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + t := buildTeamStruct(d) + + log.Printf("[INFO] Updating PagerDuty team %s", d.Id()) + + t, err := client.UpdateTeam(d.Id(), t) + + if err != nil { + return err + } + + return nil +} + +func resourcePagerDutyTeamDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Deleting PagerDuty team %s", d.Id()) + + err := client.DeleteTeam(d.Id()) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} + +func resourcePagerDutyTeamImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if err := resourcePagerDutyTeamRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_team_test.go b/builtin/providers/pagerduty/resource_pagerduty_team_test.go new file mode 100644 index 000000000..369440ee9 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_team_test.go @@ -0,0 +1,83 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutyTeam_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyTeamDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyTeamConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyTeamExists("pagerduty_team.foo"), + resource.TestCheckResourceAttr( + "pagerduty_team.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_team.foo", "description", "foo"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyTeamConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyTeamExists("pagerduty_team.foo"), + resource.TestCheckResourceAttr( + "pagerduty_team.foo", "name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_team.foo", "description", "bar"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyTeamDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_team" { + continue + } + + _, err := client.GetTeam(r.Primary.ID) + + if err == nil { + return fmt.Errorf("Team still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyTeamExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if _, err := client.GetTeam(r.Primary.ID); err != nil { + return fmt.Errorf("Received an error retrieving team %s ID: %s", err, r.Primary.ID) + } + } + return nil + } +} + +const testAccCheckPagerDutyTeamConfig = ` +resource "pagerduty_team" "foo" { + name = "foo" + description = "foo" +} +` + +const testAccCheckPagerDutyTeamConfigUpdated = ` +resource "pagerduty_team" "foo" { + name = "bar" + description = "bar" +} +` diff --git a/builtin/providers/pagerduty/resource_pagerduty_user.go b/builtin/providers/pagerduty/resource_pagerduty_user.go new file mode 100644 index 000000000..02948283c --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_user.go @@ -0,0 +1,236 @@ +package pagerduty + +import ( + "fmt" + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePagerDutyUser() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyUserCreate, + Read: resourcePagerDutyUserRead, + Update: resourcePagerDutyUserUpdate, + Delete: resourcePagerDutyUserDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyUserImport, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "email": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "color": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "role": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "user", + ValidateFunc: validatePagerDutyUserRole, + }, + "job_title": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "avatar_url": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "teams": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + "time_zone": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "html_url": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "invitation_sent": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Managed by Terraform", + }, + }, + } +} + +func buildUserStruct(d *schema.ResourceData) *pagerduty.User { + user := pagerduty.User{ + Name: d.Get("name").(string), + Email: d.Get("email").(string), + } + + if attr, ok := d.GetOk("color"); ok { + user.Color = attr.(string) + } + + if attr, ok := d.GetOk("role"); ok { + user.Role = attr.(string) + } + + if attr, ok := d.GetOk("job_title"); ok { + user.JobTitle = attr.(string) + } + + if attr, ok := d.GetOk("description"); ok { + user.Description = attr.(string) + } + + return &user +} + +func resourcePagerDutyUserCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + u := buildUserStruct(d) + + log.Printf("[INFO] Creating PagerDuty user %s", u.Name) + + u, err := client.CreateUser(*u) + + if err != nil { + return err + } + + d.SetId(u.ID) + + return resourcePagerDutyUserUpdate(d, meta) +} + +func resourcePagerDutyUserRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty user %s", d.Id()) + + u, err := client.GetUser(d.Id(), pagerduty.GetUserOptions{}) + + if err != nil { + return err + } + + d.Set("name", u.Name) + d.Set("email", u.Email) + d.Set("time_zone", u.Timezone) + d.Set("color", u.Color) + d.Set("role", u.Role) + d.Set("avatar_url", u.AvatarURL) + d.Set("description", u.Description) + d.Set("job_title", u.JobTitle) + d.Set("teams", u.Teams) + + return nil +} + +func resourcePagerDutyUserUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + u := buildUserStruct(d) + u.ID = d.Id() + + log.Printf("[INFO] Updating PagerDuty user %s", d.Id()) + + u, err := client.UpdateUser(*u) + + if err != nil { + return err + } + + if d.HasChange("teams") { + o, n := d.GetChange("teams") + + if o == nil { + o = new(schema.Set) + } + + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + + remove := expandStringList(os.Difference(ns).List()) + add := expandStringList(ns.Difference(os).List()) + + for _, t := range remove { + _, tErr := client.GetTeam(t) + + if tErr != nil { + log.Printf("[INFO] PagerDuty team: %s not found, removing dangling team reference for user %s", t, d.Id()) + continue + } + + log.Printf("[INFO] Removing PagerDuty user %s from team: %s", d.Id(), t) + + rErr := client.RemoveUserFromTeam(t, d.Id()) + if rErr != nil { + return rErr + } + } + + for _, t := range add { + log.Printf("[INFO] Adding PagerDuty user %s to team: %s", d.Id(), t) + + aErr := client.AddUserToTeam(t, d.Id()) + if aErr != nil { + return aErr + } + } + } + + return nil +} + +func resourcePagerDutyUserDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Deleting PagerDuty user %s", d.Id()) + + err := client.DeleteUser(d.Id()) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} + +func resourcePagerDutyUserImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if err := resourcePagerDutyUserRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} + +func validatePagerDutyUserRole(v interface{}, k string) (ws []string, errors []error) { + validRoles := []string{"admin", "limited_user", "owner", "read_only_user", "user"} + role := v.(string) + + if !contains(validRoles, role) { + errors = append(errors, fmt.Errorf("%q must be one of %v", k, validRoles)) + } + + return +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_user_test.go b/builtin/providers/pagerduty/resource_pagerduty_user_test.go new file mode 100644 index 000000000..bbe8f374f --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_user_test.go @@ -0,0 +1,110 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutyUser_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyUserDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyUserConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyUserExists("pagerduty_user.foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "email", "foo@bar.com"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "color", "green"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "role", "user"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "job_title", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "description", "foo"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyUserConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyUserExists("pagerduty_user.foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "email", "bar@foo.com"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "color", "red"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "role", "user"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "job_title", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "description", "bar"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyUserDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_user" { + continue + } + + opts := pagerduty.GetUserOptions{} + + _, err := client.GetUser(r.Primary.ID, opts) + + if err == nil { + return fmt.Errorf("User still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyUserExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + opts := pagerduty.GetUserOptions{} + if _, err := client.GetUser(r.Primary.ID, opts); err != nil { + return fmt.Errorf("Received an error retrieving user %s ID: %s", err, r.Primary.ID) + } + } + return nil + } +} + +const testAccCheckPagerDutyUserConfig = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} +` + +const testAccCheckPagerDutyUserConfigUpdated = ` +resource "pagerduty_user" "foo" { + name = "bar" + email = "bar@foo.com" + color = "red" + role = "user" + job_title = "bar" + description = "bar" +} +` diff --git a/builtin/providers/pagerduty/structure.go b/builtin/providers/pagerduty/structure.go new file mode 100644 index 000000000..461f3cdf1 --- /dev/null +++ b/builtin/providers/pagerduty/structure.go @@ -0,0 +1,22 @@ +package pagerduty + +// Takes the result of flatmap.Expand for an array of strings +// and returns a []string +func expandStringList(configured []interface{}) []string { + vs := make([]string, 0, len(configured)) + for _, v := range configured { + vs = append(vs, string(v.(string))) + } + return vs +} + +// Checks if a slice contains a string +func contains(slice []string, item string) bool { + set := make(map[string]struct{}, len(slice)) + for _, s := range slice { + set[s] = struct{}{} + } + + _, ok := set[item] + return ok +} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index 077dfecbf..e40de2664 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -37,6 +37,7 @@ import ( nullprovider "github.com/hashicorp/terraform/builtin/providers/null" openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack" packetprovider "github.com/hashicorp/terraform/builtin/providers/packet" + pagerdutyprovider "github.com/hashicorp/terraform/builtin/providers/pagerduty" postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql" powerdnsprovider "github.com/hashicorp/terraform/builtin/providers/powerdns" rabbitmqprovider "github.com/hashicorp/terraform/builtin/providers/rabbitmq" @@ -94,6 +95,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "null": nullprovider.Provider, "openstack": openstackprovider.Provider, "packet": packetprovider.Provider, + "pagerduty": pagerdutyprovider.Provider, "postgresql": postgresqlprovider.Provider, "powerdns": powerdnsprovider.Provider, "rabbitmq": rabbitmqprovider.Provider, From 5c99f1317a22d1216b707e5b7875b4d04d71192f Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Sun, 16 Oct 2016 03:52:45 +0200 Subject: [PATCH 04/26] Update documentation --- .../r/escalation_policy.html.markdown | 62 ++++++++------ .../pagerduty/r/schedule.html.markdown | 82 +++++++++++++++++++ .../pagerduty/r/service.html.markdown | 47 ++++++----- .../providers/pagerduty/r/team.html.markdown | 10 ++- .../providers/pagerduty/r/user.html.markdown | 27 +++--- website/source/layouts/docs.erb | 4 + website/source/layouts/pagerduty.erb | 65 ++++++++------- 7 files changed, 201 insertions(+), 96 deletions(-) create mode 100644 website/source/docs/providers/pagerduty/r/schedule.html.markdown diff --git a/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown b/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown index 1128ca196..d00d2cb3d 100644 --- a/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown +++ b/website/source/docs/providers/pagerduty/r/escalation_policy.html.markdown @@ -20,23 +20,18 @@ resource "pagerduty_user" "example" { teams = ["${pagerduty_team.example.id}"] } -resource "pagerduty_escalation_policy" "example" { - name = "Engineering" - description = "Engineering Escalation Policy" - num_loops = 2 - escalation_rules = <Packet + > + PagerDuty + + > PostgreSQL diff --git a/website/source/layouts/pagerduty.erb b/website/source/layouts/pagerduty.erb index e626046c2..4a5af0cc1 100644 --- a/website/source/layouts/pagerduty.erb +++ b/website/source/layouts/pagerduty.erb @@ -1,35 +1,38 @@ <% wrap_layout :inner do %> - <% content_for :sidebar do %> - + <% end %> - <%= yield %> - <% end %> + <%= yield %> + <% end %> From 9e8167735441d34b23fcdaae78d95f153e0bc1d2 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Sun, 16 Oct 2016 13:18:06 +0200 Subject: [PATCH 05/26] Add support for service integration --- builtin/providers/pagerduty/provider.go | 11 +- .../resource_pagerduty_service_integration.go | 134 ++++++++++++++++++ 2 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 builtin/providers/pagerduty/resource_pagerduty_service_integration.go diff --git a/builtin/providers/pagerduty/provider.go b/builtin/providers/pagerduty/provider.go index 2be5bc633..0d210354f 100644 --- a/builtin/providers/pagerduty/provider.go +++ b/builtin/providers/pagerduty/provider.go @@ -19,11 +19,12 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "pagerduty_user": resourcePagerDutyUser(), - "pagerduty_team": resourcePagerDutyTeam(), - "pagerduty_service": resourcePagerDutyService(), - "pagerduty_schedule": resourcePagerDutySchedule(), - "pagerduty_escalation_policy": resourcePagerDutyEscalationPolicy(), + "pagerduty_user": resourcePagerDutyUser(), + "pagerduty_team": resourcePagerDutyTeam(), + "pagerduty_service": resourcePagerDutyService(), + "pagerduty_service_integration": resourcePagerDutyServiceIntegration(), + "pagerduty_schedule": resourcePagerDutySchedule(), + "pagerduty_escalation_policy": resourcePagerDutyEscalationPolicy(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go new file mode 100644 index 000000000..d97bada6d --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go @@ -0,0 +1,134 @@ +package pagerduty + +import ( + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePagerDutyServiceIntegration() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyServiceIntegrationCreate, + Read: resourcePagerDutyServiceIntegrationRead, + Update: resourcePagerDutyServiceIntegrationUpdate, + + // NOTE: It's currently not possible to delete integrations via the API. + // Therefore it needs to be manually removed from the Web UI. + Delete: resourcePagerDutyServiceIntegrationDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyServiceIntegrationImport, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "service": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "integration_key": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "integration_email": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func buildServiceIntegrationStruct(d *schema.ResourceData) *pagerduty.Integration { + service := pagerduty.Integration{ + Type: d.Get("type").(string), + Name: d.Get("name").(string), + Service: &pagerduty.APIObject{ + Type: "service", + ID: d.Get("service").(string), + }, + } + + return &service +} + +func resourcePagerDutyServiceIntegrationCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + i := buildServiceIntegrationStruct(d) + + log.Printf("[INFO] Creating PagerDuty service integration %s", i.Name) + + service := d.Get("service").(string) + + s, err := client.CreateIntegration(service, *i) + + if err != nil { + return err + } + + d.SetId(s.ID) + + return resourcePagerDutyServiceIntegrationRead(d, meta) +} + +func resourcePagerDutyServiceIntegrationRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty service integration %s", d.Id()) + + service := d.Get("service").(string) + + i, err := client.GetIntegration(service, d.Id(), pagerduty.GetIntegrationOptions{}) + + if err != nil { + return err + } + + d.Set("name", i.Name) + d.Set("type", i.Type) + d.Set("service", i.Service) + d.Set("integration_key", i.IntegrationKey) + d.Set("integration_email", i.IntegrationEmail) + + return nil +} + +func resourcePagerDutyServiceIntegrationUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + s := buildServiceIntegrationStruct(d) + s.ID = d.Id() + + service := d.Get("service").(string) + + log.Printf("[INFO] Updating PagerDuty service %s", d.Id()) + + s, err := client.UpdateIntegration(service, *s) + + if err != nil { + return err + } + + return nil +} + +func resourcePagerDutyServiceIntegrationDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Removing PagerDuty service integration %s", d.Id()) + + d.SetId("") + + return nil +} + +func resourcePagerDutyServiceIntegrationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if err := resourcePagerDutyServiceIntegrationRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} From 074e989846b5cbb1c699928e78e3b28d3ec51b46 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Sun, 16 Oct 2016 16:48:20 +0200 Subject: [PATCH 06/26] Fix up tests --- ...import_pagerduty_escalation_policy_test.go | 22 +-- .../import_pagerduty_service_test.go | 19 +- .../pagerduty/import_pagerduty_team_test.go | 13 +- .../pagerduty/import_pagerduty_user_test.go | 17 +- builtin/providers/pagerduty/provider_test.go | 13 -- .../resource_pagerduty_escalation_policy.go | 26 ++- ...source_pagerduty_escalation_policy_test.go | 89 +++++----- .../pagerduty/resource_pagerduty_service.go | 2 +- .../resource_pagerduty_service_integration.go | 14 +- ...urce_pagerduty_service_integration_test.go | 166 ++++++++++++++++++ .../resource_pagerduty_service_test.go | 103 ++++++++--- 11 files changed, 340 insertions(+), 144 deletions(-) create mode 100644 builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go diff --git a/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go b/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go index f263fb871..d71e50a19 100644 --- a/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go +++ b/builtin/providers/pagerduty/import_pagerduty_escalation_policy_test.go @@ -7,32 +7,22 @@ import ( ) func TestAccPagerDutyEscalationPolicy_import(t *testing.T) { + resourceName := "pagerduty_escalation_policy.foo" + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPagerDutyEscalationPolicyDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccPagerDutyEscalationPolicyConfigImported, + Config: testAccCheckPagerDutyEscalationPolicyConfig, }, + resource.TestStep{ - ResourceName: "pagerduty_escalation_policy.foo", + ResourceName: resourceName, ImportState: true, - ImportStateVerify: false, + ImportStateVerify: true, }, }, }) } - -const testAccPagerDutyEscalationPolicyConfigImported = ` -resource "pagerduty_escalation_policy" "foo" { - name = "foo" - escalation_rule { - escalation_delay_in_minutes = 10 - target { - id = "PLBP04G" - type = "user" - } - } -} -` diff --git a/builtin/providers/pagerduty/import_pagerduty_service_test.go b/builtin/providers/pagerduty/import_pagerduty_service_test.go index 84a9bbb51..c22ba2449 100644 --- a/builtin/providers/pagerduty/import_pagerduty_service_test.go +++ b/builtin/providers/pagerduty/import_pagerduty_service_test.go @@ -7,29 +7,22 @@ import ( ) func TestAccPagerDutyService_import(t *testing.T) { + resourceName := "pagerduty_service.foo" + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPagerDutyServiceDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccPagerDutyServiceConfigImported, + Config: testAccCheckPagerDutyServiceConfig, }, + resource.TestStep{ - ResourceName: "pagerduty_service.foo", + ResourceName: resourceName, ImportState: true, - ImportStateVerify: false, + ImportStateVerify: true, }, }, }) } - -const testAccPagerDutyServiceConfigImported = ` -resource "pagerduty_service" "foo" { - name = "foo" - description = "foo" - acknowledgement_timeout = "1800" - auto_resolve_timeout = "14400" - escalation_policy = "PGOMBUU" -} -` diff --git a/builtin/providers/pagerduty/import_pagerduty_team_test.go b/builtin/providers/pagerduty/import_pagerduty_team_test.go index 78f677dcf..7bf00f919 100644 --- a/builtin/providers/pagerduty/import_pagerduty_team_test.go +++ b/builtin/providers/pagerduty/import_pagerduty_team_test.go @@ -7,26 +7,21 @@ import ( ) func TestAccPagerDutyTeam_import(t *testing.T) { + resourceName := "pagerduty_team.foo" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPagerDutyTeamDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccPagerDutyTeamConfigImported, + Config: testAccCheckPagerDutyTeamConfig, }, + resource.TestStep{ - ResourceName: "pagerduty_team.foo", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, }, }) } - -const testAccPagerDutyTeamConfigImported = ` -resource "pagerduty_team" "foo" { - name = "foo" - description = "foo" -} -` diff --git a/builtin/providers/pagerduty/import_pagerduty_user_test.go b/builtin/providers/pagerduty/import_pagerduty_user_test.go index c39e8f2b8..82d7cc99b 100644 --- a/builtin/providers/pagerduty/import_pagerduty_user_test.go +++ b/builtin/providers/pagerduty/import_pagerduty_user_test.go @@ -1,35 +1,28 @@ package pagerduty import ( - "fmt" "testing" "github.com/hashicorp/terraform/helper/resource" ) func TestAccPagerDutyUser_import(t *testing.T) { + resourceName := "pagerduty_user.foo" + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPagerDutyUserDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccPagerDutyUserConfigImported(importUserID), + Config: testAccCheckPagerDutyUserConfig, }, + resource.TestStep{ - ResourceName: "pagerduty_user.foo", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, }, }) } - -func testAccPagerDutyUserConfigImported(id string) string { - return fmt.Sprintf(` - resource "pagerduty_user" "foo" { - name = "foo" - email = "foo@bar.com" - } - `) -} diff --git a/builtin/providers/pagerduty/provider_test.go b/builtin/providers/pagerduty/provider_test.go index 4057873b0..b72ccae69 100644 --- a/builtin/providers/pagerduty/provider_test.go +++ b/builtin/providers/pagerduty/provider_test.go @@ -32,17 +32,4 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("PAGERDUTY_TOKEN"); v == "" { t.Fatal("PAGERDUTY_TOKEN must be set for acceptance tests") } - - if v := os.Getenv("PAGERDUTY_USER_ID"); v == "" { - t.Fatal("PAGERDUTY_USER_ID must be set for acceptance tests") - } - - if v := os.Getenv("PAGERDUTY_ESCALATION_POLICY_ID"); v == "" { - t.Fatal("PAGERDUTY_ESCALATION_POLICY_ID must be set for acceptance tests") - } } - -var importEscalationPolicyID = os.Getenv("PAGERDUTY_ESCALATION_POLICY_ID") -var importUserID = os.Getenv("PAGERDUTY_USER_ID") -var userID = os.Getenv("PAGERDUTY_USER_ID") -var escalationPolicyID = os.Getenv("PAGERDUTY_ESCALATION_POLICY_ID") diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go index 76cf4d709..5154f180a 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go @@ -35,6 +35,10 @@ func resourcePagerDutyEscalationPolicy() *schema.Resource { Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, "escalation_delay_in_minutes": &schema.Schema{ Type: schema.TypeInt, Required: true, @@ -142,7 +146,27 @@ func resourcePagerDutyEscalationPolicyRead(d *schema.ResourceData, meta interfac d.Set("name", e.Name) d.Set("description", e.Description) d.Set("num_loops", e.NumLoops) - d.Set("escalation_rules", e.EscalationRules) + + escalationRules := make([]map[string]interface{}, 0, len(e.EscalationRules)) + + for _, r := range e.EscalationRules { + targets := make([]map[string]interface{}, 0, len(r.Targets)) + + for _, t := range r.Targets { + targets = append(targets, map[string]interface{}{ + "id": t.ID, + "type": t.Type, + }) + } + + escalationRules = append(escalationRules, map[string]interface{}{ + "id": r.ID, + "target": targets, + "escalation_delay_in_minutes": r.Delay, + }) + } + + d.Set("escalation_rule", escalationRules) return nil } diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go index 8e07df040..83f5ec4da 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go @@ -16,7 +16,7 @@ func TestAccPagerDutyEscalationPolicy_Basic(t *testing.T) { CheckDestroy: testAccCheckPagerDutyEscalationPolicyDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccCheckPagerDutyEscalationPolicyConfig(userID), + Config: testAccCheckPagerDutyEscalationPolicyConfig, Check: resource.ComposeTestCheckFunc( testAccCheckPagerDutyEscalationPolicyExists("pagerduty_escalation_policy.foo"), resource.TestCheckResourceAttr( @@ -25,20 +25,10 @@ func TestAccPagerDutyEscalationPolicy_Basic(t *testing.T) { "pagerduty_escalation_policy.foo", "description", "foo"), resource.TestCheckResourceAttr( "pagerduty_escalation_policy.foo", "num_loops", "1"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.#", "1"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.escalation_delay_in_minutes", "10"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.target.#", "1"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.id", userID), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.type", "user"), ), }, resource.TestStep{ - Config: testAccCheckPagerDutyEscalationPolicyConfigUpdated(userID), + Config: testAccCheckPagerDutyEscalationPolicyConfigUpdated, Check: resource.ComposeTestCheckFunc( testAccCheckPagerDutyEscalationPolicyExists("pagerduty_escalation_policy.foo"), resource.TestCheckResourceAttr( @@ -47,16 +37,6 @@ func TestAccPagerDutyEscalationPolicy_Basic(t *testing.T) { "pagerduty_escalation_policy.foo", "description", "bar"), resource.TestCheckResourceAttr( "pagerduty_escalation_policy.foo", "num_loops", "2"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.#", "2"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.escalation_delay_in_minutes", "10"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.target.#", "1"), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.id", userID), - resource.TestCheckResourceAttr( - "pagerduty_escalation_policy.foo", "escalation_rule.0.target.0.type", "user"), ), }, }, @@ -82,36 +62,64 @@ func testAccCheckPagerDutyEscalationPolicyDestroy(s *terraform.State) error { func testAccCheckPagerDutyEscalationPolicyExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - client := testAccProvider.Meta().(*pagerduty.Client) - for _, r := range s.RootModule().Resources { - if _, err := client.GetEscalationPolicy(r.Primary.ID, &pagerduty.GetEscalationPolicyOptions{}); err != nil { - return fmt.Errorf("Received an error retrieving escalation_policy %s ID: %s", err, r.Primary.ID) - } + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) } + if rs.Primary.ID == "" { + return fmt.Errorf("No Escalation Policy ID is set") + } + + client := testAccProvider.Meta().(*pagerduty.Client) + + found, err := client.GetEscalationPolicy(rs.Primary.ID, &pagerduty.GetEscalationPolicyOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Escalation policy not found: %v - %v", rs.Primary.ID, found) + } + return nil } } -func testAccCheckPagerDutyEscalationPolicyConfig(id string) string { - return fmt.Sprintf(` +const testAccCheckPagerDutyEscalationPolicyConfig = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + resource "pagerduty_escalation_policy" "foo" { name = "foo" description = "foo" num_loops = 1 escalation_rule { - escalation_delay_in_minutes = 10 + escalation_delay_in_minutes = 10 target { - type = "user" - id = "%s" + type = "user_reference" + id = "${pagerduty_user.foo.id}" } } } - `, id) +` + +const testAccCheckPagerDutyEscalationPolicyConfigUpdated = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" } -func testAccCheckPagerDutyEscalationPolicyConfigUpdated(id string) string { - return fmt.Sprintf(` resource "pagerduty_escalation_policy" "foo" { name = "bar" description = "bar" @@ -120,18 +128,17 @@ resource "pagerduty_escalation_policy" "foo" { escalation_rule { escalation_delay_in_minutes = 10 target { - type = "user" - id = "%[1]v" + type = "user_reference" + id = "${pagerduty_user.foo.id}" } } escalation_rule { escalation_delay_in_minutes = 20 target { - type = "user" - id = "%[1]v" + type = "user_reference" + id = "${pagerduty_user.foo.id}" } } } -`, userID) -} +` diff --git a/builtin/providers/pagerduty/resource_pagerduty_service.go b/builtin/providers/pagerduty/resource_pagerduty_service.go index eb68f83c5..d45710306 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service.go @@ -99,7 +99,7 @@ func resourcePagerDutyServiceRead(d *schema.ResourceData, meta interface{}) erro } d.Set("name", s.Name) - d.Set("escalation_policy", s.EscalationPolicy) + d.Set("escalation_policy", s.EscalationPolicy.ID) d.Set("description", s.Description) d.Set("auto_resolve_timeout", s.AutoResolveTimeout) d.Set("acknowledgement_timeout", s.AcknowledgementTimeout) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go index d97bada6d..b9853f27e 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go @@ -16,9 +16,6 @@ func resourcePagerDutyServiceIntegration() *schema.Resource { // NOTE: It's currently not possible to delete integrations via the API. // Therefore it needs to be manually removed from the Web UI. Delete: resourcePagerDutyServiceIntegrationDelete, - Importer: &schema.ResourceImporter{ - State: resourcePagerDutyServiceIntegrationImport, - }, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, @@ -26,7 +23,7 @@ func resourcePagerDutyServiceIntegration() *schema.Resource { }, "service": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, }, "type": &schema.Schema{ Type: schema.TypeString, @@ -107,7 +104,7 @@ func resourcePagerDutyServiceIntegrationUpdate(d *schema.ResourceData, meta inte service := d.Get("service").(string) - log.Printf("[INFO] Updating PagerDuty service %s", d.Id()) + log.Printf("[INFO] Updating PagerDuty service integration %s", d.Id()) s, err := client.UpdateIntegration(service, *s) @@ -125,10 +122,3 @@ func resourcePagerDutyServiceIntegrationDelete(d *schema.ResourceData, meta inte return nil } - -func resourcePagerDutyServiceIntegrationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - if err := resourcePagerDutyServiceIntegrationRead(d, meta); err != nil { - return nil, err - } - return []*schema.ResourceData{d}, nil -} diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go b/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go new file mode 100644 index 000000000..65176372f --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go @@ -0,0 +1,166 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutyServiceIntegration_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyServiceIntegrationDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyServiceIntegrationConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceIntegrationExists("pagerduty_service_integration.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service_integration.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service_integration.foo", "type", "generic_events_api_inbound_integration"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyServiceIntegrationConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceIntegrationExists("pagerduty_service_integration.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service_integration.foo", "name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_service_integration.foo", "type", "generic_events_api_inbound_integration"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyServiceIntegrationDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_service_integration" { + continue + } + + service, _ := s.RootModule().Resources["pagerduty_service.foo"] + + _, err := client.GetIntegration(service.Primary.ID, r.Primary.ID, pagerduty.GetIntegrationOptions{}) + + if err == nil { + return fmt.Errorf("Service Integration still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyServiceIntegrationExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Service Integration ID is set") + } + + service, _ := s.RootModule().Resources["pagerduty_service.foo"] + + client := testAccProvider.Meta().(*pagerduty.Client) + + found, err := client.GetIntegration(service.Primary.ID, rs.Primary.ID, pagerduty.GetIntegrationOptions{}) + if err != nil { + return fmt.Errorf("Service integration not found: %v", rs.Primary.ID) + // return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Service Integration not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +const testAccCheckPagerDutyServiceIntegrationConfig = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "foo" + description = "foo" + num_loops = 1 + + escalation_rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } +} + +resource "pagerduty_service" "foo" { + name = "foo" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" +} + +resource "pagerduty_service_integration" "foo" { + name = "foo" + type = "generic_events_api_inbound_integration" + service = "${pagerduty_service.foo.id}" +} +` + +const testAccCheckPagerDutyServiceIntegrationConfigUpdated = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "bar" + description = "bar" + num_loops = 2 + + escalation_rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } +} + +resource "pagerduty_service" "foo" { + name = "bar" + description = "bar" + auto_resolve_timeout = 3600 + acknowledgement_timeout = 3600 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" +} + +resource "pagerduty_service_integration" "foo" { + name = "bar" + type = "generic_events_api_inbound_integration" + service = "${pagerduty_service.foo.id}" +} +` diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_test.go b/builtin/providers/pagerduty/resource_pagerduty_service_test.go index d270c7872..8ccd210ce 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_test.go @@ -16,7 +16,7 @@ func TestAccPagerDutyService_Basic(t *testing.T) { CheckDestroy: testAccCheckPagerDutyServiceDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccCheckPagerDutyServiceConfig(escalationPolicyID), + Config: testAccCheckPagerDutyServiceConfig, Check: resource.ComposeTestCheckFunc( testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), resource.TestCheckResourceAttr( @@ -27,12 +27,10 @@ func TestAccPagerDutyService_Basic(t *testing.T) { "pagerduty_service.foo", "auto_resolve_timeout", "1800"), resource.TestCheckResourceAttr( "pagerduty_service.foo", "acknowledgement_timeout", "1800"), - resource.TestCheckResourceAttr( - "pagerduty_service.foo", "escalation_policy", escalationPolicyID), ), }, resource.TestStep{ - Config: testAccCheckPagerDutyServiceConfigUpdated(escalationPolicyID), + Config: testAccCheckPagerDutyServiceConfigUpdated, Check: resource.ComposeTestCheckFunc( testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), resource.TestCheckResourceAttr( @@ -43,8 +41,6 @@ func TestAccPagerDutyService_Basic(t *testing.T) { "pagerduty_service.foo", "auto_resolve_timeout", "3600"), resource.TestCheckResourceAttr( "pagerduty_service.foo", "acknowledgement_timeout", "3600"), - resource.TestCheckResourceAttr( - "pagerduty_service.foo", "escalation_policy", escalationPolicyID), ), }, }, @@ -70,36 +66,91 @@ func testAccCheckPagerDutyServiceDestroy(s *terraform.State) error { func testAccCheckPagerDutyServiceExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - client := testAccProvider.Meta().(*pagerduty.Client) - for _, r := range s.RootModule().Resources { - if _, err := client.GetService(r.Primary.ID, pagerduty.GetServiceOptions{}); err != nil { - return fmt.Errorf("Received an error retrieving service %s ID: %s", err, r.Primary.ID) - } + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) } + if rs.Primary.ID == "" { + return fmt.Errorf("No Service ID is set") + } + + client := testAccProvider.Meta().(*pagerduty.Client) + + found, err := client.GetService(rs.Primary.ID, pagerduty.GetServiceOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Service not found: %v - %v", rs.Primary.ID, found) + } + return nil } } -func testAccCheckPagerDutyServiceConfig(id string) string { - return fmt.Sprintf(` - resource "pagerduty_service" "foo" { - name = "foo" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = "%s" - } - `, escalationPolicyID) +const testAccCheckPagerDutyServiceConfig = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "bar" + description = "bar" + num_loops = 2 + + escalation_rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } +} + +resource "pagerduty_service" "foo" { + name = "foo" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" +} +` + +const testAccCheckPagerDutyServiceConfigUpdated = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "bar" + description = "bar" + num_loops = 2 + + escalation_rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } -func testAccCheckPagerDutyServiceConfigUpdated(id string) string { - return fmt.Sprintf(` resource "pagerduty_service" "foo" { name = "bar" description = "bar" auto_resolve_timeout = 3600 acknowledgement_timeout = 3600 - escalation_policy = "%s" -} -`, escalationPolicyID) + escalation_policy = "${pagerduty_escalation_policy.foo.id}" } +` From fddefa6f33ec18c75a8c72f53c739a851ebd52d2 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Sun, 16 Oct 2016 19:01:27 +0200 Subject: [PATCH 07/26] Add documentation for `service_integration` --- .../r/service_integration.html.markdown | 72 +++++++++++++++++++ website/source/layouts/pagerduty.erb | 3 + 2 files changed, 75 insertions(+) create mode 100644 website/source/docs/providers/pagerduty/r/service_integration.html.markdown diff --git a/website/source/docs/providers/pagerduty/r/service_integration.html.markdown b/website/source/docs/providers/pagerduty/r/service_integration.html.markdown new file mode 100644 index 000000000..e37bf8606 --- /dev/null +++ b/website/source/docs/providers/pagerduty/r/service_integration.html.markdown @@ -0,0 +1,72 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_service_integration" +sidebar_current: "docs-pagerduty-resource-service-integration" +description: |- + Creates and manages a service integration in PagerDuty. +--- + +# pagerduty\_service_integration + +A [service integration](https://v2.developer.pagerduty.com/v2/page/api-reference#!/Services/post_services_id_integrations) is an integration that belongs to a service. + + +## Example Usage + +``` +resource "pagerduty_user" "example" { + name = "Earline Greenholt" + email = "125.greenholt.earline@graham.name" + teams = ["${pagerduty_team.example.id}"] +} + +resource "pagerduty_escalation_policy" "foo" { + name = "Engineering Escalation Policy" + num_loops = 2 + + escalation_rule { + escalation_delay_in_minutes = 10 + + target { + type = "user" + id = "${pagerduty_user.example.id}" + } + } +} + +resource "pagerduty_service" "example" { + name = "My Web App" + auto_resolve_timeout = 14400 + acknowledgement_timeout = 600 + escalation_policy = "${pagerduty_escalation_policy.example.id}" +} + +resource "pagerduty_service_integration" "example" { + name = "Generic API Service Integration" + type = "generic_events_api_inbound_integration" + service = "${pagerduty_service.example.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + + * `name` - (Optional) The name of the service integration. + * `type` - (Optional) The service type. Can be `aws_cloudwatch_inbound_integration`, `cloudkick_inbound_integration`, + `event_transformer_api_inbound_integration`, + `generic_email_inbound_integration`, + `generic_events_api_inbound_integration`, + `keynote_inbound_integration`, + `nagios_inbound_integration`, + `pingdom_inbound_integration`, + `sql_monitor_inbound_integration` + * `service` - (Optional) The PagerDuty service that the integration belongs to. + +## Attributes Reference + +The following attributes are exported: + + * `id` - The ID of the service integration. + * `integration_key` - This is the unique key used to route events to this integration when received via the PagerDuty Events API. + * `integration_email` - This is the unique fully-qualified email address used for routing emails to this integration for processing. diff --git a/website/source/layouts/pagerduty.erb b/website/source/layouts/pagerduty.erb index 4a5af0cc1..164b831e8 100644 --- a/website/source/layouts/pagerduty.erb +++ b/website/source/layouts/pagerduty.erb @@ -28,6 +28,9 @@ > pagerduty_service + > + pagerduty_service_integration + From d786c1cf6896954ac8860dbdbc1850bdca55b354 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Sun, 16 Oct 2016 19:28:25 +0200 Subject: [PATCH 08/26] Add test case for user with teams --- .../pagerduty/resource_pagerduty_user_test.go | 112 +++++++++++++++++- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_user_test.go b/builtin/providers/pagerduty/resource_pagerduty_user_test.go index bbe8f374f..a5258e586 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_user_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_user_test.go @@ -55,6 +55,52 @@ func TestAccPagerDutyUser_Basic(t *testing.T) { }) } +func TestAccPagerDutyUserWithTeams_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyUserDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyUserWithTeamsConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyUserExists("pagerduty_user.foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "email", "foo@bar.com"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "teams.#", "1"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyUserWithTeamsConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyUserExists("pagerduty_user.foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "email", "foo@bar.com"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "teams.#", "2"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyUserWithNoTeamsConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyUserExists("pagerduty_user.foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "email", "foo@bar.com"), + resource.TestCheckResourceAttr( + "pagerduty_user.foo", "teams.#", "0"), + ), + }, + }, + }) +} + func testAccCheckPagerDutyUserDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*pagerduty.Client) for _, r := range s.RootModule().Resources { @@ -76,13 +122,25 @@ func testAccCheckPagerDutyUserDestroy(s *terraform.State) error { func testAccCheckPagerDutyUserExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - client := testAccProvider.Meta().(*pagerduty.Client) - for _, r := range s.RootModule().Resources { - opts := pagerduty.GetUserOptions{} - if _, err := client.GetUser(r.Primary.ID, opts); err != nil { - return fmt.Errorf("Received an error retrieving user %s ID: %s", err, r.Primary.ID) - } + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) } + if rs.Primary.ID == "" { + return fmt.Errorf("No user ID is set") + } + + client := testAccProvider.Meta().(*pagerduty.Client) + + found, err := client.GetUser(rs.Primary.ID, pagerduty.GetUserOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("User not found: %v - %v", rs.Primary.ID, found) + } + return nil } } @@ -108,3 +166,45 @@ resource "pagerduty_user" "foo" { description = "bar" } ` + +const testAccCheckPagerDutyUserWithTeamsConfig = ` +resource "pagerduty_team" "foo" { + name = "Foo team" +} + +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + teams = ["${pagerduty_team.foo.id}"] +} +` +const testAccCheckPagerDutyUserWithTeamsConfigUpdated = ` +resource "pagerduty_team" "foo" { + name = "Foo team" +} + +resource "pagerduty_team" "bar" { + name = "Bar team" +} + +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + teams = ["${pagerduty_team.foo.id}", "${pagerduty_team.bar.id}"] +} +` + +const testAccCheckPagerDutyUserWithNoTeamsConfig = ` +resource "pagerduty_team" "foo" { + name = "Foo team" +} + +resource "pagerduty_team" "bar" { + name = "Bar team" +} + +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" +} +` From 819eca48a591c7289a54e9e6420c10bc996505cc Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 10:48:15 +0200 Subject: [PATCH 09/26] Prettify pagerduty layout --- website/source/layouts/pagerduty.erb | 62 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/website/source/layouts/pagerduty.erb b/website/source/layouts/pagerduty.erb index 164b831e8..d68eb852f 100644 --- a/website/source/layouts/pagerduty.erb +++ b/website/source/layouts/pagerduty.erb @@ -1,41 +1,41 @@ <% wrap_layout :inner do %> - <% content_for :sidebar do %> - +<% end %> - <%= yield %> - <% end %> +<%= yield %> +<% end %> From 3fae0454bf59d3df66706a4c6aa907d340a5c4fa Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 16:24:27 +0200 Subject: [PATCH 10/26] Fix diff bug --- .../resource_pagerduty_escalation_policy.go | 52 +----- .../pagerduty/resource_pagerduty_schedule.go | 139 +++++---------- builtin/providers/pagerduty/structure.go | 161 +++++++++++++++++- 3 files changed, 192 insertions(+), 160 deletions(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go index 5154f180a..32699179a 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go @@ -67,40 +67,12 @@ func resourcePagerDutyEscalationPolicy() *schema.Resource { } } -func buildEscalationPolicyRules(escalationRules *[]interface{}) *[]pagerduty.EscalationRule { - - rules := make([]pagerduty.EscalationRule, len(*escalationRules)) - - for i, l := range *escalationRules { - rule := l.(map[string]interface{}) - - escalationPolicyRule := pagerduty.EscalationRule{ - Delay: uint(rule["escalation_delay_in_minutes"].(int)), - } - - for _, t := range rule["target"].([]interface{}) { - target := t.(map[string]interface{}) - escalationPolicyRule.Targets = append( - escalationPolicyRule.Targets, - pagerduty.APIObject{ - Type: target["type"].(string), - ID: target["id"].(string), - }, - ) - } - - rules[i] = escalationPolicyRule - } - - return &rules -} - func buildEscalationPolicyStruct(d *schema.ResourceData) *pagerduty.EscalationPolicy { escalationRules := d.Get("escalation_rule").([]interface{}) policy := pagerduty.EscalationPolicy{ Name: d.Get("name").(string), - EscalationRules: *buildEscalationPolicyRules(&escalationRules), + EscalationRules: expandRules(escalationRules), } if attr, ok := d.GetOk("description"); ok { @@ -146,27 +118,7 @@ func resourcePagerDutyEscalationPolicyRead(d *schema.ResourceData, meta interfac d.Set("name", e.Name) d.Set("description", e.Description) d.Set("num_loops", e.NumLoops) - - escalationRules := make([]map[string]interface{}, 0, len(e.EscalationRules)) - - for _, r := range e.EscalationRules { - targets := make([]map[string]interface{}, 0, len(r.Targets)) - - for _, t := range r.Targets { - targets = append(targets, map[string]interface{}{ - "id": t.ID, - "type": t.Type, - }) - } - - escalationRules = append(escalationRules, map[string]interface{}{ - "id": r.ID, - "target": targets, - "escalation_delay_in_minutes": r.Delay, - }) - } - - d.Set("escalation_rule", escalationRules) + d.Set("escalation_rule", flattenRules(e.EscalationRules)) return nil } diff --git a/builtin/providers/pagerduty/resource_pagerduty_schedule.go b/builtin/providers/pagerduty/resource_pagerduty_schedule.go index acfb7ab47..34db9d6b7 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_schedule.go +++ b/builtin/providers/pagerduty/resource_pagerduty_schedule.go @@ -1,9 +1,12 @@ package pagerduty import ( + "bytes" + "fmt" "log" "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) @@ -31,7 +34,8 @@ func resourcePagerDutySchedule() *schema.Resource { Default: "Managed by Terraform", }, "schedule_layer": &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, + Set: resourcePagerDutyEscalationHash, Required: true, ForceNew: true, Elem: &schema.Resource{ @@ -47,12 +51,6 @@ func resourcePagerDutySchedule() *schema.Resource { "start": &schema.Schema{ Type: schema.TypeString, Required: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if old == "" { - return false - } - return true - }, }, "end": &schema.Schema{ Type: schema.TypeString, @@ -100,69 +98,15 @@ func resourcePagerDutySchedule() *schema.Resource { } } -func buildScheduleLayers(d *schema.ResourceData, scheduleLayers *[]interface{}) *[]pagerduty.ScheduleLayer { - - pagerdutyLayers := make([]pagerduty.ScheduleLayer, len(*scheduleLayers)) - - for i, l := range *scheduleLayers { - layer := l.(map[string]interface{}) - - scheduleLayer := pagerduty.ScheduleLayer{ - Name: layer["name"].(string), - Start: layer["start"].(string), - End: layer["end"].(string), - RotationVirtualStart: layer["rotation_virtual_start"].(string), - RotationTurnLengthSeconds: uint(layer["rotation_turn_length_seconds"].(int)), - } - - if layer["id"] != nil || layer["id"] != "" { - scheduleLayer.ID = layer["id"].(string) - } - - for _, u := range layer["users"].([]interface{}) { - scheduleLayer.Users = append( - scheduleLayer.Users, - pagerduty.UserReference{ - User: pagerduty.APIObject{ - ID: u.(string), - Type: "user_reference"}, - }, - ) - } - - restrictions := layer["restriction"].([]interface{}) - - if len(restrictions) > 0 { - for _, r := range restrictions { - restriction := r.(map[string]interface{}) - scheduleLayer.Restrictions = append( - scheduleLayer.Restrictions, - pagerduty.Restriction{ - Type: restriction["type"].(string), - StartTimeOfDay: restriction["start_time_of_day"].(string), - DurationSeconds: uint(restriction["duration_seconds"].(int)), - }, - ) - } - } - - pagerdutyLayers[i] = scheduleLayer - - } - - return &pagerdutyLayers -} - func buildScheduleStruct(d *schema.ResourceData) (*pagerduty.Schedule, error) { - pagerdutyLayers := d.Get("schedule_layer").([]interface{}) + pagerdutyLayers := d.Get("schedule_layer").(*schema.Set).List() schedule := pagerduty.Schedule{ - Name: d.Get("name").(string), - TimeZone: d.Get("time_zone").(string), + Name: d.Get("name").(string), + TimeZone: d.Get("time_zone").(string), + ScheduleLayers: expandLayers(pagerdutyLayers), } - schedule.ScheduleLayers = *buildScheduleLayers(d, &pagerdutyLayers) - if attr, ok := d.GetOk("description"); ok { schedule.Description = attr.(string) } @@ -185,7 +129,7 @@ func resourcePagerDutyScheduleCreate(d *schema.ResourceData, meta interface{}) e d.SetId(e.ID) - return resourcePagerDutyScheduleRead(d, meta) + return nil } func resourcePagerDutyScheduleRead(d *schema.ResourceData, meta interface{}) error { @@ -201,39 +145,7 @@ func resourcePagerDutyScheduleRead(d *schema.ResourceData, meta interface{}) err d.Set("name", s.Name) d.Set("description", s.Description) - - scheduleLayers := make([]map[string]interface{}, 0, len(s.ScheduleLayers)) - - for _, layer := range s.ScheduleLayers { - restrictions := make([]map[string]interface{}, 0, len(layer.Restrictions)) - - for _, r := range layer.Restrictions { - restrictions = append(restrictions, map[string]interface{}{ - "duration_seconds": r.DurationSeconds, - "start_time_of_day": r.StartTimeOfDay, - "type": r.Type, - }) - } - - users := make([]string, 0, len(layer.Users)) - - for _, u := range layer.Users { - users = append(users, u.User.ID) - } - - scheduleLayers = append(scheduleLayers, map[string]interface{}{ - "id": layer.ID, - "name": layer.Name, - "start": layer.Start, - "end": layer.End, - "users": users, - "rotation_turn_length_seconds": layer.RotationTurnLengthSeconds, - "rotation_virtual_start": layer.RotationVirtualStart, - "restriction": restrictions, - }) - } - - d.Set("schedule_layer", scheduleLayers) + d.Set("schedule_layer", flattenLayers(s.ScheduleLayers)) return nil } @@ -243,8 +155,6 @@ func resourcePagerDutyScheduleUpdate(d *schema.ResourceData, meta interface{}) e e, _ := buildScheduleStruct(d) - d.MarkNewResource() - log.Printf("[INFO] Updating PagerDuty schedule: %s", d.Id()) e, err := client.UpdateSchedule(d.Id(), *e) @@ -278,3 +188,30 @@ func resourcePagerDutyScheduleImport(d *schema.ResourceData, meta interface{}) ( } return []*schema.ResourceData{d}, nil } + +func resourcePagerDutyEscalationHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["rotation_turn_length_seconds"].(int))) + + if _, ok := m["name"]; ok { + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + } + + if _, ok := m["end"]; ok { + buf.WriteString(fmt.Sprintf("%s-", m["end"].(string))) + } + + for _, u := range m["users"].([]interface{}) { + buf.WriteString(fmt.Sprintf("%s-", u)) + } + + for _, r := range m["restriction"].([]interface{}) { + restriction := r.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", restriction["type"].(string))) + buf.WriteString(fmt.Sprintf("%s-", restriction["start_time_of_day"].(string))) + buf.WriteString(fmt.Sprintf("%d-", restriction["duration_seconds"].(int))) + } + + return hashcode.String(buf.String()) +} diff --git a/builtin/providers/pagerduty/structure.go b/builtin/providers/pagerduty/structure.go index 461f3cdf1..b7a9d26ef 100644 --- a/builtin/providers/pagerduty/structure.go +++ b/builtin/providers/pagerduty/structure.go @@ -1,14 +1,6 @@ package pagerduty -// Takes the result of flatmap.Expand for an array of strings -// and returns a []string -func expandStringList(configured []interface{}) []string { - vs := make([]string, 0, len(configured)) - for _, v := range configured { - vs = append(vs, string(v.(string))) - } - return vs -} +import pagerduty "github.com/PagerDuty/go-pagerduty" // Checks if a slice contains a string func contains(slice []string, item string) bool { @@ -20,3 +12,154 @@ func contains(slice []string, item string) bool { _, ok := set[item] return ok } + +// Expands an array of escalation rules into []pagerduty.EscalationRules +func expandRules(list []interface{}) []pagerduty.EscalationRule { + result := make([]pagerduty.EscalationRule, 0, len(list)) + + for _, r := range list { + rule := r.(map[string]interface{}) + + escalationRule := &pagerduty.EscalationRule{ + Delay: uint(rule["escalation_delay_in_minutes"].(int)), + } + + for _, t := range rule["target"].([]interface{}) { + target := t.(map[string]interface{}) + escalationRule.Targets = append( + escalationRule.Targets, + pagerduty.APIObject{ + ID: target["id"].(string), + Type: target["type"].(string), + }, + ) + } + + result = append(result, *escalationRule) + + } + + return result +} + +// Flattens an array of []pagerduty.EscalationRule into a map[string]interface{} +func flattenRules(list []pagerduty.EscalationRule) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + + for _, i := range list { + r := make(map[string]interface{}) + if i.ID != "" { + r["id"] = i.ID + r["escalation_delay_in_minutes"] = i.Delay + + if len(i.Targets) > 0 { + targets := make([]map[string]interface{}, 0, len(i.Targets)) + for _, t := range i.Targets { + targets = append(targets, map[string]interface{}{ + "id": t.ID, + "type": t.Type, + }) + } + r["target"] = targets + } + + result = append(result, r) + } + } + + return result +} + +// Expands an array of schedules into []pagerduty.Schedule +func expandLayers(list []interface{}) []pagerduty.ScheduleLayer { + result := make([]pagerduty.ScheduleLayer, 0, len(list)) + + for _, l := range list { + layer := l.(map[string]interface{}) + + scheduleLayer := &pagerduty.ScheduleLayer{ + Name: layer["name"].(string), + Start: layer["start"].(string), + End: layer["end"].(string), + RotationVirtualStart: layer["rotation_virtual_start"].(string), + RotationTurnLengthSeconds: uint(layer["rotation_turn_length_seconds"].(int)), + } + + for _, u := range layer["users"].([]interface{}) { + scheduleLayer.Users = append( + scheduleLayer.Users, + pagerduty.UserReference{ + User: pagerduty.APIObject{ + ID: u.(string), + Type: "user_reference", + }, + }, + ) + } + + for _, r := range layer["restriction"].([]interface{}) { + restriction := r.(map[string]interface{}) + scheduleLayer.Restrictions = append( + scheduleLayer.Restrictions, + pagerduty.Restriction{ + Type: restriction["type"].(string), + StartTimeOfDay: restriction["start_time_of_day"].(string), + DurationSeconds: uint(restriction["duration_seconds"].(int)), + }, + ) + } + + result = append(result, *scheduleLayer) + } + + return result +} + +// Flattens an array of []pagerduty.ScheduleLayer into a map[string]interface{} +func flattenLayers(list []pagerduty.ScheduleLayer) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + + for _, i := range list { + r := make(map[string]interface{}) + if i.ID != "" { + r["id"] = i.ID + r["name"] = i.Name + r["end"] = i.End + r["rotation_turn_length_seconds"] = i.RotationTurnLengthSeconds + + if len(i.Users) > 0 { + users := make([]string, 0, len(i.Users)) + for _, u := range i.Users { + users = append(users, u.User.ID) + } + r["users"] = users + } + + if len(i.Restrictions) > 0 { + restrictions := make([]map[string]interface{}, 0, len(i.Restrictions)) + for _, r := range i.Restrictions { + restrictions = append(restrictions, map[string]interface{}{ + "duration_seconds": r.DurationSeconds, + "start_time_of_day": r.StartTimeOfDay, + "type": r.Type, + }) + } + r["restriction"] = restrictions + } + + result = append(result, r) + } + } + + return result +} + +// Takes the result of flatmap.Expand for an array of strings +// and returns a []string +func expandStringList(configured []interface{}) []string { + vs := make([]string, 0, len(configured)) + for _, v := range configured { + vs = append(vs, string(v.(string))) + } + return vs +} From 69fb733ad03f4053dc927d990e64217ade999ede Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 20:36:40 +0200 Subject: [PATCH 11/26] Check for errors when setting escalation_rule --- .../pagerduty/resource_pagerduty_escalation_policy.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go index 32699179a..803fcbb93 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy.go @@ -118,7 +118,10 @@ func resourcePagerDutyEscalationPolicyRead(d *schema.ResourceData, meta interfac d.Set("name", e.Name) d.Set("description", e.Description) d.Set("num_loops", e.NumLoops) - d.Set("escalation_rule", flattenRules(e.EscalationRules)) + + if err := d.Set("escalation_rule", flattenRules(e.EscalationRules)); err != nil { + return err + } return nil } From 35312f0066210bec9250c65db4a8931b22e99809 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 20:37:55 +0200 Subject: [PATCH 12/26] Simplify setting up EscalationPolicy --- .../providers/pagerduty/resource_pagerduty_service.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service.go b/builtin/providers/pagerduty/resource_pagerduty_service.go index d45710306..aa20cf408 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service.go @@ -61,9 +61,13 @@ func buildServiceStruct(d *schema.ResourceData) *pagerduty.Service { service.AcknowledgementTimeout = &acknowledgementTimeout } - policy := &pagerduty.EscalationPolicy{} - policy.ID = d.Get("escalation_policy").(string) - policy.Type = "escalation_policy" + policy := &pagerduty.EscalationPolicy{ + APIObject: pagerduty.APIObject{ + ID: d.Get("escalation_policy").(string), + Type: "escalation_policy", + }, + } + service.EscalationPolicy = *policy return &service From 0951adab3b57d2a38e42a5f48e29e84bf18860c9 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 20:42:11 +0200 Subject: [PATCH 13/26] Check for errors when setting schedule_layer --- builtin/providers/pagerduty/resource_pagerduty_schedule.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_schedule.go b/builtin/providers/pagerduty/resource_pagerduty_schedule.go index 34db9d6b7..44fe3094f 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_schedule.go +++ b/builtin/providers/pagerduty/resource_pagerduty_schedule.go @@ -145,7 +145,10 @@ func resourcePagerDutyScheduleRead(d *schema.ResourceData, meta interface{}) err d.Set("name", s.Name) d.Set("description", s.Description) - d.Set("schedule_layer", flattenLayers(s.ScheduleLayers)) + + if err := d.Set("schedule_layer", flattenLayers(s.ScheduleLayers)); err != nil { + return err + } return nil } From ee20c11907d8c26ff8f2d6fdf766a47839e605e9 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 20:55:02 +0200 Subject: [PATCH 14/26] Simplify role check for user --- .../pagerduty/resource_pagerduty_user.go | 14 +--------- builtin/providers/pagerduty/structure.go | 11 -------- builtin/providers/pagerduty/util.go | 26 +++++++++++++++++++ 3 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 builtin/providers/pagerduty/util.go diff --git a/builtin/providers/pagerduty/resource_pagerduty_user.go b/builtin/providers/pagerduty/resource_pagerduty_user.go index 02948283c..33b7e80c8 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_user.go +++ b/builtin/providers/pagerduty/resource_pagerduty_user.go @@ -1,7 +1,6 @@ package pagerduty import ( - "fmt" "log" "github.com/PagerDuty/go-pagerduty" @@ -35,7 +34,7 @@ func resourcePagerDutyUser() *schema.Resource { Type: schema.TypeString, Optional: true, Default: "user", - ValidateFunc: validatePagerDutyUserRole, + ValidateFunc: validateValueFunc([]string{"admin", "limited_user", "owner", "read_only_user", "user"}), }, "job_title": &schema.Schema{ Type: schema.TypeString, @@ -223,14 +222,3 @@ func resourcePagerDutyUserImport(d *schema.ResourceData, meta interface{}) ([]*s } return []*schema.ResourceData{d}, nil } - -func validatePagerDutyUserRole(v interface{}, k string) (ws []string, errors []error) { - validRoles := []string{"admin", "limited_user", "owner", "read_only_user", "user"} - role := v.(string) - - if !contains(validRoles, role) { - errors = append(errors, fmt.Errorf("%q must be one of %v", k, validRoles)) - } - - return -} diff --git a/builtin/providers/pagerduty/structure.go b/builtin/providers/pagerduty/structure.go index b7a9d26ef..54601171c 100644 --- a/builtin/providers/pagerduty/structure.go +++ b/builtin/providers/pagerduty/structure.go @@ -2,17 +2,6 @@ package pagerduty import pagerduty "github.com/PagerDuty/go-pagerduty" -// Checks if a slice contains a string -func contains(slice []string, item string) bool { - set := make(map[string]struct{}, len(slice)) - for _, s := range slice { - set[s] = struct{}{} - } - - _, ok := set[item] - return ok -} - // Expands an array of escalation rules into []pagerduty.EscalationRules func expandRules(list []interface{}) []pagerduty.EscalationRule { result := make([]pagerduty.EscalationRule, 0, len(list)) diff --git a/builtin/providers/pagerduty/util.go b/builtin/providers/pagerduty/util.go new file mode 100644 index 000000000..661b7f9e6 --- /dev/null +++ b/builtin/providers/pagerduty/util.go @@ -0,0 +1,26 @@ +package pagerduty + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" +) + +// Validate a value against a set of possible values +func validateValueFunc(values []string) schema.SchemaValidateFunc { + return func(v interface{}, k string) (we []string, errors []error) { + value := v.(string) + valid := false + for _, role := range values { + if value == role { + valid = true + break + } + } + + if !valid { + errors = append(errors, fmt.Errorf("%s is an invalid value for argument %s", value, k)) + } + return + } +} From effec42278a5738675c889a88da89c823b063921 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 23:20:03 +0200 Subject: [PATCH 15/26] Add service type validation --- .../resource_pagerduty_service_integration.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go index b9853f27e..4f68c7441 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go @@ -28,6 +28,17 @@ func resourcePagerDutyServiceIntegration() *schema.Resource { "type": &schema.Schema{ Type: schema.TypeString, Required: true, + ValidateFunc: validateValueFunc([]string{ + "aws_cloudwatch_inbound_integration", + "cloudkick_inbound_integration", + "event_transformer_api_inbound_integration", + "generic_email_inbound_integration", + "generic_events_api_inbound_integration", + "keynote_inbound_integration", + "nagios_inbound_integration", + "pingdom_inbound_integration", + "sql_monitor_inbound_integration", + }), }, "integration_key": &schema.Schema{ Type: schema.TypeString, From de9a1c146c57b1326b4d3a48f5ac4135f28f6fee Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 23:20:23 +0200 Subject: [PATCH 16/26] Allowed values in error message --- .../providers/pagerduty/resource_pagerduty_user.go | 14 ++++++++++---- builtin/providers/pagerduty/util.go | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_user.go b/builtin/providers/pagerduty/resource_pagerduty_user.go index 33b7e80c8..8bf8470f7 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_user.go +++ b/builtin/providers/pagerduty/resource_pagerduty_user.go @@ -31,10 +31,16 @@ func resourcePagerDutyUser() *schema.Resource { Computed: true, }, "role": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "user", - ValidateFunc: validateValueFunc([]string{"admin", "limited_user", "owner", "read_only_user", "user"}), + Type: schema.TypeString, + Optional: true, + Default: "user", + ValidateFunc: validateValueFunc([]string{ + "admin", + "limited_user", + "owner", + "read_only_user", + "user", + }), }, "job_title": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/pagerduty/util.go b/builtin/providers/pagerduty/util.go index 661b7f9e6..541c914f8 100644 --- a/builtin/providers/pagerduty/util.go +++ b/builtin/providers/pagerduty/util.go @@ -19,7 +19,7 @@ func validateValueFunc(values []string) schema.SchemaValidateFunc { } if !valid { - errors = append(errors, fmt.Errorf("%s is an invalid value for argument %s", value, k)) + errors = append(errors, fmt.Errorf("%s is an invalid value for argument %s. Must be one of %v", value, k, values)) } return } From ffd3ceef0dd021142a83d7816e282c1edcb44edf Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Mon, 17 Oct 2016 23:35:38 +0200 Subject: [PATCH 17/26] Add schedule test --- .../resource_pagerduty_schedule_test.go | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 builtin/providers/pagerduty/resource_pagerduty_schedule_test.go diff --git a/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go b/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go new file mode 100644 index 000000000..52d3321e0 --- /dev/null +++ b/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go @@ -0,0 +1,106 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutySchedule_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyScheduleConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "Managed by Terraform"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", "Europe/Berlin"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.#", "1"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyScheduleDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_schedule" { + continue + } + + _, err := client.GetSchedule(r.Primary.ID, pagerduty.GetScheduleOptions{}) + + if err == nil { + return fmt.Errorf("Schedule still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyScheduleExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Schedule ID is set") + } + + client := testAccProvider.Meta().(*pagerduty.Client) + + found, err := client.GetSchedule(rs.Primary.ID, pagerduty.GetScheduleOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Schedule not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +const testAccCheckPagerDutyScheduleConfig = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_schedule" "foo" { + name = "foo" + time_zone = "Europe/Berlin" + + schedule_layer { + name = "foo" + start = "2015-11-06T20:00:00-05:00" + rotation_virtual_start = "2015-11-06T20:00:00-05:00" + rotation_turn_length_seconds = 86401 + users = ["${pagerduty_user.foo.id}"] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +` From ec10e031eeb98ce1336872e3e8de551a3b46ebb2 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Tue, 18 Oct 2016 14:54:33 +0200 Subject: [PATCH 18/26] Make Schedule work and add tests for import and resource + cleanups --- .../import_pagerduty_schedule_test.go | 28 ++++ ...source_pagerduty_escalation_policy_test.go | 45 +++--- .../pagerduty/resource_pagerduty_schedule.go | 61 +++---- .../resource_pagerduty_schedule_test.go | 149 ++++++++++++++++-- .../pagerduty/resource_pagerduty_service.go | 4 +- .../resource_pagerduty_service_integration.go | 4 +- ...urce_pagerduty_service_integration_test.go | 42 ++--- .../resource_pagerduty_service_test.go | 42 ++--- .../pagerduty/resource_pagerduty_user.go | 4 +- .../pagerduty/resource_pagerduty_user_test.go | 4 +- builtin/providers/pagerduty/structure.go | 89 ++++++----- 11 files changed, 318 insertions(+), 154 deletions(-) create mode 100644 builtin/providers/pagerduty/import_pagerduty_schedule_test.go diff --git a/builtin/providers/pagerduty/import_pagerduty_schedule_test.go b/builtin/providers/pagerduty/import_pagerduty_schedule_test.go new file mode 100644 index 000000000..41e06f7d3 --- /dev/null +++ b/builtin/providers/pagerduty/import_pagerduty_schedule_test.go @@ -0,0 +1,28 @@ +package pagerduty + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPagerDutySchedule_import(t *testing.T) { + resourceName := "pagerduty_schedule.foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyUserDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyScheduleConfig, + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go index 83f5ec4da..3c54a1543 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_escalation_policy_test.go @@ -100,13 +100,14 @@ resource "pagerduty_escalation_policy" "foo" { description = "foo" num_loops = 1 - escalation_rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + escalation_rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } ` @@ -125,20 +126,22 @@ resource "pagerduty_escalation_policy" "foo" { description = "bar" num_loops = 2 - escalation_rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + escalation_rule { + escalation_delay_in_minutes = 10 - escalation_rule { - escalation_delay_in_minutes = 20 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } + + escalation_rule { + escalation_delay_in_minutes = 20 + + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } ` diff --git a/builtin/providers/pagerduty/resource_pagerduty_schedule.go b/builtin/providers/pagerduty/resource_pagerduty_schedule.go index 44fe3094f..f8a102177 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_schedule.go +++ b/builtin/providers/pagerduty/resource_pagerduty_schedule.go @@ -1,12 +1,9 @@ package pagerduty import ( - "bytes" - "fmt" "log" "github.com/PagerDuty/go-pagerduty" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) @@ -34,8 +31,7 @@ func resourcePagerDutySchedule() *schema.Resource { Default: "Managed by Terraform", }, "schedule_layer": &schema.Schema{ - Type: schema.TypeSet, - Set: resourcePagerDutyEscalationHash, + Type: schema.TypeList, Required: true, ForceNew: true, Elem: &schema.Resource{ @@ -47,10 +43,18 @@ func resourcePagerDutySchedule() *schema.Resource { "name": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, }, "start": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "" { + return false + } + return true + }, }, "end": &schema.Schema{ Type: schema.TypeString, @@ -58,7 +62,14 @@ func resourcePagerDutySchedule() *schema.Resource { }, "rotation_virtual_start": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "" { + return false + } + return true + }, }, "rotation_turn_length_seconds": &schema.Schema{ Type: schema.TypeInt, @@ -72,8 +83,8 @@ func resourcePagerDutySchedule() *schema.Resource { }, }, "restriction": &schema.Schema{ - Type: schema.TypeList, Optional: true, + Type: schema.TypeList, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": &schema.Schema{ @@ -99,12 +110,12 @@ func resourcePagerDutySchedule() *schema.Resource { } func buildScheduleStruct(d *schema.ResourceData) (*pagerduty.Schedule, error) { - pagerdutyLayers := d.Get("schedule_layer").(*schema.Set).List() + scheduleLayers := d.Get("schedule_layer").([]interface{}) schedule := pagerduty.Schedule{ Name: d.Get("name").(string), TimeZone: d.Get("time_zone").(string), - ScheduleLayers: expandLayers(pagerdutyLayers), + ScheduleLayers: expandLayers(scheduleLayers), } if attr, ok := d.GetOk("description"); ok { @@ -129,7 +140,7 @@ func resourcePagerDutyScheduleCreate(d *schema.ResourceData, meta interface{}) e d.SetId(e.ID) - return nil + return resourcePagerDutyScheduleRead(d, meta) } func resourcePagerDutyScheduleRead(d *schema.ResourceData, meta interface{}) error { @@ -144,6 +155,7 @@ func resourcePagerDutyScheduleRead(d *schema.ResourceData, meta interface{}) err } d.Set("name", s.Name) + d.Set("time_zone", s.TimeZone) d.Set("description", s.Description) if err := d.Set("schedule_layer", flattenLayers(s.ScheduleLayers)); err != nil { @@ -191,30 +203,3 @@ func resourcePagerDutyScheduleImport(d *schema.ResourceData, meta interface{}) ( } return []*schema.ResourceData{d}, nil } - -func resourcePagerDutyEscalationHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", m["rotation_turn_length_seconds"].(int))) - - if _, ok := m["name"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) - } - - if _, ok := m["end"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["end"].(string))) - } - - for _, u := range m["users"].([]interface{}) { - buf.WriteString(fmt.Sprintf("%s-", u)) - } - - for _, r := range m["restriction"].([]interface{}) { - restriction := r.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", restriction["type"].(string))) - buf.WriteString(fmt.Sprintf("%s-", restriction["start_time_of_day"].(string))) - buf.WriteString(fmt.Sprintf("%d-", restriction["duration_seconds"].(int))) - } - - return hashcode.String(buf.String()) -} diff --git a/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go b/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go index 52d3321e0..573eb7743 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_schedule_test.go @@ -22,11 +22,59 @@ func TestAccPagerDutySchedule_Basic(t *testing.T) { resource.TestCheckResourceAttr( "pagerduty_schedule.foo", "name", "foo"), resource.TestCheckResourceAttr( - "pagerduty_schedule.foo", "description", "Managed by Terraform"), + "pagerduty_schedule.foo", "description", "foo"), resource.TestCheckResourceAttr( "pagerduty_schedule.foo", "time_zone", "Europe/Berlin"), resource.TestCheckResourceAttr( "pagerduty_schedule.foo", "schedule_layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.0.name", "foo"), + ), + }, + resource.TestStep{ + Config: testAccCheckPagerDutyScheduleConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "Managed by Terraform"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", "America/New_York"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.0.name", "foo"), + ), + }, + }, + }) +} + +func TestAccPagerDutySchedule_Multi(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckPagerDutyScheduleConfigMulti, + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", "America/New_York"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.#", "3"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.1.name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "schedule_layer.2.name", "foobar"), ), }, }, @@ -77,23 +125,104 @@ func testAccCheckPagerDutyScheduleExists(n string) resource.TestCheckFunc { const testAccCheckPagerDutyScheduleConfig = ` resource "pagerduty_user" "foo" { - name = "foo" - email = "foo@bar.com" - color = "green" - role = "user" - job_title = "foo" - description = "foo" + name = "foo" + email = "foo@bar.com" } resource "pagerduty_schedule" "foo" { - name = "foo" - time_zone = "Europe/Berlin" + name = "foo" + + time_zone = "Europe/Berlin" + description = "foo" schedule_layer { name = "foo" start = "2015-11-06T20:00:00-05:00" rotation_virtual_start = "2015-11-06T20:00:00-05:00" - rotation_turn_length_seconds = 86401 + rotation_turn_length_seconds = 86400 + users = ["${pagerduty_user.foo.id}"] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +` + +const testAccCheckPagerDutyScheduleConfigUpdated = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" +} + +resource "pagerduty_schedule" "foo" { + name = "bar" + + time_zone = "America/New_York" + + schedule_layer { + name = "foo" + start = "2015-11-06T20:00:00-05:00" + rotation_virtual_start = "2015-11-06T20:00:00-05:00" + rotation_turn_length_seconds = 86400 + users = ["${pagerduty_user.foo.id}"] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +` + +const testAccCheckPagerDutyScheduleConfigMulti = ` +resource "pagerduty_user" "foo" { + name = "foo" + email = "foo@bar.com" +} + +resource "pagerduty_schedule" "foo" { + name = "foo" + + time_zone = "America/New_York" + description = "foo" + + schedule_layer { + name = "foo" + start = "2015-11-06T20:00:00-05:00" + rotation_virtual_start = "2015-11-06T20:00:00-05:00" + rotation_turn_length_seconds = 86400 + users = ["${pagerduty_user.foo.id}"] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } + + schedule_layer { + name = "bar" + start = "2015-11-06T20:00:00-05:00" + rotation_virtual_start = "2015-11-06T20:00:00-05:00" + rotation_turn_length_seconds = 86400 + users = ["${pagerduty_user.foo.id}"] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } + + schedule_layer { + name = "foobar" + start = "2015-11-06T20:00:00-05:00" + rotation_virtual_start = "2015-11-06T20:00:00-05:00" + rotation_turn_length_seconds = 86400 users = ["${pagerduty_user.foo.id}"] restriction { diff --git a/builtin/providers/pagerduty/resource_pagerduty_service.go b/builtin/providers/pagerduty/resource_pagerduty_service.go index aa20cf408..79f05d61c 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service.go @@ -45,6 +45,9 @@ func resourcePagerDutyService() *schema.Resource { func buildServiceStruct(d *schema.ResourceData) *pagerduty.Service { service := pagerduty.Service{ Name: d.Get("name").(string), + APIObject: pagerduty.APIObject{ + ID: d.Id(), + }, } if attr, ok := d.GetOk("description"); ok { @@ -115,7 +118,6 @@ func resourcePagerDutyServiceUpdate(d *schema.ResourceData, meta interface{}) er client := meta.(*pagerduty.Client) s := buildServiceStruct(d) - s.ID = d.Id() log.Printf("[INFO] Updating PagerDuty service %s", d.Id()) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go index 4f68c7441..6236632e9 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_integration.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_integration.go @@ -60,6 +60,9 @@ func buildServiceIntegrationStruct(d *schema.ResourceData) *pagerduty.Integratio Type: "service", ID: d.Get("service").(string), }, + APIObject: pagerduty.APIObject{ + ID: d.Id(), + }, } return &service @@ -111,7 +114,6 @@ func resourcePagerDutyServiceIntegrationUpdate(d *schema.ResourceData, meta inte client := meta.(*pagerduty.Client) s := buildServiceIntegrationStruct(d) - s.ID = d.Id() service := d.Get("service").(string) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go b/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go index 65176372f..14a7f6dc5 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_integration_test.go @@ -102,21 +102,22 @@ resource "pagerduty_escalation_policy" "foo" { description = "foo" num_loops = 1 - escalation_rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + escalation_rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } resource "pagerduty_service" "foo" { name = "foo" description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = "${pagerduty_escalation_policy.foo.id}" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" } resource "pagerduty_service_integration" "foo" { @@ -141,21 +142,22 @@ resource "pagerduty_escalation_policy" "foo" { description = "bar" num_loops = 2 - escalation_rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + escalation_rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } resource "pagerduty_service" "foo" { name = "bar" description = "bar" - auto_resolve_timeout = 3600 - acknowledgement_timeout = 3600 - escalation_policy = "${pagerduty_escalation_policy.foo.id}" + auto_resolve_timeout = 3600 + acknowledgement_timeout = 3600 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" } resource "pagerduty_service_integration" "foo" { diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_test.go b/builtin/providers/pagerduty/resource_pagerduty_service_test.go index 8ccd210ce..29bfb96ed 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_test.go @@ -104,21 +104,22 @@ resource "pagerduty_escalation_policy" "foo" { description = "bar" num_loops = 2 - escalation_rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + escalation_rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } resource "pagerduty_service" "foo" { name = "foo" description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = "${pagerduty_escalation_policy.foo.id}" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" } ` @@ -137,20 +138,21 @@ resource "pagerduty_escalation_policy" "foo" { description = "bar" num_loops = 2 - escalation_rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = "${pagerduty_user.foo.id}" - } - } + escalation_rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = "${pagerduty_user.foo.id}" + } + } } resource "pagerduty_service" "foo" { name = "bar" description = "bar" - auto_resolve_timeout = 3600 - acknowledgement_timeout = 3600 - escalation_policy = "${pagerduty_escalation_policy.foo.id}" + auto_resolve_timeout = 3600 + acknowledgement_timeout = 3600 + escalation_policy = "${pagerduty_escalation_policy.foo.id}" } ` diff --git a/builtin/providers/pagerduty/resource_pagerduty_user.go b/builtin/providers/pagerduty/resource_pagerduty_user.go index 8bf8470f7..e83d7929d 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_user.go +++ b/builtin/providers/pagerduty/resource_pagerduty_user.go @@ -83,6 +83,9 @@ func buildUserStruct(d *schema.ResourceData) *pagerduty.User { user := pagerduty.User{ Name: d.Get("name").(string), Email: d.Get("email").(string), + APIObject: pagerduty.APIObject{ + ID: d.Id(), + }, } if attr, ok := d.GetOk("color"); ok { @@ -150,7 +153,6 @@ func resourcePagerDutyUserUpdate(d *schema.ResourceData, meta interface{}) error client := meta.(*pagerduty.Client) u := buildUserStruct(d) - u.ID = d.Id() log.Printf("[INFO] Updating PagerDuty user %s", d.Id()) diff --git a/builtin/providers/pagerduty/resource_pagerduty_user_test.go b/builtin/providers/pagerduty/resource_pagerduty_user_test.go index a5258e586..d407ae4b1 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_user_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_user_test.go @@ -175,7 +175,7 @@ resource "pagerduty_team" "foo" { resource "pagerduty_user" "foo" { name = "foo" email = "foo@bar.com" - teams = ["${pagerduty_team.foo.id}"] + teams = ["${pagerduty_team.foo.id}"] } ` const testAccCheckPagerDutyUserWithTeamsConfigUpdated = ` @@ -190,7 +190,7 @@ resource "pagerduty_team" "bar" { resource "pagerduty_user" "foo" { name = "foo" email = "foo@bar.com" - teams = ["${pagerduty_team.foo.id}", "${pagerduty_team.bar.id}"] + teams = ["${pagerduty_team.foo.id}", "${pagerduty_team.bar.id}"] } ` diff --git a/builtin/providers/pagerduty/structure.go b/builtin/providers/pagerduty/structure.go index 54601171c..e60e0788d 100644 --- a/builtin/providers/pagerduty/structure.go +++ b/builtin/providers/pagerduty/structure.go @@ -37,23 +37,21 @@ func flattenRules(list []pagerduty.EscalationRule) []map[string]interface{} { for _, i := range list { r := make(map[string]interface{}) - if i.ID != "" { - r["id"] = i.ID - r["escalation_delay_in_minutes"] = i.Delay + r["id"] = i.ID + r["escalation_delay_in_minutes"] = i.Delay - if len(i.Targets) > 0 { - targets := make([]map[string]interface{}, 0, len(i.Targets)) - for _, t := range i.Targets { - targets = append(targets, map[string]interface{}{ - "id": t.ID, - "type": t.Type, - }) - } - r["target"] = targets + if len(i.Targets) > 0 { + targets := make([]map[string]interface{}, 0, len(i.Targets)) + for _, t := range i.Targets { + targets = append(targets, map[string]interface{}{ + "id": t.ID, + "type": t.Type, + }) } - - result = append(result, r) + r["target"] = targets } + + result = append(result, r) } return result @@ -74,6 +72,10 @@ func expandLayers(list []interface{}) []pagerduty.ScheduleLayer { RotationTurnLengthSeconds: uint(layer["rotation_turn_length_seconds"].(int)), } + if layer["id"] != "" { + scheduleLayer.ID = layer["id"].(string) + } + for _, u := range layer["users"].([]interface{}) { scheduleLayer.Users = append( scheduleLayer.Users, @@ -110,37 +112,44 @@ func flattenLayers(list []pagerduty.ScheduleLayer) []map[string]interface{} { for _, i := range list { r := make(map[string]interface{}) - if i.ID != "" { - r["id"] = i.ID - r["name"] = i.Name - r["end"] = i.End - r["rotation_turn_length_seconds"] = i.RotationTurnLengthSeconds + r["id"] = i.ID + r["name"] = i.Name + r["end"] = i.End + r["start"] = i.Start + r["rotation_virtual_start"] = i.RotationVirtualStart + r["rotation_turn_length_seconds"] = i.RotationTurnLengthSeconds - if len(i.Users) > 0 { - users := make([]string, 0, len(i.Users)) - for _, u := range i.Users { - users = append(users, u.User.ID) - } - r["users"] = users + if len(i.Users) > 0 { + users := make([]string, 0, len(i.Users)) + for _, u := range i.Users { + users = append(users, u.User.ID) } - - if len(i.Restrictions) > 0 { - restrictions := make([]map[string]interface{}, 0, len(i.Restrictions)) - for _, r := range i.Restrictions { - restrictions = append(restrictions, map[string]interface{}{ - "duration_seconds": r.DurationSeconds, - "start_time_of_day": r.StartTimeOfDay, - "type": r.Type, - }) - } - r["restriction"] = restrictions - } - - result = append(result, r) + r["users"] = users } + + if len(i.Restrictions) > 0 { + restrictions := make([]map[string]interface{}, 0, len(i.Restrictions)) + for _, r := range i.Restrictions { + restrictions = append(restrictions, map[string]interface{}{ + "duration_seconds": r.DurationSeconds, + "start_time_of_day": r.StartTimeOfDay, + "type": r.Type, + }) + } + r["restriction"] = restrictions + } + + result = append(result, r) } - return result + // Reverse the final result and return it + resultReversed := make([]map[string]interface{}, 0, len(result)) + + for i := len(result) - 1; i >= 0; i-- { + resultReversed = append(resultReversed, result[i]) + } + + return resultReversed } // Takes the result of flatmap.Expand for an array of strings From fff166dca72a5a8d01593ba39f85b4c4d1131235 Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Tue, 18 Oct 2016 20:37:17 +0200 Subject: [PATCH 19/26] Adding status, created_at & last_incident_timestamp for service --- .../pagerduty/resource_pagerduty_service.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service.go b/builtin/providers/pagerduty/resource_pagerduty_service.go index 79f05d61c..ab4f97daf 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service.go @@ -30,6 +30,18 @@ func resourcePagerDutyService() *schema.Resource { Type: schema.TypeInt, Optional: true, }, + "last_incident_timestamp": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "created_at": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "status": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, "acknowledgement_timeout": &schema.Schema{ Type: schema.TypeInt, Optional: true, @@ -44,7 +56,8 @@ func resourcePagerDutyService() *schema.Resource { func buildServiceStruct(d *schema.ResourceData) *pagerduty.Service { service := pagerduty.Service{ - Name: d.Get("name").(string), + Name: d.Get("name").(string), + Status: d.Get("status").(string), APIObject: pagerduty.APIObject{ ID: d.Id(), }, @@ -106,9 +119,12 @@ func resourcePagerDutyServiceRead(d *schema.ResourceData, meta interface{}) erro } d.Set("name", s.Name) + d.Set("status", s.Status) + d.Set("created_at", s.CreateAt) d.Set("escalation_policy", s.EscalationPolicy.ID) d.Set("description", s.Description) d.Set("auto_resolve_timeout", s.AutoResolveTimeout) + d.Set("last_incident_timestamp", s.LastIncidentTimestamp) d.Set("acknowledgement_timeout", s.AcknowledgementTimeout) return nil From b85715ea51431cd6d312c44957045ae8de05c1dd Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Tue, 18 Oct 2016 20:47:21 +0200 Subject: [PATCH 20/26] bump go-pagerduty --- builtin/providers/pagerduty/resource_pagerduty_service.go | 2 +- .../providers/pagerduty/resource_pagerduty_service_test.go | 4 ++-- vendor/github.com/PagerDuty/go-pagerduty/service.go | 2 +- vendor/vendor.json | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_service.go b/builtin/providers/pagerduty/resource_pagerduty_service.go index ab4f97daf..7b1b4d930 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service.go @@ -112,7 +112,7 @@ func resourcePagerDutyServiceRead(d *schema.ResourceData, meta interface{}) erro log.Printf("[INFO] Reading PagerDuty service %s", d.Id()) - s, err := client.GetService(d.Id(), pagerduty.GetServiceOptions{}) + s, err := client.GetService(d.Id(), &pagerduty.GetServiceOptions{}) if err != nil { return err diff --git a/builtin/providers/pagerduty/resource_pagerduty_service_test.go b/builtin/providers/pagerduty/resource_pagerduty_service_test.go index 29bfb96ed..da7daf3e3 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_service_test.go +++ b/builtin/providers/pagerduty/resource_pagerduty_service_test.go @@ -54,7 +54,7 @@ func testAccCheckPagerDutyServiceDestroy(s *terraform.State) error { continue } - _, err := client.GetService(r.Primary.ID, pagerduty.GetServiceOptions{}) + _, err := client.GetService(r.Primary.ID, &pagerduty.GetServiceOptions{}) if err == nil { return fmt.Errorf("Service still exists") @@ -76,7 +76,7 @@ func testAccCheckPagerDutyServiceExists(n string) resource.TestCheckFunc { client := testAccProvider.Meta().(*pagerduty.Client) - found, err := client.GetService(rs.Primary.ID, pagerduty.GetServiceOptions{}) + found, err := client.GetService(rs.Primary.ID, &pagerduty.GetServiceOptions{}) if err != nil { return err } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/service.go b/vendor/github.com/PagerDuty/go-pagerduty/service.go index 4525bfd3d..a5b8f4aa4 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/service.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/service.go @@ -108,7 +108,7 @@ type GetServiceOptions struct { } // GetService gets details about an existing service. -func (c *Client) GetService(id string, o GetServiceOptions) (*Service, error) { +func (c *Client) GetService(id string, o *GetServiceOptions) (*Service, error) { v, err := query.Values(o) resp, err := c.get("/services/" + id + "?" + v.Encode()) return getServiceFromResponse(c, resp, err) diff --git a/vendor/vendor.json b/vendor/vendor.json index d82ac5939..187eebffa 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -296,10 +296,10 @@ "revision": "0290933f5e8afd933f2823fce32bf2847e6ea603" }, { - "checksumSHA1": "FQc+WPOoRAGM6UYOS4UjeEEAse4=", + "checksumSHA1": "QH+vxSOfdjdSBQBCTXoahGFRY0g=", "path": "github.com/PagerDuty/go-pagerduty", - "revision": "45ef5164a846163bdeea4e743dc4f5535ed023ca", - "revisionTime": "2016-10-13T06:26:24Z" + "revision": "21b2c2f0311f017d2c1d964e17a6037767bd8cbc", + "revisionTime": "2016-10-18T05:48:56Z" }, { "path": "github.com/Unknwon/com", From 9ab109363349a5c73ea114f71e61f26821f10e5f Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Tue, 18 Oct 2016 23:43:03 +0200 Subject: [PATCH 21/26] Skip setting the role if owner --- builtin/providers/pagerduty/resource_pagerduty_user.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin/providers/pagerduty/resource_pagerduty_user.go b/builtin/providers/pagerduty/resource_pagerduty_user.go index e83d7929d..627fed686 100644 --- a/builtin/providers/pagerduty/resource_pagerduty_user.go +++ b/builtin/providers/pagerduty/resource_pagerduty_user.go @@ -93,7 +93,12 @@ func buildUserStruct(d *schema.ResourceData) *pagerduty.User { } if attr, ok := d.GetOk("role"); ok { - user.Role = attr.(string) + role := attr.(string) + // Skip setting the role if the user is the owner of the account. + // Can't change this through the API. + if role != "owner" { + user.Role = role + } } if attr, ok := d.GetOk("job_title"); ok { From 97e48f659f382e5a8df2c2caf9e8288a73a21c9c Mon Sep 17 00:00:00 2001 From: Alexander Hellbom Date: Tue, 18 Oct 2016 23:43:25 +0200 Subject: [PATCH 22/26] Add data source for on call users --- .../pagerduty/data_source_on_call.go | 172 ++++++++++++++++++ .../pagerduty/data_source_on_call_test.go | 58 ++++++ builtin/providers/pagerduty/provider.go | 4 + builtin/providers/pagerduty/structure.go | 36 ++++ .../pagerduty/d/on_call.html.markdown | 59 ++++++ website/source/layouts/pagerduty.erb | 15 +- 6 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 builtin/providers/pagerduty/data_source_on_call.go create mode 100644 builtin/providers/pagerduty/data_source_on_call_test.go create mode 100644 website/source/docs/providers/pagerduty/d/on_call.html.markdown diff --git a/builtin/providers/pagerduty/data_source_on_call.go b/builtin/providers/pagerduty/data_source_on_call.go new file mode 100644 index 000000000..93e9cbf95 --- /dev/null +++ b/builtin/providers/pagerduty/data_source_on_call.go @@ -0,0 +1,172 @@ +package pagerduty + +import ( + "encoding/json" + "log" + "strconv" + + pagerduty "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourcePagerDutyOnCall() *schema.Resource { + return &schema.Resource{ + Read: dataSourcePagerDutyOnCallRead, + + Schema: map[string]*schema.Schema{ + "time_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "include": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "user_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "escalation_policy_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "schedule_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "since": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "until": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "earliest": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + "oncalls": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "escalation_level": &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + }, + "start": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "end": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "user": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + "schedule": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + "escalation_policy": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourcePagerDutyOnCallRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + o := &pagerduty.ListOnCallOptions{} + + if attr, ok := d.GetOk("time_zone"); ok { + o.TimeZone = attr.(string) + } + + if attr, ok := d.GetOk("include"); ok { + includes := make([]string, 0, len(attr.([]interface{}))) + + for _, include := range attr.([]interface{}) { + includes = append(includes, include.(string)) + } + + o.Includes = includes + } + + if attr, ok := d.GetOk("user_ids"); ok { + userIDs := make([]string, 0, len(attr.([]interface{}))) + + for _, user := range attr.([]interface{}) { + userIDs = append(userIDs, user.(string)) + } + + o.UserIDs = userIDs + } + + if attr, ok := d.GetOk("escalation_policy_ids"); ok { + escalationPolicyIDs := make([]string, 0, len(attr.([]interface{}))) + + for _, escalationPolicy := range attr.([]interface{}) { + escalationPolicyIDs = append(escalationPolicyIDs, escalationPolicy.(string)) + } + + o.EscalationPolicyIDs = escalationPolicyIDs + } + + if attr, ok := d.GetOk("since"); ok { + o.Since = attr.(string) + } + + if attr, ok := d.GetOk("until"); ok { + o.Until = attr.(string) + } + + if attr, ok := d.GetOk("earliest"); ok { + o.Earliest = attr.(bool) + } + + log.Printf("[INFO] Reading On Calls with options: %v", *o) + + resp, err := client.ListOnCalls(*o) + + if err != nil { + return err + } + + data := flattenOnCalls(resp.OnCalls) + id, err := json.Marshal(data) + + if err != nil { + return err + } + + d.SetId(strconv.Itoa(hashcode.String(string(id)))) + + if err := d.Set("oncalls", data); err != nil { + return err + } + + return nil + +} diff --git a/builtin/providers/pagerduty/data_source_on_call_test.go b/builtin/providers/pagerduty/data_source_on_call_test.go new file mode 100644 index 000000000..20c4a957d --- /dev/null +++ b/builtin/providers/pagerduty/data_source_on_call_test.go @@ -0,0 +1,58 @@ +package pagerduty + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPagerDutyOnCall_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPagerDutyOnCallsConfig, + Check: resource.ComposeTestCheckFunc( + testAccPagerDutyOnCalls("data.pagerduty_on_call.foo"), + ), + }, + }, + }) +} + +func testAccPagerDutyOnCalls(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + var size int + var err error + + if size, err = strconv.Atoi(a["oncalls.#"]); err != nil { + return err + } + + if size == 0 { + return fmt.Errorf("Expected at least one on call in the list. Found: %d", size) + } + + for i := range make([]string, size) { + escalationLevel := a[fmt.Sprintf("oncalls.%d.escalation_level", i)] + if escalationLevel == "" { + return fmt.Errorf("Expected the on call to have an escalation_level set") + } + } + + return nil + } +} + +const testAccPagerDutyOnCallsConfig = ` +data "pagerduty_on_call" "foo" {} +` diff --git a/builtin/providers/pagerduty/provider.go b/builtin/providers/pagerduty/provider.go index 0d210354f..9a4613e0e 100644 --- a/builtin/providers/pagerduty/provider.go +++ b/builtin/providers/pagerduty/provider.go @@ -18,6 +18,10 @@ func Provider() terraform.ResourceProvider { }, }, + DataSourcesMap: map[string]*schema.Resource{ + "pagerduty_on_call": dataSourcePagerDutyOnCall(), + }, + ResourcesMap: map[string]*schema.Resource{ "pagerduty_user": resourcePagerDutyUser(), "pagerduty_team": resourcePagerDutyTeam(), diff --git a/builtin/providers/pagerduty/structure.go b/builtin/providers/pagerduty/structure.go index e60e0788d..b2491f275 100644 --- a/builtin/providers/pagerduty/structure.go +++ b/builtin/providers/pagerduty/structure.go @@ -152,6 +152,42 @@ func flattenLayers(list []pagerduty.ScheduleLayer) []map[string]interface{} { return resultReversed } +// Flattens an array of []pagerduty.User into a map[string]interface{} +func flattenOnCalls(list []pagerduty.OnCall) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + + for _, i := range list { + r := make(map[string]interface{}) + r["escalation_level"] = i.EscalationLevel + r["start"] = i.Start + r["end"] = i.End + + user := make(map[string]interface{}, 1) + user["id"] = i.User.ID + user["type"] = i.User.Type + user["name"] = i.User.Summary + user["summary"] = i.User.Summary + + schedule := make(map[string]interface{}, 1) + schedule["id"] = i.Schedule.ID + schedule["type"] = i.Schedule.Type + schedule["summary"] = i.Schedule.Summary + + policy := make(map[string]interface{}, 1) + policy["id"] = i.EscalationPolicy.ID + policy["type"] = i.EscalationPolicy.Type + policy["summary"] = i.EscalationPolicy.Summary + + r["user"] = user + r["schedule"] = schedule + r["escalation_policy"] = policy + + result = append(result, r) + } + + return result +} + // Takes the result of flatmap.Expand for an array of strings // and returns a []string func expandStringList(configured []interface{}) []string { diff --git a/website/source/docs/providers/pagerduty/d/on_call.html.markdown b/website/source/docs/providers/pagerduty/d/on_call.html.markdown new file mode 100644 index 000000000..1f3435ed5 --- /dev/null +++ b/website/source/docs/providers/pagerduty/d/on_call.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_on_call" +sidebar_current: "docs-pagerduty-datasource-on_call" +description: |- + Get information about who's on call. +--- + +# pagerduty\_on_call + +Use this data source to get all of the users [on call][1] in a given schedule. + +## Example Usage + +``` +resource "pagerduty_schedule" "foo" { + name = "Daily Engineering Rotation" + time_zone = "America/New_York" + + schedule_layer { + name = "Night Shift" + start = "2015-11-06T20:00:00-05:00" + rotation_virtual_start = "2015-11-06T20:00:00-05:00" + rotation_turn_length_seconds = 86400 + users = ["${pagerduty_user.foo.id}"] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32400 + } + } +} + +data "pagerduty_on_call" "on_call" {} + +resource "pagerduty_team" "on_call" { + name = "On call" + description = "Primarily used by ${data.pagerduty_on_call.oncalls.0.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `time_zone` - (Optional) Time zone in which dates in the result will be rendered. +* `include` - (Optional) List of of additional details to include. Can be `escalation_policies`, `users`, `schedules`. +* `user_ids` - (Optional) Filters the results, showing only on-calls for the specified user IDs. +* `escalation_policy_ids` - (Optional) Filters the results, showing only on-calls for the specified escalation policy IDs. +* `user_ids` - (Optional) Filters the results, showing only on-calls for the specified schedule IDs. +* `since` - (Optional) The start of the time range over which you want to search. If an on-call period overlaps with the range, it will be included in the result. Defaults to current time. The search range cannot exceed 3 months. +* `until` - (Optional) The end of the time range over which you want to search. If an on-call period overlaps with the range, it will be included in the result. Defaults to current time. The search range cannot exceed 3 months, and the until time cannot be before the since time. +* `earliest` - (Optional) This will filter on-calls such that only the earliest on-call for each combination of escalation policy, escalation level, and user is returned. This is useful for determining when the "next" on-calls are for a given set of filters. + +## Attributes Reference +* `oncalls` - A list of on-call entries during a given time range. + +[1]: https://v2.developer.pagerduty.com/v2/page/api-reference#!/On-Calls/get_oncalls diff --git a/website/source/layouts/pagerduty.erb b/website/source/layouts/pagerduty.erb index d68eb852f..802d9faea 100644 --- a/website/source/layouts/pagerduty.erb +++ b/website/source/layouts/pagerduty.erb @@ -10,16 +10,25 @@ PagerDuty Provider + > + Data Sources + + + > Resources