openstack stack events ops and unit tests
diff --git a/openstack/orchestration/v1/stackevents/fixtures.go b/openstack/orchestration/v1/stackevents/fixtures.go
new file mode 100644
index 0000000..016ae00
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/fixtures.go
@@ -0,0 +1,446 @@
+package stackevents
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// FindExpected represents the expected object from a Find request.
+var FindExpected = []Event{
+ Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_IN_PROGRESS",
+ PhysicalResourceID: "",
+ ID: "06feb26f-9298-4a9b-8749-9d770e5d577a",
+ },
+ Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_COMPLETE",
+ PhysicalResourceID: "49181cd6-169a-4130-9455-31185bbfc5bf",
+ ID: "93940999-7d40-44ae-8de4-19624e7b8d18",
+ },
+}
+
+// FindOutput represents the response body from a Find request.
+const FindOutput = `
+{
+ "events": [
+ {
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:11Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_IN_PROGRESS",
+ "physical_resource_id": null,
+ "id": "06feb26f-9298-4a9b-8749-9d770e5d577a"
+ },
+ {
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:27Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+ "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+ }
+ ]
+}`
+
+// HandleFindSuccessfully creates an HTTP handler at `/stacks/postman_stack/events`
+// on the test handler mux that responds with a `Find` response.
+func HandleFindSuccessfully(t *testing.T, output string) {
+ th.Mux.HandleFunc("/stacks/postman_stack/events", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, output)
+ })
+}
+
+// ListExpected represents the expected object from a List request.
+var ListExpected = []Event{
+ Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_IN_PROGRESS",
+ PhysicalResourceID: "",
+ ID: "06feb26f-9298-4a9b-8749-9d770e5d577a",
+ },
+ Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_COMPLETE",
+ PhysicalResourceID: "49181cd6-169a-4130-9455-31185bbfc5bf",
+ ID: "93940999-7d40-44ae-8de4-19624e7b8d18",
+ },
+}
+
+// ListOutput represents the response body from a List request.
+const ListOutput = `
+{
+ "events": [
+ {
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:11Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_IN_PROGRESS",
+ "physical_resource_id": null,
+ "id": "06feb26f-9298-4a9b-8749-9d770e5d577a"
+ },
+ {
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:27Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+ "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+ }
+ ]
+}`
+
+// HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events`
+// on the test handler mux that responds with a `List` response.
+func HandleListSuccessfully(t *testing.T, output string) {
+ th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Set("Content-Type", "application/json")
+ r.ParseForm()
+ marker := r.Form.Get("marker")
+ switch marker {
+ case "":
+ fmt.Fprintf(w, output)
+ case "93940999-7d40-44ae-8de4-19624e7b8d18":
+ fmt.Fprintf(w, `{"events":[]}`)
+ default:
+ t.Fatalf("Unexpected marker: [%s]", marker)
+ }
+ })
+}
+
+// ListResourceEventsExpected represents the expected object from a ListResourceEvents request.
+var ListResourceEventsExpected = []Event{
+ Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_IN_PROGRESS",
+ PhysicalResourceID: "",
+ ID: "06feb26f-9298-4a9b-8749-9d770e5d577a",
+ },
+ Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_COMPLETE",
+ PhysicalResourceID: "49181cd6-169a-4130-9455-31185bbfc5bf",
+ ID: "93940999-7d40-44ae-8de4-19624e7b8d18",
+ },
+}
+
+// ListResourceEventsOutput represents the response body from a ListResourceEvents request.
+const ListResourceEventsOutput = `
+{
+ "events": [
+ {
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:11Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_IN_PROGRESS",
+ "physical_resource_id": null,
+ "id": "06feb26f-9298-4a9b-8749-9d770e5d577a"
+ },
+ {
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:27Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+ "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+ }
+ ]
+}`
+
+// HandleListResourceEventsSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events`
+// on the test handler mux that responds with a `ListResourceEvents` response.
+func HandleListResourceEventsSuccessfully(t *testing.T, output string) {
+ th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Set("Content-Type", "application/json")
+ r.ParseForm()
+ marker := r.Form.Get("marker")
+ switch marker {
+ case "":
+ fmt.Fprintf(w, output)
+ case "93940999-7d40-44ae-8de4-19624e7b8d18":
+ fmt.Fprintf(w, `{"events":[]}`)
+ default:
+ t.Fatalf("Unexpected marker: [%s]", marker)
+ }
+ })
+}
+
+// GetExpected represents the expected object from a Get request.
+var GetExpected = &Event{
+ ResourceName: "hello_world",
+ Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+ Links: []gophercloud.Link{
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ Rel: "self",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ Rel: "resource",
+ },
+ gophercloud.Link{
+ Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ Rel: "stack",
+ },
+ },
+ LogicalResourceID: "hello_world",
+ ResourceStatusReason: "state changed",
+ ResourceStatus: "CREATE_COMPLETE",
+ PhysicalResourceID: "49181cd6-169a-4130-9455-31185bbfc5bf",
+ ID: "93940999-7d40-44ae-8de4-19624e7b8d18",
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+ "event":{
+ "resource_name": "hello_world",
+ "event_time": "2015-02-05T21:33:27Z",
+ "links": [
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+ "rel": "self"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+ "rel": "resource"
+ },
+ {
+ "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "hello_world",
+ "resource_status_reason": "state changed",
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+ "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+ }
+}`
+
+// HandleGetSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+ th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, output)
+ })
+}
diff --git a/openstack/orchestration/v1/stackevents/requests.go b/openstack/orchestration/v1/stackevents/requests.go
new file mode 100644
index 0000000..db9b9cc
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/requests.go
@@ -0,0 +1,186 @@
+package stackevents
+
+import (
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// Find retreives stack events for the given stack name.
+func Find(c *gophercloud.ServiceClient, stackName string) FindResult {
+ var res FindResult
+
+ _, res.Err = perigee.Request("GET", findURL(c, stackName), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
+
+// SortDir is a type for specifying in which direction to sort a list of events.
+type SortDir string
+
+// SortKey is a type for specifying by which key to sort a list of events.
+type SortKey string
+
+// ResourceStatus is a type for specifying by which resource status to filter a
+// list of events.
+type ResourceStatus string
+
+// ResourceAction is a type for specifying by which resource action to filter a
+// list of events.
+type ResourceAction string
+
+var (
+ // SortAsc is used to sort a list of stacks in ascending order.
+ SortAsc SortDir = "asc"
+ // SortDesc is used to sort a list of stacks in descending order.
+ SortDesc SortDir = "desc"
+
+ // SortName is used to sort a list of stacks by name.
+ SortName SortKey = "name"
+ // SortResourceType is used to sort a list of stacks by resource type.
+ SortResourceType SortKey = "resource_type"
+ // SortCreatedAt is used to sort a list of stacks by date created.
+ SortCreatedAt SortKey = "created_at"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToStackEventListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Marker and Limit are used for pagination.
+type ListOpts struct {
+ // The stack resource ID with which to start the listing.
+ Marker string `q:"marker"`
+ // Integer value for the limit of values to return.
+ Limit int `q:"limit"`
+ // Filters the event list by the specified ResourceAction. You can use this
+ // filter multiple times to filter by multiple resource actions: CREATE, DELETE,
+ // UPDATE, ROLLBACK, SUSPEND, RESUME or ADOPT.
+ ResourceActions []string `q:"resource_action"`
+ // Filters the event list by the specified resource_status. You can use this
+ // filter multiple times to filter by multiple resource statuses: IN_PROGRESS,
+ // COMPLETE or FAILED.
+ ResourceStatuses []string `q:"resource_status"`
+ // Filters the event list by the specified resource_name. You can use this
+ // filter multiple times to filter by multiple resource names.
+ ResourceNames []string `q:"resource_name"`
+ // Filters the event list by the specified resource_type. You can use this
+ // filter multiple times to filter by multiple resource types: OS::Nova::Server,
+ // OS::Cinder::Volume, and so on.
+ ResourceTypes []string `q:"resource_type"`
+ // Sorts the event list by: resource_type or created_at.
+ SortKey SortKey `q:"sort_keys"`
+ // The sort direction of the event list. Which is asc (ascending) or desc (descending).
+ SortDir SortDir `q:"sort_dir"`
+}
+
+// ToStackEventListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToStackEventListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
+// List makes a request against the API to list resources for the given stack.
+func List(client *gophercloud.ServiceClient, stackName, stackID string, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(client, stackName, stackID)
+
+ if opts != nil {
+ query, err := opts.ToStackEventListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+
+ createPageFn := func(r pagination.PageResult) pagination.Page {
+ p := EventPage{pagination.MarkerPageBase{PageResult: r}}
+ p.MarkerPageBase.Owner = p
+ return p
+ }
+
+ return pagination.NewPager(client, url, createPageFn)
+}
+
+// ListResourceEventsOptsBuilder allows extensions to add additional parameters to the
+// ListResourceEvents request.
+type ListResourceEventsOptsBuilder interface {
+ ToResourceEventListQuery() (string, error)
+}
+
+// ListResourceEventsOpts allows the filtering and sorting of paginated resource events through
+// the API. Marker and Limit are used for pagination.
+type ListResourceEventsOpts struct {
+ // The stack resource ID with which to start the listing.
+ Marker string `q:"marker"`
+ // Integer value for the limit of values to return.
+ Limit int `q:"limit"`
+ // Filters the event list by the specified ResourceAction. You can use this
+ // filter multiple times to filter by multiple resource actions: CREATE, DELETE,
+ // UPDATE, ROLLBACK, SUSPEND, RESUME or ADOPT.
+ ResourceActions []string `q:"resource_action"`
+ // Filters the event list by the specified resource_status. You can use this
+ // filter multiple times to filter by multiple resource statuses: IN_PROGRESS,
+ // COMPLETE or FAILED.
+ ResourceStatuses []string `q:"resource_status"`
+ // Filters the event list by the specified resource_name. You can use this
+ // filter multiple times to filter by multiple resource names.
+ ResourceNames []string `q:"resource_name"`
+ // Filters the event list by the specified resource_type. You can use this
+ // filter multiple times to filter by multiple resource types: OS::Nova::Server,
+ // OS::Cinder::Volume, and so on.
+ ResourceTypes []string `q:"resource_type"`
+ // Sorts the event list by: resource_type or created_at.
+ SortKey SortKey `q:"sort_keys"`
+ // The sort direction of the event list. Which is asc (ascending) or desc (descending).
+ SortDir SortDir `q:"sort_dir"`
+}
+
+// ToResourceEventsListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToResourceEventsListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
+// ListResourceEvents makes a request against the API to list resources for the given stack.
+func ListResourceEvents(client *gophercloud.ServiceClient, stackName, stackID, resourceName string, opts ListResourceEventsOptsBuilder) pagination.Pager {
+ url := listResourceEventsURL(client, stackName, stackID, resourceName)
+
+ if opts != nil {
+ query, err := opts.ToResourceEventListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+
+ createPageFn := func(r pagination.PageResult) pagination.Page {
+ p := EventPage{pagination.MarkerPageBase{PageResult: r}}
+ p.MarkerPageBase.Owner = p
+ return p
+ }
+
+ return pagination.NewPager(client, url, createPageFn)
+}
+
+// Get retreives data for the given stack resource.
+func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName, eventID string) GetResult {
+ var res GetResult
+ _, res.Err = perigee.Request("GET", getURL(c, stackName, stackID, resourceName, eventID), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
diff --git a/openstack/orchestration/v1/stackevents/requests_test.go b/openstack/orchestration/v1/stackevents/requests_test.go
new file mode 100644
index 0000000..a4da4d0
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/requests_test.go
@@ -0,0 +1,71 @@
+package stackevents
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestFindEvents(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleFindSuccessfully(t, FindOutput)
+
+ actual, err := Find(fake.ServiceClient(), "postman_stack").Extract()
+ th.AssertNoErr(t, err)
+
+ expected := FindExpected
+ th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleListSuccessfully(t, ListOutput)
+
+ count := 0
+ err := List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractEvents(page)
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, ListExpected, actual)
+
+ return true, nil
+ })
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, count, 1)
+}
+
+func TestListResourceEvents(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleListResourceEventsSuccessfully(t, ListResourceEventsOutput)
+
+ count := 0
+ err := ListResourceEvents(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", nil).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractEvents(page)
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, ListResourceEventsExpected, actual)
+
+ return true, nil
+ })
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, count, 1)
+}
+
+func TestGetEvent(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleGetSuccessfully(t, GetOutput)
+
+ actual, err := Get(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", "93940999-7d40-44ae-8de4-19624e7b8d18").Extract()
+ th.AssertNoErr(t, err)
+
+ expected := GetExpected
+ th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stackevents/results.go b/openstack/orchestration/v1/stackevents/results.go
new file mode 100644
index 0000000..213a13c
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/results.go
@@ -0,0 +1,147 @@
+package stackevents
+
+import (
+ "time"
+
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// Event represents a stack event.
+type Event struct {
+ ResourceName string `mapstructure:"resource_name"`
+ Time time.Time `mapstructure:"-"`
+ Links []gophercloud.Link `mapstructure:"links"`
+ LogicalResourceID string `mapstructure:"logical_resource_id"`
+ ResourceStatusReason string `mapstructure:"resource_status_reason"`
+ ResourceStatus string `mapstructure:"resource_status"`
+ PhysicalResourceID string `mapstructure:"physical_resource_id"`
+ ID string `mapstructure:"id"`
+ ResourceProperties map[string]interface{} `mapstructure:"resource_properties"`
+}
+
+// FindResult represents the result of a Find operation.
+type FindResult struct {
+ gophercloud.Result
+}
+
+// Extract returns a slice of Event objects and is called after a
+// Find operation.
+func (r FindResult) Extract() ([]Event, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Res []Event `mapstructure:"events"`
+ }
+
+ if err := mapstructure.Decode(r.Body, &res); err != nil {
+ return nil, err
+ }
+
+ events := r.Body.(map[string]interface{})["events"].([]interface{})
+
+ for i, eventRaw := range events {
+ event := eventRaw.(map[string]interface{})
+ if date, ok := event["event_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Res[i].Time = t
+ }
+ }
+
+ return res.Res, nil
+}
+
+// EventPage abstracts the raw results of making a List() request against the API.
+// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
+// data provided through the ExtractResources call.
+type EventPage struct {
+ pagination.MarkerPageBase
+}
+
+// IsEmpty returns true if a page contains no Server results.
+func (r EventPage) IsEmpty() (bool, error) {
+ events, err := ExtractEvents(r)
+ if err != nil {
+ return true, err
+ }
+ return len(events) == 0, nil
+}
+
+// LastMarker returns the last stack ID in a ListResult.
+func (r EventPage) LastMarker() (string, error) {
+ events, err := ExtractEvents(r)
+ if err != nil {
+ return "", err
+ }
+ if len(events) == 0 {
+ return "", nil
+ }
+ return events[len(events)-1].ID, nil
+}
+
+// ExtractEvents interprets the results of a single page from a List() call, producing a slice of Event entities.
+func ExtractEvents(page pagination.Page) ([]Event, error) {
+ casted := page.(EventPage).Body
+
+ var res struct {
+ Res []Event `mapstructure:"events"`
+ }
+
+ if err := mapstructure.Decode(casted, &res); err != nil {
+ return nil, err
+ }
+
+ events := casted.(map[string]interface{})["events"].([]interface{})
+
+ for i, eventRaw := range events {
+ event := eventRaw.(map[string]interface{})
+ if date, ok := event["event_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Res[i].Time = t
+ }
+ }
+
+ return res.Res, nil
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+ gophercloud.Result
+}
+
+// Extract returns a pointer to an Event object and is called after a
+// Get operation.
+func (r GetResult) Extract() (*Event, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Res *Event `mapstructure:"event"`
+ }
+
+ if err := mapstructure.Decode(r.Body, &res); err != nil {
+ return nil, err
+ }
+
+ event := r.Body.(map[string]interface{})["event"].(map[string]interface{})
+
+ if date, ok := event["event_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Res.Time = t
+ }
+
+ return res.Res, nil
+}
diff --git a/openstack/orchestration/v1/stackevents/urls.go b/openstack/orchestration/v1/stackevents/urls.go
new file mode 100644
index 0000000..8b5eceb
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/urls.go
@@ -0,0 +1,19 @@
+package stackevents
+
+import "github.com/rackspace/gophercloud"
+
+func findURL(c *gophercloud.ServiceClient, stackName string) string {
+ return c.ServiceURL("stacks", stackName, "events")
+}
+
+func listURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "events")
+}
+
+func listResourceEventsURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "events")
+}
+
+func getURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName, eventID string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "events", eventID)
+}