From 267133bb0d3fdf38087bb94efde0197a1929337f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 10:34:37 -0500 Subject: [PATCH 1/3] Update Gophercloud to the current master. This is necessary to be able to interject a custom http.Client. Signed-off-by: Ash Wilson --- Godeps/Godeps.json | 9 +- .../src/github.com/racker/perigee/.gitignore | 2 - .../src/github.com/racker/perigee/LICENSE | 202 ------- .../src/github.com/racker/perigee/README.md | 120 ---- .../src/github.com/racker/perigee/api.go | 269 --------- .../src/github.com/racker/perigee/api_test.go | 226 -------- .../rackspace/gophercloud/.travis.yml | 3 + .../rackspace/gophercloud/CONTRIBUTORS.md | 1 + .../rackspace/gophercloud/Godeps/Godeps.json | 5 - .../rackspace/gophercloud/Godeps/Readme | 5 - .../openstack/compute/v2/floatingip_test.go | 107 ++++ .../openstack/compute/v2/volumeattach_test.go | 125 +++++ .../v2/extensions/fwaas/firewall_test.go | 116 ++++ .../networking/v2/extensions/fwaas/pkg.go | 1 + .../v2/extensions/fwaas/policy_test.go | 107 ++++ .../v2/extensions/fwaas/rule_test.go | 84 +++ .../objectstorage/v1/accounts_test.go | 16 +- .../orchestration/v1/buildinfo_test.go | 20 + .../openstack/orchestration/v1/common.go | 44 ++ .../orchestration/v1/hello-compute.json | 13 + .../orchestration/v1/stackevents_test.go | 68 +++ .../orchestration/v1/stackresources_test.go | 62 +++ .../openstack/orchestration/v1/stacks_test.go | 81 +++ .../orchestration/v1/stacktemplates_test.go | 77 +++ .../acceptance/rackspace/cdn/v1/base_test.go | 32 ++ .../acceptance/rackspace/cdn/v1/common.go | 23 + .../rackspace/cdn/v1/flavor_test.go | 47 ++ .../rackspace/cdn/v1/service_test.go | 93 ++++ .../rackspace/cdn/v1/serviceasset_test.go | 32 ++ .../compute/v2/bootfromvolume_test.go | 7 +- .../rackspace/compute/v2/servers_test.go | 13 + .../objectstorage/v1/accounts_test.go | 17 +- .../objectstorage/v1/cdncontainers_test.go | 25 +- .../objectstorage/v1/cdnobjects_test.go | 16 +- .../objectstorage/v1/containers_test.go | 27 +- .../objectstorage/v1/objects_test.go | 16 +- .../orchestration/v1/buildinfo_test.go | 20 + .../rackspace/orchestration/v1/common.go | 45 ++ .../orchestration/v1/stackevents_test.go | 70 +++ .../orchestration/v1/stackresources_test.go | 64 +++ .../rackspace/orchestration/v1/stacks_test.go | 82 +++ .../orchestration/v1/stacktemplates_test.go | 79 +++ .../rackspace/gophercloud/auth_options.go | 2 - .../blockstorage/v1/apiversions/requests.go | 10 +- .../blockstorage/v1/snapshots/requests.go | 32 +- .../blockstorage/v1/volumes/fixtures.go | 12 +- .../blockstorage/v1/volumes/requests.go | 32 +- .../blockstorage/v1/volumes/requests_test.go | 1 + .../blockstorage/v1/volumes/results.go | 4 +- .../blockstorage/v1/volumetypes/requests.go | 21 +- .../gophercloud/openstack/cdn/v1/base/doc.go | 4 + .../openstack/cdn/v1/base/fixtures.go | 53 ++ .../openstack/cdn/v1/base/requests.go | 24 + .../openstack/cdn/v1/base/requests_test.go | 43 ++ .../openstack/cdn/v1/base/results.go | 35 ++ .../gophercloud/openstack/cdn/v1/base/urls.go | 11 + .../openstack/cdn/v1/flavors/doc.go | 6 + .../openstack/cdn/v1/flavors/fixtures.go | 82 +++ .../openstack/cdn/v1/flavors/requests.go | 25 + .../openstack/cdn/v1/flavors/requests_test.go | 90 +++ .../openstack/cdn/v1/flavors/results.go | 71 +++ .../openstack/cdn/v1/flavors/urls.go | 11 + .../openstack/cdn/v1/serviceassets/doc.go | 7 + .../cdn/v1/serviceassets/fixtures.go | 19 + .../cdn/v1/serviceassets/requests.go | 50 ++ .../cdn/v1/serviceassets/requests_test.go | 18 + .../openstack/cdn/v1/serviceassets/results.go | 8 + .../openstack/cdn/v1/serviceassets/urls.go | 7 + .../openstack/cdn/v1/services/doc.go | 7 + .../openstack/cdn/v1/services/errors.go | 7 + .../openstack/cdn/v1/services/fixtures.go | 372 +++++++++++++ .../openstack/cdn/v1/services/requests.go | 386 +++++++++++++ .../cdn/v1/services/requests_test.go | 358 ++++++++++++ .../openstack/cdn/v1/services/results.go | 316 +++++++++++ .../openstack/cdn/v1/services/urls.go | 23 + .../rackspace/gophercloud/openstack/client.go | 33 +- .../openstack/common/extensions/requests.go | 8 +- .../v2/extensions/bootfromvolume/requests.go | 11 +- .../v2/extensions/defsecrules/requests.go | 23 +- .../compute/v2/extensions/floatingip/doc.go | 3 + .../v2/extensions/floatingip/fixtures.go | 174 ++++++ .../v2/extensions/floatingip/requests.go | 105 ++++ .../v2/extensions/floatingip/requests_test.go | 80 +++ .../v2/extensions/floatingip/results.go | 99 ++++ .../compute/v2/extensions/floatingip/urls.go | 37 ++ .../v2/extensions/floatingip/urls_test.go | 60 ++ .../v2/extensions/keypairs/requests.go | 22 +- .../v2/extensions/secgroups/fixtures.go | 2 +- .../v2/extensions/secgroups/requests.go | 66 +-- .../compute/v2/extensions/secgroups/urls.go | 2 +- .../v2/extensions/startstop/requests.go | 19 +- .../compute/v2/extensions/volumeattach/doc.go | 3 + .../v2/extensions/volumeattach/fixtures.go | 138 +++++ .../v2/extensions/volumeattach/requests.go | 82 +++ .../extensions/volumeattach/requests_test.go | 65 +++ .../v2/extensions/volumeattach/results.go | 84 +++ .../v2/extensions/volumeattach/urls.go | 25 + .../v2/extensions/volumeattach/urls_test.go | 46 ++ .../openstack/compute/v2/flavors/requests.go | 6 +- .../openstack/compute/v2/images/requests.go | 11 +- .../openstack/compute/v2/servers/fixtures.go | 10 + .../openstack/compute/v2/servers/requests.go | 129 +++-- .../openstack/compute/v2/servers/results.go | 43 +- .../v2/extensions/admin/roles/requests.go | 11 +- .../openstack/identity/v2/tokens/requests.go | 13 +- .../openstack/identity/v2/users/requests.go | 31 +- .../identity/v3/endpoints/requests.go | 24 +- .../identity/v3/services/requests.go | 31 +- .../openstack/identity/v3/tokens/requests.go | 31 +- .../networking/v2/extensions/fwaas/doc.go | 3 + .../v2/extensions/fwaas/firewalls/errors.go | 11 + .../v2/extensions/fwaas/firewalls/requests.go | 227 ++++++++ .../fwaas/firewalls/requests_test.go | 246 +++++++++ .../v2/extensions/fwaas/firewalls/results.go | 101 ++++ .../v2/extensions/fwaas/firewalls/urls.go | 16 + .../v2/extensions/fwaas/policies/requests.go | 258 +++++++++ .../fwaas/policies/requests_test.go | 279 ++++++++++ .../v2/extensions/fwaas/policies/results.go | 101 ++++ .../v2/extensions/fwaas/policies/urls.go | 26 + .../v2/extensions/fwaas/rules/errors.go | 12 + .../v2/extensions/fwaas/rules/requests.go | 296 ++++++++++ .../extensions/fwaas/rules/requests_test.go | 328 +++++++++++ .../v2/extensions/fwaas/rules/results.go | 110 ++++ .../v2/extensions/fwaas/rules/urls.go | 16 + .../extensions/layer3/floatingips/requests.go | 38 +- .../layer3/floatingips/requests_test.go | 49 ++ .../v2/extensions/layer3/routers/requests.go | 49 +- .../v2/extensions/lbaas/members/requests.go | 33 +- .../v2/extensions/lbaas/monitors/requests.go | 31 +- .../v2/extensions/lbaas/pools/requests.go | 45 +- .../v2/extensions/lbaas/vips/requests.go | 31 +- .../v2/extensions/security/groups/requests.go | 22 +- .../v2/extensions/security/rules/requests.go | 22 +- .../networking/v2/networks/requests.go | 32 +- .../openstack/networking/v2/ports/requests.go | 32 +- .../networking/v2/subnets/requests.go | 32 +- .../objectstorage/v1/accounts/fixtures.go | 8 +- .../objectstorage/v1/accounts/requests.go | 15 +- .../v1/accounts/requests_test.go | 19 +- .../objectstorage/v1/accounts/results.go | 78 ++- .../objectstorage/v1/containers/fixtures.go | 1 + .../objectstorage/v1/containers/requests.go | 21 +- .../v1/containers/requests_test.go | 4 +- .../objectstorage/v1/containers/results.go | 131 +++++ .../objectstorage/v1/objects/fixtures.go | 24 +- .../objectstorage/v1/objects/requests.go | 96 +++- .../objectstorage/v1/objects/requests_test.go | 14 +- .../objectstorage/v1/objects/results.go | 265 +++++++++ .../orchestration/v1/apiversions/doc.go | 4 + .../orchestration/v1/apiversions/requests.go | 13 + .../v1/apiversions/requests_test.go | 89 +++ .../orchestration/v1/apiversions/results.go | 42 ++ .../orchestration/v1/apiversions/urls.go | 7 + .../orchestration/v1/buildinfo/doc.go | 2 + .../orchestration/v1/buildinfo/fixtures.go | 45 ++ .../orchestration/v1/buildinfo/requests.go | 13 + .../v1/buildinfo/requests_test.go | 20 + .../orchestration/v1/buildinfo/results.go | 37 ++ .../orchestration/v1/buildinfo/urls.go | 7 + .../orchestration/v1/stackevents/doc.go | 4 + .../orchestration/v1/stackevents/fixtures.go | 446 +++++++++++++++ .../orchestration/v1/stackevents/requests.go | 205 +++++++ .../v1/stackevents/requests_test.go | 71 +++ .../orchestration/v1/stackevents/results.go | 162 ++++++ .../orchestration/v1/stackevents/urls.go | 19 + .../orchestration/v1/stackresources/doc.go | 5 + .../v1/stackresources/fixtures.go | 451 +++++++++++++++ .../v1/stackresources/requests.go | 126 +++++ .../v1/stackresources/requests_test.go | 107 ++++ .../v1/stackresources/results.go | 250 +++++++++ .../orchestration/v1/stackresources/urls.go | 31 ++ .../openstack/orchestration/v1/stacks/doc.go | 8 + .../orchestration/v1/stacks/fixtures.go | 374 +++++++++++++ .../orchestration/v1/stacks/requests.go | 520 ++++++++++++++++++ .../orchestration/v1/stacks/requests_test.go | 217 ++++++++ .../orchestration/v1/stacks/results.go | 296 ++++++++++ .../openstack/orchestration/v1/stacks/urls.go | 35 ++ .../orchestration/v1/stacktemplates/doc.go | 8 + .../v1/stacktemplates/fixtures.go | 118 ++++ .../v1/stacktemplates/requests.go | 61 ++ .../v1/stacktemplates/requests_test.go | 57 ++ .../v1/stacktemplates/results.go | 60 ++ .../orchestration/v1/stacktemplates/urls.go | 11 + .../openstack/utils/choose_version.go | 18 +- .../openstack/utils/choose_version_test.go | 19 +- .../rackspace/gophercloud/pagination/http.go | 20 +- .../rackspace/gophercloud/params.go | 11 + .../rackspace/gophercloud/params_test.go | 30 +- .../rackspace/gophercloud/provider_client.go | 196 +++++++ .../gophercloud/provider_client_test.go | 19 + .../blockstorage/v1/snapshots/delegate.go | 11 +- .../blockstorage/v1/snapshots/results.go | 4 +- .../rackspace/cdn/v1/base/delegate.go | 18 + .../rackspace/cdn/v1/base/delegate_test.go | 44 ++ .../gophercloud/rackspace/cdn/v1/base/doc.go | 4 + .../rackspace/cdn/v1/flavors/delegate.go | 18 + .../rackspace/cdn/v1/flavors/delegate_test.go | 90 +++ .../rackspace/cdn/v1/flavors/doc.go | 6 + .../cdn/v1/serviceassets/delegate.go | 13 + .../cdn/v1/serviceassets/delegate_test.go | 19 + .../rackspace/cdn/v1/serviceassets/doc.go | 7 + .../rackspace/cdn/v1/services/delegate.go | 37 ++ .../cdn/v1/services/delegate_test.go | 359 ++++++++++++ .../rackspace/cdn/v1/services/doc.go | 7 + .../rackspace/gophercloud/rackspace/client.go | 28 +- .../rackspace/compute/v2/networks/requests.go | 23 +- .../rackspace/compute/v2/servers/delegate.go | 5 + .../compute/v2/servers/delegate_test.go | 22 + .../rackspace/compute/v2/servers/fixtures.go | 135 +++++ .../compute/v2/virtualinterfaces/requests.go | 16 +- .../rackspace/identity/v2/roles/delegate.go | 11 +- .../rackspace/identity/v2/users/delegate.go | 17 +- .../rackspace/lb/v1/acl/requests.go | 23 +- .../rackspace/lb/v1/lbs/requests.go | 104 ++-- .../rackspace/lb/v1/monitors/requests.go | 21 +- .../rackspace/lb/v1/nodes/fixtures.go | 1 - .../rackspace/lb/v1/nodes/requests.go | 43 +- .../rackspace/lb/v1/nodes/requests_test.go | 1 - .../rackspace/lb/v1/sessions/requests.go | 23 +- .../rackspace/lb/v1/ssl/requests.go | 53 +- .../rackspace/lb/v1/throttle/requests.go | 23 +- .../rackspace/lb/v1/vips/requests.go | 21 +- .../v1/accounts/delegate_test.go | 10 +- .../objectstorage/v1/bulk/requests.go | 14 +- .../v1/cdncontainers/delegate.go | 33 -- .../v1/cdncontainers/requests.go | 103 +++- .../objectstorage/v1/cdncontainers/results.go | 145 ++++- .../objectstorage/v1/cdncontainers/urls.go | 8 + .../objectstorage/v1/cdnobjects/request.go | 15 + .../objectstorage/v1/objects/delegate.go | 4 + .../objectstorage/v1/objects/delegate_test.go | 14 +- .../orchestration/v1/buildinfo/delegate.go | 11 + .../v1/buildinfo/delegate_test.go | 21 + .../orchestration/v1/buildinfo/doc.go | 2 + .../orchestration/v1/stackevents/delegate.go | 27 + .../v1/stackevents/delegate_test.go | 72 +++ .../orchestration/v1/stackevents/doc.go | 3 + .../v1/stackresources/delegate.go | 42 ++ .../v1/stackresources/delegate_test.go | 108 ++++ .../orchestration/v1/stackresources/doc.go | 5 + .../orchestration/v1/stacks/delegate.go | 49 ++ .../orchestration/v1/stacks/delegate_test.go | 461 ++++++++++++++++ .../rackspace/orchestration/v1/stacks/doc.go | 8 + .../orchestration/v1/stacks/fixtures.go | 32 ++ .../v1/stacktemplates/delegate.go | 16 + .../v1/stacktemplates/delegate_test.go | 58 ++ .../orchestration/v1/stacktemplates/doc.go | 8 + .../rackspace/gophercloud/results.go | 28 + 248 files changed, 14196 insertions(+), 1809 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/.gitignore delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/README.md delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/api.go delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/api_test.go delete mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Godeps.json delete mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Readme create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/floatingip_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/volumeattach_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/common.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/base_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/common.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/flavor_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/service_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/serviceasset_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/buildinfo_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/common.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackevents_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackresources_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacks_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacktemplates_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/errors.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/errors.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/errors.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/results.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/urls.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdnobjects/request.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/doc.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/fixtures.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/doc.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 5b15a1f65a..e932dd050a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -123,15 +123,10 @@ "ImportPath": "github.com/mitchellh/mapstructure", "Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf" }, - { - "ImportPath": "github.com/racker/perigee", - "Comment": "v0.0.0-18-g0c00cb0", - "Rev": "0c00cb0a026b71034ebc8205263c77dad3577db5" - }, { "ImportPath": "github.com/rackspace/gophercloud", - "Comment": "v1.0.0-232-g2e7ab37", - "Rev": "2e7ab378257b8723e02cbceac7410be4db286436" + "Comment": "v1.0.0-473-g7ca169d", + "Rev": "7ca169d371b29e3dbab9e631c3a6151896b06330" }, { "ImportPath": "github.com/smartystreets/go-aws-auth", diff --git a/Godeps/_workspace/src/github.com/racker/perigee/.gitignore b/Godeps/_workspace/src/github.com/racker/perigee/.gitignore deleted file mode 100644 index 49ca32aa20..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin/* -pkg/* diff --git a/Godeps/_workspace/src/github.com/racker/perigee/LICENSE b/Godeps/_workspace/src/github.com/racker/perigee/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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/Godeps/_workspace/src/github.com/racker/perigee/README.md b/Godeps/_workspace/src/github.com/racker/perigee/README.md deleted file mode 100644 index 81cbf4a95f..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# perigee - -Perigee provides a REST client that, while it should be generic enough to use with most any RESTful API, is nonetheless optimized to the needs of the OpenStack APIs. -Perigee grew out of the need to refactor out common API access code from the [gorax](http://github.com/racker/gorax) project. - -Several things influenced the name of the project. -Numerous elements of the OpenStack ecosystem are named after astronomical artifacts. -Additionally, perigee occurs when two orbiting bodies are closest to each other. -Perigee seemed appropriate for something aiming to bring OpenStack and other RESTful services closer to the end-user. - -**This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want** - -## Installation and Testing - -To install: - -```bash -go get github.com/racker/perigee -``` - -To run unit tests: - -```bash -go test github.com/racker/perigee -``` - -## Contributing - -The following guidelines are preliminary, as this project is just starting out. -However, this should serve as a working first-draft. - -### Branching - -The master branch must always be a valid build. -The `go get` command will not work otherwise. -Therefore, development must occur on a different branch. - -When creating a feature branch, do so off the master branch: - -```bash -git checkout master -git pull -git checkout -b featureBranch -git checkout -b featureBranch-wip # optional -``` - -Perform all your editing and testing in the WIP-branch. -Feel free to make as many commits as you see fit. -You may even open "WIP" pull requests from your feature branch to seek feedback. -WIP pull requests will **never** be merged, however. - -To get code merged, you'll need to "squash" your changes into one or more clean commits in the feature branch. -These steps should be followed: - -```bash -git checkout featureBranch -git merge --squash featureBranch-wip -git commit -a -git push origin featureBranch -``` - -You may now open a nice, clean, self-contained pull request from featureBranch to master. - -The `git commit -a` command above will open a text editor so that -you may provide a comprehensive description of the changes. - -In general, when submitting a pull request against master, -be sure to answer the following questions: - -- What is the problem? -- Why is it a problem? -- What is your solution? -- How does your solution work? (Recommended for non-trivial changes.) -- Why should we use your solution over someone elses? (Recommended especially if multiple solutions being discussed.) - -Remember that monster-sized pull requests are a bear to code-review, -so having helpful commit logs are an absolute must to review changes as quickly as possible. - -Finally, (s)he who breaks master is ultimately responsible for fixing master. - -### Source Representation - -The Go community firmly believes in a consistent representation for all Go source code. -We do too. -Make sure all source code is passed through "go fmt" *before* you create your pull request. - -Please note, however, that we fully acknowledge and recognize that we no longer rely upon punch-cards for representing source files. -Therefore, no 80-column limit exists. -However, if a line exceeds 132 columns, you may want to consider splitting the line. - -### Unit and Integration Tests - -Pull requests that include non-trivial code changes without accompanying unit tests will be flatly rejected. -While we have no way of enforcing this practice, -you can ensure your code is thoroughly tested by always [writing tests first by intention.](http://en.wikipedia.org/wiki/Test-driven_development) - -When creating a pull request, if even one test fails, the PR will be rejected. -Make sure all unit tests pass. -Make sure all integration tests pass. - -### Documentation - -Private functions and methods which are obvious to anyone unfamiliar with gorax needn't be accompanied by documentation. -However, this is a code-smell; if submitting a PR, expect to justify your decision. - -Public functions, regardless of how obvious, **must** have accompanying godoc-style documentation. -This is not to suggest you should provide a tome for each function, however. -Sometimes a link to more information is more appropriate, provided the link is stable, reliable, and pertinent. - -Changing documentation often results in bizarre diffs in pull requests, due to text often spanning multiple lines. -To work around this, put [one logical thought or sentence on a single line.](http://rhodesmill.org/brandon/2012/one-sentence-per-line/) -While this looks weird in a plain-text editor, -remember that both godoc and HTML viewers will reflow text. -The source code and its comments should be easy to edit with minimal diff pollution. -Let software dedicated to presenting the documentation to human readers deal with its presentation. - -## Examples - -t.b.d. - diff --git a/Godeps/_workspace/src/github.com/racker/perigee/api.go b/Godeps/_workspace/src/github.com/racker/perigee/api.go deleted file mode 100644 index 0fcbadbee5..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/api.go +++ /dev/null @@ -1,269 +0,0 @@ -// vim: ts=8 sw=8 noet ai - -package perigee - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "strings" -) - -// The UnexpectedResponseCodeError structure represents a mismatch in understanding between server and client in terms of response codes. -// Most often, this is due to an actual error condition (e.g., getting a 404 for a resource when you expect a 200). -// However, it needn't always be the case (e.g., getting a 204 (No Content) response back when a 200 is expected). -type UnexpectedResponseCodeError struct { - Url string - Expected []int - Actual int - Body []byte -} - -func (err *UnexpectedResponseCodeError) Error() string { - return fmt.Sprintf("Expected HTTP response code %d when accessing URL(%s); got %d instead with the following body:\n%s", err.Expected, err.Url, err.Actual, string(err.Body)) -} - -// Request issues an HTTP request, marshaling parameters, and unmarshaling results, as configured in the provided Options parameter. -// The Response structure returned, if any, will include accumulated results recovered from the HTTP server. -// See the Response structure for more details. -func Request(method string, url string, opts Options) (*Response, error) { - var body io.Reader - var response Response - - client := opts.CustomClient - if client == nil { - client = new(http.Client) - } - - contentType := opts.ContentType - - body = nil - if opts.ReqBody != nil { - if contentType == "" { - contentType = "application/json" - } - - if contentType == "application/json" { - bodyText, err := json.Marshal(opts.ReqBody) - if err != nil { - return nil, err - } - body = strings.NewReader(string(bodyText)) - if opts.DumpReqJson { - log.Printf("Making request:\n%#v\n", string(bodyText)) - } - } else { - // assume opts.ReqBody implements the correct interface - body = opts.ReqBody.(io.Reader) - } - } - - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - - if contentType != "" { - req.Header.Add("Content-Type", contentType) - } - - if opts.ContentLength > 0 { - req.ContentLength = opts.ContentLength - req.Header.Add("Content-Length", string(opts.ContentLength)) - } - - if opts.MoreHeaders != nil { - for k, v := range opts.MoreHeaders { - req.Header.Add(k, v) - } - } - - if accept := req.Header.Get("Accept"); accept == "" { - accept = opts.Accept - if accept == "" { - accept = "application/json" - } - req.Header.Add("Accept", accept) - } - - if opts.SetHeaders != nil { - err = opts.SetHeaders(req) - if err != nil { - return &response, err - } - } - - httpResponse, err := client.Do(req) - if httpResponse != nil { - response.HttpResponse = *httpResponse - response.StatusCode = httpResponse.StatusCode - } - - if err != nil { - return &response, err - } - // This if-statement is legacy code, preserved for backward compatibility. - if opts.StatusCode != nil { - *opts.StatusCode = httpResponse.StatusCode - } - - acceptableResponseCodes := opts.OkCodes - if len(acceptableResponseCodes) != 0 { - if not_in(httpResponse.StatusCode, acceptableResponseCodes) { - b, _ := ioutil.ReadAll(httpResponse.Body) - httpResponse.Body.Close() - return &response, &UnexpectedResponseCodeError{ - Url: url, - Expected: acceptableResponseCodes, - Actual: httpResponse.StatusCode, - Body: b, - } - } - } - if opts.Results != nil { - defer httpResponse.Body.Close() - jsonResult, err := ioutil.ReadAll(httpResponse.Body) - response.JsonResult = jsonResult - if err != nil { - return &response, err - } - - err = json.Unmarshal(jsonResult, opts.Results) - // This if-statement is legacy code, preserved for backward compatibility. - if opts.ResponseJson != nil { - *opts.ResponseJson = jsonResult - } - } - return &response, err -} - -// not_in returns false if, and only if, the provided needle is _not_ -// in the given set of integers. -func not_in(needle int, haystack []int) bool { - for _, straw := range haystack { - if needle == straw { - return false - } - } - return true -} - -// Post makes a POST request against a server using the provided HTTP client. -// The url must be a fully-formed URL string. -// DEPRECATED. Use Request() instead. -func Post(url string, opts Options) error { - r, err := Request("POST", url, opts) - if opts.Response != nil { - *opts.Response = r - } - return err -} - -// Get makes a GET request against a server using the provided HTTP client. -// The url must be a fully-formed URL string. -// DEPRECATED. Use Request() instead. -func Get(url string, opts Options) error { - r, err := Request("GET", url, opts) - if opts.Response != nil { - *opts.Response = r - } - return err -} - -// Delete makes a DELETE request against a server using the provided HTTP client. -// The url must be a fully-formed URL string. -// DEPRECATED. Use Request() instead. -func Delete(url string, opts Options) error { - r, err := Request("DELETE", url, opts) - if opts.Response != nil { - *opts.Response = r - } - return err -} - -// Put makes a PUT request against a server using the provided HTTP client. -// The url must be a fully-formed URL string. -// DEPRECATED. Use Request() instead. -func Put(url string, opts Options) error { - r, err := Request("PUT", url, opts) - if opts.Response != nil { - *opts.Response = r - } - return err -} - -// Options describes a set of optional parameters to the various request calls. -// -// The custom client can be used for a variety of purposes beyond selecting encrypted versus unencrypted channels. -// Transports can be defined to provide augmented logging, header manipulation, et. al. -// -// If the ReqBody field is provided, it will be embedded as a JSON object. -// Otherwise, provide nil. -// -// If JSON output is to be expected from the response, -// provide either a pointer to the container structure in Results, -// or a pointer to a nil-initialized pointer variable. -// The latter method will cause the unmarshaller to allocate the container type for you. -// If no response is expected, provide a nil Results value. -// -// The MoreHeaders map, if non-nil or empty, provides a set of headers to add to those -// already present in the request. At present, only Accepted and Content-Type are set -// by default. -// -// OkCodes provides a set of acceptable, positive responses. -// -// If provided, StatusCode specifies a pointer to an integer, which will receive the -// returned HTTP status code, successful or not. DEPRECATED; use the Response.StatusCode field instead for new software. -// -// ResponseJson, if specified, provides a means for returning the raw JSON. This is -// most useful for diagnostics. DEPRECATED; use the Response.JsonResult field instead for new software. -// -// DumpReqJson, if set to true, will cause the request to appear to stdout for debugging purposes. -// This attribute may be removed at any time in the future; DO NOT use this attribute in production software. -// -// Response, if set, provides a way to communicate the complete set of HTTP response, raw JSON, status code, and -// other useful attributes back to the caller. Note that the Request() method returns a Response structure as part -// of its public interface; you don't need to set the Response field here to use this structure. The Response field -// exists primarily for legacy or deprecated functions. -// -// SetHeaders allows the caller to provide code to set any custom headers programmatically. Typically, this -// facility can invoke, e.g., SetBasicAuth() on the request to easily set up authentication. -// Any error generated will terminate the request and will propegate back to the caller. -type Options struct { - CustomClient *http.Client - ReqBody interface{} - Results interface{} - MoreHeaders map[string]string - OkCodes []int - StatusCode *int `DEPRECATED` - DumpReqJson bool `UNSUPPORTED` - ResponseJson *[]byte `DEPRECATED` - Response **Response - ContentType string `json:"Content-Type,omitempty"` - ContentLength int64 `json:"Content-Length,omitempty"` - Accept string `json:"Accept,omitempty"` - SetHeaders func(r *http.Request) error -} - -// Response contains return values from the various request calls. -// -// HttpResponse will return the http response from the request call. -// Note: HttpResponse.Body is always closed and will not be available from this return value. -// -// StatusCode specifies the returned HTTP status code, successful or not. -// -// If Results is specified in the Options: -// - JsonResult will contain the raw return from the request call -// This is most useful for diagnostics. -// - Result will contain the unmarshalled json either in the Result passed in -// or the unmarshaller will allocate the container type for you. - -type Response struct { - HttpResponse http.Response - JsonResult []byte - Results interface{} - StatusCode int -} diff --git a/Godeps/_workspace/src/github.com/racker/perigee/api_test.go b/Godeps/_workspace/src/github.com/racker/perigee/api_test.go deleted file mode 100644 index da943b247b..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/api_test.go +++ /dev/null @@ -1,226 +0,0 @@ -package perigee - -import ( - "bytes" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestNormal(t *testing.T) { - handler := http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("testing")) - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - response, err := Request("GET", ts.URL, Options{}) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - if response.StatusCode != 200 { - t.Fatalf("response code %d is not 200", response.StatusCode) - } -} - -func TestOKCodes(t *testing.T) { - expectCode := 201 - handler := http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(expectCode) - w.Write([]byte("testing")) - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - options := Options{ - OkCodes: []int{expectCode}, - } - results, err := Request("GET", ts.URL, options) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - if results.StatusCode != expectCode { - t.Fatalf("response code %d is not %d", results.StatusCode, expectCode) - } -} - -func TestLocation(t *testing.T) { - newLocation := "http://www.example.com" - handler := http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", newLocation) - w.Write([]byte("testing")) - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - response, err := Request("GET", ts.URL, Options{}) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - location, err := response.HttpResponse.Location() - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if location.String() != newLocation { - t.Fatalf("location returned \"%s\" is not \"%s\"", location.String(), newLocation) - } -} - -func TestHeaders(t *testing.T) { - newLocation := "http://www.example.com" - handler := http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", newLocation) - w.Write([]byte("testing")) - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - response, err := Request("GET", ts.URL, Options{}) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - location := response.HttpResponse.Header.Get("Location") - if location == "" { - t.Fatalf("Location should not empty") - } - - if location != newLocation { - t.Fatalf("location returned \"%s\" is not \"%s\"", location, newLocation) - } -} - -func TestCustomHeaders(t *testing.T) { - var contentType, accept, contentLength string - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - m := map[string][]string(r.Header) - contentType = m["Content-Type"][0] - accept = m["Accept"][0] - contentLength = m["Content-Length"][0] - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - _, err := Request("GET", ts.URL, Options{ - ContentLength: 5, - ContentType: "x-application/vb", - Accept: "x-application/c", - ReqBody: strings.NewReader("Hello"), - }) - if err != nil { - t.Fatalf(err.Error()) - } - - if contentType != "x-application/vb" { - t.Fatalf("I expected x-application/vb; got ", contentType) - } - - if contentLength != "5" { - t.Fatalf("I expected 5 byte content length; got ", contentLength) - } - - if accept != "x-application/c" { - t.Fatalf("I expected x-application/c; got ", accept) - } -} - -func TestJson(t *testing.T) { - newLocation := "http://www.example.com" - jsonBytes := []byte(`{"foo": {"bar": "baz"}}`) - handler := http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", newLocation) - w.Write(jsonBytes) - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - type Data struct { - Foo struct { - Bar string `json:"bar"` - } `json:"foo"` - } - var data Data - - response, err := Request("GET", ts.URL, Options{Results: &data}) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if bytes.Compare(jsonBytes, response.JsonResult) != 0 { - t.Fatalf("json returned \"%s\" is not \"%s\"", response.JsonResult, jsonBytes) - } - - if data.Foo.Bar != "baz" { - t.Fatalf("Results returned %v", data) - } -} - -func TestSetHeaders(t *testing.T) { - var wasCalled bool - handler := http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hi")) - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - _, err := Request("GET", ts.URL, Options{ - SetHeaders: func(r *http.Request) error { - wasCalled = true - return nil - }, - }) - - if err != nil { - t.Fatal(err) - } - - if !wasCalled { - t.Fatal("I expected header setter callback to be called, but it wasn't") - } - - myError := fmt.Errorf("boo") - - _, err = Request("GET", ts.URL, Options{ - SetHeaders: func(r *http.Request) error { - return myError - }, - }) - - if err != myError { - t.Fatal("I expected errors to propegate back to the caller.") - } -} - -func TestBodilessMethodsAreSentWithoutContentHeaders(t *testing.T) { - var h map[string][]string - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h = r.Header - }) - ts := httptest.NewServer(handler) - defer ts.Close() - - _, err := Request("GET", ts.URL, Options{}) - if err != nil { - t.Fatalf(err.Error()) - } - - if len(h["Content-Type"]) != 0 { - t.Fatalf("I expected nothing for Content-Type but got ", h["Content-Type"]) - } - - if len(h["Content-Length"]) != 0 { - t.Fatalf("I expected nothing for Content-Length but got ", h["Content-Length"]) - } -} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml b/Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml index cf4f8cafcc..946f98cb3d 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml @@ -4,6 +4,8 @@ install: go: - 1.1 - 1.2 + - 1.3 + - 1.4 - tip script: script/cibuild after_success: @@ -12,3 +14,4 @@ after_success: - go get github.com/mattn/goveralls - export PATH=$PATH:$HOME/gopath/bin/ - goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8 +sudo: false diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md b/Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md index eb97094b73..63beb30b20 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md @@ -10,3 +10,4 @@ Contributors | Ash Wilson | | Jamie Hannaford | | Don Schenck | don.schenck@rackspace.com> +| Joe Topjian | diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Godeps.json b/Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Godeps.json deleted file mode 100644 index dbe26bbf76..0000000000 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Godeps.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ImportPath": "github.com/rackspace/gophercloud", - "GoVersion": "go1.3.3", - "Deps": [] -} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Readme b/Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Readme deleted file mode 100644 index 4cdaa53d56..0000000000 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Readme +++ /dev/null @@ -1,5 +0,0 @@ -This directory tree is generated automatically by godep. - -Please do not edit. - -See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/floatingip_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/floatingip_test.go new file mode 100644 index 0000000000..ab7554b5a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/floatingip_test.go @@ -0,0 +1,107 @@ +// +build acceptance compute servers + +package v2 + +import ( + "os" + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/acceptance/tools" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + th "github.com/rackspace/gophercloud/testhelper" +) + +func createFIPServer(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) { + if testing.Short() { + t.Skip("Skipping test that requires server creation in short mode.") + } + + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create server: %s\n", name) + + pwd := tools.MakeNewPassword("") + + server, err := servers.Create(client, servers.CreateOpts{ + Name: name, + FlavorRef: choices.FlavorID, + ImageRef: choices.ImageID, + AdminPass: pwd, + }).Extract() + if err != nil { + t.Fatalf("Unable to create server: %v", err) + } + + th.AssertEquals(t, pwd, server.AdminPass) + + return server, err +} + +func createFloatingIP(t *testing.T, client *gophercloud.ServiceClient) (*floatingip.FloatingIP, error) { + pool := os.Getenv("OS_POOL_NAME") + fip, err := floatingip.Create(client, &floatingip.CreateOpts{ + Pool: pool, + }).Extract() + th.AssertNoErr(t, err) + t.Logf("Obtained Floating IP: %v", fip.IP) + + return fip, err +} + +func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) { + err := floatingip.Associate(client, serverId, fip.IP).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId) + defer func() { + err = floatingip.Disassociate(client, serverId, fip.IP).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Disassociated floating IP %v from instance %v", fip.IP, serverId) + }() + floatingIp, err := floatingip.Get(client, fip.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP) +} + +func TestFloatingIP(t *testing.T) { + pool := os.Getenv("OS_POOL_NAME") + if pool == "" { + t.Fatalf("OS_POOL_NAME must be set") + } + + choices, err := ComputeChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + client, err := newClient() + if err != nil { + t.Fatalf("Unable to create a compute client: %v", err) + } + + server, err := createFIPServer(t, client, choices) + if err != nil { + t.Fatalf("Unable to create server: %v", err) + } + defer func() { + servers.Delete(client, server.ID) + t.Logf("Server deleted.") + }() + + if err = waitForStatus(client, server, "ACTIVE"); err != nil { + t.Fatalf("Unable to wait for server: %v", err) + } + + fip, err := createFloatingIP(t, client) + if err != nil { + t.Fatalf("Unable to create floating IP: %v", err) + } + defer func() { + err = floatingip.Delete(client, fip.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Floating IP deleted.") + }() + + associateFloatingIP(t, client, server.ID, fip) + +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/volumeattach_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/volumeattach_test.go new file mode 100644 index 0000000000..34634c9d2f --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/volumeattach_test.go @@ -0,0 +1,125 @@ +// +build acceptance compute servers + +package v2 + +import ( + "os" + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/acceptance/tools" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + th "github.com/rackspace/gophercloud/testhelper" +) + +func newBlockClient(t *testing.T) (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + client, err := openstack.AuthenticatedClient(ao) + th.AssertNoErr(t, err) + + return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +func createVAServer(t *testing.T, computeClient *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) { + if testing.Short() { + t.Skip("Skipping test that requires server creation in short mode.") + } + + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create server: %s\n", name) + + pwd := tools.MakeNewPassword("") + + server, err := servers.Create(computeClient, servers.CreateOpts{ + Name: name, + FlavorRef: choices.FlavorID, + ImageRef: choices.ImageID, + AdminPass: pwd, + }).Extract() + if err != nil { + t.Fatalf("Unable to create server: %v", err) + } + + th.AssertEquals(t, pwd, server.AdminPass) + + return server, err +} + +func createVAVolume(t *testing.T, blockClient *gophercloud.ServiceClient) (*volumes.Volume, error) { + volume, err := volumes.Create(blockClient, &volumes.CreateOpts{ + Size: 1, + Name: "gophercloud-test-volume", + }).Extract() + th.AssertNoErr(t, err) + defer func() { + err = volumes.WaitForStatus(blockClient, volume.ID, "available", 60) + th.AssertNoErr(t, err) + }() + + return volume, err +} + +func createVolumeAttachment(t *testing.T, computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, volumeId string) { + va, err := volumeattach.Create(computeClient, serverId, &volumeattach.CreateOpts{ + VolumeID: volumeId, + }).Extract() + th.AssertNoErr(t, err) + defer func() { + err = volumes.WaitForStatus(blockClient, volumeId, "in-use", 60) + th.AssertNoErr(t, err) + err = volumeattach.Delete(computeClient, serverId, va.ID).ExtractErr() + th.AssertNoErr(t, err) + err = volumes.WaitForStatus(blockClient, volumeId, "available", 60) + th.AssertNoErr(t, err) + }() +} + +func TestAttachVolume(t *testing.T) { + choices, err := ComputeChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + computeClient, err := newClient() + if err != nil { + t.Fatalf("Unable to create a compute client: %v", err) + } + + blockClient, err := newBlockClient(t) + if err != nil { + t.Fatalf("Unable to create a blockstorage client: %v", err) + } + + server, err := createVAServer(t, computeClient, choices) + if err != nil { + t.Fatalf("Unable to create server: %v", err) + } + defer func() { + servers.Delete(computeClient, server.ID) + t.Logf("Server deleted.") + }() + + if err = waitForStatus(computeClient, server, "ACTIVE"); err != nil { + t.Fatalf("Unable to wait for server: %v", err) + } + + volume, err := createVAVolume(t, blockClient) + if err != nil { + t.Fatalf("Unable to create volume: %v", err) + } + defer func() { + err = volumes.Delete(blockClient, volume.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Volume deleted.") + }() + + createVolumeAttachment(t, computeClient, blockClient, server.ID, volume.ID) + +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go new file mode 100644 index 0000000000..80246b6481 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go @@ -0,0 +1,116 @@ +// +build acceptance networking fwaas + +package fwaas + +import ( + "testing" + "time" + + "github.com/rackspace/gophercloud" + base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func firewallSetup(t *testing.T) string { + base.Setup(t) + return createPolicy(t, &policies.CreateOpts{}) +} + +func firewallTeardown(t *testing.T, policyID string) { + defer base.Teardown() + deletePolicy(t, policyID) +} + +func TestFirewall(t *testing.T) { + policyID := firewallSetup(t) + defer firewallTeardown(t, policyID) + + firewallID := createFirewall(t, &firewalls.CreateOpts{ + Name: "gophercloud test", + Description: "acceptance test", + PolicyID: policyID, + }) + + waitForFirewallToBeActive(t, firewallID) + + listFirewalls(t) + + updateFirewall(t, firewallID, &firewalls.UpdateOpts{ + Description: "acceptance test updated", + }) + + waitForFirewallToBeActive(t, firewallID) + + deleteFirewall(t, firewallID) + + waitForFirewallToBeDeleted(t, firewallID) +} + +func createFirewall(t *testing.T, opts *firewalls.CreateOpts) string { + f, err := firewalls.Create(base.Client, *opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created firewall: %#v", opts) + return f.ID +} + +func listFirewalls(t *testing.T) { + err := firewalls.List(base.Client, firewalls.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + firewallList, err := firewalls.ExtractFirewalls(page) + if err != nil { + t.Errorf("Failed to extract firewalls: %v", err) + return false, err + } + + for _, r := range firewallList { + t.Logf("Listing firewalls: ID [%s]", r.ID) + } + + return true, nil + }) + th.AssertNoErr(t, err) +} + +func updateFirewall(t *testing.T, firewallID string, opts *firewalls.UpdateOpts) { + f, err := firewalls.Update(base.Client, firewallID, *opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Updated firewall ID [%s]", f.ID) +} + +func getFirewall(t *testing.T, firewallID string) *firewalls.Firewall { + f, err := firewalls.Get(base.Client, firewallID).Extract() + th.AssertNoErr(t, err) + t.Logf("Getting firewall ID [%s]", f.ID) + return f +} + +func deleteFirewall(t *testing.T, firewallID string) { + res := firewalls.Delete(base.Client, firewallID) + th.AssertNoErr(t, res.Err) + t.Logf("Deleted firewall %s", firewallID) +} + +func waitForFirewallToBeActive(t *testing.T, firewallID string) { + for i := 0; i < 10; i++ { + fw := getFirewall(t, firewallID) + if fw.Status == "ACTIVE" { + break + } + time.Sleep(time.Second) + } +} + +func waitForFirewallToBeDeleted(t *testing.T, firewallID string) { + for i := 0; i < 10; i++ { + err := firewalls.Get(base.Client, firewallID).Err + if err != nil { + httpStatus := err.(*gophercloud.UnexpectedResponseCodeError) + if httpStatus.Actual == 404 { + return + } + } + time.Sleep(time.Second) + } +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go new file mode 100644 index 0000000000..206bf3313a --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go @@ -0,0 +1 @@ +package fwaas diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go new file mode 100644 index 0000000000..fdca22e3fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go @@ -0,0 +1,107 @@ +// +build acceptance networking fwaas + +package fwaas + +import ( + "testing" + + base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func firewallPolicySetup(t *testing.T) string { + base.Setup(t) + return createRule(t, &rules.CreateOpts{ + Protocol: "tcp", + Action: "allow", + }) +} + +func firewallPolicyTeardown(t *testing.T, ruleID string) { + defer base.Teardown() + deleteRule(t, ruleID) +} + +func TestFirewallPolicy(t *testing.T) { + ruleID := firewallPolicySetup(t) + defer firewallPolicyTeardown(t, ruleID) + + policyID := createPolicy(t, &policies.CreateOpts{ + Name: "gophercloud test", + Description: "acceptance test", + Rules: []string{ + ruleID, + }, + }) + + listPolicies(t) + + updatePolicy(t, policyID, &policies.UpdateOpts{ + Description: "acceptance test updated", + }) + + getPolicy(t, policyID) + + removeRuleFromPolicy(t, policyID, ruleID) + + addRuleToPolicy(t, policyID, ruleID) + + deletePolicy(t, policyID) +} + +func createPolicy(t *testing.T, opts *policies.CreateOpts) string { + p, err := policies.Create(base.Client, *opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created policy: %#v", opts) + return p.ID +} + +func listPolicies(t *testing.T) { + err := policies.List(base.Client, policies.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + policyList, err := policies.ExtractPolicies(page) + if err != nil { + t.Errorf("Failed to extract policies: %v", err) + return false, err + } + + for _, p := range policyList { + t.Logf("Listing policies: ID [%s]", p.ID) + } + + return true, nil + }) + th.AssertNoErr(t, err) +} + +func updatePolicy(t *testing.T, policyID string, opts *policies.UpdateOpts) { + p, err := policies.Update(base.Client, policyID, *opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Updated policy ID [%s]", p.ID) +} + +func removeRuleFromPolicy(t *testing.T, policyID string, ruleID string) { + err := policies.RemoveRule(base.Client, policyID, ruleID) + th.AssertNoErr(t, err) + t.Logf("Removed rule [%s] from policy ID [%s]", ruleID, policyID) +} + +func addRuleToPolicy(t *testing.T, policyID string, ruleID string) { + err := policies.InsertRule(base.Client, policyID, ruleID, "", "") + th.AssertNoErr(t, err) + t.Logf("Inserted rule [%s] into policy ID [%s]", ruleID, policyID) +} + +func getPolicy(t *testing.T, policyID string) { + p, err := policies.Get(base.Client, policyID).Extract() + th.AssertNoErr(t, err) + t.Logf("Getting policy ID [%s]", p.ID) +} + +func deletePolicy(t *testing.T, policyID string) { + res := policies.Delete(base.Client, policyID) + th.AssertNoErr(t, res.Err) + t.Logf("Deleted policy %s", policyID) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go new file mode 100644 index 0000000000..144aa0998f --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go @@ -0,0 +1,84 @@ +// +build acceptance networking fwaas + +package fwaas + +import ( + "testing" + + base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestFirewallRules(t *testing.T) { + base.Setup(t) + defer base.Teardown() + + ruleID := createRule(t, &rules.CreateOpts{ + Name: "gophercloud_test", + Description: "acceptance test", + Protocol: "tcp", + Action: "allow", + DestinationIPAddress: "192.168.0.0/24", + DestinationPort: "22", + }) + + listRules(t) + + destinationIPAddress := "192.168.1.0/24" + destinationPort := "" + sourcePort := "1234" + + updateRule(t, ruleID, &rules.UpdateOpts{ + DestinationIPAddress: &destinationIPAddress, + DestinationPort: &destinationPort, + SourcePort: &sourcePort, + }) + + getRule(t, ruleID) + + deleteRule(t, ruleID) +} + +func createRule(t *testing.T, opts *rules.CreateOpts) string { + r, err := rules.Create(base.Client, *opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created rule: %#v", opts) + return r.ID +} + +func listRules(t *testing.T) { + err := rules.List(base.Client, rules.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + ruleList, err := rules.ExtractRules(page) + if err != nil { + t.Errorf("Failed to extract rules: %v", err) + return false, err + } + + for _, r := range ruleList { + t.Logf("Listing rules: ID [%s]", r.ID) + } + + return true, nil + }) + th.AssertNoErr(t, err) +} + +func updateRule(t *testing.T, ruleID string, opts *rules.UpdateOpts) { + r, err := rules.Update(base.Client, ruleID, *opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Updated rule ID [%s]", r.ID) +} + +func getRule(t *testing.T, ruleID string) { + r, err := rules.Get(base.Client, ruleID).Extract() + th.AssertNoErr(t, err) + t.Logf("Getting rule ID [%s]", r.ID) +} + +func deleteRule(t *testing.T, ruleID string) { + res := rules.Delete(base.Client, ruleID) + th.AssertNoErr(t, res.Err) + t.Logf("Deleted rule %s", ruleID) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/accounts_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/accounts_test.go index f7c01a7c11..24cc62b4aa 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/accounts_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/accounts_test.go @@ -17,7 +17,10 @@ func TestAccounts(t *testing.T) { // Update an account's metadata. updateres := accounts.Update(client, accounts.UpdateOpts{Metadata: metadata}) - th.AssertNoErr(t, updateres.Err) + t.Logf("Update Account Response: %+v\n", updateres) + updateHeaders, err := updateres.Extract() + th.AssertNoErr(t, err) + t.Logf("Update Account Response Headers: %+v\n", updateHeaders) // Defer the deletion of the metadata set above. defer func() { @@ -29,11 +32,14 @@ func TestAccounts(t *testing.T) { th.AssertNoErr(t, updateres.Err) }() - // Retrieve account metadata. - getres := accounts.Get(client, nil) - th.AssertNoErr(t, getres.Err) // Extract the custom metadata from the 'Get' response. - am, err := getres.ExtractMetadata() + res := accounts.Get(client, nil) + + h, err := res.Extract() + th.AssertNoErr(t, err) + t.Logf("Get Account Response Headers: %+v\n", h) + + am, err := res.ExtractMetadata() th.AssertNoErr(t, err) for k := range metadata { if am[k] != metadata[strings.Title(k)] { diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go new file mode 100644 index 0000000000..05a5e1d67e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go @@ -0,0 +1,20 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestBuildInfo(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + bi, err := buildinfo.Get(client).Extract() + th.AssertNoErr(t, err) + t.Logf("retrieved build info: %+v\n", bi) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/common.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/common.go new file mode 100644 index 0000000000..2c28dcbcc9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/common.go @@ -0,0 +1,44 @@ +// +build acceptance + +package v1 + +import ( + "fmt" + "os" + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + th "github.com/rackspace/gophercloud/testhelper" +) + +var template = fmt.Sprintf(` +{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": {}, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "flavor": "%s", + "image": "%s", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } +}`, os.Getenv("OS_FLAVOR_ID"), os.Getenv("OS_IMAGE_ID")) + +func newClient(t *testing.T) *gophercloud.ServiceClient { + ao, err := openstack.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + client, err := openstack.AuthenticatedClient(ao) + th.AssertNoErr(t, err) + + c, err := openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + th.AssertNoErr(t, err) + return c +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json new file mode 100644 index 0000000000..11cfc80534 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json @@ -0,0 +1,13 @@ +{ + "heat_template_version": "2013-05-23", + "resources": { + "compute_instance": { + "type": "OS::Nova::Server", + "properties": { + "flavor": "m1.small", + "image": "cirros-0.3.2-x86_64-disk", + "name": "Single Compute Instance" + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go new file mode 100644 index 0000000000..e356c86aa9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go @@ -0,0 +1,68 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStackEvents(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName := "postman_stack_2" + resourceName := "hello_world" + var eventID string + + createOpts := stacks.CreateOpts{ + Name: stackName, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + err = stackevents.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) { + events, err := stackevents.ExtractEvents(page) + th.AssertNoErr(t, err) + t.Logf("listed events: %+v\n", events) + eventID = events[0].ID + return false, nil + }) + th.AssertNoErr(t, err) + + err = stackevents.ListResourceEvents(client, stackName, stack.ID, resourceName, nil).EachPage(func(page pagination.Page) (bool, error) { + resourceEvents, err := stackevents.ExtractEvents(page) + th.AssertNoErr(t, err) + t.Logf("listed resource events: %+v\n", resourceEvents) + return false, nil + }) + th.AssertNoErr(t, err) + + event, err := stackevents.Get(client, stackName, stack.ID, resourceName, eventID).Extract() + th.AssertNoErr(t, err) + t.Logf("retrieved event: %+v\n", event) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go new file mode 100644 index 0000000000..b614f1cef5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go @@ -0,0 +1,62 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStackResources(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName := "postman_stack_2" + + createOpts := stacks.CreateOpts{ + Name: stackName, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + resourceName := "hello_world" + resource, err := stackresources.Get(client, stackName, stack.ID, resourceName).Extract() + th.AssertNoErr(t, err) + t.Logf("Got stack resource: %+v\n", resource) + + metadata, err := stackresources.Metadata(client, stackName, stack.ID, resourceName).Extract() + th.AssertNoErr(t, err) + t.Logf("Got stack resource metadata: %+v\n", metadata) + + err = stackresources.List(client, stackName, stack.ID, stackresources.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + resources, err := stackresources.ExtractResources(page) + th.AssertNoErr(t, err) + t.Logf("resources: %+v\n", resources) + return false, nil + }) + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go new file mode 100644 index 0000000000..01e76d6140 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go @@ -0,0 +1,81 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStacks(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName1 := "gophercloud-test-stack-2" + createOpts := stacks.CreateOpts{ + Name: stackName1, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName1, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName1) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + updateOpts := stacks.UpdateOpts{ + Template: template, + Timeout: 20, + } + err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr() + th.AssertNoErr(t, err) + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "UPDATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + t.Logf("Updated stack") + + err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) { + stackList, err := stacks.ExtractStacks(page) + th.AssertNoErr(t, err) + + t.Logf("Got stack list: %+v\n", stackList) + + return true, nil + }) + th.AssertNoErr(t, err) + + getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("Got stack: %+v\n", getStack) + + abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("Abandonded stack %+v\n", abandonedStack) + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go new file mode 100644 index 0000000000..14d8f4437a --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go @@ -0,0 +1,77 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStackTemplates(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName := "postman_stack_2" + + createOpts := stacks.CreateOpts{ + Name: stackName, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + tmpl, err := stacktemplates.Get(client, stackName, stack.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("retrieved template: %+v\n", tmpl) + + validateOpts := stacktemplates.ValidateOpts{ + Template: map[string]interface{}{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": map[string]interface{}{ + "flavor": map[string]interface{}{ + "default": "m1.tiny", + "type": "string", + }, + }, + "resources": map[string]interface{}{ + "hello_world": map[string]interface{}{ + "type": "OS::Nova::Server", + "properties": map[string]interface{}{ + "key_name": "heat_key", + "flavor": map[string]interface{}{ + "get_param": "flavor", + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", + }, + }, + }, + }, + } + validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("validated template: %+v\n", validatedTemplate) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/base_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/base_test.go new file mode 100644 index 0000000000..135f5b330a --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/base_test.go @@ -0,0 +1,32 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/rackspace/cdn/v1/base" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestBaseOps(t *testing.T) { + client := newClient(t) + t.Log("Retrieving Home Document") + testHomeDocumentGet(t, client) + + t.Log("Pinging root URL") + testPing(t, client) +} + +func testHomeDocumentGet(t *testing.T, client *gophercloud.ServiceClient) { + hd, err := base.Get(client).Extract() + th.AssertNoErr(t, err) + t.Logf("Retrieved home document: %+v", *hd) +} + +func testPing(t *testing.T, client *gophercloud.ServiceClient) { + err := base.Ping(client).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Successfully pinged root URL") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/common.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/common.go new file mode 100644 index 0000000000..2333ca77bf --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/common.go @@ -0,0 +1,23 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/rackspace" + th "github.com/rackspace/gophercloud/testhelper" +) + +func newClient(t *testing.T) *gophercloud.ServiceClient { + ao, err := rackspace.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + client, err := rackspace.AuthenticatedClient(ao) + th.AssertNoErr(t, err) + + c, err := rackspace.NewCDNV1(client, gophercloud.EndpointOpts{}) + th.AssertNoErr(t, err) + return c +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/flavor_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/flavor_test.go new file mode 100644 index 0000000000..f26cff0140 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/flavor_test.go @@ -0,0 +1,47 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/cdn/v1/flavors" + "github.com/rackspace/gophercloud/pagination" + "github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestFlavor(t *testing.T) { + client := newClient(t) + + t.Log("Listing Flavors") + id := testFlavorsList(t, client) + + t.Log("Retrieving Flavor") + testFlavorGet(t, client, id) +} + +func testFlavorsList(t *testing.T, client *gophercloud.ServiceClient) string { + var id string + err := flavors.List(client).EachPage(func(page pagination.Page) (bool, error) { + flavorList, err := os.ExtractFlavors(page) + th.AssertNoErr(t, err) + + for _, flavor := range flavorList { + t.Logf("Listing flavor: ID [%s] Providers [%+v]", flavor.ID, flavor.Providers) + id = flavor.ID + } + + return true, nil + }) + + th.AssertNoErr(t, err) + return id +} + +func testFlavorGet(t *testing.T, client *gophercloud.ServiceClient, id string) { + flavor, err := flavors.Get(client, id).Extract() + th.AssertNoErr(t, err) + t.Logf("Retrieved Flavor: %+v", *flavor) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/service_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/service_test.go new file mode 100644 index 0000000000..c19c241c36 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/service_test.go @@ -0,0 +1,93 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/cdn/v1/services" + "github.com/rackspace/gophercloud/pagination" + "github.com/rackspace/gophercloud/rackspace/cdn/v1/services" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestService(t *testing.T) { + client := newClient(t) + + t.Log("Creating Service") + loc := testServiceCreate(t, client, "test-site-1") + t.Logf("Created service at location: %s", loc) + + defer testServiceDelete(t, client, loc) + + t.Log("Updating Service") + testServiceUpdate(t, client, loc) + + t.Log("Retrieving Service") + testServiceGet(t, client, loc) + + t.Log("Listing Services") + testServiceList(t, client) +} + +func testServiceCreate(t *testing.T, client *gophercloud.ServiceClient, name string) string { + createOpts := os.CreateOpts{ + Name: name, + Domains: []os.Domain{ + os.Domain{ + Domain: "www." + name + ".com", + }, + }, + Origins: []os.Origin{ + os.Origin{ + Origin: name + ".com", + Port: 80, + SSL: false, + }, + }, + FlavorID: "cdn", + } + l, err := services.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + return l +} + +func testServiceGet(t *testing.T, client *gophercloud.ServiceClient, id string) { + s, err := services.Get(client, id).Extract() + th.AssertNoErr(t, err) + t.Logf("Retrieved service: %+v", *s) +} + +func testServiceUpdate(t *testing.T, client *gophercloud.ServiceClient, id string) { + opts := os.UpdateOpts{ + os.Append{ + Value: os.Domain{Domain: "newDomain.com", Protocol: "http"}, + }, + } + + loc, err := services.Update(client, id, opts).Extract() + th.AssertNoErr(t, err) + t.Logf("Successfully updated service at location: %s", loc) +} + +func testServiceList(t *testing.T, client *gophercloud.ServiceClient) { + err := services.List(client, nil).EachPage(func(page pagination.Page) (bool, error) { + serviceList, err := os.ExtractServices(page) + th.AssertNoErr(t, err) + + for _, service := range serviceList { + t.Logf("Listing service: %+v", service) + } + + return true, nil + }) + + th.AssertNoErr(t, err) +} + +func testServiceDelete(t *testing.T, client *gophercloud.ServiceClient, id string) { + err := services.Delete(client, id).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Successfully deleted service (%s)", id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/serviceasset_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/serviceasset_test.go new file mode 100644 index 0000000000..c32bf253da --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/cdn/v1/serviceasset_test.go @@ -0,0 +1,32 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + osServiceAssets "github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets" + "github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestServiceAsset(t *testing.T) { + client := newClient(t) + + t.Log("Creating Service") + loc := testServiceCreate(t, client, "test-site-2") + t.Logf("Created service at location: %s", loc) + + t.Log("Deleting Service Assets") + testServiceAssetDelete(t, client, loc) +} + +func testServiceAssetDelete(t *testing.T, client *gophercloud.ServiceClient, url string) { + deleteOpts := osServiceAssets.DeleteOpts{ + All: true, + } + err := serviceassets.Delete(client, url, deleteOpts).ExtractErr() + th.AssertNoErr(t, err) + t.Log("Successfully deleted all Service Assets") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/bootfromvolume_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/bootfromvolume_test.go index c8c8e21058..d7e6aa712c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/bootfromvolume_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/bootfromvolume_test.go @@ -41,6 +41,9 @@ func TestBootFromVolume(t *testing.T) { }).Extract() th.AssertNoErr(t, err) t.Logf("Created server: %+v\n", server) - //defer deleteServer(t, client, server) - t.Logf("Deleting server [%s]...", name) + defer deleteServer(t, client, server) + + getServer(t, client, server) + + listServers(t, client) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/servers_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/servers_test.go index 81c8599f3d..a8b5937b6e 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/servers_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/servers_test.go @@ -100,6 +100,18 @@ func getServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Serve logServer(t, details, -1) } +func updateServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) { + t.Logf("> servers.Get") + + opts := os.UpdateOpts{ + Name: "updated-server", + } + updatedServer, err := servers.Update(client, server.ID, opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "updated-server", updatedServer.Name) + logServer(t, updatedServer, -1) +} + func listServers(t *testing.T, client *gophercloud.ServiceClient) { t.Logf("> servers.List") @@ -197,6 +209,7 @@ func TestServerOperations(t *testing.T) { defer deleteServer(t, client, server) getServer(t, client, server) + updateServer(t, client, server) listServers(t, client) changeAdminPassword(t, client, server) rebootServer(t, client, server) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/accounts_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/accounts_test.go index 145e4e0482..8b3cde45e4 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/accounts_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/accounts_test.go @@ -13,11 +13,11 @@ func TestAccounts(t *testing.T) { c, err := createClient(t, false) th.AssertNoErr(t, err) - updateres := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}) - th.AssertNoErr(t, updateres.Err) - t.Logf("Headers from Update Account request: %+v\n", updateres.Header) + updateHeaders, err := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).Extract() + th.AssertNoErr(t, err) + t.Logf("Update Account Response Headers: %+v\n", updateHeaders) defer func() { - updateres = raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}}) + updateres := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}}) th.AssertNoErr(t, updateres.Err) metadata, err := raxAccounts.Get(c).ExtractMetadata() th.AssertNoErr(t, err) @@ -25,8 +25,13 @@ func TestAccounts(t *testing.T) { th.CheckEquals(t, metadata["White"], "") }() - metadata, err := raxAccounts.Get(c).ExtractMetadata() - th.AssertNoErr(t, err) + getResp := raxAccounts.Get(c) + th.AssertNoErr(t, getResp.Err) + + getHeaders, _ := getResp.Extract() + t.Logf("Get Account Response Headers: %+v\n", getHeaders) + + metadata, _ := getResp.ExtractMetadata() t.Logf("Metadata from Get Account request (after update): %+v\n", metadata) th.CheckEquals(t, metadata["White"], "mountains") diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go index e1bf38b16f..0f56f4978a 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go @@ -26,10 +26,11 @@ func TestCDNContainers(t *testing.T) { raxCDNClient, err := createClient(t, true) th.AssertNoErr(t, err) - - r := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900}) - th.AssertNoErr(t, r.Err) - t.Logf("Headers from Enable CDN Container request: %+v\n", r.Header) + enableRes := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900}) + t.Logf("Header map from Enable CDN Container request: %+v\n", enableRes.Header) + enableHeader, err := enableRes.Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Enable CDN Container request: %+v\n", enableHeader) t.Logf("Container Names available to the currently issued token:") count := 0 @@ -51,11 +52,15 @@ func TestCDNContainers(t *testing.T) { t.Errorf("No CDN containers listed for your current token.") } - updateres := raxCDNContainers.Update(raxCDNClient, "gophercloud-test", raxCDNContainers.UpdateOpts{CDNEnabled: false}) - th.AssertNoErr(t, updateres.Err) - t.Logf("Headers from Update CDN Container request: %+v\n", updateres.Header) - - metadata, err := raxCDNContainers.Get(raxCDNClient, "gophercloud-test").ExtractMetadata() + updateOpts := raxCDNContainers.UpdateOpts{XCDNEnabled: raxCDNContainers.Disabled, XLogRetention: raxCDNContainers.Enabled} + updateHeader, err := raxCDNContainers.Update(raxCDNClient, "gophercloud-test", updateOpts).Extract() th.AssertNoErr(t, err) - t.Logf("Headers from Get CDN Container request (after update): %+v\n", metadata) + t.Logf("Headers from Update CDN Container request: %+v\n", updateHeader) + + getRes := raxCDNContainers.Get(raxCDNClient, "gophercloud-test") + getHeader, err := getRes.Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Get CDN Container request (after update): %+v\n", getHeader) + metadata, err := getRes.ExtractMetadata() + t.Logf("Metadata from Get CDN Container request (after update): %+v\n", metadata) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go index 6e477ae704..0c0ab8a1ed 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go @@ -36,11 +36,15 @@ func TestCDNObjects(t *testing.T) { raxCDNClient, err := createClient(t, true) th.AssertNoErr(t, err) - enableResult := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900}) - th.AssertNoErr(t, enableResult.Err) - t.Logf("Headers from Enable CDN Container request: %+v\n", enableResult.Header) + enableHeader, err := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900}).Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Enable CDN Container request: %+v\n", enableHeader) - deleteResult := raxCDNObjects.Delete(raxCDNClient, "gophercloud-test", "test-object", nil) - th.AssertNoErr(t, deleteResult.Err) - t.Logf("Headers from Delete CDN Object request: %+v\n", deleteResult.Err) + objCDNURL, err := raxCDNObjects.CDNURL(raxCDNClient, "gophercloud-test", "test-object") + th.AssertNoErr(t, err) + t.Logf("%s CDN URL: %s\n", "test_object", objCDNURL) + + deleteHeader, err := raxCDNObjects.Delete(raxCDNClient, "gophercloud-test", "test-object", nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Delete CDN Object request: %+v\n", deleteHeader) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/containers_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/containers_test.go index a7339cf388..c89551373f 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/containers_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/containers_test.go @@ -57,29 +57,34 @@ func TestContainers(t *testing.T) { t.Errorf("No containers listed for your current token.") } - createres := raxContainers.Create(c, "gophercloud-test", nil) - th.AssertNoErr(t, createres.Err) + createHeader, err := raxContainers.Create(c, "gophercloud-test", nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Create Container request: %+v\n", createHeader) defer func() { - res := raxContainers.Delete(c, "gophercloud-test") - th.AssertNoErr(t, res.Err) + deleteres := raxContainers.Delete(c, "gophercloud-test") + deleteHeader, err := deleteres.Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Delete Container request: %+v\n", deleteres.Header) + t.Logf("Headers from Delete Container request: %+v\n", deleteHeader) }() - updateres := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}) - th.AssertNoErr(t, updateres.Err) - t.Logf("Headers from Update Account request: %+v\n", updateres.Header) + updateHeader, err := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).Extract() + th.AssertNoErr(t, err) + t.Logf("Headers from Update Container request: %+v\n", updateHeader) defer func() { res := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": ""}}) th.AssertNoErr(t, res.Err) metadata, err := raxContainers.Get(c, "gophercloud-test").ExtractMetadata() th.AssertNoErr(t, err) - t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata) + t.Logf("Metadata from Get Container request (after update reverted): %+v\n", metadata) th.CheckEquals(t, metadata["White"], "") }() getres := raxContainers.Get(c, "gophercloud-test") - t.Logf("Headers from Get Account request (after update): %+v\n", getres.Header) - metadata, err := getres.ExtractMetadata() + getHeader, err := getres.Extract() th.AssertNoErr(t, err) - t.Logf("Metadata from Get Account request (after update): %+v\n", metadata) + t.Logf("Headers from Get Container request (after update): %+v\n", getHeader) + metadata, err := getres.ExtractMetadata() + t.Logf("Metadata from Get Container request (after update): %+v\n", metadata) th.CheckEquals(t, metadata["White"], "mountains") } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/objects_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/objects_test.go index 462f2847db..585dea7696 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/objects_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/objects_test.go @@ -21,6 +21,7 @@ func TestObjects(t *testing.T) { th.AssertNoErr(t, res.Err) defer func() { + t.Logf("Deleting container...") res := raxContainers.Delete(c, "gophercloud-test") th.AssertNoErr(t, res.Err) }() @@ -29,7 +30,9 @@ func TestObjects(t *testing.T) { options := &osObjects.CreateOpts{ContentType: "text/plain"} createres := raxObjects.Create(c, "gophercloud-test", "o1", content, options) th.AssertNoErr(t, createres.Err) + defer func() { + t.Logf("Deleting object o1...") res := raxObjects.Delete(c, "gophercloud-test", "o1", nil) th.AssertNoErr(t, res.Err) }() @@ -80,6 +83,7 @@ func TestObjects(t *testing.T) { copyres := raxObjects.Copy(c, "gophercloud-test", "o1", &raxObjects.CopyOpts{Destination: "gophercloud-test/o2"}) th.AssertNoErr(t, copyres.Err) defer func() { + t.Logf("Deleting object o2...") res := raxObjects.Delete(c, "gophercloud-test", "o2", nil) th.AssertNoErr(t, res.Err) }() @@ -99,7 +103,7 @@ func TestObjects(t *testing.T) { metadata, err := raxObjects.Get(c, "gophercloud-test", "o2", nil).ExtractMetadata() th.AssertNoErr(t, err) t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata) - th.CheckEquals(t, metadata["White"], "") + th.CheckEquals(t, "", metadata["White"]) }() getres := raxObjects.Get(c, "gophercloud-test", "o2", nil) @@ -108,5 +112,13 @@ func TestObjects(t *testing.T) { metadata, err := getres.ExtractMetadata() th.AssertNoErr(t, err) t.Logf("Metadata from Get Account request (after update): %+v\n", metadata) - th.CheckEquals(t, metadata["White"], "mountains") + th.CheckEquals(t, "mountains", metadata["White"]) + + createTempURLOpts := osObjects.CreateTempURLOpts{ + Method: osObjects.GET, + TTL: 600, + } + tempURL, err := raxObjects.CreateTempURL(c, "gophercloud-test", "o1", createTempURLOpts) + th.AssertNoErr(t, err) + t.Logf("TempURL for object (%s): %s", "o1", tempURL) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/buildinfo_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/buildinfo_test.go new file mode 100644 index 0000000000..42cc048e3b --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/buildinfo_test.go @@ -0,0 +1,20 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestBuildInfo(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + bi, err := buildinfo.Get(client).Extract() + th.AssertNoErr(t, err) + t.Logf("retrieved build info: %+v\n", bi) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/common.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/common.go new file mode 100644 index 0000000000..b9d51979d7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/common.go @@ -0,0 +1,45 @@ +// +build acceptance + +package v1 + +import ( + "fmt" + "os" + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/rackspace" + th "github.com/rackspace/gophercloud/testhelper" +) + +var template = fmt.Sprintf(` +{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": {}, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "flavor": "%s", + "image": "%s", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } +} +`, os.Getenv("RS_FLAVOR_ID"), os.Getenv("RS_IMAGE_ID")) + +func newClient(t *testing.T) *gophercloud.ServiceClient { + ao, err := rackspace.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + client, err := rackspace.AuthenticatedClient(ao) + th.AssertNoErr(t, err) + + c, err := rackspace.NewOrchestrationV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("RS_REGION_NAME"), + }) + th.AssertNoErr(t, err) + return c +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackevents_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackevents_test.go new file mode 100644 index 0000000000..9e3fc084ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackevents_test.go @@ -0,0 +1,70 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + osStackEvents "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents" + osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStackEvents(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName := "postman_stack_2" + resourceName := "hello_world" + var eventID string + + createOpts := osStacks.CreateOpts{ + Name: stackName, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + err = stackevents.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) { + events, err := osStackEvents.ExtractEvents(page) + th.AssertNoErr(t, err) + t.Logf("listed events: %+v\n", events) + eventID = events[0].ID + return false, nil + }) + th.AssertNoErr(t, err) + + err = stackevents.ListResourceEvents(client, stackName, stack.ID, resourceName, nil).EachPage(func(page pagination.Page) (bool, error) { + resourceEvents, err := osStackEvents.ExtractResourceEvents(page) + th.AssertNoErr(t, err) + t.Logf("listed resource events: %+v\n", resourceEvents) + return false, nil + }) + th.AssertNoErr(t, err) + + event, err := stackevents.Get(client, stackName, stack.ID, resourceName, eventID).Extract() + th.AssertNoErr(t, err) + t.Logf("retrieved event: %+v\n", event) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackresources_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackresources_test.go new file mode 100644 index 0000000000..65926e78dc --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stackresources_test.go @@ -0,0 +1,64 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + osStackResources "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources" + osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStackResources(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName := "postman_stack_2" + + createOpts := osStacks.CreateOpts{ + Name: stackName, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + resourceName := "hello_world" + resource, err := stackresources.Get(client, stackName, stack.ID, resourceName).Extract() + th.AssertNoErr(t, err) + t.Logf("Got stack resource: %+v\n", resource) + + metadata, err := stackresources.Metadata(client, stackName, stack.ID, resourceName).Extract() + th.AssertNoErr(t, err) + t.Logf("Got stack resource metadata: %+v\n", metadata) + + err = stackresources.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) { + resources, err := osStackResources.ExtractResources(page) + th.AssertNoErr(t, err) + t.Logf("resources: %+v\n", resources) + return false, nil + }) + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacks_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacks_test.go new file mode 100644 index 0000000000..cfec4e9817 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacks_test.go @@ -0,0 +1,82 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStacks(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName1 := "gophercloud-test-stack-2" + createOpts := osStacks.CreateOpts{ + Name: stackName1, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName1, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName1) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + updateOpts := osStacks.UpdateOpts{ + Template: template, + Timeout: 20, + } + err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr() + th.AssertNoErr(t, err) + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "UPDATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + t.Logf("Updated stack") + + err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) { + stackList, err := osStacks.ExtractStacks(page) + th.AssertNoErr(t, err) + + t.Logf("Got stack list: %+v\n", stackList) + + return true, nil + }) + th.AssertNoErr(t, err) + + getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("Got stack: %+v\n", getStack) + + abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("Abandonded stack %+v\n", abandonedStack) + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacktemplates_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacktemplates_test.go new file mode 100644 index 0000000000..1f7b217106 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/orchestration/v1/stacktemplates_test.go @@ -0,0 +1,79 @@ +// +build acceptance + +package v1 + +import ( + "testing" + + "github.com/rackspace/gophercloud" + osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + osStacktemplates "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestStackTemplates(t *testing.T) { + // Create a provider client for making the HTTP requests. + // See common.go in this directory for more information. + client := newClient(t) + + stackName := "postman_stack_2" + + createOpts := osStacks.CreateOpts{ + Name: stackName, + Template: template, + Timeout: 5, + } + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("Created stack: %+v\n", stack) + defer func() { + err := stacks.Delete(client, stackName, stack.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Deleted stack (%s)", stackName) + }() + err = gophercloud.WaitFor(60, func() (bool, error) { + getStack, err := stacks.Get(client, stackName, stack.ID).Extract() + if err != nil { + return false, err + } + if getStack.Status == "CREATE_COMPLETE" { + return true, nil + } + return false, nil + }) + + tmpl, err := stacktemplates.Get(client, stackName, stack.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("retrieved template: %+v\n", tmpl) + + validateOpts := osStacktemplates.ValidateOpts{ + Template: map[string]interface{}{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": map[string]interface{}{ + "flavor": map[string]interface{}{ + "default": "m1.tiny", + "type": "string", + }, + }, + "resources": map[string]interface{}{ + "hello_world": map[string]interface{}{ + "type": "OS::Nova::Server", + "properties": map[string]interface{}{ + "key_name": "heat_key", + "flavor": map[string]interface{}{ + "get_param": "flavor", + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", + }, + }, + }, + }, + } + validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract() + th.AssertNoErr(t, err) + t.Logf("validated template: %+v\n", validatedTemplate) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/auth_options.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/auth_options.go index 19ce5d4a99..9819e45f2d 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/auth_options.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/auth_options.go @@ -42,7 +42,5 @@ type AuthOptions struct { // re-authenticate automatically if/when your token expires. If you set it to // false, it will not cache these settings, but re-authentication will not be // possible. This setting defaults to false. - // - // This setting is speculative and is currently not respected! AllowReauth bool } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/apiversions/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/apiversions/requests.go index 016bf374e3..f5a793c35c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/apiversions/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/apiversions/requests.go @@ -3,8 +3,6 @@ package apiversions import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // List lists all the Cinder API versions available to end-users. @@ -18,11 +16,9 @@ func List(c *gophercloud.ServiceClient) pagination.Pager { // type from the result, call the Extract method on the GetResult. func Get(client *gophercloud.ServiceClient, v string) GetResult { var res GetResult - _, err := perigee.Request("GET", getURL(client, v), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, - Results: &res.Body, + _, res.Err = client.Request("GET", getURL(client, v), gophercloud.RequestOpts{ + OkCodes: []int{200}, + JSONResponse: &res.Body, }) - res.Err = err return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots/requests.go index 443f696057..1b313a6080 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots/requests.go @@ -5,8 +5,6 @@ import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // CreateOptsBuilder allows extensions to add additional parameters to the @@ -69,11 +67,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return res } - _, res.Err = perigee.Request("POST", createURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200, 201}, - ReqBody: &reqBody, - Results: &res.Body, + _, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + JSONBody: &reqBody, + JSONResponse: &res.Body, }) return res } @@ -81,9 +78,8 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes // Delete will delete the existing Snapshot with the provided ID. func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202, 204}, + _, res.Err = client.Request("DELETE", deleteURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{202, 204}, }) return res } @@ -92,10 +88,9 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { // object from the response, call the Extract method on the GetResult. func Get(client *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{ - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, res.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{200}, + JSONResponse: &res.Body, }) return res } @@ -178,11 +173,10 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet return res } - _, res.Err = perigee.Request("PUT", updateMetadataURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, - ReqBody: &reqBody, - Results: &res.Body, + _, res.Err = client.Request("PUT", updateMetadataURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{200}, + JSONBody: &reqBody, + JSONResponse: &res.Body, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/fixtures.go index a01ad05a38..a1b8697c2a 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/fixtures.go @@ -45,8 +45,16 @@ func MockGetResponse(t *testing.T) { { "volume": { "display_name": "vol-001", - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22" - } + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "attachments": [ + { + "device": "/dev/vde", + "server_id": "a740d24b-dc5b-4d59-ac75-53971c2920ba", + "id": "d6da11e5-2ed3-413e-88d8-b772ba62193d", + "volume_id": "d6da11e5-2ed3-413e-88d8-b772ba62193d" + } + ] + } } `) }) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests.go index f4332de657..e620ca6140 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests.go @@ -5,8 +5,6 @@ import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // CreateOptsBuilder allows extensions to add additional parameters to the @@ -85,11 +83,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return res } - _, res.Err = perigee.Request("POST", createURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201}, + _, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + JSONBody: &reqBody, + JSONResponse: &res.Body, }) return res } @@ -97,9 +94,8 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes // Delete will delete the existing Volume with the provided ID. func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202, 204}, + _, res.Err = client.Request("DELETE", deleteURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{202, 204}, }) return res } @@ -108,10 +104,9 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { // from the response, call the Extract method on the GetResult. func Get(client *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{ - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, res.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -207,11 +202,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder return res } - _, res.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, - ReqBody: &reqBody, - Results: &res.Body, + _, res.Err = client.Request("PUT", updateURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{200}, + JSONBody: &reqBody, + JSONResponse: &res.Body, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests_test.go index 067f89bdd9..7643375a72 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests_test.go @@ -56,6 +56,7 @@ func TestGet(t *testing.T) { th.AssertEquals(t, v.Name, "vol-001") th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, v.Attachments[0]["device"], "/dev/vde") } func TestCreate(t *testing.T) { diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/results.go index c6ddbb5167..2fd4ef14ea 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/results.go @@ -16,7 +16,7 @@ type Volume struct { Name string `mapstructure:"display_name"` // Instances onto which the volume is attached. - Attachments []string `mapstructure:"attachments"` + Attachments []map[string]interface{} `mapstructure:"attachments"` // This parameter is no longer used. AvailabilityZone string `mapstructure:"availability_zone"` @@ -28,7 +28,7 @@ type Volume struct { CreatedAt string `mapstructure:"created_at"` // Human-readable description for the volume. - Description string `mapstructure:"display_discription"` + Description string `mapstructure:"display_description"` // The type of volume to create, either SATA or SSD. VolumeType string `mapstructure:"volume_type"` diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes/requests.go index 87e20f6003..6fedaa6896 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes/requests.go @@ -1,7 +1,6 @@ package volumetypes import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -45,11 +44,11 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return res } - _, res.Err = perigee.Request("POST", createURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200, 201}, - ReqBody: &reqBody, - Results: &res.Body, + _, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{ + MoreHeaders: client.AuthenticatedHeaders(), + OkCodes: []int{200, 201}, + JSONBody: &reqBody, + JSONResponse: &res.Body, }) return res } @@ -57,7 +56,7 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes // Delete will delete the volume type with the provided ID. func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(client, id), perigee.Options{ + _, res.Err = client.Request("DELETE", deleteURL(client, id), gophercloud.RequestOpts{ MoreHeaders: client.AuthenticatedHeaders(), OkCodes: []int{202}, }) @@ -68,10 +67,10 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { // type from the result, call the Extract method on the GetResult. func Get(client *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, err := perigee.Request("GET", getURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, - Results: &res.Body, + _, err := client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + MoreHeaders: client.AuthenticatedHeaders(), + OkCodes: []int{200}, + JSONResponse: &res.Body, }) res.Err = err return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/doc.go new file mode 100644 index 0000000000..f78d4f7355 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/doc.go @@ -0,0 +1,4 @@ +// Package base provides information and interaction with the base API +// resource in the OpenStack CDN service. This API resource allows for +// retrieving the Home Document and pinging the root URL. +package base diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/fixtures.go new file mode 100644 index 0000000000..19b5ece461 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/fixtures.go @@ -0,0 +1,53 @@ +package base + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// HandleGetSuccessfully creates an HTTP handler at `/` on the test handler mux +// that responds with a `Get` response. +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/", 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.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ` + { + "resources": { + "rel/cdn": { + "href-template": "services{?marker,limit}", + "href-vars": { + "marker": "param/marker", + "limit": "param/limit" + }, + "hints": { + "allow": [ + "GET" + ], + "formats": { + "application/json": {} + } + } + } + } + } + `) + + }) +} + +// HandlePingSuccessfully creates an HTTP handler at `/ping` on the test handler +// mux that responds with a `Ping` response. +func HandlePingSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests.go new file mode 100644 index 0000000000..b63dc95a71 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests.go @@ -0,0 +1,24 @@ +package base + +import "github.com/rackspace/gophercloud" + +// Get retrieves the home document, allowing the user to discover the +// entire API. +func Get(c *gophercloud.ServiceClient) GetResult { + var res GetResult + _, res.Err = c.Request("GET", getURL(c), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Ping retrieves a ping to the server. +func Ping(c *gophercloud.ServiceClient) PingResult { + var res PingResult + _, res.Err = c.Request("GET", pingURL(c), gophercloud.RequestOpts{ + OkCodes: []int{204}, + MoreHeaders: map[string]string{"Accept": ""}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests_test.go new file mode 100644 index 0000000000..a8d95f9ace --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests_test.go @@ -0,0 +1,43 @@ +package base + +import ( + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestGetHomeDocument(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t) + + actual, err := Get(fake.ServiceClient()).Extract() + th.CheckNoErr(t, err) + + expected := HomeDocument{ + "rel/cdn": map[string]interface{}{ + "href-template": "services{?marker,limit}", + "href-vars": map[string]interface{}{ + "marker": "param/marker", + "limit": "param/limit", + }, + "hints": map[string]interface{}{ + "allow": []string{"GET"}, + "formats": map[string]interface{}{ + "application/json": map[string]interface{}{}, + }, + }, + }, + } + th.CheckDeepEquals(t, expected, *actual) +} + +func TestPing(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePingSuccessfully(t) + + err := Ping(fake.ServiceClient()).ExtractErr() + th.CheckNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/results.go new file mode 100644 index 0000000000..bef1da8a17 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/results.go @@ -0,0 +1,35 @@ +package base + +import ( + "errors" + + "github.com/rackspace/gophercloud" +) + +// HomeDocument is a resource that contains all the resources for the CDN API. +type HomeDocument map[string]interface{} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a home document resource. +func (r GetResult) Extract() (*HomeDocument, error) { + if r.Err != nil { + return nil, r.Err + } + + submap, ok := r.Body.(map[string]interface{})["resources"] + if !ok { + return nil, errors.New("Unexpected HomeDocument structure") + } + casted := HomeDocument(submap.(map[string]interface{})) + + return &casted, nil +} + +// PingResult represents the result of a Ping operation. +type PingResult struct { + gophercloud.ErrResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/urls.go new file mode 100644 index 0000000000..a95e18bca9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/urls.go @@ -0,0 +1,11 @@ +package base + +import "github.com/rackspace/gophercloud" + +func getURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL() +} + +func pingURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("ping") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/doc.go new file mode 100644 index 0000000000..d4066985cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/doc.go @@ -0,0 +1,6 @@ +// Package flavors provides information and interaction with the flavors API +// resource in the OpenStack CDN service. This API resource allows for +// listing flavors and retrieving a specific flavor. +// +// A flavor is a mapping configuration to a CDN provider. +package flavors diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/fixtures.go new file mode 100644 index 0000000000..f413b6bc41 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/fixtures.go @@ -0,0 +1,82 @@ +package flavors + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// HandleListCDNFlavorsSuccessfully creates an HTTP handler at `/flavors` on the test handler mux +// that responds with a `List` response. +func HandleListCDNFlavorsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/flavors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ` + { + "flavors": [ + { + "id": "europe", + "providers": [ + { + "provider": "Fastly", + "links": [ + { + "href": "http://www.fastly.com", + "rel": "provider_url" + } + ] + } + ], + "links": [ + { + "href": "https://www.poppycdn.io/v1.0/flavors/europe", + "rel": "self" + } + ] + } + ] + } + `) + }) +} + +// HandleGetCDNFlavorSuccessfully creates an HTTP handler at `/flavors/{id}` on the test handler mux +// that responds with a `Get` response. +func HandleGetCDNFlavorSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/flavors/asia", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ` + { + "id" : "asia", + "providers" : [ + { + "provider" : "ChinaCache", + "links": [ + { + "href": "http://www.chinacache.com", + "rel": "provider_url" + } + ] + } + ], + "links": [ + { + "href": "https://www.poppycdn.io/v1.0/flavors/asia", + "rel": "self" + } + ] + } + `) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests.go new file mode 100644 index 0000000000..138fd97631 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests.go @@ -0,0 +1,25 @@ +package flavors + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// List returns a single page of CDN flavors. +func List(c *gophercloud.ServiceClient) pagination.Pager { + url := listURL(c) + createPage := func(r pagination.PageResult) pagination.Page { + return FlavorPage{pagination.SinglePageBase(r)} + } + return pagination.NewPager(c, url, createPage) +} + +// Get retrieves a specific flavor based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Request("GET", getURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests_test.go new file mode 100644 index 0000000000..7ddf1b1c66 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/requests_test.go @@ -0,0 +1,90 @@ +package flavors + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListCDNFlavorsSuccessfully(t) + + count := 0 + + err := List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractFlavors(page) + if err != nil { + t.Errorf("Failed to extract flavors: %v", err) + return false, err + } + + expected := []Flavor{ + Flavor{ + ID: "europe", + Providers: []Provider{ + Provider{ + Provider: "Fastly", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://www.fastly.com", + Rel: "provider_url", + }, + }, + }, + }, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/europe", + Rel: "self", + }, + }, + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetCDNFlavorSuccessfully(t) + + expected := &Flavor{ + ID: "asia", + Providers: []Provider{ + Provider{ + Provider: "ChinaCache", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://www.chinacache.com", + Rel: "provider_url", + }, + }, + }, + }, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/asia", + Rel: "self", + }, + }, + } + + + actual, err := Get(fake.ServiceClient(), "asia").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/results.go new file mode 100644 index 0000000000..8cab48b536 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/results.go @@ -0,0 +1,71 @@ +package flavors + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Provider represents a provider for a particular flavor. +type Provider struct { + // Specifies the name of the provider. The name must not exceed 64 bytes in + // length and is limited to unicode, digits, underscores, and hyphens. + Provider string `mapstructure:"provider"` + // Specifies a list with an href where rel is provider_url. + Links []gophercloud.Link `mapstructure:"links"` +} + +// Flavor represents a mapping configuration to a CDN provider. +type Flavor struct { + // Specifies the name of the flavor. The name must not exceed 64 bytes in + // length and is limited to unicode, digits, underscores, and hyphens. + ID string `mapstructure:"id"` + // Specifies the list of providers mapped to this flavor. + Providers []Provider `mapstructure:"providers"` + // Specifies the self-navigating JSON document paths. + Links []gophercloud.Link `mapstructure:"links"` +} + +// FlavorPage is the page returned by a pager when traversing over a +// collection of CDN flavors. +type FlavorPage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a FlavorPage contains no Flavors. +func (r FlavorPage) IsEmpty() (bool, error) { + flavors, err := ExtractFlavors(r) + if err != nil { + return true, err + } + return len(flavors) == 0, nil +} + +// ExtractFlavors extracts and returns Flavors. It is used while iterating over +// a flavors.List call. +func ExtractFlavors(page pagination.Page) ([]Flavor, error) { + var response struct { + Flavors []Flavor `json:"flavors"` + } + + err := mapstructure.Decode(page.(FlavorPage).Body, &response) + return response.Flavors, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract is a function that extracts a flavor from a GetResult. +func (r GetResult) Extract() (*Flavor, error) { + if r.Err != nil { + return nil, r.Err + } + + var res Flavor + + err := mapstructure.Decode(r.Body, &res) + + return &res, err +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/urls.go new file mode 100644 index 0000000000..6eb38d2939 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/flavors/urls.go @@ -0,0 +1,11 @@ +package flavors + +import "github.com/rackspace/gophercloud" + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("flavors") +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("flavors", id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/doc.go new file mode 100644 index 0000000000..ceecaa5a5e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/doc.go @@ -0,0 +1,7 @@ +// Package serviceassets provides information and interaction with the +// serviceassets API resource in the OpenStack CDN service. This API resource +// allows for deleting cached assets. +// +// A service distributes assets across the network. Service assets let you +// interrogate properties about these assets and perform certain actions on them. +package serviceassets diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/fixtures.go new file mode 100644 index 0000000000..38e7fc55f8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/fixtures.go @@ -0,0 +1,19 @@ +package serviceassets + +import ( + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// HandleDeleteCDNAssetSuccessfully creates an HTTP handler at `/services/{id}/assets` on the test handler mux +// that responds with a `Delete` response. +func HandleDeleteCDNAssetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0/assets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests.go new file mode 100644 index 0000000000..a80aa0db28 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests.go @@ -0,0 +1,50 @@ +package serviceassets + +import ( + "strings" + + "github.com/rackspace/gophercloud" +) + +// DeleteOptsBuilder allows extensions to add additional parameters to the Delete +// request. +type DeleteOptsBuilder interface { + ToCDNAssetDeleteParams() (string, error) +} + +// DeleteOpts is a structure that holds options for deleting CDN service assets. +type DeleteOpts struct { + // If all is set to true, specifies that the delete occurs against all of the + // assets for the service. + All bool `q:"all"` + // Specifies the relative URL of the asset to be deleted. + URL string `q:"url"` +} + +// ToCDNAssetDeleteParams formats a DeleteOpts into a query string. +func (opts DeleteOpts) ToCDNAssetDeleteParams() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// Delete accepts a unique service ID or URL and deletes the CDN service asset associated with +// it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and +// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" +// are valid options for idOrURL. +func Delete(c *gophercloud.ServiceClient, idOrURL string, opts DeleteOptsBuilder) DeleteResult { + var url string + if strings.Contains(idOrURL, "/") { + url = idOrURL + } else { + url = deleteURL(c, idOrURL) + } + + var res DeleteResult + _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests_test.go new file mode 100644 index 0000000000..32896eef7a --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/requests_test.go @@ -0,0 +1,18 @@ +package serviceassets + +import ( + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteCDNAssetSuccessfully(t) + + err := Delete(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", nil).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/results.go new file mode 100644 index 0000000000..1d8734b7c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/results.go @@ -0,0 +1,8 @@ +package serviceassets + +import "github.com/rackspace/gophercloud" + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/urls.go new file mode 100644 index 0000000000..cb0aea8fca --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets/urls.go @@ -0,0 +1,7 @@ +package serviceassets + +import "github.com/rackspace/gophercloud" + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("services", id, "assets") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/doc.go new file mode 100644 index 0000000000..41f7c60dae --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/doc.go @@ -0,0 +1,7 @@ +// Package services provides information and interaction with the services API +// resource in the OpenStack CDN service. This API resource allows for +// listing, creating, updating, retrieving, and deleting services. +// +// A service represents an application that has its content cached to the edge +// nodes. +package services diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/errors.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/errors.go new file mode 100644 index 0000000000..359584c2a6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/errors.go @@ -0,0 +1,7 @@ +package services + +import "fmt" + +func no(str string) error { + return fmt.Errorf("Required parameter %s not provided", str) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/fixtures.go new file mode 100644 index 0000000000..d9bc9f20b7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/fixtures.go @@ -0,0 +1,372 @@ +package services + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// HandleListCDNServiceSuccessfully creates an HTTP handler at `/services` on the test handler mux +// that responds with a `List` response. +func HandleListCDNServiceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, ` + { + "links": [ + { + "rel": "next", + "href": "https://www.poppycdn.io/v1.0/services?marker=96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0&limit=20" + } + ], + "services": [ + { + "id": "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + "name": "mywebsite.com", + "domains": [ + { + "domain": "www.mywebsite.com" + } + ], + "origins": [ + { + "origin": "mywebsite.com", + "port": 80, + "ssl": false + } + ], + "caching": [ + { + "name": "default", + "ttl": 3600 + }, + { + "name": "home", + "ttl": 17200, + "rules": [ + { + "name": "index", + "request_url": "/index.htm" + } + ] + }, + { + "name": "images", + "ttl": 12800, + "rules": [ + { + "name": "images", + "request_url": "*.png" + } + ] + } + ], + "restrictions": [ + { + "name": "website only", + "rules": [ + { + "name": "mywebsite.com", + "referrer": "www.mywebsite.com" + } + ] + } + ], + "flavor_id": "asia", + "status": "deployed", + "errors" : [], + "links": [ + { + "href": "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + "rel": "self" + }, + { + "href": "mywebsite.com.cdn123.poppycdn.net", + "rel": "access_url" + }, + { + "href": "https://www.poppycdn.io/v1.0/flavors/asia", + "rel": "flavor" + } + ] + }, + { + "id": "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1", + "name": "myothersite.com", + "domains": [ + { + "domain": "www.myothersite.com" + } + ], + "origins": [ + { + "origin": "44.33.22.11", + "port": 80, + "ssl": false + }, + { + "origin": "77.66.55.44", + "port": 80, + "ssl": false, + "rules": [ + { + "name": "videos", + "request_url": "^/videos/*.m3u" + } + ] + } + ], + "caching": [ + { + "name": "default", + "ttl": 3600 + } + ], + "restrictions": [ + {} + ], + "flavor_id": "europe", + "status": "deployed", + "links": [ + { + "href": "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1", + "rel": "self" + }, + { + "href": "myothersite.com.poppycdn.net", + "rel": "access_url" + }, + { + "href": "https://www.poppycdn.io/v1.0/flavors/europe", + "rel": "flavor" + } + ] + } + ] + } + `) + case "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1": + fmt.Fprintf(w, `{ + "services": [] + }`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +// HandleCreateCDNServiceSuccessfully creates an HTTP handler at `/services` on the test handler mux +// that responds with a `Create` response. +func HandleCreateCDNServiceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, ` + { + "name": "mywebsite.com", + "domains": [ + { + "domain": "www.mywebsite.com" + }, + { + "domain": "blog.mywebsite.com" + } + ], + "origins": [ + { + "origin": "mywebsite.com", + "port": 80, + "ssl": false + } + ], + "restrictions": [ + { + "name": "website only", + "rules": [ + { + "name": "mywebsite.com", + "referrer": "www.mywebsite.com" + } + ] + } + ], + "caching": [ + { + "name": "default", + "ttl": 3600 + } + ], + + "flavor_id": "cdn" + } + `) + w.Header().Add("Location", "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0") + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleGetCDNServiceSuccessfully creates an HTTP handler at `/services/{id}` on the test handler mux +// that responds with a `Get` response. +func HandleGetCDNServiceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ` + { + "id": "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + "name": "mywebsite.com", + "domains": [ + { + "domain": "www.mywebsite.com", + "protocol": "http" + } + ], + "origins": [ + { + "origin": "mywebsite.com", + "port": 80, + "ssl": false + } + ], + "caching": [ + { + "name": "default", + "ttl": 3600 + }, + { + "name": "home", + "ttl": 17200, + "rules": [ + { + "name": "index", + "request_url": "/index.htm" + } + ] + }, + { + "name": "images", + "ttl": 12800, + "rules": [ + { + "name": "images", + "request_url": "*.png" + } + ] + } + ], + "restrictions": [ + { + "name": "website only", + "rules": [ + { + "name": "mywebsite.com", + "referrer": "www.mywebsite.com" + } + ] + } + ], + "flavor_id": "cdn", + "status": "deployed", + "errors" : [], + "links": [ + { + "href": "https://global.cdn.api.rackspacecloud.com/v1.0/110011/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + "rel": "self" + }, + { + "href": "blog.mywebsite.com.cdn1.raxcdn.com", + "rel": "access_url" + }, + { + "href": "https://global.cdn.api.rackspacecloud.com/v1.0/110011/flavors/cdn", + "rel": "flavor" + } + ] + } + `) + }) +} + +// HandleUpdateCDNServiceSuccessfully creates an HTTP handler at `/services/{id}` on the test handler mux +// that responds with a `Update` response. +func HandleUpdateCDNServiceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, ` + [ + { + "op": "add", + "path": "/domains/-", + "value": {"domain": "appended.mocksite4.com"} + }, + { + "op": "add", + "path": "/domains/4", + "value": {"domain": "inserted.mocksite4.com"} + }, + { + "op": "add", + "path": "/domains", + "value": [ + {"domain": "bulkadded1.mocksite4.com"}, + {"domain": "bulkadded2.mocksite4.com"} + ] + }, + { + "op": "replace", + "path": "/origins/2", + "value": {"origin": "44.33.22.11", "port": 80, "ssl": false} + }, + { + "op": "replace", + "path": "/origins", + "value": [ + {"origin": "44.33.22.11", "port": 80, "ssl": false}, + {"origin": "55.44.33.22", "port": 443, "ssl": true} + ] + }, + { + "op": "remove", + "path": "/caching/8" + }, + { + "op": "remove", + "path": "/caching" + }, + { + "op": "replace", + "path": "/name", + "value": "differentServiceName" + } + ] + `) + w.Header().Add("Location", "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0") + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleDeleteCDNServiceSuccessfully creates an HTTP handler at `/services/{id}` on the test handler mux +// that responds with a `Delete` response. +func HandleDeleteCDNServiceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests.go new file mode 100644 index 0000000000..78a3087c98 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests.go @@ -0,0 +1,386 @@ +package services + +import ( + "fmt" + "strings" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToCDNServiceListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Marker and Limit are used for pagination. +type ListOpts struct { + Marker string `q:"marker"` + Limit int `q:"limit"` +} + +// ToCDNServiceListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToCDNServiceListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// CDN services. It accepts a ListOpts struct, which allows for pagination via +// marker and limit. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToCDNServiceListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + createPage := func(r pagination.PageResult) pagination.Page { + p := ServicePage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + } + + pager := pagination.NewPager(c, url, createPage) + return pager +} + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToCDNServiceCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // REQUIRED. Specifies the name of the service. The minimum length for name is + // 3. The maximum length is 256. + Name string + // REQUIRED. Specifies a list of domains used by users to access their website. + Domains []Domain + // REQUIRED. Specifies a list of origin domains or IP addresses where the + // original assets are stored. + Origins []Origin + // REQUIRED. Specifies the CDN provider flavor ID to use. For a list of + // flavors, see the operation to list the available flavors. The minimum + // length for flavor_id is 1. The maximum length is 256. + FlavorID string + // OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control. + Caching []CacheRule + // OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache). + Restrictions []Restriction +} + +// ToCDNServiceCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) { + s := make(map[string]interface{}) + + if opts.Name == "" { + return nil, no("Name") + } + s["name"] = opts.Name + + if opts.Domains == nil { + return nil, no("Domains") + } + for _, domain := range opts.Domains { + if domain.Domain == "" { + return nil, no("Domains[].Domain") + } + } + s["domains"] = opts.Domains + + if opts.Origins == nil { + return nil, no("Origins") + } + for _, origin := range opts.Origins { + if origin.Origin == "" { + return nil, no("Origins[].Origin") + } + if origin.Rules == nil && len(opts.Origins) > 1 { + return nil, no("Origins[].Rules") + } + for _, rule := range origin.Rules { + if rule.Name == "" { + return nil, no("Origins[].Rules[].Name") + } + if rule.RequestURL == "" { + return nil, no("Origins[].Rules[].RequestURL") + } + } + } + s["origins"] = opts.Origins + + if opts.FlavorID == "" { + return nil, no("FlavorID") + } + s["flavor_id"] = opts.FlavorID + + if opts.Caching != nil { + for _, cache := range opts.Caching { + if cache.Name == "" { + return nil, no("Caching[].Name") + } + if cache.Rules != nil { + for _, rule := range cache.Rules { + if rule.Name == "" { + return nil, no("Caching[].Rules[].Name") + } + if rule.RequestURL == "" { + return nil, no("Caching[].Rules[].RequestURL") + } + } + } + } + s["caching"] = opts.Caching + } + + if opts.Restrictions != nil { + for _, restriction := range opts.Restrictions { + if restriction.Name == "" { + return nil, no("Restrictions[].Name") + } + if restriction.Rules != nil { + for _, rule := range restriction.Rules { + if rule.Name == "" { + return nil, no("Restrictions[].Rules[].Name") + } + } + } + } + s["restrictions"] = opts.Restrictions + } + + return s, nil +} + +// Create accepts a CreateOpts struct and creates a new CDN service using the +// values provided. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToCDNServiceCreateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + resp, err := c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, + }) + res.Header = resp.Header + res.Err = err + return res +} + +// Get retrieves a specific service based on its URL or its unique ID. For +// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and +// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" +// are valid options for idOrURL. +func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult { + var url string + if strings.Contains(idOrURL, "/") { + url = idOrURL + } else { + url = getURL(c, idOrURL) + } + + var res GetResult + _, res.Err = c.Request("GET", url, gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Path is a JSON pointer location that indicates which service parameter is being added, replaced, +// or removed. +type Path struct { + baseElement string +} + +func (p Path) renderRoot() string { + return "/" + p.baseElement +} + +func (p Path) renderDash() string { + return fmt.Sprintf("/%s/-", p.baseElement) +} + +func (p Path) renderIndex(index int64) string { + return fmt.Sprintf("/%s/%d", p.baseElement, index) +} + +var ( + // PathDomains indicates that an update operation is to be performed on a Domain. + PathDomains = Path{baseElement: "domains"} + + // PathOrigins indicates that an update operation is to be performed on an Origin. + PathOrigins = Path{baseElement: "origins"} + + // PathCaching indicates that an update operation is to be performed on a CacheRule. + PathCaching = Path{baseElement: "caching"} +) + +type value interface { + toPatchValue() interface{} + appropriatePath() Path + renderRootOr(func(p Path) string) string +} + +// Patch represents a single update to an existing Service. Multiple updates to a service can be +// submitted at the same time. +type Patch interface { + ToCDNServiceUpdateMap() map[string]interface{} +} + +// Insertion is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to +// a Service at a fixed index. Use an Append instead to append the new value to the end of its +// collection. Pass it to the Update function as part of the Patch slice. +type Insertion struct { + Index int64 + Value value +} + +// ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the +// Update call. +func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "op": "add", + "path": i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }), + "value": i.Value.toPatchValue(), + } +} + +// Append is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to a +// Service at the end of its respective collection. Use an Insertion instead to insert the value +// at a fixed index within the collection. Pass this to the Update function as part of its +// Patch slice. +type Append struct { + Value value +} + +// ToCDNServiceUpdateMap converts an Append into a request body fragment suitable for the +// Update call. +func (a Append) ToCDNServiceUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "op": "add", + "path": a.Value.renderRootOr(func(p Path) string { return p.renderDash() }), + "value": a.Value.toPatchValue(), + } +} + +// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule) +// in-place by index. Pass it to the Update function as part of the Patch slice. +type Replacement struct { + Value value + Index int64 +} + +// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the +// Update call. +func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "op": "replace", + "path": r.Value.renderRootOr(func(p Path) string { return p.renderIndex(r.Index) }), + "value": r.Value.toPatchValue(), + } +} + +// NameReplacement specifically updates the Service name. Pass it to the Update function as part +// of the Patch slice. +type NameReplacement struct { + NewName string +} + +// ToCDNServiceUpdateMap converts a NameReplacement into a request body fragment suitable for the +// Update call. +func (r NameReplacement) ToCDNServiceUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "op": "replace", + "path": "/name", + "value": r.NewName, + } +} + +// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or +// CacheRule) by index. Pass it to the Update function as part of the Patch slice. +type Removal struct { + Path Path + Index int64 + All bool +} + +// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the +// Update call. +func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} { + result := map[string]interface{}{"op": "remove"} + if r.All { + result["path"] = r.Path.renderRoot() + } else { + result["path"] = r.Path.renderIndex(r.Index) + } + return result +} + +type UpdateOpts []Patch + +// Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and +// updates an existing CDN service using the values provided. idOrURL can be either the service's +// URL or its ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and +// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" +// are valid options for idOrURL. +func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) UpdateResult { + var url string + if strings.Contains(idOrURL, "/") { + url = idOrURL + } else { + url = updateURL(c, idOrURL) + } + + reqBody := make([]map[string]interface{}, len(opts)) + for i, patch := range opts { + reqBody[i] = patch.ToCDNServiceUpdateMap() + } + + resp, err := c.Request("PATCH", url, gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, + }) + var result UpdateResult + result.Header = resp.Header + result.Err = err + return result +} + +// Delete accepts a service's ID or its URL and deletes the CDN service +// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and +// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" +// are valid options for idOrURL. +func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult { + var url string + if strings.Contains(idOrURL, "/") { + url = idOrURL + } else { + url = deleteURL(c, idOrURL) + } + + var res DeleteResult + _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests_test.go new file mode 100644 index 0000000000..59e826f048 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/requests_test.go @@ -0,0 +1,358 @@ +package services + +import ( + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListCDNServiceSuccessfully(t) + + count := 0 + + err := List(fake.ServiceClient(), &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractServices(page) + if err != nil { + t.Errorf("Failed to extract services: %v", err) + return false, err + } + + expected := []Service{ + Service{ + ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Name: "mywebsite.com", + Domains: []Domain{ + Domain{ + Domain: "www.mywebsite.com", + }, + }, + Origins: []Origin{ + Origin{ + Origin: "mywebsite.com", + Port: 80, + SSL: false, + }, + }, + Caching: []CacheRule{ + CacheRule{ + Name: "default", + TTL: 3600, + }, + CacheRule{ + Name: "home", + TTL: 17200, + Rules: []TTLRule{ + TTLRule{ + Name: "index", + RequestURL: "/index.htm", + }, + }, + }, + CacheRule{ + Name: "images", + TTL: 12800, + Rules: []TTLRule{ + TTLRule{ + Name: "images", + RequestURL: "*.png", + }, + }, + }, + }, + Restrictions: []Restriction{ + Restriction{ + Name: "website only", + Rules: []RestrictionRule{ + RestrictionRule{ + Name: "mywebsite.com", + Referrer: "www.mywebsite.com", + }, + }, + }, + }, + FlavorID: "asia", + Status: "deployed", + Errors: []Error{}, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Rel: "self", + }, + gophercloud.Link{ + Href: "mywebsite.com.cdn123.poppycdn.net", + Rel: "access_url", + }, + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/asia", + Rel: "flavor", + }, + }, + }, + Service{ + ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1", + Name: "myothersite.com", + Domains: []Domain{ + Domain{ + Domain: "www.myothersite.com", + }, + }, + Origins: []Origin{ + Origin{ + Origin: "44.33.22.11", + Port: 80, + SSL: false, + }, + Origin{ + Origin: "77.66.55.44", + Port: 80, + SSL: false, + Rules: []OriginRule{ + OriginRule{ + Name: "videos", + RequestURL: "^/videos/*.m3u", + }, + }, + }, + }, + Caching: []CacheRule{ + CacheRule{ + Name: "default", + TTL: 3600, + }, + }, + Restrictions: []Restriction{}, + FlavorID: "europe", + Status: "deployed", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1", + Rel: "self", + }, + gophercloud.Link{ + Href: "myothersite.com.poppycdn.net", + Rel: "access_url", + }, + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/europe", + Rel: "flavor", + }, + }, + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateCDNServiceSuccessfully(t) + + createOpts := CreateOpts{ + Name: "mywebsite.com", + Domains: []Domain{ + Domain{ + Domain: "www.mywebsite.com", + }, + Domain{ + Domain: "blog.mywebsite.com", + }, + }, + Origins: []Origin{ + Origin{ + Origin: "mywebsite.com", + Port: 80, + SSL: false, + }, + }, + Restrictions: []Restriction{ + Restriction{ + Name: "website only", + Rules: []RestrictionRule{ + RestrictionRule{ + Name: "mywebsite.com", + Referrer: "www.mywebsite.com", + }, + }, + }, + }, + Caching: []CacheRule{ + CacheRule{ + Name: "default", + TTL: 3600, + }, + }, + FlavorID: "cdn", + } + + expected := "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" + actual, err := Create(fake.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, expected, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetCDNServiceSuccessfully(t) + + expected := &Service{ + ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Name: "mywebsite.com", + Domains: []Domain{ + Domain{ + Domain: "www.mywebsite.com", + Protocol: "http", + }, + }, + Origins: []Origin{ + Origin{ + Origin: "mywebsite.com", + Port: 80, + SSL: false, + }, + }, + Caching: []CacheRule{ + CacheRule{ + Name: "default", + TTL: 3600, + }, + CacheRule{ + Name: "home", + TTL: 17200, + Rules: []TTLRule{ + TTLRule{ + Name: "index", + RequestURL: "/index.htm", + }, + }, + }, + CacheRule{ + Name: "images", + TTL: 12800, + Rules: []TTLRule{ + TTLRule{ + Name: "images", + RequestURL: "*.png", + }, + }, + }, + }, + Restrictions: []Restriction{ + Restriction{ + Name: "website only", + Rules: []RestrictionRule{ + RestrictionRule{ + Name: "mywebsite.com", + Referrer: "www.mywebsite.com", + }, + }, + }, + }, + FlavorID: "cdn", + Status: "deployed", + Errors: []Error{}, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://global.cdn.api.rackspacecloud.com/v1.0/110011/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Rel: "self", + }, + gophercloud.Link{ + Href: "blog.mywebsite.com.cdn1.raxcdn.com", + Rel: "access_url", + }, + gophercloud.Link{ + Href: "https://global.cdn.api.rackspacecloud.com/v1.0/110011/flavors/cdn", + Rel: "flavor", + }, + }, + } + + actual, err := Get(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, expected, actual) +} + +func TestSuccessfulUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateCDNServiceSuccessfully(t) + + expected := "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" + ops := UpdateOpts{ + // Append a single Domain + Append{Value: Domain{Domain: "appended.mocksite4.com"}}, + // Insert a single Domain + Insertion{ + Index: 4, + Value: Domain{Domain: "inserted.mocksite4.com"}, + }, + // Bulk addition + Append{ + Value: DomainList{ + Domain{Domain: "bulkadded1.mocksite4.com"}, + Domain{Domain: "bulkadded2.mocksite4.com"}, + }, + }, + // Replace a single Origin + Replacement{ + Index: 2, + Value: Origin{Origin: "44.33.22.11", Port: 80, SSL: false}, + }, + // Bulk replace Origins + Replacement{ + Index: 0, // Ignored + Value: OriginList{ + Origin{Origin: "44.33.22.11", Port: 80, SSL: false}, + Origin{Origin: "55.44.33.22", Port: 443, SSL: true}, + }, + }, + // Remove a single CacheRule + Removal{ + Index: 8, + Path: PathCaching, + }, + // Bulk removal + Removal{ + All: true, + Path: PathCaching, + }, + // Service name replacement + NameReplacement{ + NewName: "differentServiceName", + }, + } + + actual, err := Update(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", ops).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, expected, actual) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteCDNServiceSuccessfully(t) + + err := Delete(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/results.go new file mode 100644 index 0000000000..33406c482b --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/results.go @@ -0,0 +1,316 @@ +package services + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" + + "github.com/mitchellh/mapstructure" +) + +// Domain represents a domain used by users to access their website. +type Domain struct { + // Specifies the domain used to access the assets on their website, for which + // a CNAME is given to the CDN provider. + Domain string `mapstructure:"domain" json:"domain"` + // Specifies the protocol used to access the assets on this domain. Only "http" + // or "https" are currently allowed. The default is "http". + Protocol string `mapstructure:"protocol" json:"protocol,omitempty"` +} + +func (d Domain) toPatchValue() interface{} { + r := make(map[string]interface{}) + r["domain"] = d.Domain + if d.Protocol != "" { + r["protocol"] = d.Protocol + } + return r +} + +func (d Domain) appropriatePath() Path { + return PathDomains +} + +func (d Domain) renderRootOr(render func(p Path) string) string { + return render(d.appropriatePath()) +} + +// DomainList provides a useful way to perform bulk operations in a single Patch. +type DomainList []Domain + +func (list DomainList) toPatchValue() interface{} { + r := make([]interface{}, len(list)) + for i, domain := range list { + r[i] = domain.toPatchValue() + } + return r +} + +func (list DomainList) appropriatePath() Path { + return PathDomains +} + +func (list DomainList) renderRootOr(_ func(p Path) string) string { + return list.appropriatePath().renderRoot() +} + +// OriginRule represents a rule that defines when an origin should be accessed. +type OriginRule struct { + // Specifies the name of this rule. + Name string `mapstructure:"name" json:"name"` + // Specifies the request URL this rule should match for this origin to be used. Regex is supported. + RequestURL string `mapstructure:"request_url" json:"request_url"` +} + +// Origin specifies a list of origin domains or IP addresses where the original assets are stored. +type Origin struct { + // Specifies the URL or IP address to pull origin content from. + Origin string `mapstructure:"origin" json:"origin"` + // Specifies the port used to access the origin. The default is port 80. + Port int `mapstructure:"port" json:"port,omitempty"` + // Specifies whether or not to use HTTPS to access the origin. The default + // is false. + SSL bool `mapstructure:"ssl" json:"ssl"` + // Specifies a collection of rules that define the conditions when this origin + // should be accessed. If there is more than one origin, the rules parameter is required. + Rules []OriginRule `mapstructure:"rules" json:"rules,omitempty"` +} + +func (o Origin) toPatchValue() interface{} { + r := make(map[string]interface{}) + r["origin"] = o.Origin + r["port"] = o.Port + r["ssl"] = o.SSL + if len(o.Rules) > 0 { + r["rules"] = make([]map[string]interface{}, len(o.Rules)) + for index, rule := range o.Rules { + submap := r["rules"].([]map[string]interface{})[index] + submap["name"] = rule.Name + submap["request_url"] = rule.RequestURL + } + } + return r +} + +func (o Origin) appropriatePath() Path { + return PathOrigins +} + +func (o Origin) renderRootOr(render func(p Path) string) string { + return render(o.appropriatePath()) +} + +// OriginList provides a useful way to perform bulk operations in a single Patch. +type OriginList []Origin + +func (list OriginList) toPatchValue() interface{} { + r := make([]interface{}, len(list)) + for i, origin := range list { + r[i] = origin.toPatchValue() + } + return r +} + +func (list OriginList) appropriatePath() Path { + return PathOrigins +} + +func (list OriginList) renderRootOr(_ func(p Path) string) string { + return list.appropriatePath().renderRoot() +} + +// TTLRule specifies a rule that determines if a TTL should be applied to an asset. +type TTLRule struct { + // Specifies the name of this rule. + Name string `mapstructure:"name" json:"name"` + // Specifies the request URL this rule should match for this TTL to be used. Regex is supported. + RequestURL string `mapstructure:"request_url" json:"request_url"` +} + +// CacheRule specifies the TTL rules for the assets under this service. +type CacheRule struct { + // Specifies the name of this caching rule. Note: 'default' is a reserved name used for the default TTL setting. + Name string `mapstructure:"name" json:"name"` + // Specifies the TTL to apply. + TTL int `mapstructure:"ttl" json:"ttl"` + // Specifies a collection of rules that determine if this TTL should be applied to an asset. + Rules []TTLRule `mapstructure:"rules" json:"rules,omitempty"` +} + +func (c CacheRule) toPatchValue() interface{} { + r := make(map[string]interface{}) + r["name"] = c.Name + r["ttl"] = c.TTL + r["rules"] = make([]map[string]interface{}, len(c.Rules)) + for index, rule := range c.Rules { + submap := r["rules"].([]map[string]interface{})[index] + submap["name"] = rule.Name + submap["request_url"] = rule.RequestURL + } + return r +} + +func (c CacheRule) appropriatePath() Path { + return PathCaching +} + +func (c CacheRule) renderRootOr(render func(p Path) string) string { + return render(c.appropriatePath()) +} + +// CacheRuleList provides a useful way to perform bulk operations in a single Patch. +type CacheRuleList []CacheRule + +func (list CacheRuleList) toPatchValue() interface{} { + r := make([]interface{}, len(list)) + for i, rule := range list { + r[i] = rule.toPatchValue() + } + return r +} + +func (list CacheRuleList) appropriatePath() Path { + return PathCaching +} + +func (list CacheRuleList) renderRootOr(_ func(p Path) string) string { + return list.appropriatePath().renderRoot() +} + +// RestrictionRule specifies a rule that determines if this restriction should be applied to an asset. +type RestrictionRule struct { + // Specifies the name of this rule. + Name string `mapstructure:"name" json:"name"` + // Specifies the http host that requests must come from. + Referrer string `mapstructure:"referrer" json:"referrer,omitempty"` +} + +// Restriction specifies a restriction that defines who can access assets (content from the CDN cache). +type Restriction struct { + // Specifies the name of this restriction. + Name string `mapstructure:"name" json:"name"` + // Specifies a collection of rules that determine if this TTL should be applied to an asset. + Rules []RestrictionRule `mapstructure:"rules" json:"rules"` +} + +// Error specifies an error that occurred during the previous service action. +type Error struct { + // Specifies an error message detailing why there is an error. + Message string `mapstructure:"message"` +} + +// Service represents a CDN service resource. +type Service struct { + // Specifies the service ID that represents distributed content. The value is + // a UUID, such as 96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0, that is generated by the server. + ID string `mapstructure:"id"` + // Specifies the name of the service. + Name string `mapstructure:"name"` + // Specifies a list of domains used by users to access their website. + Domains []Domain `mapstructure:"domains"` + // Specifies a list of origin domains or IP addresses where the original assets are stored. + Origins []Origin `mapstructure:"origins"` + // Specifies the TTL rules for the assets under this service. Supports wildcards for fine grained control. + Caching []CacheRule `mapstructure:"caching"` + // Specifies the restrictions that define who can access assets (content from the CDN cache). + Restrictions []Restriction `mapstructure:"restrictions" json:"restrictions,omitempty"` + // Specifies the CDN provider flavor ID to use. For a list of flavors, see the operation to list the available flavors. + FlavorID string `mapstructure:"flavor_id"` + // Specifies the current status of the service. + Status string `mapstructure:"status"` + // Specifies the list of errors that occurred during the previous service action. + Errors []Error `mapstructure:"errors"` + // Specifies the self-navigating JSON document paths. + Links []gophercloud.Link `mapstructure:"links"` +} + +// ServicePage is the page returned by a pager when traversing over a +// collection of CDN services. +type ServicePage struct { + pagination.MarkerPageBase +} + +// IsEmpty returns true if a ListResult contains no services. +func (r ServicePage) IsEmpty() (bool, error) { + services, err := ExtractServices(r) + if err != nil { + return true, err + } + return len(services) == 0, nil +} + +// LastMarker returns the last service in a ListResult. +func (r ServicePage) LastMarker() (string, error) { + services, err := ExtractServices(r) + if err != nil { + return "", err + } + if len(services) == 0 { + return "", nil + } + return (services[len(services)-1]).ID, nil +} + +// ExtractServices is a function that takes a ListResult and returns the services' information. +func ExtractServices(page pagination.Page) ([]Service, error) { + var response struct { + Services []Service `mapstructure:"services"` + } + + err := mapstructure.Decode(page.(ServicePage).Body, &response) + return response.Services, err +} + +// CreateResult represents the result of a Create operation. +type CreateResult struct { + gophercloud.Result +} + +// Extract is a method that extracts the location of a newly created service. +func (r CreateResult) Extract() (string, error) { + if r.Err != nil { + return "", r.Err + } + if l, ok := r.Header["Location"]; ok && len(l) > 0 { + return l[0], nil + } + return "", nil +} + +// GetResult represents the result of a get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract is a function that extracts a service from a GetResult. +func (r GetResult) Extract() (*Service, error) { + if r.Err != nil { + return nil, r.Err + } + + var res Service + + err := mapstructure.Decode(r.Body, &res) + + return &res, err +} + +// UpdateResult represents the result of a Update operation. +type UpdateResult struct { + gophercloud.Result +} + +// Extract is a method that extracts the location of an updated service. +func (r UpdateResult) Extract() (string, error) { + if r.Err != nil { + return "", r.Err + } + if l, ok := r.Header["Location"]; ok && len(l) > 0 { + return l[0], nil + } + return "", nil +} + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/urls.go new file mode 100644 index 0000000000..d953d4c198 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/services/urls.go @@ -0,0 +1,23 @@ +package services + +import "github.com/rackspace/gophercloud" + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("services") +} + +func createURL(c *gophercloud.ServiceClient) string { + return listURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("services", id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return getURL(c, id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return getURL(c, id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/client.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/client.go index 99b3d466d3..6818d9d734 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/client.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/client.go @@ -68,7 +68,7 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp &utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"}, } - chosen, endpoint, err := utils.ChooseVersion(client.IdentityBase, client.IdentityEndpoint, versions) + chosen, endpoint, err := utils.ChooseVersion(client, versions) if err != nil { return err } @@ -107,6 +107,11 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc return err } + if options.AllowReauth { + client.ReauthFunc = func() error { + return AuthenticateV2(client, options) + } + } client.TokenID = token.ID client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V2EndpointURL(catalog, opts) @@ -133,6 +138,11 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc } client.TokenID = token.ID + if options.AllowReauth { + client.ReauthFunc = func() error { + return AuthenticateV3(client, options) + } + } client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V3EndpointURL(v3Client, opts) } @@ -203,3 +213,24 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } + +// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 +// CDN service. +func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + eo.ApplyDefaults("cdn") + url, err := client.EndpointLocator(eo) + if err != nil { + return nil, err + } + return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil +} + +// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service. +func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + eo.ApplyDefaults("orchestration") + url, err := client.EndpointLocator(eo) + if err != nil { + return nil, err + } + return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/common/extensions/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/common/extensions/requests.go index 3ca6e12f19..dfd81c99a0 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/common/extensions/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/common/extensions/requests.go @@ -1,7 +1,6 @@ package extensions import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -9,10 +8,9 @@ import ( // Get retrieves information for a specific extension using its alias. func Get(c *gophercloud.ServiceClient, alias string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", ExtensionURL(c, alias), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", ExtensionURL(c, alias), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go index 5a976d1146..b64014f36b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go @@ -6,8 +6,6 @@ import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" - - "github.com/racker/perigee" ) // SourceType represents the type of medium being used to create the volume. @@ -101,11 +99,10 @@ func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) s return res } - _, res.Err = perigee.Request("POST", createURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: reqBody, - Results: &res.Body, - OkCodes: []int{200, 202}, + _, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{ + JSONBody: reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 202}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go index 7d19741e10..294bae3578 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go @@ -3,8 +3,6 @@ package defsecrules import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -75,11 +73,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return result } - _, result.Err = perigee.Request("POST", rootURL(client), perigee.Options{ - Results: &result.Body, - ReqBody: &reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("POST", rootURL(client), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: &reqBody, + OkCodes: []int{200}, }) return result @@ -89,10 +86,9 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes func Get(client *gophercloud.ServiceClient, id string) GetResult { var result GetResult - _, result.Err = perigee.Request("GET", resourceURL(client, id), perigee.Options{ - Results: &result.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("GET", resourceURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result @@ -102,9 +98,8 @@ func Get(client *gophercloud.ServiceClient, id string) GetResult { func Delete(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { var result gophercloud.ErrResult - _, result.Err = perigee.Request("DELETE", resourceURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, result.Err = client.Request("DELETE", resourceURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return result diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/doc.go new file mode 100644 index 0000000000..f74f58ce83 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/doc.go @@ -0,0 +1,3 @@ +// Package floatingip provides the ability to manage floating ips through +// nova-network +package floatingip diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/fixtures.go new file mode 100644 index 0000000000..26f32995fd --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/fixtures.go @@ -0,0 +1,174 @@ +// +build fixtures + +package floatingip + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +// ListOutput is a sample response to a List call. +const ListOutput = ` +{ + "floating_ips": [ + { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + }, + { + "fixed_ip": "166.78.185.201", + "id": 2, + "instance_id": "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + "ip": "10.10.10.2", + "pool": "nova" + } + ] +} +` + +// GetOutput is a sample response to a Get call. +const GetOutput = ` +{ + "floating_ip": { + "fixed_ip": "166.78.185.201", + "id": 2, + "instance_id": "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + "ip": "10.10.10.2", + "pool": "nova" + } +} +` + +// CreateOutput is a sample response to a Post call +const CreateOutput = ` +{ + "floating_ip": { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + } +} +` + +// FirstFloatingIP is the first result in ListOutput. +var FirstFloatingIP = FloatingIP{ + ID: "1", + IP: "10.10.10.1", + Pool: "nova", +} + +// SecondFloatingIP is the first result in ListOutput. +var SecondFloatingIP = FloatingIP{ + FixedIP: "166.78.185.201", + ID: "2", + InstanceID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + IP: "10.10.10.2", + Pool: "nova", +} + +// ExpectedFloatingIPsSlice is the slice of results that should be parsed +// from ListOutput, in the expected order. +var ExpectedFloatingIPsSlice = []FloatingIP{FirstFloatingIP, SecondFloatingIP} + +// CreatedFloatingIP is the parsed result from CreateOutput. +var CreatedFloatingIP = FloatingIP{ + ID: "1", + IP: "10.10.10.1", + Pool: "nova", +} + +// HandleListSuccessfully configures the test server to respond to a List request. +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request +// for an existing floating ip +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/os-floating-ips/2", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateSuccessfully configures the test server to respond to a Create request +// for a new floating ip +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` +{ + "pool": "nova" +} +`) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, CreateOutput) + }) +} + +// HandleDeleteSuccessfully configures the test server to respond to a Delete request for a +// an existing floating ip +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/os-floating-ips/1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleAssociateSuccessfully configures the test server to respond to a Post request +// to associate an allocated floating IP +func HandleAssociateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` +{ + "addFloatingIp": { + "address": "10.10.10.2" + } +} +`) + + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleDisassociateSuccessfully configures the test server to respond to a Post request +// to disassociate an allocated floating IP +func HandleDisassociateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` +{ + "removeFloatingIp": { + "address": "10.10.10.2" + } +} +`) + + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests.go new file mode 100644 index 0000000000..d154038010 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests.go @@ -0,0 +1,105 @@ +package floatingip + +import ( + "errors" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// List returns a Pager that allows you to iterate over a collection of FloatingIPs. +func List(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { + return FloatingIPsPage{pagination.SinglePageBase(r)} + }) +} + +// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the +// CreateOpts struct in this package does. +type CreateOptsBuilder interface { + ToFloatingIPCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies a Floating IP allocation request +type CreateOpts struct { + // Pool is the pool of floating IPs to allocate one from + Pool string +} + +// ToFloatingIPCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) { + if opts.Pool == "" { + return nil, errors.New("Missing field required for floating IP creation: Pool") + } + + return map[string]interface{}{"pool": opts.Pool}, nil +} + +// Create requests the creation of a new floating IP +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToFloatingIPCreateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{ + JSONBody: reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Get returns data about a previously created FloatingIP. +func Get(client *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Delete requests the deletion of a previous allocated FloatingIP. +func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = client.Request("DELETE", deleteURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return res +} + +// association / disassociation + +// Associate pairs an allocated floating IP with an instance +func Associate(client *gophercloud.ServiceClient, serverId, fip string) AssociateResult { + var res AssociateResult + + addFloatingIp := make(map[string]interface{}) + addFloatingIp["address"] = fip + reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp} + + _, res.Err = client.Request("POST", associateURL(client, serverId), gophercloud.RequestOpts{ + JSONBody: reqBody, + OkCodes: []int{202}, + }) + return res +} + +// Disassociate decouples an allocated floating IP from an instance +func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) DisassociateResult { + var res DisassociateResult + + removeFloatingIp := make(map[string]interface{}) + removeFloatingIp["address"] = fip + reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp} + + _, res.Err = client.Request("POST", disassociateURL(client, serverId), gophercloud.RequestOpts{ + JSONBody: reqBody, + OkCodes: []int{202}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests_test.go new file mode 100644 index 0000000000..ed2460edc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/requests_test.go @@ -0,0 +1,80 @@ +package floatingip + +import ( + "testing" + + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSuccessfully(t) + + count := 0 + err := List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractFloatingIPs(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedFloatingIPsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t) + + actual, err := Create(client.ServiceClient(), CreateOpts{ + Pool: "nova", + }).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &CreatedFloatingIP, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t) + + actual, err := Get(client.ServiceClient(), "2").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &SecondFloatingIP, actual) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + + err := Delete(client.ServiceClient(), "1").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestAssociate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAssociateSuccessfully(t) + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + fip := "10.10.10.2" + + err := Associate(client.ServiceClient(), serverId, fip).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDisassociate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDisassociateSuccessfully(t) + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + fip := "10.10.10.2" + + err := Disassociate(client.ServiceClient(), serverId, fip).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/results.go new file mode 100644 index 0000000000..be77fa1792 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/results.go @@ -0,0 +1,99 @@ +package floatingip + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// A FloatingIP is an IP that can be associated with an instance +type FloatingIP struct { + // ID is a unique ID of the Floating IP + ID string `mapstructure:"id"` + + // FixedIP is the IP of the instance related to the Floating IP + FixedIP string `mapstructure:"fixed_ip,omitempty"` + + // InstanceID is the ID of the instance that is using the Floating IP + InstanceID string `mapstructure:"instance_id"` + + // IP is the actual Floating IP + IP string `mapstructure:"ip"` + + // Pool is the pool of floating IPs that this floating IP belongs to + Pool string `mapstructure:"pool"` +} + +// FloatingIPsPage stores a single, only page of FloatingIPs +// results from a List call. +type FloatingIPsPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines whether or not a FloatingIPsPage is empty. +func (page FloatingIPsPage) IsEmpty() (bool, error) { + va, err := ExtractFloatingIPs(page) + return len(va) == 0, err +} + +// ExtractFloatingIPs interprets a page of results as a slice of +// FloatingIPs. +func ExtractFloatingIPs(page pagination.Page) ([]FloatingIP, error) { + casted := page.(FloatingIPsPage).Body + var response struct { + FloatingIPs []FloatingIP `mapstructure:"floating_ips"` + } + + err := mapstructure.WeakDecode(casted, &response) + + return response.FloatingIPs, err +} + +type FloatingIPResult struct { + gophercloud.Result +} + +// Extract is a method that attempts to interpret any FloatingIP resource +// response as a FloatingIP struct. +func (r FloatingIPResult) Extract() (*FloatingIP, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + FloatingIP *FloatingIP `json:"floating_ip" mapstructure:"floating_ip"` + } + + err := mapstructure.WeakDecode(r.Body, &res) + return res.FloatingIP, err +} + +// CreateResult is the response from a Create operation. Call its Extract method to interpret it +// as a FloatingIP. +type CreateResult struct { + FloatingIPResult +} + +// GetResult is the response from a Get operation. Call its Extract method to interpret it +// as a FloatingIP. +type GetResult struct { + FloatingIPResult +} + +// DeleteResult is the response from a Delete operation. Call its Extract method to determine if +// the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AssociateResult is the response from a Delete operation. Call its Extract method to determine if +// the call succeeded or failed. +type AssociateResult struct { + gophercloud.ErrResult +} + +// DisassociateResult is the response from a Delete operation. Call its Extract method to determine if +// the call succeeded or failed. +type DisassociateResult struct { + gophercloud.ErrResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls.go new file mode 100644 index 0000000000..54198f8529 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls.go @@ -0,0 +1,37 @@ +package floatingip + +import "github.com/rackspace/gophercloud" + +const resourcePath = "os-floating-ips" + +func resourceURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func listURL(c *gophercloud.ServiceClient) string { + return resourceURL(c) +} + +func createURL(c *gophercloud.ServiceClient) string { + return resourceURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return getURL(c, id) +} + +func serverURL(c *gophercloud.ServiceClient, serverId string) string { + return c.ServiceURL("servers/" + serverId + "/action") +} + +func associateURL(c *gophercloud.ServiceClient, serverId string) string { + return serverURL(c, serverId) +} + +func disassociateURL(c *gophercloud.ServiceClient, serverId string) string { + return serverURL(c, serverId) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls_test.go new file mode 100644 index 0000000000..f73d6fb0f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip/urls_test.go @@ -0,0 +1,60 @@ +package floatingip + +import ( + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestListURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + + th.CheckEquals(t, c.Endpoint+"os-floating-ips", listURL(c)) +} + +func TestCreateURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + + th.CheckEquals(t, c.Endpoint+"os-floating-ips", createURL(c)) +} + +func TestGetURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + id := "1" + + th.CheckEquals(t, c.Endpoint+"os-floating-ips/"+id, getURL(c, id)) +} + +func TestDeleteURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + id := "1" + + th.CheckEquals(t, c.Endpoint+"os-floating-ips/"+id, deleteURL(c, id)) +} + +func TestAssociateURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + th.CheckEquals(t, c.Endpoint+"servers/"+serverId+"/action", associateURL(c, serverId)) +} + +func TestDisassociateURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + th.CheckEquals(t, c.Endpoint+"servers/"+serverId+"/action", disassociateURL(c, serverId)) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go index 7b372a355f..287e4127c5 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go @@ -3,7 +3,6 @@ package keypairs import ( "errors" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/pagination" @@ -82,11 +81,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return res } - _, res.Err = perigee.Request("POST", createURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{ + JSONBody: reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -94,10 +92,9 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes // Get returns public data about a previously uploaded KeyPair. func Get(client *gophercloud.ServiceClient, name string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(client, name), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = client.Request("GET", getURL(client, name), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -105,9 +102,8 @@ func Get(client *gophercloud.ServiceClient, name string) GetResult { // Delete requests the deletion of a previous stored KeyPair from the server. func Delete(client *gophercloud.ServiceClient, name string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(client, name), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = client.Request("DELETE", deleteURL(client, name), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/fixtures.go index ca76f686b8..1c6ba39492 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/fixtures.go @@ -38,7 +38,7 @@ func mockListGroupsResponse(t *testing.T) { } func mockListGroupsByServerResponse(t *testing.T, serverID string) { - url := fmt.Sprintf("%s/servers/%s%s", rootPath, serverID, rootPath) + url := fmt.Sprintf("/servers/%s%s", serverID, rootPath) th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go index 09503d715e..8f0a7a032f 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go @@ -3,8 +3,6 @@ package secgroups import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -80,11 +78,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return result } - _, result.Err = perigee.Request("POST", rootURL(client), perigee.Options{ - Results: &result.Body, - ReqBody: &reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("POST", rootURL(client), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: &reqBody, + OkCodes: []int{200}, }) return result @@ -126,11 +123,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder return result } - _, result.Err = perigee.Request("PUT", resourceURL(client, id), perigee.Options{ - Results: &result.Body, - ReqBody: &reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("PUT", resourceURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: &reqBody, + OkCodes: []int{200}, }) return result @@ -140,10 +136,9 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder func Get(client *gophercloud.ServiceClient, id string) GetResult { var result GetResult - _, result.Err = perigee.Request("GET", resourceURL(client, id), perigee.Options{ - Results: &result.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("GET", resourceURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result @@ -153,9 +148,8 @@ func Get(client *gophercloud.ServiceClient, id string) GetResult { func Delete(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { var result gophercloud.ErrResult - _, result.Err = perigee.Request("DELETE", resourceURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, result.Err = client.Request("DELETE", resourceURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return result @@ -222,7 +216,7 @@ func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { rule["cidr"] = opts.CIDR } if opts.FromGroupID != "" { - rule["from_group_id"] = opts.FromGroupID + rule["group_id"] = opts.FromGroupID } return map[string]interface{}{"security_group_rule": rule}, nil @@ -240,11 +234,10 @@ func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) C return result } - _, result.Err = perigee.Request("POST", rootRuleURL(client), perigee.Options{ - Results: &result.Body, - ReqBody: &reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("POST", rootRuleURL(client), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: &reqBody, + OkCodes: []int{200}, }) return result @@ -254,9 +247,8 @@ func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) C func DeleteRule(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { var result gophercloud.ErrResult - _, result.Err = perigee.Request("DELETE", resourceRuleURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, result.Err = client.Request("DELETE", resourceRuleURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return result @@ -273,11 +265,10 @@ func actionMap(prefix, groupName string) map[string]map[string]string { func AddServerToGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult { var result gophercloud.ErrResult - _, result.Err = perigee.Request("POST", serverActionURL(client, serverID), perigee.Options{ - Results: &result.Body, - ReqBody: actionMap("add", groupName), - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, result.Err = client.Request("POST", serverActionURL(client, serverID), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: actionMap("add", groupName), + OkCodes: []int{202}, }) return result @@ -287,11 +278,10 @@ func AddServerToGroup(client *gophercloud.ServiceClient, serverID, groupName str func RemoveServerFromGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult { var result gophercloud.ErrResult - _, result.Err = perigee.Request("POST", serverActionURL(client, serverID), perigee.Options{ - Results: &result.Body, - ReqBody: actionMap("remove", groupName), - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, result.Err = client.Request("POST", serverActionURL(client, serverID), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: actionMap("remove", groupName), + OkCodes: []int{202}, }) return result diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/urls.go index f4760b6f2e..dc53fbfac6 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/urls.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/urls.go @@ -16,7 +16,7 @@ func rootURL(c *gophercloud.ServiceClient) string { } func listByServerURL(c *gophercloud.ServiceClient, serverID string) string { - return c.ServiceURL(secgrouppath, "servers", serverID, secgrouppath) + return c.ServiceURL("servers", serverID, secgrouppath) } func rootRuleURL(c *gophercloud.ServiceClient) string { diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go index 99c91b054a..04b5909372 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go @@ -1,9 +1,6 @@ package startstop -import ( - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" -) +import "github.com/rackspace/gophercloud" func actionURL(client *gophercloud.ServiceClient, id string) string { return client.ServiceURL("servers", id, "action") @@ -15,10 +12,9 @@ func Start(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { reqBody := map[string]interface{}{"os-start": nil} - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: reqBody, + OkCodes: []int{202}, }) return res @@ -30,10 +26,9 @@ func Stop(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { reqBody := map[string]interface{}{"os-stop": nil} - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: reqBody, + OkCodes: []int{202}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go new file mode 100644 index 0000000000..22f68d80e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go @@ -0,0 +1,3 @@ +// Package volumeattach provides the ability to attach and detach volumes +// to instances +package volumeattach diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/fixtures.go new file mode 100644 index 0000000000..a7f03b322c --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/fixtures.go @@ -0,0 +1,138 @@ +// +build fixtures + +package volumeattach + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +// ListOutput is a sample response to a List call. +const ListOutput = ` +{ + "volumeAttachments": [ + { + "device": "/dev/vdd", + "id": "a26887c6-c47b-4654-abb5-dfadf7d3f803", + "serverId": "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + "volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803" + }, + { + "device": "/dev/vdc", + "id": "a26887c6-c47b-4654-abb5-dfadf7d3f804", + "serverId": "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + "volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804" + } + ] +} +` + +// GetOutput is a sample response to a Get call. +const GetOutput = ` +{ + "volumeAttachment": { + "device": "/dev/vdc", + "id": "a26887c6-c47b-4654-abb5-dfadf7d3f804", + "serverId": "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + "volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804" + } +} +` + +// CreateOutput is a sample response to a Create call. +const CreateOutput = ` +{ + "volumeAttachment": { + "device": "/dev/vdc", + "id": "a26887c6-c47b-4654-abb5-dfadf7d3f804", + "serverId": "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + "volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804" + } +} +` + +// FirstVolumeAttachment is the first result in ListOutput. +var FirstVolumeAttachment = VolumeAttachment{ + Device: "/dev/vdd", + ID: "a26887c6-c47b-4654-abb5-dfadf7d3f803", + ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f803", +} + +// SecondVolumeAttachment is the first result in ListOutput. +var SecondVolumeAttachment = VolumeAttachment{ + Device: "/dev/vdc", + ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", + ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", +} + +// ExpectedVolumeAttachmentSlide is the slice of results that should be parsed +// from ListOutput, in the expected order. +var ExpectedVolumeAttachmentSlice = []VolumeAttachment{FirstVolumeAttachment, SecondVolumeAttachment} + +// CreatedVolumeAttachment is the parsed result from CreatedOutput. +var CreatedVolumeAttachment = VolumeAttachment{ + Device: "/dev/vdc", + ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", + ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", +} + +// HandleListSuccessfully configures the test server to respond to a List request. +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request +// for an existing attachment +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments/a26887c6-c47b-4654-abb5-dfadf7d3f804", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateSuccessfully configures the test server to respond to a Create request +// for a new attachment +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` +{ + "volumeAttachment": { + "volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804", + "device": "/dev/vdc" + } +} +`) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, CreateOutput) + }) +} + +// HandleDeleteSuccessfully configures the test server to respond to a Delete request for a +// an existing attachment +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments/a26887c6-c47b-4654-abb5-dfadf7d3f804", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go new file mode 100644 index 0000000000..79709fdbe4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go @@ -0,0 +1,82 @@ +package volumeattach + +import ( + "errors" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// List returns a Pager that allows you to iterate over a collection of VolumeAttachments. +func List(client *gophercloud.ServiceClient, serverId string) pagination.Pager { + return pagination.NewPager(client, listURL(client, serverId), func(r pagination.PageResult) pagination.Page { + return VolumeAttachmentsPage{pagination.SinglePageBase(r)} + }) +} + +// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the +// CreateOpts struct in this package does. +type CreateOptsBuilder interface { + ToVolumeAttachmentCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies volume attachment creation or import parameters. +type CreateOpts struct { + // Device is the device that the volume will attach to the instance as. Omit for "auto" + Device string + + // VolumeID is the ID of the volume to attach to the instance + VolumeID string +} + +// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) { + if opts.VolumeID == "" { + return nil, errors.New("Missing field required for volume attachment creation: VolumeID") + } + + volumeAttachment := make(map[string]interface{}) + volumeAttachment["volumeId"] = opts.VolumeID + if opts.Device != "" { + volumeAttachment["device"] = opts.Device + } + + return map[string]interface{}{"volumeAttachment": volumeAttachment}, nil +} + +// Create requests the creation of a new volume attachment on the server +func Create(client *gophercloud.ServiceClient, serverId string, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToVolumeAttachmentCreateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = client.Request("POST", createURL(client, serverId), gophercloud.RequestOpts{ + JSONBody: reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Get returns public data about a previously created VolumeAttachment. +func Get(client *gophercloud.ServiceClient, serverId, aId string) GetResult { + var res GetResult + _, res.Err = client.Request("GET", getURL(client, serverId, aId), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Delete requests the deletion of a previous stored VolumeAttachment from the server. +func Delete(client *gophercloud.ServiceClient, serverId, aId string) DeleteResult { + var res DeleteResult + _, res.Err = client.Request("DELETE", deleteURL(client, serverId, aId), gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests_test.go new file mode 100644 index 0000000000..e17f7e0063 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/requests_test.go @@ -0,0 +1,65 @@ +package volumeattach + +import ( + "testing" + + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSuccessfully(t) + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + count := 0 + err := List(client.ServiceClient(), serverId).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractVolumeAttachments(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedVolumeAttachmentSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t) + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + actual, err := Create(client.ServiceClient(), serverId, CreateOpts{ + Device: "/dev/vdc", + VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", + }).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &CreatedVolumeAttachment, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t) + aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804" + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + actual, err := Get(client.ServiceClient(), serverId, aId).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &SecondVolumeAttachment, actual) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804" + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + err := Delete(client.ServiceClient(), serverId, aId).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go new file mode 100644 index 0000000000..26be39e4f7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go @@ -0,0 +1,84 @@ +package volumeattach + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// VolumeAttach controls the attachment of a volume to an instance. +type VolumeAttachment struct { + // ID is a unique id of the attachment + ID string `mapstructure:"id"` + + // Device is what device the volume is attached as + Device string `mapstructure:"device"` + + // VolumeID is the ID of the attached volume + VolumeID string `mapstructure:"volumeId"` + + // ServerID is the ID of the instance that has the volume attached + ServerID string `mapstructure:"serverId"` +} + +// VolumeAttachmentsPage stores a single, only page of VolumeAttachments +// results from a List call. +type VolumeAttachmentsPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines whether or not a VolumeAttachmentsPage is empty. +func (page VolumeAttachmentsPage) IsEmpty() (bool, error) { + va, err := ExtractVolumeAttachments(page) + return len(va) == 0, err +} + +// ExtractVolumeAttachments interprets a page of results as a slice of +// VolumeAttachments. +func ExtractVolumeAttachments(page pagination.Page) ([]VolumeAttachment, error) { + casted := page.(VolumeAttachmentsPage).Body + var response struct { + VolumeAttachments []VolumeAttachment `mapstructure:"volumeAttachments"` + } + + err := mapstructure.WeakDecode(casted, &response) + + return response.VolumeAttachments, err +} + +type VolumeAttachmentResult struct { + gophercloud.Result +} + +// Extract is a method that attempts to interpret any VolumeAttachment resource +// response as a VolumeAttachment struct. +func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + VolumeAttachment *VolumeAttachment `json:"volumeAttachment" mapstructure:"volumeAttachment"` + } + + err := mapstructure.Decode(r.Body, &res) + return res.VolumeAttachment, err +} + +// CreateResult is the response from a Create operation. Call its Extract method to interpret it +// as a VolumeAttachment. +type CreateResult struct { + VolumeAttachmentResult +} + +// GetResult is the response from a Get operation. Call its Extract method to interpret it +// as a VolumeAttachment. +type GetResult struct { + VolumeAttachmentResult +} + +// DeleteResult is the response from a Delete operation. Call its Extract method to determine if +// the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go new file mode 100644 index 0000000000..9d9d1786db --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go @@ -0,0 +1,25 @@ +package volumeattach + +import "github.com/rackspace/gophercloud" + +const resourcePath = "os-volume_attachments" + +func resourceURL(c *gophercloud.ServiceClient, serverId string) string { + return c.ServiceURL("servers", serverId, resourcePath) +} + +func listURL(c *gophercloud.ServiceClient, serverId string) string { + return resourceURL(c, serverId) +} + +func createURL(c *gophercloud.ServiceClient, serverId string) string { + return resourceURL(c, serverId) +} + +func getURL(c *gophercloud.ServiceClient, serverId, aId string) string { + return c.ServiceURL("servers", serverId, resourcePath, aId) +} + +func deleteURL(c *gophercloud.ServiceClient, serverId, aId string) string { + return getURL(c, serverId, aId) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls_test.go new file mode 100644 index 0000000000..8ee0e42d45 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/urls_test.go @@ -0,0 +1,46 @@ +package volumeattach + +import ( + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestListURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + th.CheckEquals(t, c.Endpoint+"servers/"+serverId+"/os-volume_attachments", listURL(c, serverId)) +} + +func TestCreateURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + th.CheckEquals(t, c.Endpoint+"servers/"+serverId+"/os-volume_attachments", createURL(c, serverId)) +} + +func TestGetURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804" + + th.CheckEquals(t, c.Endpoint+"servers/"+serverId+"/os-volume_attachments/"+aId, getURL(c, serverId, aId)) +} + +func TestDeleteURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + c := client.ServiceClient() + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804" + + th.CheckEquals(t, c.Endpoint+"servers/"+serverId+"/os-volume_attachments/"+aId, deleteURL(c, serverId, aId)) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/requests.go index 065a2ec472..1d33f58ad5 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/requests.go @@ -1,7 +1,6 @@ package flavors import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -64,9 +63,8 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat // Use ExtractFlavor to convert its result into a Flavor. func Get(client *gophercloud.ServiceClient, id string) GetResult { var gr GetResult - gr.Err = perigee.Get(getURL(client, id), perigee.Options{ - Results: &gr.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, gr.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &gr.Body, }) return gr } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/images/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/images/requests.go index bc61ddb9df..9e9c3b1d4e 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/images/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/images/requests.go @@ -3,8 +3,6 @@ package images import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // ListOptsBuilder allows extensions to add additional parameters to the @@ -22,7 +20,7 @@ type ListOpts struct { // UUID of the Image at which to set a marker. Marker string `q:"marker"` // The name of the Image. - Name string `q:"name:"` + Name string `q:"name"` // The name of the Server (in URL format). Server string `q:"server"` // The current status of the Image. @@ -62,10 +60,9 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat // Use ExtractImage() to interpret the result as an openstack Image. func Get(client *gophercloud.ServiceClient, id string) GetResult { var result GetResult - _, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &result.Body, - OkCodes: []int{200}, + _, result.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/fixtures.go index 01646058e6..6125d530b3 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/fixtures.go @@ -285,6 +285,11 @@ var ( Created: "2014-09-25T13:10:02Z", TenantID: "fcad67a6189847c4aecfa3c81a05783b", Metadata: map[string]interface{}{}, + SecurityGroups: []map[string]interface{}{ + map[string]interface{}{ + "name": "default", + }, + }, } // ServerDerp is a Server struct that should correspond to the second server in ServerListBody. @@ -336,6 +341,11 @@ var ( Created: "2014-09-25T13:04:41Z", TenantID: "fcad67a6189847c4aecfa3c81a05783b", Metadata: map[string]interface{}{}, + SecurityGroups: []map[string]interface{}{ + map[string]interface{}{ + "name": "default", + }, + }, } ) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests.go index 79d7998937..b7c1611d3c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -136,6 +135,12 @@ type CreateOpts struct { // AdminPass [optional] sets the root user password. If not set, a randomly-generated // password will be created and returned in the response. AdminPass string + + // AccessIPv4 [optional] specifies an IPv4 address for the instance. + AccessIPv4 string + + // AccessIPv6 [optional] specifies an IPv6 address for the instance. + AccessIPv6 string } // ToServerCreateMap assembles a request body based on the contents of a CreateOpts. @@ -166,6 +171,12 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { if opts.AdminPass != "" { server["adminPass"] = opts.AdminPass } + if opts.AccessIPv4 != "" { + server["accessIPv4"] = opts.AccessIPv4 + } + if opts.AccessIPv6 != "" { + server["accessIPv6"] = opts.AccessIPv6 + } if len(opts.SecurityGroups) > 0 { securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups)) @@ -205,11 +216,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return res } - _, res.Err = perigee.Request("POST", listURL(client), perigee.Options{ - Results: &res.Body, - ReqBody: reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = client.Request("POST", listURL(client), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + JSONBody: reqBody, + OkCodes: []int{202}, }) return res } @@ -217,9 +227,8 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes // Delete requests that a server previously provisioned be removed from your account. func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = client.Request("DELETE", deleteURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } @@ -227,9 +236,9 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { // Get requests details on a single server, by ID. func Get(client *gophercloud.ServiceClient, id string) GetResult { var result GetResult - _, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{ - Results: &result.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, result.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200, 203}, }) return result } @@ -271,10 +280,9 @@ func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} { // Update requests that various attributes of the indicated server be changed. func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { var result UpdateResult - _, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{ - Results: &result.Body, - ReqBody: opts.ToServerUpdateMap(), - MoreHeaders: client.AuthenticatedHeaders(), + _, result.Err = client.Request("PUT", updateURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: opts.ToServerUpdateMap(), }) return result } @@ -291,10 +299,9 @@ func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword stri var res ActionResult - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - ReqBody: req, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: req, + OkCodes: []int{202}, }) return res @@ -360,14 +367,13 @@ func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) Acti return res } - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - ReqBody: struct { + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: struct { C map[string]string `json:"reboot"` }{ map[string]string{"type": string(how)}, }, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + OkCodes: []int{202}, }) return res @@ -462,11 +468,10 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild return result } - _, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - ReqBody: &reqBody, - Results: &result.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, result.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &result.Body, + OkCodes: []int{202}, }) return result @@ -509,10 +514,9 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder return res } - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - ReqBody: reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: reqBody, + OkCodes: []int{202}, }) return res @@ -523,10 +527,9 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder func ConfirmResize(client *gophercloud.ServiceClient, id string) ActionResult { var res ActionResult - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - ReqBody: map[string]interface{}{"confirmResize": nil}, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: map[string]interface{}{"confirmResize": nil}, + OkCodes: []int{204}, }) return res @@ -537,10 +540,9 @@ func ConfirmResize(client *gophercloud.ServiceClient, id string) ActionResult { func RevertResize(client *gophercloud.ServiceClient, id string) ActionResult { var res ActionResult - _, res.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - ReqBody: map[string]interface{}{"revertResize": nil}, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONBody: map[string]interface{}{"revertResize": nil}, + OkCodes: []int{202}, }) return res @@ -584,11 +586,10 @@ func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder return result } - _, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{ - Results: &result.Body, - ReqBody: &reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: &reqBody, + OkCodes: []int{200}, }) return result @@ -624,10 +625,9 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad res.Err = err return res } - _, res.Err = perigee.Request("PUT", metadataURL(client, id), perigee.Options{ - ReqBody: metadata, - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, res.Err = client.Request("PUT", metadataURL(client, id), gophercloud.RequestOpts{ + JSONBody: metadata, + JSONResponse: &res.Body, }) return res } @@ -635,9 +635,8 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad // Metadata requests all the metadata for the given server ID. func Metadata(client *gophercloud.ServiceClient, id string) GetMetadataResult { var res GetMetadataResult - _, res.Err = perigee.Request("GET", metadataURL(client, id), perigee.Options{ - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, res.Err = client.Request("GET", metadataURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, }) return res } @@ -658,10 +657,9 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet res.Err = err return res } - _, res.Err = perigee.Request("POST", metadataURL(client, id), perigee.Options{ - ReqBody: metadata, - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, res.Err = client.Request("POST", metadataURL(client, id), gophercloud.RequestOpts{ + JSONBody: metadata, + JSONResponse: &res.Body, }) return res } @@ -697,10 +695,9 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu return res } - _, res.Err = perigee.Request("PUT", metadatumURL(client, id, key), perigee.Options{ - ReqBody: metadatum, - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, res.Err = client.Request("PUT", metadatumURL(client, id, key), gophercloud.RequestOpts{ + JSONBody: metadatum, + JSONResponse: &res.Body, }) return res } @@ -708,9 +705,8 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu // Metadatum requests the key-value pair with the given key for the given server ID. func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumResult { var res GetMetadatumResult - _, res.Err = perigee.Request("GET", metadatumURL(client, id, key), perigee.Options{ - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, res.Err = client.Request("GET", metadatumURL(client, id, key), gophercloud.RequestOpts{ + JSONResponse: &res.Body, }) return res } @@ -718,9 +714,8 @@ func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumRe // DeleteMetadatum will delete the key-value pair with the given key for the given server ID. func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) DeleteMetadatumResult { var res DeleteMetadatumResult - _, res.Err = perigee.Request("DELETE", metadatumURL(client, id, key), perigee.Options{ - Results: &res.Body, - MoreHeaders: client.AuthenticatedHeaders(), + _, res.Err = client.Request("DELETE", metadatumURL(client, id, key), gophercloud.RequestOpts{ + JSONResponse: &res.Body, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/results.go index 3a145f8007..1b22f219b2 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/results.go @@ -1,6 +1,8 @@ package servers import ( + "reflect" + "github.com/mitchellh/mapstructure" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" @@ -20,8 +22,21 @@ func (r serverResult) Extract() (*Server, error) { Server Server `mapstructure:"server"` } - err := mapstructure.Decode(r.Body, &response) - return &response.Server, err + config := &mapstructure.DecoderConfig{ + DecodeHook: toMapFromString, + Result: &response, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + + err = decoder.Decode(r.Body) + if err != nil { + return nil, err + } + + return &response.Server, nil } // CreateResult temporarily contains the response from a Create call. @@ -124,6 +139,9 @@ type Server struct { // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place. // Note that this is the ONLY time this field will be valid. AdminPass string `json:"adminPass" mapstructure:"adminPass"` + + // SecurityGroups includes the security groups that this instance has applied to it + SecurityGroups []map[string]interface{} `json:"security_groups" mapstructure:"security_groups"` } // ServerPage abstracts the raw results of making a List() request against the API. @@ -164,7 +182,19 @@ func ExtractServers(page pagination.Page) ([]Server, error) { var response struct { Servers []Server `mapstructure:"servers"` } - err := mapstructure.Decode(casted, &response) + + config := &mapstructure.DecoderConfig{ + DecodeHook: toMapFromString, + Result: &response, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + + err = decoder.Decode(casted) + + //err := mapstructure.Decode(casted, &response) return response.Servers, err } @@ -235,3 +265,10 @@ func (r MetadatumResult) Extract() (map[string]string, error) { err := mapstructure.Decode(r.Body, &response) return response.Metadatum, err } + +func toMapFromString(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) { + if (from == reflect.String) && (to == reflect.Map) { + return map[string]interface{}{}, nil + } + return data, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles/requests.go index 152031ac35..bbdf76a46c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles/requests.go @@ -1,7 +1,6 @@ package roles import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -21,9 +20,8 @@ func List(client *gophercloud.ServiceClient) pagination.Pager { func AddUserRole(client *gophercloud.ServiceClient, tenantID, userID, roleID string) UserRoleResult { var result UserRoleResult - _, result.Err = perigee.Request("PUT", userRoleURL(client, tenantID, userID, roleID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200, 201}, + _, result.Err = client.Request("PUT", userRoleURL(client, tenantID, userID, roleID), gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, }) return result @@ -35,9 +33,8 @@ func AddUserRole(client *gophercloud.ServiceClient, tenantID, userID, roleID str func DeleteUserRole(client *gophercloud.ServiceClient, tenantID, userID, roleID string) UserRoleResult { var result UserRoleResult - _, result.Err = perigee.Request("DELETE", userRoleURL(client, tenantID, userID, roleID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, result.Err = client.Request("DELETE", userRoleURL(client, tenantID, userID, roleID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return result diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/requests.go index 87c923a2b4..db1ac8284f 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/requests.go @@ -1,9 +1,6 @@ package tokens -import ( - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" -) +import "github.com/rackspace/gophercloud" // AuthOptionsBuilder describes any argument that may be passed to the Create call. type AuthOptionsBuilder interface { @@ -78,10 +75,10 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) CreateRe } var result CreateResult - _, result.Err = perigee.Request("POST", CreateURL(client), perigee.Options{ - ReqBody: &request, - Results: &result.Body, - OkCodes: []int{200, 203}, + _, result.Err = client.Request("POST", CreateURL(client), gophercloud.RequestOpts{ + JSONBody: &request, + JSONResponse: &result.Body, + OkCodes: []int{200, 203}, }) return result } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/users/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/users/requests.go index 4ce395f2dd..2afe62a613 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/users/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v2/users/requests.go @@ -3,7 +3,6 @@ package users import ( "errors" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -91,11 +90,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes return res } - _, res.Err = perigee.Request("POST", rootURL(client), perigee.Options{ - Results: &res.Body, - ReqBody: reqBody, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200, 201}, + _, res.Err = client.Request("POST", rootURL(client), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + JSONBody: reqBody, + OkCodes: []int{200, 201}, }) return res @@ -105,10 +103,9 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes func Get(client *gophercloud.ServiceClient, id string) GetResult { var result GetResult - _, result.Err = perigee.Request("GET", ResourceURL(client, id), perigee.Options{ - Results: &result.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("GET", ResourceURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result @@ -149,11 +146,10 @@ func (opts UpdateOpts) ToUserUpdateMap() map[string]interface{} { func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { var result UpdateResult - _, result.Err = perigee.Request("PUT", ResourceURL(client, id), perigee.Options{ - Results: &result.Body, - ReqBody: opts.ToUserUpdateMap(), - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("PUT", ResourceURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: opts.ToUserUpdateMap(), + OkCodes: []int{200}, }) return result @@ -163,9 +159,8 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder func Delete(client *gophercloud.ServiceClient, id string) DeleteResult { var result DeleteResult - _, result.Err = perigee.Request("DELETE", ResourceURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, result.Err = client.Request("DELETE", ResourceURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return result diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/endpoints/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/endpoints/requests.go index 7bdb7cef2e..3e09b2aef0 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/endpoints/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/endpoints/requests.go @@ -1,7 +1,6 @@ package endpoints import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -57,11 +56,10 @@ func Create(client *gophercloud.ServiceClient, opts EndpointOpts) CreateResult { reqBody.Endpoint.Region = gophercloud.MaybeString(opts.Region) var result CreateResult - _, result.Err = perigee.Request("POST", listURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &result.Body, - OkCodes: []int{201}, + _, result.Err = client.Request("POST", listURL(client), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &result.Body, + OkCodes: []int{201}, }) return result } @@ -113,11 +111,10 @@ func Update(client *gophercloud.ServiceClient, endpointID string, opts EndpointO reqBody.Endpoint.ServiceID = gophercloud.MaybeString(opts.ServiceID) var result UpdateResult - _, result.Err = perigee.Request("PATCH", endpointURL(client, endpointID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &result.Body, - OkCodes: []int{200}, + _, result.Err = client.Request("PATCH", endpointURL(client, endpointID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result } @@ -125,9 +122,8 @@ func Update(client *gophercloud.ServiceClient, endpointID string, opts EndpointO // Delete removes an endpoint from the service catalog. func Delete(client *gophercloud.ServiceClient, endpointID string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", endpointURL(client, endpointID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = client.Request("DELETE", endpointURL(client, endpointID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/services/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/services/requests.go index 1d9aaa873a..c6820c700b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/services/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/services/requests.go @@ -1,7 +1,6 @@ package services import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -19,11 +18,10 @@ func Create(client *gophercloud.ServiceClient, serviceType string) CreateResult req := request{Type: serviceType} var result CreateResult - _, result.Err = perigee.Request("POST", listURL(client), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &req, - Results: &result.Body, - OkCodes: []int{201}, + _, result.Err = client.Request("POST", listURL(client), gophercloud.RequestOpts{ + JSONBody: &req, + JSONResponse: &result.Body, + OkCodes: []int{201}, }) return result } @@ -53,10 +51,9 @@ func List(client *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { // Get returns additional information about a service, given its ID. func Get(client *gophercloud.ServiceClient, serviceID string) GetResult { var result GetResult - _, result.Err = perigee.Request("GET", serviceURL(client, serviceID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &result.Body, - OkCodes: []int{200}, + _, result.Err = client.Request("GET", serviceURL(client, serviceID), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result } @@ -70,11 +67,10 @@ func Update(client *gophercloud.ServiceClient, serviceID string, serviceType str req := request{Type: serviceType} var result UpdateResult - _, result.Err = perigee.Request("PATCH", serviceURL(client, serviceID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &req, - Results: &result.Body, - OkCodes: []int{200}, + _, result.Err = client.Request("PATCH", serviceURL(client, serviceID), gophercloud.RequestOpts{ + JSONBody: &req, + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result } @@ -83,9 +79,8 @@ func Update(client *gophercloud.ServiceClient, serviceID string, serviceType str // It either deletes all associated endpoints, or fails until all endpoints are deleted. func Delete(client *gophercloud.ServiceClient, serviceID string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", serviceURL(client, serviceID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = client.Request("DELETE", serviceURL(client, serviceID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/requests.go index 5ca1031c41..bbd3c56231 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/requests.go @@ -1,7 +1,8 @@ package tokens import ( - "github.com/racker/perigee" + "net/http" + "github.com/rackspace/gophercloud" ) @@ -233,38 +234,38 @@ func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope } var result CreateResult - var response *perigee.Response - response, result.Err = perigee.Request("POST", tokenURL(c), perigee.Options{ - ReqBody: &req, - Results: &result.Body, - OkCodes: []int{201}, + var response *http.Response + response, result.Err = c.Request("POST", tokenURL(c), gophercloud.RequestOpts{ + JSONBody: &req, + JSONResponse: &result.Body, + OkCodes: []int{201}, }) if result.Err != nil { return result } - result.Header = response.HttpResponse.Header + result.Header = response.Header return result } // Get validates and retrieves information about another token. func Get(c *gophercloud.ServiceClient, token string) GetResult { var result GetResult - var response *perigee.Response - response, result.Err = perigee.Request("GET", tokenURL(c), perigee.Options{ - MoreHeaders: subjectTokenHeaders(c, token), - Results: &result.Body, - OkCodes: []int{200, 203}, + var response *http.Response + response, result.Err = c.Request("GET", tokenURL(c), gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(c, token), + JSONResponse: &result.Body, + OkCodes: []int{200, 203}, }) if result.Err != nil { return result } - result.Header = response.HttpResponse.Header + result.Header = response.Header return result } // Validate determines if a specified token is valid or not. func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { - response, err := perigee.Request("HEAD", tokenURL(c), perigee.Options{ + response, err := c.Request("HEAD", tokenURL(c), gophercloud.RequestOpts{ MoreHeaders: subjectTokenHeaders(c, token), OkCodes: []int{204, 404}, }) @@ -278,7 +279,7 @@ func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { // Revoke immediately makes specified token invalid. func Revoke(c *gophercloud.ServiceClient, token string) RevokeResult { var res RevokeResult - _, res.Err = perigee.Request("DELETE", tokenURL(c), perigee.Options{ + _, res.Err = c.Request("DELETE", tokenURL(c), gophercloud.RequestOpts{ MoreHeaders: subjectTokenHeaders(c, token), OkCodes: []int{204}, }) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/doc.go new file mode 100644 index 0000000000..3ec450a7b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/doc.go @@ -0,0 +1,3 @@ +// Package fwaas provides information and interaction with the Firewall +// as a Service extension for the OpenStack Networking service. +package fwaas diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/errors.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/errors.go new file mode 100644 index 0000000000..dd92bb20db --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/errors.go @@ -0,0 +1,11 @@ +package firewalls + +import "fmt" + +func err(str string) error { + return fmt.Errorf("%s", str) +} + +var ( + errPolicyRequired = err("A policy ID is required") +) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go new file mode 100644 index 0000000000..69f3dcad13 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go @@ -0,0 +1,227 @@ +package firewalls + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// AdminState gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Up` and `Down` enums. +type AdminState *bool + +// Shared gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Yes` and `No` enums. +type Shared *bool + +// Convenience vars for AdminStateUp and Shared values. +var ( + iTrue = true + iFalse = false + Up AdminState = &iTrue + Down AdminState = &iFalse + Yes Shared = &iTrue + No Shared = &iFalse +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToFirewallListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the firewall attributes you want to see returned. SortKey allows you to sort +// by a particular firewall attribute. SortDir sets the direction, and is either +// `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + TenantID string `q:"tenant_id"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp bool `q:"admin_state_up"` + Shared bool `q:"shared"` + PolicyID string `q:"firewall_policy_id"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToFirewallListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToFirewallListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// firewalls. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +// +// Default policy settings return only those firewalls that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + + if opts != nil { + query, err := opts.ToFirewallListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return FirewallPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToFirewallCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new firewall. +type CreateOpts struct { + // Only required if the caller has an admin role and wants to create a firewall + // for another tenant. + TenantID string + Name string + Description string + AdminStateUp *bool + Shared *bool + PolicyID string +} + +// ToFirewallCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToFirewallCreateMap() (map[string]interface{}, error) { + if opts.PolicyID == "" { + return nil, errPolicyRequired + } + + f := make(map[string]interface{}) + + if opts.TenantID != "" { + f["tenant_id"] = opts.TenantID + } + if opts.Name != "" { + f["name"] = opts.Name + } + if opts.Description != "" { + f["description"] = opts.Description + } + if opts.Shared != nil { + f["shared"] = *opts.Shared + } + if opts.AdminStateUp != nil { + f["admin_state_up"] = *opts.AdminStateUp + } + if opts.PolicyID != "" { + f["firewall_policy_id"] = opts.PolicyID + } + + return map[string]interface{}{"firewall": f}, nil +} + +// Create accepts a CreateOpts struct and uses the values to create a new firewall +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToFirewallCreateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, + }) + return res +} + +// Get retrieves a particular firewall based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToFirewallUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a firewall. +type UpdateOpts struct { + // Name of the firewall. + Name string + Description string + AdminStateUp *bool + Shared *bool + PolicyID string +} + +// ToFirewallUpdateMap casts a CreateOpts struct to a map. +func (opts UpdateOpts) ToFirewallUpdateMap() (map[string]interface{}, error) { + f := make(map[string]interface{}) + + if opts.Name != "" { + f["name"] = opts.Name + } + if opts.Description != "" { + f["description"] = opts.Description + } + if opts.Shared != nil { + f["shared"] = *opts.Shared + } + if opts.AdminStateUp != nil { + f["admin_state_up"] = *opts.AdminStateUp + } + if opts.PolicyID != "" { + f["firewall_policy_id"] = opts.PolicyID + } + + return map[string]interface{}{"firewall": f}, nil +} + +// Update allows firewalls to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToFirewallUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Delete will permanently delete a particular firewall based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests_test.go new file mode 100644 index 0000000000..f24e2835ea --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests_test.go @@ -0,0 +1,246 @@ +package firewalls + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/rackspace/gophercloud/openstack/networking/v2/common" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestURLs(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.AssertEquals(t, th.Endpoint()+"v2.0/fw/firewalls", rootURL(fake.ServiceClient())) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewalls", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewalls":[ + { + "status": "ACTIVE", + "name": "fw1", + "admin_state_up": false, + "tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b", + "firewall_policy_id": "34be8c83-4d42-4dca-a74e-b77fffb8e28a", + "id": "fb5b5315-64f6-4ea3-8e58-981cc37c6f61", + "description": "OpenStack firewall 1" + }, + { + "status": "PENDING_UPDATE", + "name": "fw2", + "admin_state_up": true, + "tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b", + "firewall_policy_id": "34be8c83-4d42-4dca-a74e-b77fffb8e299", + "id": "fb5b5315-64f6-4ea3-8e58-981cc37c6f99", + "description": "OpenStack firewall 2" + } + ] +} + `) + }) + + count := 0 + + List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractFirewalls(page) + if err != nil { + t.Errorf("Failed to extract members: %v", err) + return false, err + } + + expected := []Firewall{ + Firewall{ + Status: "ACTIVE", + Name: "fw1", + AdminStateUp: false, + TenantID: "b4eedccc6fb74fa8a7ad6b08382b852b", + PolicyID: "34be8c83-4d42-4dca-a74e-b77fffb8e28a", + ID: "fb5b5315-64f6-4ea3-8e58-981cc37c6f61", + Description: "OpenStack firewall 1", + }, + Firewall{ + Status: "PENDING_UPDATE", + Name: "fw2", + AdminStateUp: true, + TenantID: "b4eedccc6fb74fa8a7ad6b08382b852b", + PolicyID: "34be8c83-4d42-4dca-a74e-b77fffb8e299", + ID: "fb5b5315-64f6-4ea3-8e58-981cc37c6f99", + Description: "OpenStack firewall 2", + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewalls", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "firewall":{ + "name": "fw", + "description": "OpenStack firewall", + "admin_state_up": true, + "firewall_policy_id": "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", + "tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` +{ + "firewall":{ + "status": "PENDING_CREATE", + "name": "fw", + "description": "OpenStack firewall", + "admin_state_up": true, + "tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b", + "firewall_policy_id": "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c" + } +} + `) + }) + + options := CreateOpts{ + TenantID: "b4eedccc6fb74fa8a7ad6b08382b852b", + Name: "fw", + Description: "OpenStack firewall", + AdminStateUp: Up, + PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", + } + _, err := Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewalls/fb5b5315-64f6-4ea3-8e58-981cc37c6f61", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall": { + "status": "ACTIVE", + "name": "fw", + "admin_state_up": true, + "tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b", + "firewall_policy_id": "34be8c83-4d42-4dca-a74e-b77fffb8e28a", + "id": "fb5b5315-64f6-4ea3-8e58-981cc37c6f61", + "description": "OpenStack firewall" + } +} + `) + }) + + fw, err := Get(fake.ServiceClient(), "fb5b5315-64f6-4ea3-8e58-981cc37c6f61").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "ACTIVE", fw.Status) + th.AssertEquals(t, "fw", fw.Name) + th.AssertEquals(t, "OpenStack firewall", fw.Description) + th.AssertEquals(t, true, fw.AdminStateUp) + th.AssertEquals(t, "34be8c83-4d42-4dca-a74e-b77fffb8e28a", fw.PolicyID) + th.AssertEquals(t, "fb5b5315-64f6-4ea3-8e58-981cc37c6f61", fw.ID) + th.AssertEquals(t, "b4eedccc6fb74fa8a7ad6b08382b852b", fw.TenantID) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewalls/ea5b5315-64f6-4ea3-8e58-981cc37c6576", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "firewall":{ + "name": "fw", + "description": "updated fw", + "admin_state_up":false, + "firewall_policy_id": "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall": { + "status": "ACTIVE", + "name": "fw", + "admin_state_up": false, + "tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b", + "firewall_policy_id": "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c" + "id": "ea5b5315-64f6-4ea3-8e58-981cc37c6576", + "description": "OpenStack firewall", + } +} + `) + }) + + options := UpdateOpts{ + Name: "fw", + Description: "updated fw", + AdminStateUp: Down, + PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", + } + + _, err := Update(fake.ServiceClient(), "ea5b5315-64f6-4ea3-8e58-981cc37c6576", options).Extract() + th.AssertNoErr(t, err) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewalls/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := Delete(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304") + th.AssertNoErr(t, res.Err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go new file mode 100644 index 0000000000..a8c76eef23 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go @@ -0,0 +1,101 @@ +package firewalls + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +type Firewall struct { + ID string `json:"id" mapstructure:"id"` + Name string `json:"name" mapstructure:"name"` + Description string `json:"description" mapstructure:"description"` + AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"` + Status string `json:"status" mapstructure:"status"` + PolicyID string `json:"firewall_policy_id" mapstructure:"firewall_policy_id"` + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a firewall. +func (r commonResult) Extract() (*Firewall, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Firewall *Firewall `json:"firewall"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Firewall, err +} + +// FirewallPage is the page returned by a pager when traversing over a +// collection of firewalls. +type FirewallPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of firewalls has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (p FirewallPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"firewalls_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a FirewallPage struct is empty. +func (p FirewallPage) IsEmpty() (bool, error) { + is, err := ExtractFirewalls(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractFirewalls accepts a Page struct, specifically a RouterPage struct, +// and extracts the elements into a slice of Router structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractFirewalls(page pagination.Page) ([]Firewall, error) { + var resp struct { + Firewalls []Firewall `mapstructure:"firewalls" json:"firewalls"` + } + + err := mapstructure.Decode(page.(FirewallPage).Body, &resp) + + return resp.Firewalls, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/urls.go new file mode 100644 index 0000000000..4dde53005a --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/urls.go @@ -0,0 +1,16 @@ +package firewalls + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "fw" + resourcePath = "firewalls" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go new file mode 100644 index 0000000000..95081dfa03 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go @@ -0,0 +1,258 @@ +package policies + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Binary gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Yes` and `No` enums +type Binary *bool + +// Convenience vars for Audited and Shared values. +var ( + iTrue = true + iFalse = false + Yes Binary = &iTrue + No Binary = &iFalse +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPolicyListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the firewall policy attributes you want to see returned. SortKey allows you +// to sort by a particular firewall policy attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + TenantID string `q:"tenant_id"` + Name string `q:"name"` + Description string `q:"description"` + Shared bool `q:"shared"` + Audited bool `q:"audited"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToPolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// firewall policies. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +// +// Default policy settings return only those firewall policies that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + + if opts != nil { + query, err := opts.ToPolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToPolicyCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new firewall policy. +type CreateOpts struct { + // Only required if the caller has an admin role and wants to create a firewall policy + // for another tenant. + TenantID string + Name string + Description string + Shared *bool + Audited *bool + Rules []string +} + +// ToPolicyCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToPolicyCreateMap() (map[string]interface{}, error) { + p := make(map[string]interface{}) + + if opts.TenantID != "" { + p["tenant_id"] = opts.TenantID + } + if opts.Name != "" { + p["name"] = opts.Name + } + if opts.Description != "" { + p["description"] = opts.Description + } + if opts.Shared != nil { + p["shared"] = *opts.Shared + } + if opts.Audited != nil { + p["audited"] = *opts.Audited + } + if opts.Rules != nil { + p["firewall_rules"] = opts.Rules + } + + return map[string]interface{}{"firewall_policy": p}, nil +} + +// Create accepts a CreateOpts struct and uses the values to create a new firewall policy +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToPolicyCreateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, + }) + return res +} + +// Get retrieves a particular firewall policy based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToPolicyUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a firewall policy. +type UpdateOpts struct { + // Name of the firewall policy. + Name string + Description string + Shared *bool + Audited *bool + Rules []string +} + +// ToPolicyUpdateMap casts a CreateOpts struct to a map. +func (opts UpdateOpts) ToPolicyUpdateMap() (map[string]interface{}, error) { + p := make(map[string]interface{}) + + if opts.Name != "" { + p["name"] = opts.Name + } + if opts.Description != "" { + p["description"] = opts.Description + } + if opts.Shared != nil { + p["shared"] = *opts.Shared + } + if opts.Audited != nil { + p["audited"] = *opts.Audited + } + if opts.Rules != nil { + p["firewall_rules"] = opts.Rules + } + + return map[string]interface{}{"firewall_policy": p}, nil +} + +// Update allows firewall policies to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToPolicyUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Delete will permanently delete a particular firewall policy based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return res +} + +func InsertRule(c *gophercloud.ServiceClient, policyID, ruleID, beforeID, afterID string) error { + type request struct { + RuleId string `json:"firewall_rule_id"` + Before string `json:"insert_before,omitempty"` + After string `json:"insert_after,omitempty"` + } + + reqBody := request{ + RuleId: ruleID, + Before: beforeID, + After: afterID, + } + + // Send request to API + var res commonResult + _, res.Err = c.Request("PUT", insertURL(c, policyID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res.Err +} + +func RemoveRule(c *gophercloud.ServiceClient, policyID, ruleID string) error { + type request struct { + RuleId string `json:"firewall_rule_id"` + } + + reqBody := request{ + RuleId: ruleID, + } + + // Send request to API + var res commonResult + _, res.Err = c.Request("PUT", removeURL(c, policyID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res.Err +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests_test.go new file mode 100644 index 0000000000..b9d78652c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests_test.go @@ -0,0 +1,279 @@ +package policies + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/rackspace/gophercloud/openstack/networking/v2/common" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestURLs(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.AssertEquals(t, th.Endpoint()+"v2.0/fw/firewall_policies", rootURL(fake.ServiceClient())) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall_policies": [ + { + "name": "policy1", + "firewall_rules": [ + "75452b36-268e-4e75-aaf4-f0e7ed50bc97", + "c9e77ca0-1bc8-497d-904d-948107873dc6" + ], + "tenant_id": "9145d91459d248b1b02fdaca97c6a75d", + "audited": true, + "shared": false, + "id": "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", + "description": "Firewall policy 1" + }, + { + "name": "policy2", + "firewall_rules": [ + "03d2a6ad-633f-431a-8463-4370d06a22c8" + ], + "tenant_id": "9145d91459d248b1b02fdaca97c6a75d", + "audited": false, + "shared": true, + "id": "c854fab5-bdaf-4a86-9359-78de93e5df01", + "description": "Firewall policy 2" + } + ] +} + `) + }) + + count := 0 + + List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractPolicies(page) + if err != nil { + t.Errorf("Failed to extract members: %v", err) + return false, err + } + + expected := []Policy{ + Policy{ + Name: "policy1", + Rules: []string{ + "75452b36-268e-4e75-aaf4-f0e7ed50bc97", + "c9e77ca0-1bc8-497d-904d-948107873dc6", + }, + TenantID: "9145d91459d248b1b02fdaca97c6a75d", + Audited: true, + Shared: false, + ID: "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", + Description: "Firewall policy 1", + }, + Policy{ + Name: "policy2", + Rules: []string{ + "03d2a6ad-633f-431a-8463-4370d06a22c8", + }, + TenantID: "9145d91459d248b1b02fdaca97c6a75d", + Audited: false, + Shared: true, + ID: "c854fab5-bdaf-4a86-9359-78de93e5df01", + Description: "Firewall policy 2", + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "firewall_policy":{ + "name": "policy", + "firewall_rules": [ + "98a58c87-76be-ae7c-a74e-b77fffb88d95", + "11a58c87-76be-ae7c-a74e-b77fffb88a32" + ], + "description": "Firewall policy", + "tenant_id": "9145d91459d248b1b02fdaca97c6a75d", + "audited": true, + "shared": false + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` +{ + "firewall_policy":{ + "name": "policy", + "firewall_rules": [ + "98a58c87-76be-ae7c-a74e-b77fffb88d95", + "11a58c87-76be-ae7c-a74e-b77fffb88a32" + ], + "tenant_id": "9145d91459d248b1b02fdaca97c6a75d", + "audited": false, + "id": "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", + "description": "Firewall policy" + } +} + `) + }) + + options := CreateOpts{ + TenantID: "9145d91459d248b1b02fdaca97c6a75d", + Name: "policy", + Description: "Firewall policy", + Shared: No, + Audited: Yes, + Rules: []string{ + "98a58c87-76be-ae7c-a74e-b77fffb88d95", + "11a58c87-76be-ae7c-a74e-b77fffb88a32", + }, + } + + _, err := Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_policies/bcab5315-64f6-4ea3-8e58-981cc37c6f61", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall_policy":{ + "name": "www", + "firewall_rules": [ + "75452b36-268e-4e75-aaf4-f0e7ed50bc97", + "c9e77ca0-1bc8-497d-904d-948107873dc6", + "03d2a6ad-633f-431a-8463-4370d06a22c8" + ], + "tenant_id": "9145d91459d248b1b02fdaca97c6a75d", + "audited": false, + "id": "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", + "description": "Firewall policy web" + } +} + `) + }) + + policy, err := Get(fake.ServiceClient(), "bcab5315-64f6-4ea3-8e58-981cc37c6f61").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "www", policy.Name) + th.AssertEquals(t, "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", policy.ID) + th.AssertEquals(t, "Firewall policy web", policy.Description) + th.AssertEquals(t, 3, len(policy.Rules)) + th.AssertEquals(t, "75452b36-268e-4e75-aaf4-f0e7ed50bc97", policy.Rules[0]) + th.AssertEquals(t, "c9e77ca0-1bc8-497d-904d-948107873dc6", policy.Rules[1]) + th.AssertEquals(t, "03d2a6ad-633f-431a-8463-4370d06a22c8", policy.Rules[2]) + th.AssertEquals(t, "9145d91459d248b1b02fdaca97c6a75d", policy.TenantID) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_policies/f2b08c1e-aa81-4668-8ae1-1401bcb0576c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "firewall_policy":{ + "name": "policy", + "firewall_rules": [ + "98a58c87-76be-ae7c-a74e-b77fffb88d95", + "11a58c87-76be-ae7c-a74e-b77fffb88a32" + ], + "description": "Firewall policy" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall_policy":{ + "name": "policy", + "firewall_rules": [ + "75452b36-268e-4e75-aaf4-f0e7ed50bc97", + "c9e77ca0-1bc8-497d-904d-948107873dc6", + "03d2a6ad-633f-431a-8463-4370d06a22c8" + ], + "tenant_id": "9145d91459d248b1b02fdaca97c6a75d", + "audited": false, + "id": "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", + "description": "Firewall policy" + } +} + `) + }) + + options := UpdateOpts{ + Name: "policy", + Description: "Firewall policy", + Rules: []string{ + "98a58c87-76be-ae7c-a74e-b77fffb88d95", + "11a58c87-76be-ae7c-a74e-b77fffb88a32", + }, + } + + _, err := Update(fake.ServiceClient(), "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", options).Extract() + th.AssertNoErr(t, err) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_policies/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := Delete(fake.ServiceClient(), "4ec89077-d057-4a2b-911f-60a3b47ee304") + th.AssertNoErr(t, res.Err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go new file mode 100644 index 0000000000..a9a0c358d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go @@ -0,0 +1,101 @@ +package policies + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +type Policy struct { + ID string `json:"id" mapstructure:"id"` + Name string `json:"name" mapstructure:"name"` + Description string `json:"description" mapstructure:"description"` + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` + Audited bool `json:"audited" mapstructure:"audited"` + Shared bool `json:"shared" mapstructure:"shared"` + Rules []string `json:"firewall_rules,omitempty" mapstructure:"firewall_rules"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a firewall policy. +func (r commonResult) Extract() (*Policy, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Policy *Policy `json:"firewall_policy" mapstructure:"firewall_policy"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Policy, err +} + +// PolicyPage is the page returned by a pager when traversing over a +// collection of firewall policies. +type PolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of firewall policies has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (p PolicyPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"firewall_policies_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a PolicyPage struct is empty. +func (p PolicyPage) IsEmpty() (bool, error) { + is, err := ExtractPolicies(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractPolicies accepts a Page struct, specifically a RouterPage struct, +// and extracts the elements into a slice of Router structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPolicies(page pagination.Page) ([]Policy, error) { + var resp struct { + Policies []Policy `mapstructure:"firewall_policies" json:"firewall_policies"` + } + + err := mapstructure.Decode(page.(PolicyPage).Body, &resp) + + return resp.Policies, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/urls.go new file mode 100644 index 0000000000..27ea9ae614 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies/urls.go @@ -0,0 +1,26 @@ +package policies + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "fw" + resourcePath = "firewall_policies" + insertPath = "insert_rule" + removePath = "remove_rule" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func insertURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, insertPath) +} + +func removeURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, removePath) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/errors.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/errors.go new file mode 100644 index 0000000000..0b29d39fd9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/errors.go @@ -0,0 +1,12 @@ +package rules + +import "fmt" + +func err(str string) error { + return fmt.Errorf("%s", str) +} + +var ( + errProtocolRequired = err("A protocol is required (tcp, udp, icmp or any)") + errActionRequired = err("An action is required (allow or deny)") +) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go new file mode 100644 index 0000000000..37801068bc --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go @@ -0,0 +1,296 @@ +package rules + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Binary gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Yes` and `No` enums +type Binary *bool + +// Convenience vars for Enabled and Shared values. +var ( + iTrue = true + iFalse = false + Yes Binary = &iTrue + No Binary = &iFalse +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToRuleListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Firewall rule attributes you want to see returned. SortKey allows you to +// sort by a particular firewall rule attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + TenantID string `q:"tenant_id"` + Name string `q:"name"` + Description string `q:"description"` + Protocol string `q:"protocol"` + Action string `q:"action"` + IPVersion int `q:"ip_version"` + SourceIPAddress string `q:"source_ip_address"` + DestinationIPAddress string `q:"destination_ip_address"` + SourcePort string `q:"source_port"` + DestinationPort string `q:"destination_port"` + Enabled bool `q:"enabled"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToRuleListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToRuleListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// firewall rules. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +// +// Default policy settings return only those firewall rules that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + + if opts != nil { + query, err := opts.ToRuleListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return RulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToRuleCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new firewall rule. +type CreateOpts struct { + // Mandatory for create + Protocol string + Action string + // Optional + TenantID string + Name string + Description string + IPVersion int + SourceIPAddress string + DestinationIPAddress string + SourcePort string + DestinationPort string + Shared *bool + Enabled *bool +} + +// ToRuleCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) { + if opts.Protocol == "" { + return nil, errProtocolRequired + } + + if opts.Action == "" { + return nil, errActionRequired + } + + r := make(map[string]interface{}) + + r["protocol"] = opts.Protocol + r["action"] = opts.Action + + if opts.TenantID != "" { + r["tenant_id"] = opts.TenantID + } + if opts.Name != "" { + r["name"] = opts.Name + } + if opts.Description != "" { + r["description"] = opts.Description + } + if opts.IPVersion != 0 { + r["ip_version"] = opts.IPVersion + } + if opts.SourceIPAddress != "" { + r["source_ip_address"] = opts.SourceIPAddress + } + if opts.DestinationIPAddress != "" { + r["destination_ip_address"] = opts.DestinationIPAddress + } + if opts.SourcePort != "" { + r["source_port"] = opts.SourcePort + } + if opts.DestinationPort != "" { + r["destination_port"] = opts.DestinationPort + } + if opts.Shared != nil { + r["shared"] = *opts.Shared + } + if opts.Enabled != nil { + r["enabled"] = *opts.Enabled + } + + return map[string]interface{}{"firewall_rule": r}, nil +} + +// Create accepts a CreateOpts struct and uses the values to create a new firewall rule +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToRuleCreateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, + }) + return res +} + +// Get retrieves a particular firewall rule based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToRuleUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a firewall rule. +// Optional +type UpdateOpts struct { + Protocol string + Action string + Name string + Description string + IPVersion int + SourceIPAddress *string + DestinationIPAddress *string + SourcePort *string + DestinationPort *string + Shared *bool + Enabled *bool +} + +// ToRuleUpdateMap casts a UpdateOpts struct to a map. +func (opts UpdateOpts) ToRuleUpdateMap() (map[string]interface{}, error) { + r := make(map[string]interface{}) + + if opts.Protocol != "" { + r["protocol"] = opts.Protocol + } + if opts.Action != "" { + r["action"] = opts.Action + } + if opts.Name != "" { + r["name"] = opts.Name + } + if opts.Description != "" { + r["description"] = opts.Description + } + if opts.IPVersion != 0 { + r["ip_version"] = opts.IPVersion + } + if opts.SourceIPAddress != nil { + s := *opts.SourceIPAddress + if s == "" { + r["source_ip_address"] = nil + } else { + r["source_ip_address"] = s + } + } + if opts.DestinationIPAddress != nil { + s := *opts.DestinationIPAddress + if s == "" { + r["destination_ip_address"] = nil + } else { + r["destination_ip_address"] = s + } + } + if opts.SourcePort != nil { + s := *opts.SourcePort + if s == "" { + r["source_port"] = nil + } else { + r["source_port"] = s + } + } + if opts.DestinationPort != nil { + s := *opts.DestinationPort + if s == "" { + r["destination_port"] = nil + } else { + r["destination_port"] = s + } + } + if opts.Shared != nil { + r["shared"] = *opts.Shared + } + if opts.Enabled != nil { + r["enabled"] = *opts.Enabled + } + + return map[string]interface{}{"firewall_rule": r}, nil +} + +// Update allows firewall policies to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToRuleUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + + return res +} + +// Delete will permanently delete a particular firewall rule based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests_test.go new file mode 100644 index 0000000000..36f89fa5c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests_test.go @@ -0,0 +1,328 @@ +package rules + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/rackspace/gophercloud/openstack/networking/v2/common" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" +) + +func TestURLs(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.AssertEquals(t, th.Endpoint()+"v2.0/fw/firewall_rules", rootURL(fake.ServiceClient())) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall_rules": [ + { + "protocol": "tcp", + "description": "ssh rule", + "source_port": null, + "source_ip_address": null, + "destination_ip_address": "192.168.1.0/24", + "firewall_policy_id": "e2a5fb51-698c-4898-87e8-f1eee6b50919", + "position": 2, + "destination_port": "22", + "id": "f03bd950-6c56-4f5e-a307-45967078f507", + "name": "ssh_form_any", + "tenant_id": "80cf934d6ffb4ef5b244f1c512ad1e61", + "enabled": true, + "action": "allow", + "ip_version": 4, + "shared": false + }, + { + "protocol": "udp", + "description": "udp rule", + "source_port": null, + "source_ip_address": null, + "destination_ip_address": null, + "firewall_policy_id": "98d7fb51-698c-4123-87e8-f1eee6b5ab7e", + "position": 1, + "destination_port": null, + "id": "ab7bd950-6c56-4f5e-a307-45967078f890", + "name": "deny_all_udp", + "tenant_id": "80cf934d6ffb4ef5b244f1c512ad1e61", + "enabled": true, + "action": "deny", + "ip_version": 4, + "shared": false + } + ] +} + `) + }) + + count := 0 + + List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractRules(page) + if err != nil { + t.Errorf("Failed to extract members: %v", err) + return false, err + } + + expected := []Rule{ + Rule{ + Protocol: "tcp", + Description: "ssh rule", + SourcePort: "", + SourceIPAddress: "", + DestinationIPAddress: "192.168.1.0/24", + PolicyID: "e2a5fb51-698c-4898-87e8-f1eee6b50919", + Position: 2, + DestinationPort: "22", + ID: "f03bd950-6c56-4f5e-a307-45967078f507", + Name: "ssh_form_any", + TenantID: "80cf934d6ffb4ef5b244f1c512ad1e61", + Enabled: true, + Action: "allow", + IPVersion: 4, + Shared: false, + }, + Rule{ + Protocol: "udp", + Description: "udp rule", + SourcePort: "", + SourceIPAddress: "", + DestinationIPAddress: "", + PolicyID: "98d7fb51-698c-4123-87e8-f1eee6b5ab7e", + Position: 1, + DestinationPort: "", + ID: "ab7bd950-6c56-4f5e-a307-45967078f890", + Name: "deny_all_udp", + TenantID: "80cf934d6ffb4ef5b244f1c512ad1e61", + Enabled: true, + Action: "deny", + IPVersion: 4, + Shared: false, + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "firewall_rule": { + "protocol": "tcp", + "description": "ssh rule", + "destination_ip_address": "192.168.1.0/24", + "destination_port": "22", + "name": "ssh_form_any", + "action": "allow", + "tenant_id": "80cf934d6ffb4ef5b244f1c512ad1e61" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` +{ + "firewall_rule":{ + "protocol": "tcp", + "description": "ssh rule", + "source_port": null, + "source_ip_address": null, + "destination_ip_address": "192.168.1.0/24", + "firewall_policy_id": "e2a5fb51-698c-4898-87e8-f1eee6b50919", + "position": 2, + "destination_port": "22", + "id": "f03bd950-6c56-4f5e-a307-45967078f507", + "name": "ssh_form_any", + "tenant_id": "80cf934d6ffb4ef5b244f1c512ad1e61", + "enabled": true, + "action": "allow", + "ip_version": 4, + "shared": false + } +} + `) + }) + + options := CreateOpts{ + TenantID: "80cf934d6ffb4ef5b244f1c512ad1e61", + Protocol: "tcp", + Description: "ssh rule", + DestinationIPAddress: "192.168.1.0/24", + DestinationPort: "22", + Name: "ssh_form_any", + Action: "allow", + } + + _, err := Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_rules/f03bd950-6c56-4f5e-a307-45967078f507", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall_rule":{ + "protocol": "tcp", + "description": "ssh rule", + "source_port": null, + "source_ip_address": null, + "destination_ip_address": "192.168.1.0/24", + "firewall_policy_id": "e2a5fb51-698c-4898-87e8-f1eee6b50919", + "position": 2, + "destination_port": "22", + "id": "f03bd950-6c56-4f5e-a307-45967078f507", + "name": "ssh_form_any", + "tenant_id": "80cf934d6ffb4ef5b244f1c512ad1e61", + "enabled": true, + "action": "allow", + "ip_version": 4, + "shared": false + } +} + `) + }) + + rule, err := Get(fake.ServiceClient(), "f03bd950-6c56-4f5e-a307-45967078f507").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "tcp", rule.Protocol) + th.AssertEquals(t, "ssh rule", rule.Description) + th.AssertEquals(t, "192.168.1.0/24", rule.DestinationIPAddress) + th.AssertEquals(t, "e2a5fb51-698c-4898-87e8-f1eee6b50919", rule.PolicyID) + th.AssertEquals(t, 2, rule.Position) + th.AssertEquals(t, "22", rule.DestinationPort) + th.AssertEquals(t, "f03bd950-6c56-4f5e-a307-45967078f507", rule.ID) + th.AssertEquals(t, "ssh_form_any", rule.Name) + th.AssertEquals(t, "80cf934d6ffb4ef5b244f1c512ad1e61", rule.TenantID) + th.AssertEquals(t, true, rule.Enabled) + th.AssertEquals(t, "allow", rule.Action) + th.AssertEquals(t, 4, rule.IPVersion) + th.AssertEquals(t, false, rule.Shared) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_rules/f03bd950-6c56-4f5e-a307-45967078f507", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "firewall_rule":{ + "protocol": "tcp", + "description": "ssh rule", + "destination_ip_address": "192.168.1.0/24", + "destination_port": "22", + "source_ip_address": null, + "source_port": null, + "name": "ssh_form_any", + "action": "allow", + "enabled": false + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "firewall_rule":{ + "protocol": "tcp", + "description": "ssh rule", + "source_port": null, + "source_ip_address": null, + "destination_ip_address": "192.168.1.0/24", + "firewall_policy_id": "e2a5fb51-698c-4898-87e8-f1eee6b50919", + "position": 2, + "destination_port": "22", + "id": "f03bd950-6c56-4f5e-a307-45967078f507", + "name": "ssh_form_any", + "tenant_id": "80cf934d6ffb4ef5b244f1c512ad1e61", + "enabled": false, + "action": "allow", + "ip_version": 4, + "shared": false + } +} + `) + }) + + destinationIPAddress := "192.168.1.0/24" + destinationPort := "22" + empty := "" + + options := UpdateOpts{ + Protocol: "tcp", + Description: "ssh rule", + DestinationIPAddress: &destinationIPAddress, + DestinationPort: &destinationPort, + Name: "ssh_form_any", + SourceIPAddress: &empty, + SourcePort: &empty, + Action: "allow", + Enabled: No, + } + + _, err := Update(fake.ServiceClient(), "f03bd950-6c56-4f5e-a307-45967078f507", options).Extract() + th.AssertNoErr(t, err) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/fw/firewall_rules/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := Delete(fake.ServiceClient(), "4ec89077-d057-4a2b-911f-60a3b47ee304") + th.AssertNoErr(t, res.Err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go new file mode 100644 index 0000000000..d772024b39 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go @@ -0,0 +1,110 @@ +package rules + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Rule represents a firewall rule +type Rule struct { + ID string `json:"id" mapstructure:"id"` + Name string `json:"name,omitempty" mapstructure:"name"` + Description string `json:"description,omitempty" mapstructure:"description"` + Protocol string `json:"protocol" mapstructure:"protocol"` + Action string `json:"action" mapstructure:"action"` + IPVersion int `json:"ip_version,omitempty" mapstructure:"ip_version"` + SourceIPAddress string `json:"source_ip_address,omitempty" mapstructure:"source_ip_address"` + DestinationIPAddress string `json:"destination_ip_address,omitempty" mapstructure:"destination_ip_address"` + SourcePort string `json:"source_port,omitempty" mapstructure:"source_port"` + DestinationPort string `json:"destination_port,omitempty" mapstructure:"destination_port"` + Shared bool `json:"shared,omitempty" mapstructure:"shared"` + Enabled bool `json:"enabled,omitempty" mapstructure:"enabled"` + PolicyID string `json:"firewall_policy_id" mapstructure:"firewall_policy_id"` + Position int `json:"position" mapstructure:"position"` + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` +} + +// RulePage is the page returned by a pager when traversing over a +// collection of firewall rules. +type RulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of firewall rules has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (p RulePage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"firewall_rules_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a RulePage struct is empty. +func (p RulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractRules accepts a Page struct, specifically a RouterPage struct, +// and extracts the elements into a slice of Router structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(page pagination.Page) ([]Rule, error) { + var resp struct { + Rules []Rule `mapstructure:"firewall_rules" json:"firewall_rules"` + } + + err := mapstructure.Decode(page.(RulePage).Body, &resp) + + return resp.Rules, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a firewall rule. +func (r commonResult) Extract() (*Rule, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Rule *Rule `json:"firewall_rule" mapstructure:"firewall_rule"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Rule, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/urls.go new file mode 100644 index 0000000000..20b08791ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules/urls.go @@ -0,0 +1,16 @@ +package rules + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "fw" + resourcePath = "firewall_rules" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go index d23f9e2b5a..46f2b22cb1 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go @@ -3,7 +3,6 @@ package floatingips import ( "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -53,7 +52,6 @@ type CreateOpts struct { var ( errFloatingNetworkIDRequired = fmt.Errorf("A NetworkID is required") - errPortIDRequired = fmt.Errorf("A PortID is required") ) // Create accepts a CreateOpts struct and uses the values provided to create a @@ -88,16 +86,12 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { res.Err = errFloatingNetworkIDRequired return res } - if opts.PortID == "" { - res.Err = errPortIDRequired - return res - } // Define structures type floatingIP struct { FloatingNetworkID string `json:"floating_network_id"` FloatingIP string `json:"floating_ip_address,omitempty"` - PortID string `json:"port_id"` + PortID string `json:"port_id,omitempty"` FixedIP string `json:"fixed_ip_address,omitempty"` TenantID string `json:"tenant_id,omitempty"` } @@ -114,11 +108,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { }} // Send request to API - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -127,10 +120,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular floating IP resource based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -167,11 +159,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Send request to API var res UpdateResult - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -182,9 +173,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // internal ports. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go index 19614be2ef..d914a799bf 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go @@ -170,6 +170,55 @@ func TestCreate(t *testing.T) { th.AssertEquals(t, "10.0.0.3", ip.FixedIP) } +func TestCreateEmptyPort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` + { + "floatingip": { + "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57" + } + } + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` + { + "floatingip": { + "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", + "tenant_id": "4969c491a3c74ee4af974e6d800c62de", + "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57", + "fixed_ip_address": "10.0.0.3", + "floating_ip_address": "", + "id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + } + } + `) + }) + + options := CreateOpts{ + FloatingNetworkID: "376da547-b977-4cfe-9cba-275c80debf57", + } + + ip, err := Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "2f245a7b-796b-4f26-9cf9-9e82d248fda7", ip.ID) + th.AssertEquals(t, "4969c491a3c74ee4af974e6d800c62de", ip.TenantID) + th.AssertEquals(t, "376da547-b977-4cfe-9cba-275c80debf57", ip.FloatingNetworkID) + th.AssertEquals(t, "", ip.FloatingIP) + th.AssertEquals(t, "", ip.PortID) + th.AssertEquals(t, "10.0.0.3", ip.FixedIP) +} + func TestGet(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go index e3a144171b..12640dee1e 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go @@ -3,7 +3,6 @@ package routers import ( "errors" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -82,11 +81,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { } var res CreateResult - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res } @@ -94,10 +92,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular router based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -136,11 +133,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Send request to API var res UpdateResult - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -149,9 +145,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Delete will permanently delete a particular router based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } @@ -202,11 +197,10 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) I body := request{SubnetID: opts.SubnetID, PortID: opts.PortID} - _, res.Err = perigee.Request("PUT", addInterfaceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &body, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", addInterfaceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &body, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -235,11 +229,10 @@ func RemoveInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts body := request{SubnetID: opts.SubnetID, PortID: opts.PortID} - _, res.Err = perigee.Request("PUT", removeInterfaceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &body, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", removeInterfaceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &body, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go index 58ec580dbc..023a04dc04 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go @@ -1,7 +1,6 @@ package members import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -63,7 +62,7 @@ type CreateOpts struct { // load balancer pool member. func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { type member struct { - TenantID string `json:"tenant_id"` + TenantID string `json:"tenant_id,omitempty"` ProtocolPort int `json:"protocol_port"` Address string `json:"address"` PoolID string `json:"pool_id"` @@ -80,11 +79,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { }} var res CreateResult - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res } @@ -92,10 +90,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular pool member based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -119,11 +116,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Send request to API var res UpdateResult - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -131,9 +127,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Delete will permanently delete a particular member based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go index e2b590ecb5..de6f68862f 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go @@ -3,7 +3,6 @@ package monitors import ( "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -177,11 +176,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { AdminStateUp: opts.AdminStateUp, }} - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -190,10 +188,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular health monitor based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -261,11 +258,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu AdminStateUp: opts.AdminStateUp, }} - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 202}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 202}, }) return res @@ -274,9 +270,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Delete will permanently delete a particular monitor based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go index ca8d33b8d5..e7e6d944d0 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go @@ -1,7 +1,6 @@ package pools import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -100,11 +99,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { }} var res CreateResult - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res } @@ -112,10 +110,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular pool based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -148,11 +145,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Send request to API var res UpdateResult - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -160,9 +156,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Delete will permanently delete a particular pool based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } @@ -183,11 +178,10 @@ func AssociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) As reqBody := request{hm{ID: monitorID}} var res AssociateResult - _, res.Err = perigee.Request("POST", associateURL(c, poolID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", associateURL(c, poolID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res } @@ -197,9 +191,8 @@ func AssociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) As // check for the health of the members of the pool. func DisassociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) AssociateResult { var res AssociateResult - _, res.Err = perigee.Request("DELETE", disassociateURL(c, poolID, monitorID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", disassociateURL(c, poolID, monitorID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go index ec929d6397..5b0bfd9a0b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go @@ -3,7 +3,6 @@ package vips import ( "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -179,11 +178,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { reqBody.VirtualIP.Persistence = opts.Persistence } - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -192,10 +190,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular virtual IP based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -252,11 +249,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu } var res UpdateResult - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 202}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 202}, }) return res @@ -265,9 +261,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu // Delete will permanently delete a particular virtual IP based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go index 0c970ae6f2..c07508bd7b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -3,7 +3,6 @@ package groups import ( "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -75,11 +74,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { Description: opts.Description, }} - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -88,10 +86,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular security group based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -99,9 +96,8 @@ func Get(c *gophercloud.ServiceClient, id string) GetResult { // Delete will permanently delete a particular security group based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go index edaebe82cd..108acf670d 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -3,7 +3,6 @@ package rules import ( "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -151,11 +150,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { RemoteIPPrefix: opts.RemoteIPPrefix, }} - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -164,10 +162,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { // Get retrieves a particular security group based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -175,9 +172,8 @@ func Get(c *gophercloud.ServiceClient, id string) GetResult { // Delete will permanently delete a particular security group based on its unique ID. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/networks/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/networks/requests.go index dedbb252de..b0db67e7d0 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/networks/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/networks/requests.go @@ -3,8 +3,6 @@ package networks import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // AdminState gives users a solid type to work with for create and update @@ -81,10 +79,9 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific network based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", getURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -138,11 +135,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { } // Send request to API - _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res } @@ -188,11 +184,10 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild } // Send request to API - _, res.Err = perigee.Request("PUT", updateURL(c, networkID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201}, + _, res.Err = c.Request("PUT", updateURL(c, networkID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 201}, }) return res @@ -201,9 +196,8 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild // Delete accepts a unique ID and deletes the network associated with it. func Delete(c *gophercloud.ServiceClient, networkID string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(c, networkID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", deleteURL(c, networkID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/ports/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/ports/requests.go index 06d273ef19..01d550fc1a 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/ports/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/ports/requests.go @@ -3,8 +3,6 @@ package ports import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // AdminState gives users a solid type to work with for create and update @@ -81,10 +79,9 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific port based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", getURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -159,11 +156,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { } // Response - _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -224,11 +220,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) Upd return res } - _, res.Err = perigee.Request("PUT", updateURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201}, + _, res.Err = c.Request("PUT", updateURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 201}, }) return res } @@ -236,9 +231,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) Upd // Delete accepts a unique ID and deletes the port associated with it. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", deleteURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/subnets/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/subnets/requests.go index cd7c663c2d..63ac2901b7 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/subnets/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/subnets/requests.go @@ -3,8 +3,6 @@ package subnets import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // AdminState gives users a solid type to work with for create and update @@ -80,10 +78,9 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific subnet based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", getURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -174,11 +171,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { return res } - _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{201}, + _, res.Err = c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, }) return res @@ -233,11 +229,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) Upd return res } - _, res.Err = perigee.Request("PUT", updateURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201}, + _, res.Err = c.Request("PUT", updateURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 201}, }) return res @@ -246,9 +241,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) Upd // Delete accepts a unique ID and deletes the subnet associated with it. func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", deleteURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/fixtures.go index 3dad0c5a9b..f22b687005 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/fixtures.go @@ -14,9 +14,8 @@ import ( // responds with a `Get` response. func HandleGetAccountSuccessfully(t *testing.T) { th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") + th.TestMethod(t, r, "HEAD") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts") w.Header().Set("X-Account-Container-Count", "2") w.Header().Set("X-Account-Bytes-Used", "14") @@ -30,9 +29,10 @@ func HandleGetAccountSuccessfully(t *testing.T) { // responds with a `Update` response. func HandleUpdateAccountSuccessfully(t *testing.T) { th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "HEAD") + th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.Header().Set("X-Account-Meta-Foo", "bar") + th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts") + w.WriteHeader(http.StatusNoContent) }) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests.go index e6f5f9594c..3e404c3b6b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests.go @@ -1,9 +1,6 @@ package accounts -import ( - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" -) +import "github.com/rackspace/gophercloud" // GetOptsBuilder allows extensions to add additional headers to the Get // request. @@ -42,11 +39,11 @@ func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) GetResult { } } - resp, err := perigee.Request("HEAD", getURL(c), perigee.Options{ + resp, err := c.Request("HEAD", getURL(c), gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -83,7 +80,7 @@ func (opts UpdateOpts) ToAccountUpdateMap() (map[string]string, error) { // To extract the headers returned, call the Extract method on the UpdateResult. func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult { var res UpdateResult - h := c.AuthenticatedHeaders() + h := make(map[string]string) if opts != nil { headers, err := opts.ToAccountUpdateMap() @@ -96,11 +93,11 @@ func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult { } } - resp, err := perigee.Request("POST", updateURL(c), perigee.Options{ + resp, err := c.Request("POST", updateURL(c), gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests_test.go index d6dc26b650..6454c0ac4c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests_test.go @@ -7,12 +7,10 @@ import ( fake "github.com/rackspace/gophercloud/testhelper/client" ) -var metadata = map[string]string{"gophercloud-test": "accounts"} - func TestUpdateAccount(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - HandleGetAccountSuccessfully(t) + HandleUpdateAccountSuccessfully(t) options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}} res := Update(fake.ServiceClient(), options) @@ -22,12 +20,13 @@ func TestUpdateAccount(t *testing.T) { func TestGetAccount(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - HandleUpdateAccountSuccessfully(t) + HandleGetAccountSuccessfully(t) - expected := map[string]string{"Foo": "bar"} - actual, err := Get(fake.ServiceClient(), &GetOpts{}).ExtractMetadata() - if err != nil { - t.Fatalf("Unable to get account metadata: %v", err) - } - th.CheckDeepEquals(t, expected, actual) + expectedMetadata := map[string]string{"Subject": "books"} + res := Get(fake.ServiceClient(), &GetOpts{}) + th.AssertNoErr(t, res.Err) + actualMetadata, _ := res.ExtractMetadata() + th.CheckDeepEquals(t, expectedMetadata, actualMetadata) + //headers, err := res.Extract() + //th.AssertNoErr(t, err) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/results.go index abae02659c..6ab1a23061 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/results.go @@ -2,15 +2,88 @@ package accounts import ( "strings" + "time" "github.com/rackspace/gophercloud" ) +// UpdateResult is returned from a call to the Update function. +type UpdateResult struct { + gophercloud.HeaderResult +} + +// UpdateHeader represents the headers returned in the response from an Update request. +type UpdateHeader struct { + ContentLength string `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + +// Extract will return a struct of headers returned from a call to Get. To obtain +// a map of headers, call the ExtractHeader method on the GetResult. +func (ur UpdateResult) Extract() (UpdateHeader, error) { + var uh UpdateHeader + if ur.Err != nil { + return uh, ur.Err + } + + if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil { + return uh, err + } + + if date, ok := ur.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, ur.Header["Date"][0]) + if err != nil { + return uh, err + } + uh.Date = t + } + + return uh, nil +} + +// GetHeader represents the headers returned in the response from a Get request. +type GetHeader struct { + BytesUsed int64 `mapstructure:"X-Account-Bytes-Used"` + ContainerCount int `mapstructure:"X-Account-Container-Count"` + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + ObjectCount int64 `mapstructure:"X-Account-Object-Count"` + TransID string `mapstructure:"X-Trans-Id"` + TempURLKey string `mapstructure:"X-Account-Meta-Temp-URL-Key"` + TempURLKey2 string `mapstructure:"X-Account-Meta-Temp-URL-Key-2"` +} + // GetResult is returned from a call to the Get function. type GetResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Get. To obtain +// a map of headers, call the ExtractHeader method on the GetResult. +func (gr GetResult) Extract() (GetHeader, error) { + var gh GetHeader + if gr.Err != nil { + return gh, gr.Err + } + + if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil { + return gh, err + } + + if date, ok := gr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, gr.Header["Date"][0]) + if err != nil { + return gh, err + } + gh.Date = t + } + + return gh, nil +} + // ExtractMetadata is a function that takes a GetResult (of type *http.Response) // and returns the custom metatdata associated with the account. func (gr GetResult) ExtractMetadata() (map[string]string, error) { @@ -27,8 +100,3 @@ func (gr GetResult) ExtractMetadata() (map[string]string, error) { } return metadata, nil } - -// UpdateResult is returned from a call to the Update function. -type UpdateResult struct { - gophercloud.HeaderResult -} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/fixtures.go index 1c0a915cb7..9c84bce0a4 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/fixtures.go @@ -94,6 +94,7 @@ func HandleCreateContainerSuccessfully(t *testing.T) { th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("X-Container-Meta-Foo", "bar") + w.Header().Add("X-Trans-Id", "1234567") w.WriteHeader(http.StatusNoContent) }) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests.go index 9f3b2af0a6..a29d7da5d6 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests.go @@ -1,7 +1,6 @@ package containers import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -111,11 +110,11 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB } } - resp, err := perigee.Request("PUT", createURL(c, containerName), perigee.Options{ + resp, err := c.Request("PUT", createURL(c, containerName), gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{201, 202, 204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -123,9 +122,8 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB // Delete is a function that deletes a container. func Delete(c *gophercloud.ServiceClient, containerName string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(c, containerName), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202, 204}, + _, res.Err = c.Request("DELETE", deleteURL(c, containerName), gophercloud.RequestOpts{ + OkCodes: []int{202, 204}, }) return res } @@ -180,11 +178,11 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB } } - resp, err := perigee.Request("POST", updateURL(c, containerName), perigee.Options{ + resp, err := c.Request("POST", updateURL(c, containerName), gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{202, 204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -194,11 +192,10 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB // function. func Get(c *gophercloud.ServiceClient, containerName string) GetResult { var res GetResult - resp, err := perigee.Request("HEAD", getURL(c, containerName), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{200, 204}, + resp, err := c.Request("HEAD", getURL(c, containerName), gophercloud.RequestOpts{ + OkCodes: []int{200, 204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests_test.go index d0ce7f1e58..f650696e48 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/requests_test.go @@ -58,8 +58,10 @@ func TestCreateContainer(t *testing.T) { options := CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}} res := Create(fake.ServiceClient(), "testContainer", options) - th.CheckNoErr(t, res.Err) + c, err := res.Extract() + th.CheckNoErr(t, err) th.CheckEquals(t, "bar", res.Header["X-Container-Meta-Foo"][0]) + th.CheckEquals(t, "1234567", c.TransID) } func TestDeleteContainer(t *testing.T) { diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/results.go index 74f3286046..e682b8dccb 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/results.go @@ -3,6 +3,7 @@ package containers import ( "fmt" "strings" + "time" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" @@ -96,11 +97,48 @@ func ExtractNames(page pagination.Page) ([]string, error) { } } +// GetHeader represents the headers returned in the response from a Get request. +type GetHeader struct { + AcceptRanges string `mapstructure:"Accept-Ranges"` + BytesUsed int64 `mapstructure:"X-Account-Bytes-Used"` + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + ObjectCount int64 `mapstructure:"X-Container-Object-Count"` + Read string `mapstructure:"X-Container-Read"` + TransID string `mapstructure:"X-Trans-Id"` + VersionsLocation string `mapstructure:"X-Versions-Location"` + Write string `mapstructure:"X-Container-Write"` +} + // GetResult represents the result of a get operation. type GetResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Get. To obtain +// a map of headers, call the ExtractHeader method on the GetResult. +func (gr GetResult) Extract() (GetHeader, error) { + var gh GetHeader + if gr.Err != nil { + return gh, gr.Err + } + + if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil { + return gh, err + } + + if date, ok := gr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, gr.Header["Date"][0]) + if err != nil { + return gh, err + } + gh.Date = t + } + + return gh, nil +} + // ExtractMetadata is a function that takes a GetResult (of type *http.Response) // and returns the custom metadata associated with the container. func (gr GetResult) ExtractMetadata() (map[string]string, error) { @@ -117,6 +155,14 @@ func (gr GetResult) ExtractMetadata() (map[string]string, error) { return metadata, nil } +// CreateHeader represents the headers returned in the response from a Create request. +type CreateHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // CreateResult represents the result of a create operation. To extract the // the headers from the HTTP response, you can invoke the 'ExtractHeader' // method on the result struct. @@ -124,6 +170,37 @@ type CreateResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Create. To obtain +// a map of headers, call the ExtractHeader method on the CreateResult. +func (cr CreateResult) Extract() (CreateHeader, error) { + var ch CreateHeader + if cr.Err != nil { + return ch, cr.Err + } + + if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil { + return ch, err + } + + if date, ok := cr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, cr.Header["Date"][0]) + if err != nil { + return ch, err + } + ch.Date = t + } + + return ch, nil +} + +// UpdateHeader represents the headers returned in the response from a Update request. +type UpdateHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // UpdateResult represents the result of an update operation. To extract the // the headers from the HTTP response, you can invoke the 'ExtractHeader' // method on the result struct. @@ -131,9 +208,63 @@ type UpdateResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Update. To obtain +// a map of headers, call the ExtractHeader method on the UpdateResult. +func (ur UpdateResult) Extract() (UpdateHeader, error) { + var uh UpdateHeader + if ur.Err != nil { + return uh, ur.Err + } + + if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil { + return uh, err + } + + if date, ok := ur.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, ur.Header["Date"][0]) + if err != nil { + return uh, err + } + uh.Date = t + } + + return uh, nil +} + +// DeleteHeader represents the headers returned in the response from a Delete request. +type DeleteHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // DeleteResult represents the result of a delete operation. To extract the // the headers from the HTTP response, you can invoke the 'ExtractHeader' // method on the result struct. type DeleteResult struct { gophercloud.HeaderResult } + +// Extract will return a struct of headers returned from a call to Delete. To obtain +// a map of headers, call the ExtractHeader method on the DeleteResult. +func (dr DeleteResult) Extract() (DeleteHeader, error) { + var dh DeleteHeader + if dr.Err != nil { + return dh, dr.Err + } + + if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil { + return dh, err + } + + if date, ok := dr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, dr.Header["Date"][0]) + if err != nil { + return dh, err + } + dh.Date = t + } + + return dh, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go index d951160e3a..ec616371a3 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go @@ -105,13 +105,31 @@ func HandleListObjectNamesSuccessfully(t *testing.T) { }) } -// HandleCreateObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that -// responds with a `Create` response. -func HandleCreateObjectSuccessfully(t *testing.T) { +// HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux +// that responds with a `Create` response. A Content-Type of "text/plain" is expected. +func HandleCreateTextObjectSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "text/plain") + th.TestHeader(t, r, "Accept", "application/json") + w.WriteHeader(http.StatusCreated) + }) +} + +// HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler +// mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server- +// side content-type detection will be triggered properly. +func HandleCreateTypelessObjectSuccessfully(t *testing.T) { th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") + + if contentType, present := r.Header["Content-Type"]; present { + t.Errorf("Expected Content-Type header to be omitted, but was %#v", contentType) + } + w.WriteHeader(http.StatusCreated) }) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go index 7b96fa2fe5..30ea94cc2d 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go @@ -1,12 +1,15 @@ package objects import ( + "crypto/hmac" + "crypto/sha1" "fmt" "io" + "strings" "time" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts" "github.com/rackspace/gophercloud/pagination" ) @@ -127,14 +130,14 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op url += query } - resp, err := perigee.Request("GET", url, perigee.Options{ + resp, err := c.Request("GET", url, gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{200, 304}, }) - res.Body = resp.HttpResponse.Body + res.Body = resp.Body res.Err = err - res.Header = resp.HttpResponse.Header + res.Header = resp.Header return res } @@ -189,7 +192,7 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont var res CreateResult url := createURL(c, containerName, objectName) - h := c.AuthenticatedHeaders() + h := make(map[string]string) if opts != nil { headers, query, err := opts.ToObjectCreateParams() @@ -205,15 +208,14 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont url += query } - contentType := h["Content-Type"] - - resp, err := perigee.Request("PUT", url, perigee.Options{ - ContentType: contentType, - ReqBody: content, + ropts := gophercloud.RequestOpts{ + RawBody: content, MoreHeaders: h, OkCodes: []int{201, 202}, - }) - res.Header = resp.HttpResponse.Header + } + + resp, err := c.Request("PUT", url, ropts) + res.Header = resp.Header res.Err = err return res } @@ -265,11 +267,11 @@ func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts C } url := copyURL(c, containerName, objectName) - resp, err := perigee.Request("COPY", url, perigee.Options{ + resp, err := c.Request("COPY", url, gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{201}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -308,11 +310,10 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts url += query } - resp, err := perigee.Request("DELETE", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + resp, err := c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -353,11 +354,10 @@ func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts Ge url += query } - resp, err := perigee.Request("HEAD", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{200, 204}, + resp, err := c.Request("HEAD", url, gophercloud.RequestOpts{ + OkCodes: []int{200, 204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -410,11 +410,59 @@ func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts } url := updateURL(c, containerName, objectName) - resp, err := perigee.Request("POST", url, perigee.Options{ + resp, err := c.Request("POST", url, gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{202}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } + +// HTTPMethod represents an HTTP method string (e.g. "GET"). +type HTTPMethod string + +var ( + // GET represents an HTTP "GET" method. + GET HTTPMethod = "GET" + // POST represents an HTTP "POST" method. + POST HTTPMethod = "POST" +) + +// CreateTempURLOpts are options for creating a temporary URL for an object. +type CreateTempURLOpts struct { + // (REQUIRED) Method is the HTTP method to allow for users of the temp URL. Valid values + // are "GET" and "POST". + Method HTTPMethod + // (REQUIRED) TTL is the number of seconds the temp URL should be active. + TTL int + // (Optional) Split is the string on which to split the object URL. Since only + // the object path is used in the hash, the object URL needs to be parsed. If + // empty, the default OpenStack URL split point will be used ("/v1/"). + Split string +} + +// CreateTempURL is a function for creating a temporary URL for an object. It +// allows users to have "GET" or "POST" access to a particular tenant's object +// for a limited amount of time. +func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateTempURLOpts) (string, error) { + if opts.Split == "" { + opts.Split = "/v1/" + } + duration := time.Duration(opts.TTL) * time.Second + expiry := time.Now().Add(duration).Unix() + getHeader, err := accounts.Get(c, nil).Extract() + if err != nil { + return "", err + } + secretKey := []byte(getHeader.TempURLKey) + url := getURL(c, containerName, objectName) + splitPath := strings.Split(url, opts.Split) + baseURL, objectPath := splitPath[0], splitPath[1] + objectPath = opts.Split + objectPath + body := fmt.Sprintf("%s\n%d\n%s", opts.Method, expiry, objectPath) + hash := hmac.New(sha1.New, secretKey) + hash.Write([]byte(body)) + hexsum := fmt.Sprintf("%x", hash.Sum(nil)) + return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests_test.go index c3c28a789b..6be3534205 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests_test.go @@ -83,14 +83,24 @@ func TestListObjectNames(t *testing.T) { func TestCreateObject(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - HandleCreateObjectSuccessfully(t) + HandleCreateTextObjectSuccessfully(t) content := bytes.NewBufferString("Did gyre and gimble in the wabe") - options := &CreateOpts{ContentType: "application/json"} + options := &CreateOpts{ContentType: "text/plain"} res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options) th.AssertNoErr(t, res.Err) } +func TestCreateObjectWithoutContentType(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateTypelessObjectSuccessfully(t) + + content := bytes.NewBufferString("The sky was the color of television, tuned to a dead channel.") + res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &CreateOpts{}) + th.AssertNoErr(t, res.Err) +} + func TestCopyObject(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/results.go index b51b840c99..ecb2c54582 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/results.go @@ -4,7 +4,9 @@ import ( "fmt" "io" "io/ioutil" + "strconv" "strings" + "time" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" @@ -108,12 +110,67 @@ func ExtractNames(page pagination.Page) ([]string, error) { } } +// DownloadHeader represents the headers returned in the response from a Download request. +type DownloadHeader struct { + AcceptRanges string `mapstructure:"Accept-Ranges"` + ContentDisposition string `mapstructure:"Content-Disposition"` + ContentEncoding string `mapstructure:"Content-Encoding"` + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + DeleteAt time.Time `mapstructure:"-"` + ETag string `mapstructure:"Etag"` + LastModified time.Time `mapstructure:"-"` + ObjectManifest string `mapstructure:"X-Object-Manifest"` + StaticLargeObject bool `mapstructure:"X-Static-Large-Object"` + TransID string `mapstructure:"X-Trans-Id"` +} + // DownloadResult is a *http.Response that is returned from a call to the Download function. type DownloadResult struct { gophercloud.HeaderResult Body io.ReadCloser } +// Extract will return a struct of headers returned from a call to Download. To obtain +// a map of headers, call the ExtractHeader method on the DownloadResult. +func (dr DownloadResult) Extract() (DownloadHeader, error) { + var dh DownloadHeader + if dr.Err != nil { + return dh, dr.Err + } + + if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil { + return dh, err + } + + if date, ok := dr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, date[0]) + if err != nil { + return dh, err + } + dh.Date = t + } + + if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, date[0]) + if err != nil { + return dh, err + } + dh.LastModified = t + } + + if date, ok := dr.Header["X-Delete-At"]; ok && len(date) > 0 { + unix, err := strconv.ParseInt(date[0], 10, 64) + if err != nil { + return dh, err + } + dh.DeleteAt = time.Unix(unix, 0) + } + + return dh, nil +} + // ExtractContent is a function that takes a DownloadResult's io.Reader body // and reads all available data into a slice of bytes. Please be aware that due // the nature of io.Reader is forward-only - meaning that it can only be read @@ -131,11 +188,65 @@ func (dr DownloadResult) ExtractContent() ([]byte, error) { return body, nil } +// GetHeader represents the headers returned in the response from a Get request. +type GetHeader struct { + ContentDisposition string `mapstructure:"Content-Disposition"` + ContentEncoding string `mapstructure:"Content-Encoding"` + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + DeleteAt time.Time `mapstructure:"-"` + ETag string `mapstructure:"Etag"` + LastModified time.Time `mapstructure:"-"` + ObjectManifest string `mapstructure:"X-Object-Manifest"` + StaticLargeObject bool `mapstructure:"X-Static-Large-Object"` + TransID string `mapstructure:"X-Trans-Id"` +} + // GetResult is a *http.Response that is returned from a call to the Get function. type GetResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Get. To obtain +// a map of headers, call the ExtractHeader method on the GetResult. +func (gr GetResult) Extract() (GetHeader, error) { + var gh GetHeader + if gr.Err != nil { + return gh, gr.Err + } + + if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil { + return gh, err + } + + if date, ok := gr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, gr.Header["Date"][0]) + if err != nil { + return gh, err + } + gh.Date = t + } + + if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0]) + if err != nil { + return gh, err + } + gh.LastModified = t + } + + if date, ok := gr.Header["X-Delete-At"]; ok && len(date) > 0 { + unix, err := strconv.ParseInt(date[0], 10, 64) + if err != nil { + return gh, err + } + gh.DeleteAt = time.Unix(unix, 0) + } + + return gh, nil +} + // ExtractMetadata is a function that takes a GetResult (of type *http.Response) // and returns the custom metadata associated with the object. func (gr GetResult) ExtractMetadata() (map[string]string, error) { @@ -152,22 +263,176 @@ func (gr GetResult) ExtractMetadata() (map[string]string, error) { return metadata, nil } +// CreateHeader represents the headers returned in the response from a Create request. +type CreateHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + ETag string `mapstructure:"Etag"` + LastModified time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // CreateResult represents the result of a create operation. type CreateResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Create. To obtain +// a map of headers, call the ExtractHeader method on the CreateResult. +func (cr CreateResult) Extract() (CreateHeader, error) { + var ch CreateHeader + if cr.Err != nil { + return ch, cr.Err + } + + if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil { + return ch, err + } + + if date, ok := cr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, cr.Header["Date"][0]) + if err != nil { + return ch, err + } + ch.Date = t + } + + if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0]) + if err != nil { + return ch, err + } + ch.LastModified = t + } + + return ch, nil +} + +// UpdateHeader represents the headers returned in the response from a Update request. +type UpdateHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // UpdateResult represents the result of an update operation. type UpdateResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Update. To obtain +// a map of headers, call the ExtractHeader method on the UpdateResult. +func (ur UpdateResult) Extract() (UpdateHeader, error) { + var uh UpdateHeader + if ur.Err != nil { + return uh, ur.Err + } + + if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil { + return uh, err + } + + if date, ok := ur.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, ur.Header["Date"][0]) + if err != nil { + return uh, err + } + uh.Date = t + } + + return uh, nil +} + +// DeleteHeader represents the headers returned in the response from a Delete request. +type DeleteHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // DeleteResult represents the result of a delete operation. type DeleteResult struct { gophercloud.HeaderResult } +// Extract will return a struct of headers returned from a call to Delete. To obtain +// a map of headers, call the ExtractHeader method on the DeleteResult. +func (dr DeleteResult) Extract() (DeleteHeader, error) { + var dh DeleteHeader + if dr.Err != nil { + return dh, dr.Err + } + + if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil { + return dh, err + } + + if date, ok := dr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, dr.Header["Date"][0]) + if err != nil { + return dh, err + } + dh.Date = t + } + + return dh, nil +} + +// CopyHeader represents the headers returned in the response from a Copy request. +type CopyHeader struct { + ContentLength int64 `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + CopiedFrom string `mapstructure:"X-Copied-From"` + CopiedFromLastModified time.Time `mapstructure:"-"` + Date time.Time `mapstructure:"-"` + ETag string `mapstructure:"Etag"` + LastModified time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + // CopyResult represents the result of a copy operation. type CopyResult struct { gophercloud.HeaderResult } + +// Extract will return a struct of headers returned from a call to Copy. To obtain +// a map of headers, call the ExtractHeader method on the CopyResult. +func (cr CopyResult) Extract() (CopyHeader, error) { + var ch CopyHeader + if cr.Err != nil { + return ch, cr.Err + } + + if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil { + return ch, err + } + + if date, ok := cr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, cr.Header["Date"][0]) + if err != nil { + return ch, err + } + ch.Date = t + } + + if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0]) + if err != nil { + return ch, err + } + ch.LastModified = t + } + + if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0]) + if err != nil { + return ch, err + } + ch.CopiedFromLastModified = t + } + + return ch, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/doc.go new file mode 100644 index 0000000000..f2db622d1f --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/doc.go @@ -0,0 +1,4 @@ +// Package apiversions provides information and interaction with the different +// API versions for the OpenStack Heat service. This functionality is not +// restricted to this particular version. +package apiversions diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests.go new file mode 100644 index 0000000000..f6454c8609 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests.go @@ -0,0 +1,13 @@ +package apiversions + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// ListVersions lists all the Neutron API versions available to end-users +func ListVersions(c *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(c, apiVersionsURL(c), func(r pagination.PageResult) pagination.Page { + return APIVersionPage{pagination.SinglePageBase(r)} + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests_test.go new file mode 100644 index 0000000000..a2fc980d35 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/requests_test.go @@ -0,0 +1,89 @@ +package apiversions + +import ( + "fmt" + "net/http" + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestListVersions(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "versions": [ + { + "status": "CURRENT", + "id": "v1.0", + "links": [ + { + "href": "http://23.253.228.211:8000/v1", + "rel": "self" + } + ] + } + ] +}`) + }) + + count := 0 + + ListVersions(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractAPIVersions(page) + if err != nil { + t.Errorf("Failed to extract API versions: %v", err) + return false, err + } + + expected := []APIVersion{ + APIVersion{ + Status: "CURRENT", + ID: "v1.0", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://23.253.228.211:8000/v1", + Rel: "self", + }, + }, + }, + } + + th.AssertDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestNonJSONCannotBeExtractedIntoAPIVersions(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + ListVersions(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + if _, err := ExtractAPIVersions(page); err == nil { + t.Fatalf("Expected error, got nil") + } + return true, nil + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/results.go new file mode 100644 index 0000000000..0700ab0afb --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/results.go @@ -0,0 +1,42 @@ +package apiversions + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// APIVersion represents an API version for Neutron. It contains the status of +// the API, and its unique ID. +type APIVersion struct { + Status string `mapstructure:"status"` + ID string `mapstructure:"id"` + Links []gophercloud.Link `mapstructure:"links"` +} + +// APIVersionPage is the page returned by a pager when traversing over a +// collection of API versions. +type APIVersionPage struct { + pagination.SinglePageBase +} + +// IsEmpty checks whether an APIVersionPage struct is empty. +func (r APIVersionPage) IsEmpty() (bool, error) { + is, err := ExtractAPIVersions(r) + if err != nil { + return true, err + } + return len(is) == 0, nil +} + +// ExtractAPIVersions takes a collection page, extracts all of the elements, +// and returns them a slice of APIVersion structs. It is effectively a cast. +func ExtractAPIVersions(page pagination.Page) ([]APIVersion, error) { + var resp struct { + Versions []APIVersion `mapstructure:"versions"` + } + + err := mapstructure.Decode(page.(APIVersionPage).Body, &resp) + + return resp.Versions, err +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/urls.go new file mode 100644 index 0000000000..55d6e0e7a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/apiversions/urls.go @@ -0,0 +1,7 @@ +package apiversions + +import "github.com/rackspace/gophercloud" + +func apiVersionsURL(c *gophercloud.ServiceClient) string { + return c.Endpoint +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/doc.go new file mode 100644 index 0000000000..183e8dfa76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/doc.go @@ -0,0 +1,2 @@ +// Package buildinfo provides build information about heat deployments. +package buildinfo diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/fixtures.go new file mode 100644 index 0000000000..20ea09b442 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/fixtures.go @@ -0,0 +1,45 @@ +package buildinfo + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// GetExpected represents the expected object from a Get request. +var GetExpected = &BuildInfo{ + API: Revision{ + Revision: "2.4.5", + }, + Engine: Revision{ + Revision: "1.2.1", + }, +} + +// GetOutput represents the response body from a Get request. +const GetOutput = ` +{ + "api": { + "revision": "2.4.5" + }, + "engine": { + "revision": "1.2.1" + } +}` + +// HandleGetSuccessfully creates an HTTP handler at `/build_info` +// on the test handler mux that responds with a `Get` response. +func HandleGetSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/build_info", 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/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests.go new file mode 100644 index 0000000000..379f34f30b --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests.go @@ -0,0 +1,13 @@ +package buildinfo + +import "github.com/rackspace/gophercloud" + +// Get retreives data for the given stack template. +func Get(c *gophercloud.ServiceClient) GetResult { + var res GetResult + _, res.Err = c.Request("GET", getURL(c), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests_test.go new file mode 100644 index 0000000000..1e0fe230d6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/requests_test.go @@ -0,0 +1,20 @@ +package buildinfo + +import ( + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestGetTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t, GetOutput) + + actual, err := Get(fake.ServiceClient()).Extract() + th.AssertNoErr(t, err) + + expected := GetExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/results.go new file mode 100644 index 0000000000..683a434a05 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/results.go @@ -0,0 +1,37 @@ +package buildinfo + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" +) + +// Revision represents the API/Engine revision of a Heat deployment. +type Revision struct { + Revision string `mapstructure:"revision"` +} + +// BuildInfo represents the build information for a Heat deployment. +type BuildInfo struct { + API Revision `mapstructure:"api"` + Engine Revision `mapstructure:"engine"` +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a BuildInfo object and is called after a +// Get operation. +func (r GetResult) Extract() (*BuildInfo, error) { + if r.Err != nil { + return nil, r.Err + } + + var res BuildInfo + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/urls.go new file mode 100644 index 0000000000..2c873d0235 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo/urls.go @@ -0,0 +1,7 @@ +package buildinfo + +import "github.com/rackspace/gophercloud" + +func getURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("build_info") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/doc.go new file mode 100644 index 0000000000..51cdd97473 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/doc.go @@ -0,0 +1,4 @@ +// Package stackevents provides operations for finding, listing, and retrieving +// stack events. Stack events are events that take place on stacks such as +// updating and abandoning. +package stackevents diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/fixtures.go new file mode 100644 index 0000000000..016ae003b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/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/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests.go new file mode 100644 index 0000000000..37eab1e763 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests.go @@ -0,0 +1,205 @@ +package stackevents + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Find retrieves stack events for the given stack name. +func Find(c *gophercloud.ServiceClient, stackName string) FindResult { + var res FindResult + + _, res.Err = c.Request("GET", findURL(c, stackName), gophercloud.RequestOpts{ + JSONResponse: &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 ( + // ResourceStatusInProgress is used to filter a List request by the 'IN_PROGRESS' status. + ResourceStatusInProgress ResourceStatus = "IN_PROGRESS" + // ResourceStatusComplete is used to filter a List request by the 'COMPLETE' status. + ResourceStatusComplete ResourceStatus = "COMPLETE" + // ResourceStatusFailed is used to filter a List request by the 'FAILED' status. + ResourceStatusFailed ResourceStatus = "FAILED" + + // ResourceActionCreate is used to filter a List request by the 'CREATE' action. + ResourceActionCreate ResourceAction = "CREATE" + // ResourceActionDelete is used to filter a List request by the 'DELETE' action. + ResourceActionDelete ResourceAction = "DELETE" + // ResourceActionUpdate is used to filter a List request by the 'UPDATE' action. + ResourceActionUpdate ResourceAction = "UPDATE" + // ResourceActionRollback is used to filter a List request by the 'ROLLBACK' action. + ResourceActionRollback ResourceAction = "ROLLBACK" + // ResourceActionSuspend is used to filter a List request by the 'SUSPEND' action. + ResourceActionSuspend ResourceAction = "SUSPEND" + // ResourceActionResume is used to filter a List request by the 'RESUME' action. + ResourceActionResume ResourceAction = "RESUME" + // ResourceActionAbandon is used to filter a List request by the 'ABANDON' action. + ResourceActionAbandon ResourceAction = "ABANDON" + + // 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 []ResourceAction `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 []ResourceStatus `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 = c.Request("GET", getURL(c, stackName, stackID, resourceName, eventID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/requests_test.go new file mode 100644 index 0000000000..a4da4d04e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/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/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/results.go new file mode 100644 index 0000000000..bf233ae2ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/results.go @@ -0,0 +1,162 @@ +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 { + // The name of the resource for which the event occurred. + ResourceName string `mapstructure:"resource_name"` + // The time the event occurred. + Time time.Time `mapstructure:"-"` + // The URLs to the event. + Links []gophercloud.Link `mapstructure:"links"` + // The logical ID of the stack resource. + LogicalResourceID string `mapstructure:"logical_resource_id"` + // The reason of the status of the event. + ResourceStatusReason string `mapstructure:"resource_status_reason"` + // The status of the event. + ResourceStatus string `mapstructure:"resource_status"` + // The physical ID of the stack resource. + PhysicalResourceID string `mapstructure:"physical_resource_id"` + // The event ID. + ID string `mapstructure:"id"` + // Properties of the stack resource. + 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 +} + +// ExtractResourceEvents interprets the results of a single page from a +// ListResourceEvents() call, producing a slice of Event entities. +func ExtractResourceEvents(page pagination.Page) ([]Event, error) { + return ExtractEvents(page) +} + +// 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/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents/urls.go new file mode 100644 index 0000000000..8b5eceb170 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/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) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/doc.go new file mode 100644 index 0000000000..e4f8b08dcc --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/doc.go @@ -0,0 +1,5 @@ +// Package stackresources provides operations for working with stack resources. +// A resource is a template artifact that represents some component of your +// desired architecture (a Cloud Server, a group of scaled Cloud Servers, a load +// balancer, some configuration management system, and so forth). +package stackresources diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/fixtures.go new file mode 100644 index 0000000000..0b930f4841 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/fixtures.go @@ -0,0 +1,451 @@ +package stackresources + +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 = []Resource{ + Resource{ + Name: "hello_world", + 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", + Rel: "self", + }, + gophercloud.Link{ + Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b", + Rel: "stack", + }, + }, + LogicalID: "hello_world", + StatusReason: "state changed", + UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), + RequiredBy: []interface{}{}, + Status: "CREATE_IN_PROGRESS", + PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf", + Type: "OS::Nova::Server", + }, +} + +// FindOutput represents the response body from a Find request. +const FindOutput = ` +{ + "resources": [ + { + "resource_name": "hello_world", + "links": [ + { + "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world", + "rel": "self" + }, + { + "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", + "updated_time": "2015-02-05T21:33:11Z", + "required_by": [], + "resource_status": "CREATE_IN_PROGRESS", + "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf", + "resource_type": "OS::Nova::Server" + } + ] +}` + +// HandleFindSuccessfully creates an HTTP handler at `/stacks/hello_world/resources` +// on the test handler mux that responds with a `Find` response. +func HandleFindSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/hello_world/resources", 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 = []Resource{ + Resource{ + Name: "hello_world", + 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", + Rel: "self", + }, + gophercloud.Link{ + Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b", + Rel: "stack", + }, + }, + LogicalID: "hello_world", + StatusReason: "state changed", + UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), + RequiredBy: []interface{}{}, + Status: "CREATE_IN_PROGRESS", + PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf", + Type: "OS::Nova::Server", + }, +} + +// ListOutput represents the response body from a List request. +const ListOutput = `{ + "resources": [ + { + "resource_name": "hello_world", + "links": [ + { + "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world", + "rel": "self" + }, + { + "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", + "updated_time": "2015-02-05T21:33:11Z", + "required_by": [], + "resource_status": "CREATE_IN_PROGRESS", + "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf", + "resource_type": "OS::Nova::Server" + } +] +}` + +// HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources` +// 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/resources", 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 "49181cd6-169a-4130-9455-31185bbfc5bf": + fmt.Fprintf(w, `{"resources":[]}`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +// GetExpected represents the expected object from a Get request. +var GetExpected = &Resource{ + Name: "wordpress_instance", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", + Rel: "self", + }, + gophercloud.Link{ + Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e", + Rel: "stack", + }, + }, + LogicalID: "wordpress_instance", + StatusReason: "state changed", + UpdatedTime: time.Date(2014, 12, 10, 18, 34, 35, 0, time.UTC), + RequiredBy: []interface{}{}, + Status: "CREATE_COMPLETE", + PhysicalID: "00e3a2fe-c65d-403c-9483-4db9930dd194", + Type: "OS::Nova::Server", +} + +// GetOutput represents the response body from a Get request. +const GetOutput = ` +{ + "resource": { + "resource_name": "wordpress_instance", + "description": "", + "links": [ + { + "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", + "rel": "self" + }, + { + "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e", + "rel": "stack" + } + ], + "logical_resource_id": "wordpress_instance", + "resource_status": "CREATE_COMPLETE", + "updated_time": "2014-12-10T18:34:35Z", + "required_by": [], + "resource_status_reason": "state changed", + "physical_resource_id": "00e3a2fe-c65d-403c-9483-4db9930dd194", + "resource_type": "OS::Nova::Server" + } +}` + +// HandleGetSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance` +// on the test handler mux that responds with a `Get` response. +func HandleGetSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", 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) + }) +} + +// MetadataExpected represents the expected object from a Metadata request. +var MetadataExpected = map[string]string{ + "number": "7", + "animal": "auk", +} + +// MetadataOutput represents the response body from a Metadata request. +const MetadataOutput = ` +{ + "metadata": { + "number": "7", + "animal": "auk" + } +}` + +// HandleMetadataSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata` +// on the test handler mux that responds with a `Metadata` response. +func HandleMetadataSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata", 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) + }) +} + +// ListTypesExpected represents the expected object from a ListTypes request. +var ListTypesExpected = []string{ + "OS::Nova::Server", + "OS::Heat::RandomString", + "OS::Swift::Container", + "OS::Trove::Instance", + "OS::Nova::FloatingIPAssociation", + "OS::Cinder::VolumeAttachment", + "OS::Nova::FloatingIP", + "OS::Nova::KeyPair", +} + +// ListTypesOutput represents the response body from a ListTypes request. +const ListTypesOutput = ` +{ + "resource_types": [ + "OS::Nova::Server", + "OS::Heat::RandomString", + "OS::Swift::Container", + "OS::Trove::Instance", + "OS::Nova::FloatingIPAssociation", + "OS::Cinder::VolumeAttachment", + "OS::Nova::FloatingIP", + "OS::Nova::KeyPair" + ] +}` + +// HandleListTypesSuccessfully creates an HTTP handler at `/resource_types` +// on the test handler mux that responds with a `ListTypes` response. +func HandleListTypesSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/resource_types", 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) + }) +} + +// GetSchemaExpected represents the expected object from a Schema request. +var GetSchemaExpected = &TypeSchema{ + Attributes: map[string]interface{}{ + "an_attribute": map[string]interface{}{ + "description": "An attribute description .", + }, + }, + Properties: map[string]interface{}{ + "a_property": map[string]interface{}{ + "update_allowed": false, + "required": true, + "type": "string", + "description": "A resource description.", + }, + }, + ResourceType: "OS::Heat::AResourceName", +} + +// GetSchemaOutput represents the response body from a Schema request. +const GetSchemaOutput = ` +{ + "attributes": { + "an_attribute": { + "description": "An attribute description ." + } + }, + "properties": { + "a_property": { + "update_allowed": false, + "required": true, + "type": "string", + "description": "A resource description." + } + }, + "resource_type": "OS::Heat::AResourceName" +}` + +// HandleGetSchemaSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName` +// on the test handler mux that responds with a `Schema` response. +func HandleGetSchemaSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName", 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) + }) +} + +// GetTemplateExpected represents the expected object from a Template request. +var GetTemplateExpected = &TypeTemplate{ + HeatTemplateFormatVersion: "2012-12-12", + Outputs: map[string]interface{}{ + "private_key": map[string]interface{}{ + "Description": "The private key if it has been saved.", + "Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"private_key\"]}", + }, + "public_key": map[string]interface{}{ + "Description": "The public key.", + "Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"public_key\"]}", + }, + }, + Parameters: map[string]interface{}{ + "name": map[string]interface{}{ + "Description": "The name of the key pair.", + "Type": "String", + }, + "public_key": map[string]interface{}{ + "Description": "The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.", + "Type": "String", + }, + "save_private_key": map[string]interface{}{ + "AllowedValues": []string{ + "True", + "true", + "False", + "false", + }, + "Default": false, + "Description": "True if the system should remember a generated private key; False otherwise.", + "Type": "String", + }, + }, + Resources: map[string]interface{}{ + "KeyPair": map[string]interface{}{ + "Properties": map[string]interface{}{ + "name": map[string]interface{}{ + "Ref": "name", + }, + "public_key": map[string]interface{}{ + "Ref": "public_key", + }, + "save_private_key": map[string]interface{}{ + "Ref": "save_private_key", + }, + }, + "Type": "OS::Nova::KeyPair", + }, + }, +} + +// GetTemplateOutput represents the response body from a Template request. +const GetTemplateOutput = ` +{ + "HeatTemplateFormatVersion": "2012-12-12", + "Outputs": { + "private_key": { + "Description": "The private key if it has been saved.", + "Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"private_key\"]}" + }, + "public_key": { + "Description": "The public key.", + "Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"public_key\"]}" + } + }, + "Parameters": { + "name": { + "Description": "The name of the key pair.", + "Type": "String" + }, + "public_key": { + "Description": "The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.", + "Type": "String" + }, + "save_private_key": { + "AllowedValues": [ + "True", + "true", + "False", + "false" + ], + "Default": false, + "Description": "True if the system should remember a generated private key; False otherwise.", + "Type": "String" + } + }, + "Resources": { + "KeyPair": { + "Properties": { + "name": { + "Ref": "name" + }, + "public_key": { + "Ref": "public_key" + }, + "save_private_key": { + "Ref": "save_private_key" + } + }, + "Type": "OS::Nova::KeyPair" + } + } +}` + +// HandleGetTemplateSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName/template` +// on the test handler mux that responds with a `Template` response. +func HandleGetTemplateSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName/template", 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/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests.go new file mode 100644 index 0000000000..2a66edc8c2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests.go @@ -0,0 +1,126 @@ +package stackresources + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Find retrieves stack resources for the given stack name. +func Find(c *gophercloud.ServiceClient, stackName string) FindResult { + var res FindResult + + // Send request to API + _, res.Err = c.Request("GET", findURL(c, stackName), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToStackResourceListQuery() (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"` + + // Include resources from nest stacks up to Depth levels of recursion. + Depth int `q:"nested_depth"` +} + +// ToStackResourceListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToStackResourceListQuery() (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.ToStackResourceListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + createPageFn := func(r pagination.PageResult) pagination.Page { + p := ResourcePage{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 string) GetResult { + var res GetResult + + // Send request to API + _, res.Err = c.Request("GET", getURL(c, stackName, stackID, resourceName), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Metadata retreives the metadata for the given stack resource. +func Metadata(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) MetadataResult { + var res MetadataResult + + // Send request to API + _, res.Err = c.Request("GET", metadataURL(c, stackName, stackID, resourceName), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// ListTypes makes a request against the API to list resource types. +func ListTypes(client *gophercloud.ServiceClient) pagination.Pager { + url := listTypesURL(client) + + createPageFn := func(r pagination.PageResult) pagination.Page { + return ResourceTypePage{pagination.SinglePageBase(r)} + } + + return pagination.NewPager(client, url, createPageFn) +} + +// Schema retreives the schema for the given resource type. +func Schema(c *gophercloud.ServiceClient, resourceType string) SchemaResult { + var res SchemaResult + + // Send request to API + _, res.Err = c.Request("GET", schemaURL(c, resourceType), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Template retreives the template representation for the given resource type. +func Template(c *gophercloud.ServiceClient, resourceType string) TemplateResult { + var res TemplateResult + + // Send request to API + _, res.Err = c.Request("GET", templateURL(c, resourceType), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests_test.go new file mode 100644 index 0000000000..f1378785f8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/requests_test.go @@ -0,0 +1,107 @@ +package stackresources + +import ( + "testing" + + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestFindResources(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleFindSuccessfully(t, FindOutput) + + actual, err := Find(fake.ServiceClient(), "hello_world").Extract() + th.AssertNoErr(t, err) + + expected := FindExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestListResources(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 := ExtractResources(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ListExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestGetResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t, GetOutput) + + actual, err := Get(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() + th.AssertNoErr(t, err) + + expected := GetExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestResourceMetadata(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMetadataSuccessfully(t, MetadataOutput) + + actual, err := Metadata(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() + th.AssertNoErr(t, err) + + expected := MetadataExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestListResourceTypes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListTypesSuccessfully(t, ListTypesOutput) + + count := 0 + err := ListTypes(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractResourceTypes(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ListTypesExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestGetResourceSchema(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSchemaSuccessfully(t, GetSchemaOutput) + + actual, err := Schema(fake.ServiceClient(), "OS::Heat::AResourceName").Extract() + th.AssertNoErr(t, err) + + expected := GetSchemaExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestGetResourceTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetTemplateSuccessfully(t, GetTemplateOutput) + + actual, err := Template(fake.ServiceClient(), "OS::Heat::AResourceName").Extract() + th.AssertNoErr(t, err) + + expected := GetTemplateExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/results.go new file mode 100644 index 0000000000..13f5dd21fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/results.go @@ -0,0 +1,250 @@ +package stackresources + +import ( + "time" + + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Resource represents a stack resource. +type Resource struct { + Links []gophercloud.Link `mapstructure:"links"` + LogicalID string `mapstructure:"logical_resource_id"` + Name string `mapstructure:"resource_name"` + PhysicalID string `mapstructure:"physical_resource_id"` + RequiredBy []interface{} `mapstructure:"required_by"` + Status string `mapstructure:"resource_status"` + StatusReason string `mapstructure:"resource_status_reason"` + Type string `mapstructure:"resource_type"` + UpdatedTime time.Time `mapstructure:"-"` +} + +// FindResult represents the result of a Find operation. +type FindResult struct { + gophercloud.Result +} + +// Extract returns a slice of Resource objects and is called after a +// Find operation. +func (r FindResult) Extract() ([]Resource, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Res []Resource `mapstructure:"resources"` + } + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + resources := r.Body.(map[string]interface{})["resources"].([]interface{}) + + for i, resourceRaw := range resources { + resource := resourceRaw.(map[string]interface{}) + if date, ok := resource["updated_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + res.Res[i].UpdatedTime = t + } + } + + return res.Res, nil +} + +// ResourcePage 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 ResourcePage struct { + pagination.MarkerPageBase +} + +// IsEmpty returns true if a page contains no Server results. +func (r ResourcePage) IsEmpty() (bool, error) { + resources, err := ExtractResources(r) + if err != nil { + return true, err + } + return len(resources) == 0, nil +} + +// LastMarker returns the last container name in a ListResult. +func (r ResourcePage) LastMarker() (string, error) { + resources, err := ExtractResources(r) + if err != nil { + return "", err + } + if len(resources) == 0 { + return "", nil + } + return resources[len(resources)-1].PhysicalID, nil +} + +// ExtractResources interprets the results of a single page from a List() call, producing a slice of Resource entities. +func ExtractResources(page pagination.Page) ([]Resource, error) { + casted := page.(ResourcePage).Body + + var response struct { + Resources []Resource `mapstructure:"resources"` + } + err := mapstructure.Decode(casted, &response) + + resources := casted.(map[string]interface{})["resources"].([]interface{}) + + for i, resourceRaw := range resources { + resource := resourceRaw.(map[string]interface{}) + if date, ok := resource["updated_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + response.Resources[i].UpdatedTime = t + } + } + + return response.Resources, err +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a Resource object and is called after a +// Get operation. +func (r GetResult) Extract() (*Resource, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Res *Resource `mapstructure:"resource"` + } + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + resource := r.Body.(map[string]interface{})["resource"].(map[string]interface{}) + + if date, ok := resource["updated_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + res.Res.UpdatedTime = t + } + + return res.Res, nil +} + +// MetadataResult represents the result of a Metadata operation. +type MetadataResult struct { + gophercloud.Result +} + +// Extract returns a map object and is called after a +// Metadata operation. +func (r MetadataResult) Extract() (map[string]string, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Meta map[string]string `mapstructure:"metadata"` + } + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return res.Meta, nil +} + +// ResourceTypePage abstracts the raw results of making a ListTypes() 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 ExtractResourceTypes call. +type ResourceTypePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a ResourceTypePage contains no resource types. +func (r ResourceTypePage) IsEmpty() (bool, error) { + rts, err := ExtractResourceTypes(r) + if err != nil { + return true, err + } + return len(rts) == 0, nil +} + +// ExtractResourceTypes extracts and returns resource types. +func ExtractResourceTypes(page pagination.Page) ([]string, error) { + var response struct { + ResourceTypes []string `mapstructure:"resource_types"` + } + + err := mapstructure.Decode(page.(ResourceTypePage).Body, &response) + return response.ResourceTypes, err +} + +// TypeSchema represents a stack resource schema. +type TypeSchema struct { + Attributes map[string]interface{} `mapstructure:"attributes"` + Properties map[string]interface{} `mapstrucutre:"properties"` + ResourceType string `mapstructure:"resource_type"` +} + +// SchemaResult represents the result of a Schema operation. +type SchemaResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a TypeSchema object and is called after a +// Schema operation. +func (r SchemaResult) Extract() (*TypeSchema, error) { + if r.Err != nil { + return nil, r.Err + } + + var res TypeSchema + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return &res, nil +} + +// TypeTemplate represents a stack resource template. +type TypeTemplate struct { + HeatTemplateFormatVersion string + Outputs map[string]interface{} + Parameters map[string]interface{} + Resources map[string]interface{} +} + +// TemplateResult represents the result of a Template operation. +type TemplateResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a TypeTemplate object and is called after a +// Template operation. +func (r TemplateResult) Extract() (*TypeTemplate, error) { + if r.Err != nil { + return nil, r.Err + } + + var res TypeTemplate + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/urls.go new file mode 100644 index 0000000000..ef078d9c9b --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources/urls.go @@ -0,0 +1,31 @@ +package stackresources + +import "github.com/rackspace/gophercloud" + +func findURL(c *gophercloud.ServiceClient, stackName string) string { + return c.ServiceURL("stacks", stackName, "resources") +} + +func listURL(c *gophercloud.ServiceClient, stackName, stackID string) string { + return c.ServiceURL("stacks", stackName, stackID, "resources") +} + +func getURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string { + return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName) +} + +func metadataURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string { + return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "metadata") +} + +func listTypesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("resource_types") +} + +func schemaURL(c *gophercloud.ServiceClient, typeName string) string { + return c.ServiceURL("resource_types", typeName) +} + +func templateURL(c *gophercloud.ServiceClient, typeName string) string { + return c.ServiceURL("resource_types", typeName, "template") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/doc.go new file mode 100644 index 0000000000..19231b5137 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/doc.go @@ -0,0 +1,8 @@ +// Package stacks provides operation for working with Heat stacks. A stack is a +// group of resources (servers, load balancers, databases, and so forth) +// combined to fulfill a useful purpose. Based on a template, Heat orchestration +// engine creates an instantiated set of resources (a stack) to run the +// application framework or component specified (in the template). A stack is a +// running instance of a template. The result of creating a stack is a deployment +// of the application framework or component. +package stacks diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/fixtures.go new file mode 100644 index 0000000000..6d3e9597a2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/fixtures.go @@ -0,0 +1,374 @@ +package stacks + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/rackspace/gophercloud" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// CreateExpected represents the expected object from a Create request. +var CreateExpected = &CreatedStack{ + ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://168.28.170.117:8004/v1/98606384f58drad0bhdb7d02779549ac/stacks/stackcreated/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + Rel: "self", + }, + }, +} + +// CreateOutput represents the response body from a Create request. +const CreateOutput = ` +{ + "stack": { + "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + "links": [ + { + "href": "http://168.28.170.117:8004/v1/98606384f58drad0bhdb7d02779549ac/stacks/stackcreated/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + "rel": "self" + } + ] + } +}` + +// HandleCreateSuccessfully creates an HTTP handler at `/stacks` on the test handler mux +// that responds with a `Create` response. +func HandleCreateSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, output) + }) +} + +// ListExpected represents the expected object from a List request. +var ListExpected = []ListedStack{ + ListedStack{ + Description: "Simple template to test heat commands", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + Rel: "self", + }, + }, + StatusReason: "Stack CREATE completed successfully", + Name: "postman_stack", + CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC), + Status: "CREATE_COMPLETE", + ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + }, + ListedStack{ + Description: "Simple template to test heat commands", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", + Rel: "self", + }, + }, + StatusReason: "Stack successfully updated", + Name: "gophercloud-test-stack-2", + CreationTime: time.Date(2014, 12, 11, 17, 39, 16, 0, time.UTC), + UpdatedTime: time.Date(2014, 12, 11, 17, 40, 37, 0, time.UTC), + Status: "UPDATE_COMPLETE", + ID: "db6977b2-27aa-4775-9ae7-6213212d4ada", + }, +} + +// FullListOutput represents the response body from a List request without a marker. +const FullListOutput = ` +{ + "stacks": [ + { + "description": "Simple template to test heat commands", + "links": [ + { + "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + "rel": "self" + } + ], + "stack_status_reason": "Stack CREATE completed successfully", + "stack_name": "postman_stack", + "creation_time": "2015-02-03T20:07:39Z", + "updated_time": null, + "stack_status": "CREATE_COMPLETE", + "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87" + }, + { + "description": "Simple template to test heat commands", + "links": [ + { + "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", + "rel": "self" + } + ], + "stack_status_reason": "Stack successfully updated", + "stack_name": "gophercloud-test-stack-2", + "creation_time": "2014-12-11T17:39:16Z", + "updated_time": "2014-12-11T17:40:37Z", + "stack_status": "UPDATE_COMPLETE", + "id": "db6977b2-27aa-4775-9ae7-6213212d4ada" + } + ] +} +` + +// HandleListSuccessfully creates an HTTP handler at `/stacks` on the test handler mux +// that responds with a `List` response. +func HandleListSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks", 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 "db6977b2-27aa-4775-9ae7-6213212d4ada": + fmt.Fprintf(w, `[]`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +// GetExpected represents the expected object from a Get request. +var GetExpected = &RetrievedStack{ + DisableRollback: true, + Description: "Simple template to test heat commands", + Parameters: map[string]string{ + "flavor": "m1.tiny", + "OS::stack_name": "postman_stack", + "OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + }, + StatusReason: "Stack CREATE completed successfully", + Name: "postman_stack", + Outputs: []map[string]interface{}{}, + CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC), + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + Rel: "self", + }, + }, + Capabilities: []interface{}{}, + NotificationTopics: []interface{}{}, + Status: "CREATE_COMPLETE", + ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + TemplateDescription: "Simple template to test heat commands", +} + +// GetOutput represents the response body from a Get request. +const GetOutput = ` +{ + "stack": { + "disable_rollback": true, + "description": "Simple template to test heat commands", + "parameters": { + "flavor": "m1.tiny", + "OS::stack_name": "postman_stack", + "OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87" + }, + "stack_status_reason": "Stack CREATE completed successfully", + "stack_name": "postman_stack", + "outputs": [], + "creation_time": "2015-02-03T20:07:39Z", + "links": [ + { + "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + "rel": "self" + } + ], + "capabilities": [], + "notification_topics": [], + "timeout_mins": null, + "stack_status": "CREATE_COMPLETE", + "updated_time": null, + "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + "template_description": "Simple template to test heat commands" + } +} +` + +// HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` +// on the test handler mux that responds with a `Get` response. +func HandleGetSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", 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) + }) +} + +// HandleUpdateSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` +// on the test handler mux that responds with an `Update` response. +func HandleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + 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.StatusAccepted) + }) +} + +// HandleDeleteSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` +// on the test handler mux that responds with a `Delete` response. +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + 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.StatusNoContent) + }) +} + +// GetExpected represents the expected object from a Get request. +var PreviewExpected = &PreviewedStack{ + DisableRollback: true, + Description: "Simple template to test heat commands", + Parameters: map[string]string{ + "flavor": "m1.tiny", + "OS::stack_name": "postman_stack", + "OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + }, + StatusReason: "Stack CREATE completed successfully", + Name: "postman_stack", + CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC), + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", + Rel: "self", + }, + }, + Capabilities: []interface{}{}, + NotificationTopics: []interface{}{}, + Status: "CREATE_COMPLETE", + ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + TemplateDescription: "Simple template to test heat commands", +} + +// HandlePreviewSuccessfully creates an HTTP handler at `/stacks/preview` +// on the test handler mux that responds with a `Preview` response. +func HandlePreviewSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/preview", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + 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) + }) +} + +// AbandonExpected represents the expected object from an Abandon request. +var AbandonExpected = &AbandonedStack{ + Status: "COMPLETE", + Name: "postman_stack", + Template: map[string]interface{}{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": map[string]interface{}{ + "flavor": map[string]interface{}{ + "default": "m1.tiny", + "type": "string", + }, + }, + "resources": map[string]interface{}{ + "hello_world": map[string]interface{}{ + "type": "OS::Nova::Server", + "properties": map[string]interface{}{ + "key_name": "heat_key", + "flavor": map[string]interface{}{ + "get_param": "flavor", + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", + }, + }, + }, + }, + Action: "CREATE", + ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + Resources: map[string]interface{}{ + "hello_world": map[string]interface{}{ + "status": "COMPLETE", + "name": "hello_world", + "resource_id": "8a310d36-46fc-436f-8be4-37a696b8ac63", + "action": "CREATE", + "type": "OS::Nova::Server", + }, + }, +} + +// AbandonOutput represents the response body from an Abandon request. +const AbandonOutput = ` +{ + "status": "COMPLETE", + "name": "postman_stack", + "template": { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type": "OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + }, + "action": "CREATE", + "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", + "resources": { + "hello_world": { + "status": "COMPLETE", + "name": "hello_world", + "resource_id": "8a310d36-46fc-436f-8be4-37a696b8ac63", + "action": "CREATE", + "type": "OS::Nova::Server", + } + } +}` + +// HandleAbandonSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon` +// on the test handler mux that responds with an `Abandon` response. +func HandleAbandonSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + 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, AbandonOutput) + }) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests.go new file mode 100644 index 0000000000..c0388c366d --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests.go @@ -0,0 +1,520 @@ +package stacks + +import ( + "errors" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// Rollback is used to specify whether or not a stack can be rolled back. +type Rollback *bool + +var ( + disable = true + // Disable is used to specify that a stack cannot be rolled back. + Disable Rollback = &disable + enable = false + // Enable is used to specify that a stack can be rolled back. + Enable Rollback = &enable +) + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToStackCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // (REQUIRED) The name of the stack. It must start with an alphabetic character. + Name string + // (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate. + // This value is ignored if Template is supplied inline. + TemplateURL string + // (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value + // is a stringified version of the JSON/YAML template. Since the template will likely + // be located in a file, one way to set this variable is by using ioutil.ReadFile: + // import "io/ioutil" + // var opts stacks.CreateOpts + // b, err := ioutil.ReadFile("path/to/you/template/file.json") + // if err != nil { + // // handle error... + // } + // opts.Template = string(b) + Template string + // (OPTIONAL) Enables or disables deletion of all stack resources when a stack + // creation fails. Default is true, meaning all resources are not deleted when + // stack creation fails. + DisableRollback Rollback + // (OPTIONAL) A stringified JSON environment for the stack. + Environment string + // (OPTIONAL) A map that maps file names to file contents. It can also be used + // to pass provider template contents. Example: + // Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}` + Files map[string]interface{} + // (OPTIONAL) User-defined parameters to pass to the template. + Parameters map[string]string + // (OPTIONAL) The timeout for stack creation in minutes. + Timeout int +} + +// ToStackCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) { + s := make(map[string]interface{}) + + if opts.Name == "" { + return s, errors.New("Required field 'Name' not provided.") + } + s["stack_name"] = opts.Name + + if opts.Template != "" { + s["template"] = opts.Template + } else if opts.TemplateURL != "" { + s["template_url"] = opts.TemplateURL + } else { + return s, errors.New("Either Template or TemplateURL must be provided.") + } + + if opts.DisableRollback != nil { + s["disable_rollback"] = &opts.DisableRollback + } + + if opts.Environment != "" { + s["environment"] = opts.Environment + } + if opts.Files != nil { + s["files"] = opts.Files + } + if opts.Parameters != nil { + s["parameters"] = opts.Parameters + } + + if opts.Timeout != 0 { + s["timeout_mins"] = opts.Timeout + } + + return s, nil +} + +// Create accepts a CreateOpts struct and creates a new stack using the values +// provided. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToStackCreateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, + }) + return res +} + +// AdoptOptsBuilder is the interface options structs have to satisfy in order +// to be used in the Adopt function in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type AdoptOptsBuilder interface { + ToStackAdoptMap() (map[string]interface{}, error) +} + +// AdoptOpts is the common options struct used in this package's Adopt +// operation. +type AdoptOpts struct { + // (REQUIRED) Existing resources data represented as a string to add to the + // new stack. Data returned by Abandon could be provided as AdoptsStackData. + AdoptStackData string + // (REQUIRED) The name of the stack. It must start with an alphabetic character. + Name string + // (REQUIRED) The timeout for stack creation in minutes. + Timeout int + // (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate. + // This value is ignored if Template is supplied inline. + TemplateURL string + // (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value + // is a stringified version of the JSON/YAML template. Since the template will likely + // be located in a file, one way to set this variable is by using ioutil.ReadFile: + // import "io/ioutil" + // var opts stacks.CreateOpts + // b, err := ioutil.ReadFile("path/to/you/template/file.json") + // if err != nil { + // // handle error... + // } + // opts.Template = string(b) + Template string + // (OPTIONAL) Enables or disables deletion of all stack resources when a stack + // creation fails. Default is true, meaning all resources are not deleted when + // stack creation fails. + DisableRollback Rollback + // (OPTIONAL) A stringified JSON environment for the stack. + Environment string + // (OPTIONAL) A map that maps file names to file contents. It can also be used + // to pass provider template contents. Example: + // Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}` + Files map[string]interface{} + // (OPTIONAL) User-defined parameters to pass to the template. + Parameters map[string]string +} + +// ToStackAdoptMap casts a CreateOpts struct to a map. +func (opts AdoptOpts) ToStackAdoptMap() (map[string]interface{}, error) { + s := make(map[string]interface{}) + + if opts.Name == "" { + return s, errors.New("Required field 'Name' not provided.") + } + s["stack_name"] = opts.Name + + if opts.Template != "" { + s["template"] = opts.Template + } else if opts.TemplateURL != "" { + s["template_url"] = opts.TemplateURL + } else { + return s, errors.New("Either Template or TemplateURL must be provided.") + } + + if opts.AdoptStackData == "" { + return s, errors.New("Required field 'AdoptStackData' not provided.") + } + s["adopt_stack_data"] = opts.AdoptStackData + + if opts.DisableRollback != nil { + s["disable_rollback"] = &opts.DisableRollback + } + + if opts.Environment != "" { + s["environment"] = opts.Environment + } + if opts.Files != nil { + s["files"] = opts.Files + } + if opts.Parameters != nil { + s["parameters"] = opts.Parameters + } + + if opts.Timeout == 0 { + return nil, errors.New("Required field 'Timeout' not provided.") + } + s["timeout_mins"] = opts.Timeout + + return map[string]interface{}{"stack": s}, nil +} + +// Adopt accepts an AdoptOpts struct and creates a new stack using the resources +// from another stack. +func Adopt(c *gophercloud.ServiceClient, opts AdoptOptsBuilder) AdoptResult { + var res AdoptResult + + reqBody, err := opts.ToStackAdoptMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("POST", adoptURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{201}, + }) + return res +} + +// SortDir is a type for specifying in which direction to sort a list of stacks. +type SortDir string + +// SortKey is a type for specifying by which key to sort a list of stacks. +type SortKey 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" + // SortStatus is used to sort a list of stacks by status. + SortStatus SortKey = "status" + // SortCreatedAt is used to sort a list of stacks by date created. + SortCreatedAt SortKey = "created_at" + // SortUpdatedAt is used to sort a list of stacks by date updated. + SortUpdatedAt SortKey = "updated_at" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToStackListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the network attributes you want to see returned. SortKey allows you to sort +// by a particular network attribute. SortDir sets the direction, and is either +// `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Status string `q:"status"` + Name string `q:"name"` + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey SortKey `q:"sort_keys"` + SortDir SortDir `q:"sort_dir"` +} + +// ToStackListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToStackListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// stacks. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToStackListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + createPage := func(r pagination.PageResult) pagination.Page { + return StackPage{pagination.SinglePageBase(r)} + } + return pagination.NewPager(c, url, createPage) +} + +// Get retreives a stack based on the stack name and stack ID. +func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult { + var res GetResult + + // Send request to API + _, res.Err = c.Request("GET", getURL(c, stackName, stackID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the Update operation in this package. +type UpdateOptsBuilder interface { + ToStackUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate. + // This value is ignored if Template is supplied inline. + TemplateURL string + // (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value + // is a stringified version of the JSON/YAML template. Since the template will likely + // be located in a file, one way to set this variable is by using ioutil.ReadFile: + // import "io/ioutil" + // var opts stacks.CreateOpts + // b, err := ioutil.ReadFile("path/to/you/template/file.json") + // if err != nil { + // // handle error... + // } + // opts.Template = string(b) + Template string + // (OPTIONAL) A stringified JSON environment for the stack. + Environment string + // (OPTIONAL) A map that maps file names to file contents. It can also be used + // to pass provider template contents. Example: + // Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}` + Files map[string]interface{} + // (OPTIONAL) User-defined parameters to pass to the template. + Parameters map[string]string + // (OPTIONAL) The timeout for stack creation in minutes. + Timeout int +} + +// ToStackUpdateMap casts a CreateOpts struct to a map. +func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) { + s := make(map[string]interface{}) + + if opts.Template != "" { + s["template"] = opts.Template + } else if opts.TemplateURL != "" { + s["template_url"] = opts.TemplateURL + } else { + return s, errors.New("Either Template or TemplateURL must be provided.") + } + + if opts.Environment != "" { + s["environment"] = opts.Environment + } + + if opts.Files != nil { + s["files"] = opts.Files + } + + if opts.Parameters != nil { + s["parameters"] = opts.Parameters + } + + if opts.Timeout != 0 { + s["timeout_mins"] = opts.Timeout + } + + return s, nil +} + +// Update accepts an UpdateOpts struct and updates an existing stack using the values +// provided. +func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToStackUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("PUT", updateURL(c, stackName, stackID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, + }) + return res +} + +// Delete deletes a stack based on the stack name and stack ID. +func Delete(c *gophercloud.ServiceClient, stackName, stackID string) DeleteResult { + var res DeleteResult + + // Send request to API + _, res.Err = c.Request("DELETE", deleteURL(c, stackName, stackID), gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return res +} + +// PreviewOptsBuilder is the interface options structs have to satisfy in order +// to be used in the Preview operation in this package. +type PreviewOptsBuilder interface { + ToStackPreviewMap() (map[string]interface{}, error) +} + +// PreviewOpts contains the common options struct used in this package's Preview +// operation. +type PreviewOpts struct { + // (REQUIRED) The name of the stack. It must start with an alphabetic character. + Name string + // (REQUIRED) The timeout for stack creation in minutes. + Timeout int + // (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate. + // This value is ignored if Template is supplied inline. + TemplateURL string + // (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value + // is a stringified version of the JSON/YAML template. Since the template will likely + // be located in a file, one way to set this variable is by using ioutil.ReadFile: + // import "io/ioutil" + // var opts stacks.CreateOpts + // b, err := ioutil.ReadFile("path/to/you/template/file.json") + // if err != nil { + // // handle error... + // } + // opts.Template = string(b) + Template string + // (OPTIONAL) Enables or disables deletion of all stack resources when a stack + // creation fails. Default is true, meaning all resources are not deleted when + // stack creation fails. + DisableRollback Rollback + // (OPTIONAL) A stringified JSON environment for the stack. + Environment string + // (OPTIONAL) A map that maps file names to file contents. It can also be used + // to pass provider template contents. Example: + // Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}` + Files map[string]interface{} + // (OPTIONAL) User-defined parameters to pass to the template. + Parameters map[string]string +} + +// ToStackPreviewMap casts a PreviewOpts struct to a map. +func (opts PreviewOpts) ToStackPreviewMap() (map[string]interface{}, error) { + s := make(map[string]interface{}) + + if opts.Name == "" { + return s, errors.New("Required field 'Name' not provided.") + } + s["stack_name"] = opts.Name + + if opts.Template != "" { + s["template"] = opts.Template + } else if opts.TemplateURL != "" { + s["template_url"] = opts.TemplateURL + } else { + return s, errors.New("Either Template or TemplateURL must be provided.") + } + + if opts.DisableRollback != nil { + s["disable_rollback"] = &opts.DisableRollback + } + + if opts.Environment != "" { + s["environment"] = opts.Environment + } + if opts.Files != nil { + s["files"] = opts.Files + } + if opts.Parameters != nil { + s["parameters"] = opts.Parameters + } + + if opts.Timeout != 0 { + s["timeout_mins"] = opts.Timeout + } + + return s, nil +} + +// Preview accepts a PreviewOptsBuilder interface and creates a preview of a stack using the values +// provided. +func Preview(c *gophercloud.ServiceClient, opts PreviewOptsBuilder) PreviewResult { + var res PreviewResult + + reqBody, err := opts.ToStackPreviewMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Request("POST", previewURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// Abandon deletes the stack with the provided stackName and stackID, but leaves its +// resources intact, and returns data describing the stack and its resources. +func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) AbandonResult { + var res AbandonResult + + // Send request to API + _, res.Err = c.Request("DELETE", abandonURL(c, stackName, stackID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests_test.go new file mode 100644 index 0000000000..1e32ca2a9e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/requests_test.go @@ -0,0 +1,217 @@ +package stacks + +import ( + "testing" + + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestCreateStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t, CreateOutput) + + createOpts := CreateOpts{ + Name: "stackcreated", + Timeout: 60, + Template: ` + { + "stack_name": "postman_stack", + "template": { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + } + }`, + DisableRollback: Disable, + } + actual, err := Create(fake.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + + expected := CreateExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestAdoptStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t, CreateOutput) + + adoptOpts := AdoptOpts{ + AdoptStackData: `{environment{parameters{}}}`, + Name: "stackcreated", + Timeout: 60, + Template: ` + { + "stack_name": "postman_stack", + "template": { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + } + }`, + DisableRollback: Disable, + } + actual, err := Adopt(fake.ServiceClient(), adoptOpts).Extract() + th.AssertNoErr(t, err) + + expected := CreateExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestListStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSuccessfully(t, FullListOutput) + + count := 0 + err := List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := ExtractStacks(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ListExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestGetStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t, GetOutput) + + actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + th.AssertNoErr(t, err) + + expected := GetExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestUpdateStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateSuccessfully(t) + + updateOpts := UpdateOpts{ + Template: ` + { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + }`, + } + err := Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + + err := Delete(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestPreviewStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePreviewSuccessfully(t, GetOutput) + + previewOpts := PreviewOpts{ + Name: "stackcreated", + Timeout: 60, + Template: ` + { + "stack_name": "postman_stack", + "template": { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + } + }`, + DisableRollback: Disable, + } + actual, err := Preview(fake.ServiceClient(), previewOpts).Extract() + th.AssertNoErr(t, err) + + expected := PreviewExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/results.go new file mode 100644 index 0000000000..ff971e8b8b --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/results.go @@ -0,0 +1,296 @@ +package stacks + +import ( + "encoding/json" + "time" + + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// CreatedStack represents the object extracted from a Create operation. +type CreatedStack struct { + ID string `mapstructure:"id"` + Links []gophercloud.Link `mapstructure:"links"` +} + +// CreateResult represents the result of a Create operation. +type CreateResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a CreatedStack object and is called after a +// Create operation. +func (r CreateResult) Extract() (*CreatedStack, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Stack *CreatedStack `mapstructure:"stack"` + } + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return res.Stack, nil +} + +// AdoptResult represents the result of an Adopt operation. AdoptResult has the +// same form as CreateResult. +type AdoptResult struct { + CreateResult +} + +// StackPage is a pagination.Pager that is returned from a call to the List function. +type StackPage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a ListResult contains no Stacks. +func (r StackPage) IsEmpty() (bool, error) { + stacks, err := ExtractStacks(r) + if err != nil { + return true, err + } + return len(stacks) == 0, nil +} + +// ListedStack represents an element in the slice extracted from a List operation. +type ListedStack struct { + CreationTime time.Time `mapstructure:"-"` + Description string `mapstructure:"description"` + ID string `mapstructure:"id"` + Links []gophercloud.Link `mapstructure:"links"` + Name string `mapstructure:"stack_name"` + Status string `mapstructure:"stack_status"` + StatusReason string `mapstructure:"stack_status_reason"` + UpdatedTime time.Time `mapstructure:"-"` +} + +// ExtractStacks extracts and returns a slice of ListedStack. It is used while iterating +// over a stacks.List call. +func ExtractStacks(page pagination.Page) ([]ListedStack, error) { + var res struct { + Stacks []ListedStack `mapstructure:"stacks"` + } + + err := mapstructure.Decode(page.(StackPage).Body, &res) + if err != nil { + return nil, err + } + + rawStacks := (((page.(StackPage).Body).(map[string]interface{}))["stacks"]).([]interface{}) + for i := range rawStacks { + thisStack := (rawStacks[i]).(map[string]interface{}) + + if t, ok := thisStack["creation_time"].(string); ok && t != "" { + creationTime, err := time.Parse(time.RFC3339, t) + if err != nil { + return res.Stacks, err + } + res.Stacks[i].CreationTime = creationTime + } + + if t, ok := thisStack["updated_time"].(string); ok && t != "" { + updatedTime, err := time.Parse(time.RFC3339, t) + if err != nil { + return res.Stacks, err + } + res.Stacks[i].UpdatedTime = updatedTime + } + } + + return res.Stacks, nil +} + +// RetrievedStack represents the object extracted from a Get operation. +type RetrievedStack struct { + Capabilities []interface{} `mapstructure:"capabilities"` + CreationTime time.Time `mapstructure:"-"` + Description string `mapstructure:"description"` + DisableRollback bool `mapstructure:"disable_rollback"` + ID string `mapstructure:"id"` + Links []gophercloud.Link `mapstructure:"links"` + NotificationTopics []interface{} `mapstructure:"notification_topics"` + Outputs []map[string]interface{} `mapstructure:"outputs"` + Parameters map[string]string `mapstructure:"parameters"` + Name string `mapstructure:"stack_name"` + Status string `mapstructure:"stack_status"` + StatusReason string `mapstructure:"stack_status_reason"` + TemplateDescription string `mapstructure:"template_description"` + Timeout int `mapstructure:"timeout_mins"` + UpdatedTime time.Time `mapstructure:"-"` +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a RetrievedStack object and is called after a +// Get operation. +func (r GetResult) Extract() (*RetrievedStack, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Stack *RetrievedStack `mapstructure:"stack"` + } + + config := &mapstructure.DecoderConfig{ + Result: &res, + WeaklyTypedInput: true, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + + if err := decoder.Decode(r.Body); err != nil { + return nil, err + } + + b := r.Body.(map[string]interface{})["stack"].(map[string]interface{}) + + if date, ok := b["creation_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + res.Stack.CreationTime = t + } + + if date, ok := b["updated_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + res.Stack.UpdatedTime = t + } + + return res.Stack, err +} + +// UpdateResult represents the result of a Update operation. +type UpdateResult struct { + gophercloud.ErrResult +} + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// PreviewedStack represents the result of a Preview operation. +type PreviewedStack struct { + Capabilities []interface{} `mapstructure:"capabilities"` + CreationTime time.Time `mapstructure:"-"` + Description string `mapstructure:"description"` + DisableRollback bool `mapstructure:"disable_rollback"` + ID string `mapstructure:"id"` + Links []gophercloud.Link `mapstructure:"links"` + Name string `mapstructure:"stack_name"` + NotificationTopics []interface{} `mapstructure:"notification_topics"` + Parameters map[string]string `mapstructure:"parameters"` + Resources []map[string]interface{} `mapstructure:"resources"` + Status string `mapstructure:"stack_status"` + StatusReason string `mapstructure:"stack_status_reason"` + TemplateDescription string `mapstructure:"template_description"` + Timeout int `mapstructure:"timeout_mins"` + UpdatedTime time.Time `mapstructure:"-"` +} + +// PreviewResult represents the result of a Preview operation. +type PreviewResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a PreviewedStack object and is called after a +// Preview operation. +func (r PreviewResult) Extract() (*PreviewedStack, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Stack *PreviewedStack `mapstructure:"stack"` + } + + config := &mapstructure.DecoderConfig{ + Result: &res, + WeaklyTypedInput: true, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + + if err := decoder.Decode(r.Body); err != nil { + return nil, err + } + + b := r.Body.(map[string]interface{})["stack"].(map[string]interface{}) + + if date, ok := b["creation_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + res.Stack.CreationTime = t + } + + if date, ok := b["updated_time"]; ok && date != nil { + t, err := time.Parse(time.RFC3339, date.(string)) + if err != nil { + return nil, err + } + res.Stack.UpdatedTime = t + } + + return res.Stack, err +} + +// AbandonedStack represents the result of an Abandon operation. +type AbandonedStack struct { + Status string `mapstructure:"status"` + Name string `mapstructure:"name"` + Template map[string]interface{} `mapstructure:"template"` + Action string `mapstructure:"action"` + ID string `mapstructure:"id"` + Resources map[string]interface{} `mapstructure:"resources"` +} + +// AbandonResult represents the result of an Abandon operation. +type AbandonResult struct { + gophercloud.Result +} + +// Extract returns a pointer to an AbandonedStack object and is called after an +// Abandon operation. +func (r AbandonResult) Extract() (*AbandonedStack, error) { + if r.Err != nil { + return nil, r.Err + } + + var res AbandonedStack + + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return &res, nil +} + +// String converts an AbandonResult to a string. This is useful to when passing +// the result of an Abandon operation to an AdoptOpts AdoptStackData field. +func (r AbandonResult) String() (string, error) { + out, err := json.Marshal(r) + if err != nil { + return "", err + } + return string(out), nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/urls.go new file mode 100644 index 0000000000..3dd2bb32ea --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/urls.go @@ -0,0 +1,35 @@ +package stacks + +import "github.com/rackspace/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("stacks") +} + +func adoptURL(c *gophercloud.ServiceClient) string { + return createURL(c) +} + +func listURL(c *gophercloud.ServiceClient) string { + return createURL(c) +} + +func getURL(c *gophercloud.ServiceClient, name, id string) string { + return c.ServiceURL("stacks", name, id) +} + +func updateURL(c *gophercloud.ServiceClient, name, id string) string { + return getURL(c, name, id) +} + +func deleteURL(c *gophercloud.ServiceClient, name, id string) string { + return getURL(c, name, id) +} + +func previewURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("stacks", "preview") +} + +func abandonURL(c *gophercloud.ServiceClient, name, id string) string { + return c.ServiceURL("stacks", name, id, "abandon") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go new file mode 100644 index 0000000000..5af0bd62a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go @@ -0,0 +1,8 @@ +// Package stacktemplates provides operations for working with Heat templates. +// A Cloud Orchestration template is a portable file, written in a user-readable +// language, that describes how a set of resources should be assembled and what +// software should be installed in order to produce a working stack. The template +// specifies what resources should be used, what attributes can be set, and other +// parameters that are critical to the successful, repeatable automation of a +// specific application stack. +package stacktemplates diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/fixtures.go new file mode 100644 index 0000000000..71fa80891e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/fixtures.go @@ -0,0 +1,118 @@ +package stacktemplates + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +// GetExpected represents the expected object from a Get request. +var GetExpected = &Template{ + Description: "Simple template to test heat commands", + HeatTemplateVersion: "2013-05-23", + Parameters: map[string]interface{}{ + "flavor": map[string]interface{}{ + "default": "m1.tiny", + "type": "string", + }, + }, + Resources: map[string]interface{}{ + "hello_world": map[string]interface{}{ + "type": "OS::Nova::Server", + "properties": map[string]interface{}{ + "key_name": "heat_key", + "flavor": map[string]interface{}{ + "get_param": "flavor", + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", + }, + }, + }, +} + +// GetOutput represents the response body from a Get request. +const GetOutput = ` +{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type": "OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } +}` + +// HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template` +// on the test handler mux that responds with a `Get` response. +func HandleGetSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template", 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) + }) +} + +// ValidateExpected represents the expected object from a Validate request. +var ValidateExpected = &ValidatedTemplate{ + Description: "Simple template to test heat commands", + Parameters: map[string]interface{}{ + "flavor": map[string]interface{}{ + "Default": "m1.tiny", + "Type": "String", + "NoEcho": "false", + "Description": "", + "Label": "flavor", + }, + }, +} + +// ValidateOutput represents the response body from a Validate request. +const ValidateOutput = ` +{ + "Description": "Simple template to test heat commands", + "Parameters": { + "flavor": { + "Default": "m1.tiny", + "Type": "String", + "NoEcho": "false", + "Description": "", + "Label": "flavor" + } + } +}` + +// HandleValidateSuccessfully creates an HTTP handler at `/validate` +// on the test handler mux that responds with a `Validate` response. +func HandleValidateSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + 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/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests.go new file mode 100644 index 0000000000..f57e226efb --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests.go @@ -0,0 +1,61 @@ +package stacktemplates + +import ( + "fmt" + + "github.com/rackspace/gophercloud" +) + +// Get retreives data for the given stack template. +func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult { + var res GetResult + _, res.Err = c.Request("GET", getURL(c, stackName, stackID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} + +// ValidateOptsBuilder describes struct types that can be accepted by the Validate call. +// The ValidateOpts struct in this package does. +type ValidateOptsBuilder interface { + ToStackTemplateValidateMap() (map[string]interface{}, error) +} + +// ValidateOpts specifies the template validation parameters. +type ValidateOpts struct { + Template map[string]interface{} + TemplateURL string +} + +// ToStackTemplateValidateMap assembles a request body based on the contents of a ValidateOpts. +func (opts ValidateOpts) ToStackTemplateValidateMap() (map[string]interface{}, error) { + vo := make(map[string]interface{}) + if opts.Template != nil { + vo["template"] = opts.Template + return vo, nil + } + if opts.TemplateURL != "" { + vo["template_url"] = opts.TemplateURL + return vo, nil + } + return vo, fmt.Errorf("One of Template or TemplateURL is required.") +} + +// Validate validates the given stack template. +func Validate(c *gophercloud.ServiceClient, opts ValidateOptsBuilder) ValidateResult { + var res ValidateResult + + reqBody, err := opts.ToStackTemplateValidateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = c.Request("POST", validateURL(c), gophercloud.RequestOpts{ + JSONBody: reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, + }) + return res +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests_test.go new file mode 100644 index 0000000000..d31c4ac9a2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/requests_test.go @@ -0,0 +1,57 @@ +package stacktemplates + +import ( + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestGetTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t, GetOutput) + + actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + th.AssertNoErr(t, err) + + expected := GetExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestValidateTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleValidateSuccessfully(t, ValidateOutput) + + opts := ValidateOpts{ + Template: map[string]interface{}{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": map[string]interface{}{ + "flavor": map[string]interface{}{ + "default": "m1.tiny", + "type": "string", + }, + }, + "resources": map[string]interface{}{ + "hello_world": map[string]interface{}{ + "type": "OS::Nova::Server", + "properties": map[string]interface{}{ + "key_name": "heat_key", + "flavor": map[string]interface{}{ + "get_param": "flavor", + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", + }, + }, + }, + }, + } + actual, err := Validate(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + expected := ValidateExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/results.go new file mode 100644 index 0000000000..ac2f24b80b --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/results.go @@ -0,0 +1,60 @@ +package stacktemplates + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" +) + +// Template represents a stack template. +type Template struct { + Description string `mapstructure:"description"` + HeatTemplateVersion string `mapstructure:"heat_template_version"` + Parameters map[string]interface{} `mapstructure:"parameters"` + Resources map[string]interface{} `mapstructure:"resources"` +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a Template object and is called after a +// Get operation. +func (r GetResult) Extract() (*Template, error) { + if r.Err != nil { + return nil, r.Err + } + + var res Template + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return &res, nil +} + +// ValidatedTemplate represents the parsed object returned from a Validate request. +type ValidatedTemplate struct { + Description string + Parameters map[string]interface{} +} + +// ValidateResult represents the result of a Validate operation. +type ValidateResult struct { + gophercloud.Result +} + +// Extract returns a pointer to a ValidatedTemplate object and is called after a +// Validate operation. +func (r ValidateResult) Extract() (*ValidatedTemplate, error) { + if r.Err != nil { + return nil, r.Err + } + + var res ValidatedTemplate + if err := mapstructure.Decode(r.Body, &res); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/urls.go new file mode 100644 index 0000000000..c30b7ca1af --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates/urls.go @@ -0,0 +1,11 @@ +package stacktemplates + +import "github.com/rackspace/gophercloud" + +func getURL(c *gophercloud.ServiceClient, stackName, stackID string) string { + return c.ServiceURL("stacks", stackName, stackID, "template") +} + +func validateURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("validate") +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version.go index a0d5b26468..b697ba8160 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" ) // Version is a supported API version, corresponding to a vN package within the appropriate service. @@ -23,7 +23,7 @@ var goodStatus = map[string]bool{ // ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's // published versions. // It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint. -func ChooseVersion(identityBase string, identityEndpoint string, recognized []*Version) (*Version, string, error) { +func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) { type linkResp struct { Href string `json:"href"` Rel string `json:"rel"` @@ -49,7 +49,7 @@ func ChooseVersion(identityBase string, identityEndpoint string, recognized []*V } return endpoint } - identityEndpoint = normalize(identityEndpoint) + identityEndpoint := normalize(client.IdentityEndpoint) // If a full endpoint is specified, check version suffixes for a match first. for _, v := range recognized { @@ -59,9 +59,9 @@ func ChooseVersion(identityBase string, identityEndpoint string, recognized []*V } var resp response - _, err := perigee.Request("GET", identityBase, perigee.Options{ - Results: &resp, - OkCodes: []int{200, 300}, + _, err := client.Request("GET", client.IdentityBase, gophercloud.RequestOpts{ + JSONResponse: &resp, + OkCodes: []int{200, 300}, }) if err != nil { @@ -88,7 +88,7 @@ func ChooseVersion(identityBase string, identityEndpoint string, recognized []*V // Prefer a version that exactly matches the provided endpoint. if href == identityEndpoint { if href == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, identityBase) + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) } return matching, href, nil } @@ -104,10 +104,10 @@ func ChooseVersion(identityBase string, identityEndpoint string, recognized []*V } if highest == nil { - return nil, "", fmt.Errorf("No supported version available from endpoint %s", identityBase) + return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase) } if endpoint == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, identityBase) + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase) } return highest, endpoint, nil diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version_test.go index 9552696232..388d6892cf 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/utils/choose_version_test.go @@ -5,6 +5,7 @@ import ( "net/http" "testing" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/testhelper" ) @@ -43,7 +44,11 @@ func TestChooseVersion(t *testing.T) { v2 := &Version{ID: "v2.0", Priority: 2, Suffix: "blarg"} v3 := &Version{ID: "v3.0", Priority: 3, Suffix: "hargl"} - v, endpoint, err := ChooseVersion(testhelper.Endpoint(), "", []*Version{v2, v3}) + c := &gophercloud.ProviderClient{ + IdentityBase: testhelper.Endpoint(), + IdentityEndpoint: "", + } + v, endpoint, err := ChooseVersion(c, []*Version{v2, v3}) if err != nil { t.Fatalf("Unexpected error from ChooseVersion: %v", err) @@ -67,7 +72,11 @@ func TestChooseVersionOpinionatedLink(t *testing.T) { v2 := &Version{ID: "v2.0", Priority: 2, Suffix: "nope"} v3 := &Version{ID: "v3.0", Priority: 3, Suffix: "northis"} - v, endpoint, err := ChooseVersion(testhelper.Endpoint(), testhelper.Endpoint()+"v2.0/", []*Version{v2, v3}) + c := &gophercloud.ProviderClient{ + IdentityBase: testhelper.Endpoint(), + IdentityEndpoint: testhelper.Endpoint() + "v2.0/", + } + v, endpoint, err := ChooseVersion(c, []*Version{v2, v3}) if err != nil { t.Fatalf("Unexpected error from ChooseVersion: %v", err) } @@ -89,7 +98,11 @@ func TestChooseVersionFromSuffix(t *testing.T) { v2 := &Version{ID: "v2.0", Priority: 2, Suffix: "/v2.0/"} v3 := &Version{ID: "v3.0", Priority: 3, Suffix: "/v3.0/"} - v, endpoint, err := ChooseVersion(testhelper.Endpoint(), testhelper.Endpoint()+"v2.0/", []*Version{v2, v3}) + c := &gophercloud.ProviderClient{ + IdentityBase: testhelper.Endpoint(), + IdentityEndpoint: testhelper.Endpoint() + "v2.0/", + } + v, endpoint, err := ChooseVersion(c, []*Version{v2, v3}) if err != nil { t.Fatalf("Unexpected error from ChooseVersion: %v", err) } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/http.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/http.go index 1e108c8039..cabcccd79f 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/http.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/http.go @@ -7,7 +7,6 @@ import ( "net/url" "strings" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" ) @@ -19,7 +18,7 @@ type PageResult struct { // PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the // results, interpreting it as JSON if the content type indicates. -func PageResultFrom(resp http.Response) (PageResult, error) { +func PageResultFrom(resp *http.Response) (PageResult, error) { var parsedBody interface{} defer resp.Body.Close() @@ -46,19 +45,10 @@ func PageResultFrom(resp http.Response) (PageResult, error) { }, err } -// Request performs a Perigee request and extracts the http.Response from the result. -func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (http.Response, error) { - h := client.AuthenticatedHeaders() - for key, value := range headers { - h[key] = value - } - - resp, err := perigee.Request("GET", url, perigee.Options{ - MoreHeaders: h, +// Request performs an HTTP request and extracts the http.Response from the result. +func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { + return client.Request("GET", url, gophercloud.RequestOpts{ + MoreHeaders: headers, OkCodes: []int{200, 204}, }) - if err != nil { - return http.Response{}, err - } - return resp.HttpResponse, nil } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/params.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/params.go index 948783b073..4d0f1e6e02 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/params.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/params.go @@ -144,6 +144,17 @@ func BuildQueryString(opts interface{}) (*url.URL, error) { params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) case reflect.Bool: params.Add(tags[0], strconv.FormatBool(v.Bool())) + case reflect.Slice: + switch v.Type().Elem() { + case reflect.TypeOf(0): + for i := 0; i < v.Len(); i++ { + params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10)) + } + default: + for i := 0; i < v.Len(); i++ { + params.Add(tags[0], v.Index(i).String()) + } + } } } else { // Otherwise, the field is not set. diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/params_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/params_test.go index 4a2c9fe341..2f40eec812 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/params_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/params_test.go @@ -34,16 +34,23 @@ func TestMaybeInt(t *testing.T) { } func TestBuildQueryString(t *testing.T) { + type testVar string opts := struct { - J int `q:"j"` - R string `q:"r,required"` - C bool `q:"c"` + J int `q:"j"` + R string `q:"r,required"` + C bool `q:"c"` + S []string `q:"s"` + TS []testVar `q:"ts"` + TI []int `q:"ti"` }{ - J: 2, - R: "red", - C: true, + J: 2, + R: "red", + C: true, + S: []string{"one", "two", "three"}, + TS: []testVar{"a", "b"}, + TI: []int{1, 2}, } - expected := &url.URL{RawQuery: "c=true&j=2&r=red"} + expected := &url.URL{RawQuery: "c=true&j=2&r=red&s=one&s=two&s=three&ti=1&ti=2&ts=a&ts=b"} actual, err := BuildQueryString(&opts) if err != nil { t.Errorf("Error building query string: %v", err) @@ -51,9 +58,12 @@ func TestBuildQueryString(t *testing.T) { th.CheckDeepEquals(t, expected, actual) opts = struct { - J int `q:"j"` - R string `q:"r,required"` - C bool `q:"c"` + J int `q:"j"` + R string `q:"r,required"` + C bool `q:"c"` + S []string `q:"s"` + TS []testVar `q:"ts"` + TI []int `q:"ti"` }{ J: 2, C: true, diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go index 7754c20812..200ee0b1fd 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go @@ -1,5 +1,38 @@ package gophercloud +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" +) + +// DefaultUserAgent is the default User-Agent string set in the request header. +const DefaultUserAgent = "gophercloud/v1.0" + +// UserAgent represents a User-Agent header. +type UserAgent struct { + // prepend is the slice of User-Agent strings to prepend to DefaultUserAgent. + // All the strings to prepend are accumulated and prepended in the Join method. + prepend []string +} + +// Prepend prepends a user-defined string to the default User-Agent string. Users +// may pass in one or more strings to prepend. +func (ua *UserAgent) Prepend(s ...string) { + ua.prepend = append(s, ua.prepend...) +} + +// Join concatenates all the user-defined User-Agend strings with the default +// Gophercloud User-Agent string. +func (ua *UserAgent) Join() string { + uaSlice := append(ua.prepend, DefaultUserAgent) + return strings.Join(uaSlice, " ") +} + // ProviderClient stores details that are required to interact with any // services within a specific provider's API. // @@ -24,10 +57,173 @@ type ProviderClient struct { // EndpointLocator describes how this provider discovers the endpoints for // its constituent services. EndpointLocator EndpointLocator + + // HTTPClient allows users to interject arbitrary http, https, or other transit behaviors. + HTTPClient http.Client + + // UserAgent represents the User-Agent header in the HTTP request. + UserAgent UserAgent + + // ReauthFunc is the function used to re-authenticate the user if the request + // fails with a 401 HTTP response code. This a needed because there may be multiple + // authentication functions for different Identity service versions. + ReauthFunc func() error } // AuthenticatedHeaders returns a map of HTTP headers that are common for all // authenticated service requests. func (client *ProviderClient) AuthenticatedHeaders() map[string]string { + if client.TokenID == "" { + return map[string]string{} + } return map[string]string{"X-Auth-Token": client.TokenID} } + +// RequestOpts customizes the behavior of the provider.Request() method. +type RequestOpts struct { + // JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The + // content type of the request will default to "application/json" unless overridden by MoreHeaders. + // It's an error to specify both a JSONBody and a RawBody. + JSONBody interface{} + // RawBody contains an io.Reader that will be consumed by the request directly. No content-type + // will be set unless one is provided explicitly by MoreHeaders. + RawBody io.Reader + + // JSONResponse, if provided, will be populated with the contents of the response body parsed as + // JSON. + JSONResponse interface{} + // OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If + // the response has a different code, an error will be returned. + OkCodes []int + + // MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is + // provided with a blank value (""), that header will be *omitted* instead: use this to suppress + // the default Accept header or an inferred Content-Type, for example. + MoreHeaders map[string]string +} + +// UnexpectedResponseCodeError is returned by the Request method when a response code other than +// those listed in OkCodes is encountered. +type UnexpectedResponseCodeError struct { + URL string + Method string + Expected []int + Actual int + Body []byte +} + +func (err *UnexpectedResponseCodeError) Error() string { + return fmt.Sprintf( + "Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s", + err.Expected, err.Method, err.URL, err.Actual, err.Body, + ) +} + +var applicationJSON = "application/json" + +// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication +// header will automatically be provided. +func (client *ProviderClient) Request(method, url string, options RequestOpts) (*http.Response, error) { + var body io.Reader + var contentType *string + + // Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided + // io.Reader as-is. Default the content-type to application/json. + if options.JSONBody != nil { + if options.RawBody != nil { + panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().") + } + + rendered, err := json.Marshal(options.JSONBody) + if err != nil { + return nil, err + } + + body = bytes.NewReader(rendered) + contentType = &applicationJSON + } + + if options.RawBody != nil { + body = options.RawBody + } + + // Construct the http.Request. + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + // Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to + // modify or omit any header. + if contentType != nil { + req.Header.Set("Content-Type", *contentType) + } + req.Header.Set("Accept", applicationJSON) + + for k, v := range client.AuthenticatedHeaders() { + req.Header.Add(k, v) + } + + // Set the User-Agent header + req.Header.Set("User-Agent", client.UserAgent.Join()) + + if options.MoreHeaders != nil { + for k, v := range options.MoreHeaders { + fmt.Printf("Applying header [%s: %v]\n", k, v) + if v != "" { + req.Header.Set(k, v) + } else { + req.Header.Del(k) + } + } + } + + // Issue the request. + resp, err := client.HTTPClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode == http.StatusUnauthorized { + if client.ReauthFunc != nil { + err = client.ReauthFunc() + if err != nil { + return nil, fmt.Errorf("Error trying to re-authenticate: %s", err) + } + resp, err = client.Request(method, url, options) + if err != nil { + return nil, fmt.Errorf("Successfully re-authenticated, but got error executing request: %s", err) + } + } + } + + // Validate the response code, if requested to do so. + if options.OkCodes != nil { + var ok bool + for _, code := range options.OkCodes { + if resp.StatusCode == code { + ok = true + break + } + } + if !ok { + body, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + return resp, &UnexpectedResponseCodeError{ + URL: url, + Method: method, + Expected: options.OkCodes, + Actual: resp.StatusCode, + Body: body, + } + } + } + + // Parse the response body as JSON, if requested to do so. + if options.JSONResponse != nil { + defer resp.Body.Close() + json.NewDecoder(resp.Body).Decode(options.JSONResponse) + } + + return resp, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client_test.go index b260246c5a..8fd20f8f81 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client_test.go @@ -14,3 +14,22 @@ func TestAuthenticatedHeaders(t *testing.T) { actual := p.AuthenticatedHeaders() th.CheckDeepEquals(t, expected, actual) } + +func TestUserAgent(t *testing.T) { + p := &ProviderClient{} + + p.UserAgent.Prepend("custom-user-agent/v2.4") + expected := "custom-user-agent/v2.4 gophercloud/v1.0" + actual := p.UserAgent.Join() + th.CheckEquals(t, expected, actual) + + p.UserAgent.Prepend("another-custom-user-agent/v0.3", "a-third-ua/v5.9") + expected = "another-custom-user-agent/v0.3 a-third-ua/v5.9 custom-user-agent/v2.4 gophercloud/v1.0" + actual = p.UserAgent.Join() + th.CheckEquals(t, expected, actual) + + p.UserAgent = UserAgent{} + expected = "gophercloud/v1.0" + actual = p.UserAgent.Join() + th.CheckEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/delegate.go index b338c36b71..1cd1b6e30c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/delegate.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/delegate.go @@ -3,8 +3,6 @@ package snapshots import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" @@ -123,11 +121,10 @@ func Update(c *gophercloud.ServiceClient, snapshotID string, opts UpdateOptsBuil } // Send request to API - _, res.Err = perigee.Request("PUT", updateURL(c, snapshotID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201}, + _, res.Err = c.Request("PUT", updateURL(c, snapshotID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 201}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/results.go index 0fab2828bc..c81644c5dd 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots/results.go @@ -1,8 +1,6 @@ package snapshots import ( - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots" "github.com/rackspace/gophercloud/pagination" @@ -138,7 +136,7 @@ func (snapshot Snapshot) WaitUntilDeleted(c *gophercloud.ServiceClient, timeout _, err := Get(c, snapshot.ID).Extract() // Check for a 404 - if casted, ok := err.(*perigee.UnexpectedResponseCodeError); ok && casted.Actual == 404 { + if casted, ok := err.(*gophercloud.UnexpectedResponseCodeError); ok && casted.Actual == 404 { return true, nil } else if err != nil { return false, err diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate.go new file mode 100644 index 0000000000..5af7e07784 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate.go @@ -0,0 +1,18 @@ +package base + +import ( + "github.com/rackspace/gophercloud" + + os "github.com/rackspace/gophercloud/openstack/cdn/v1/base" +) + +// Get retrieves the home document, allowing the user to discover the +// entire API. +func Get(c *gophercloud.ServiceClient) os.GetResult { + return os.Get(c) +} + +// Ping retrieves a ping to the server. +func Ping(c *gophercloud.ServiceClient) os.PingResult { + return os.Ping(c) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate_test.go new file mode 100644 index 0000000000..3c058016a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/delegate_test.go @@ -0,0 +1,44 @@ +package base + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/cdn/v1/base" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestGetHomeDocument(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetSuccessfully(t) + + actual, err := Get(fake.ServiceClient()).Extract() + th.CheckNoErr(t, err) + + expected := os.HomeDocument{ + "rel/cdn": map[string]interface{}{ + "href-template": "services{?marker,limit}", + "href-vars": map[string]interface{}{ + "marker": "param/marker", + "limit": "param/limit", + }, + "hints": map[string]interface{}{ + "allow": []string{"GET"}, + "formats": map[string]interface{}{ + "application/json": map[string]interface{}{}, + }, + }, + }, + } + th.CheckDeepEquals(t, expected, *actual) +} + +func TestPing(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandlePingSuccessfully(t) + + err := Ping(fake.ServiceClient()).ExtractErr() + th.CheckNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/doc.go new file mode 100644 index 0000000000..5582306a8e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/base/doc.go @@ -0,0 +1,4 @@ +// Package base provides information and interaction with the base API +// resource in the Rackspace CDN service. This API resource allows for +// retrieving the Home Document and pinging the root URL. +package base diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate.go new file mode 100644 index 0000000000..7152fa23af --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate.go @@ -0,0 +1,18 @@ +package flavors + +import ( + "github.com/rackspace/gophercloud" + + os "github.com/rackspace/gophercloud/openstack/cdn/v1/flavors" + "github.com/rackspace/gophercloud/pagination" +) + +// List returns a single page of CDN flavors. +func List(c *gophercloud.ServiceClient) pagination.Pager { + return os.List(c) +} + +// Get retrieves a specific flavor based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) os.GetResult { + return os.Get(c, id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate_test.go new file mode 100644 index 0000000000..d6d299df8e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/delegate_test.go @@ -0,0 +1,90 @@ +package flavors + +import ( + "testing" + + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/cdn/v1/flavors" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleListCDNFlavorsSuccessfully(t) + + count := 0 + + err := List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := os.ExtractFlavors(page) + if err != nil { + t.Errorf("Failed to extract flavors: %v", err) + return false, err + } + + expected := []os.Flavor{ + os.Flavor{ + ID: "europe", + Providers: []os.Provider{ + os.Provider{ + Provider: "Fastly", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://www.fastly.com", + Rel: "provider_url", + }, + }, + }, + }, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/europe", + Rel: "self", + }, + }, + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleGetCDNFlavorSuccessfully(t) + + expected := &os.Flavor{ + ID: "asia", + Providers: []os.Provider{ + os.Provider{ + Provider: "ChinaCache", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "http://www.chinacache.com", + Rel: "provider_url", + }, + }, + }, + }, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/asia", + Rel: "self", + }, + }, + } + + actual, err := Get(fake.ServiceClient(), "asia").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/doc.go new file mode 100644 index 0000000000..4ad966eac8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors/doc.go @@ -0,0 +1,6 @@ +// Package flavors provides information and interaction with the flavors API +// resource in the Rackspace CDN service. This API resource allows for +// listing flavors and retrieving a specific flavor. +// +// A flavor is a mapping configuration to a CDN provider. +package flavors diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate.go new file mode 100644 index 0000000000..07c93a8dcd --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate.go @@ -0,0 +1,13 @@ +package serviceassets + +import ( + "github.com/rackspace/gophercloud" + + os "github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets" +) + +// Delete accepts a unique ID and deletes the CDN service asset associated with +// it. +func Delete(c *gophercloud.ServiceClient, id string, opts os.DeleteOptsBuilder) os.DeleteResult { + return os.Delete(c, id, opts) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate_test.go new file mode 100644 index 0000000000..328e1682d3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/delegate_test.go @@ -0,0 +1,19 @@ +package serviceassets + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleDeleteCDNAssetSuccessfully(t) + + err := Delete(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", nil).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/doc.go new file mode 100644 index 0000000000..46b3d50a81 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets/doc.go @@ -0,0 +1,7 @@ +// Package serviceassets provides information and interaction with the +// serviceassets API resource in the Rackspace CDN service. This API resource +// allows for deleting cached assets. +// +// A service distributes assets across the network. Service assets let you +// interrogate properties about these assets and perform certain actions on them. +package serviceassets diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate.go new file mode 100644 index 0000000000..e3f1459977 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate.go @@ -0,0 +1,37 @@ +package services + +import ( + "github.com/rackspace/gophercloud" + + os "github.com/rackspace/gophercloud/openstack/cdn/v1/services" + "github.com/rackspace/gophercloud/pagination" +) + +// List returns a Pager which allows you to iterate over a collection of +// CDN services. It accepts a ListOpts struct, which allows for pagination via +// marker and limit. +func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager { + return os.List(c, opts) +} + +// Create accepts a CreateOpts struct and creates a new CDN service using the +// values provided. +func Create(c *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult { + return os.Create(c, opts) +} + +// Get retrieves a specific service based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) os.GetResult { + return os.Get(c, id) +} + +// Update accepts a UpdateOpts struct and updates an existing CDN service using +// the values provided. +func Update(c *gophercloud.ServiceClient, id string, patches []os.Patch) os.UpdateResult { + return os.Update(c, id, patches) +} + +// Delete accepts a unique ID and deletes the CDN service associated with it. +func Delete(c *gophercloud.ServiceClient, id string) os.DeleteResult { + return os.Delete(c, id) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate_test.go new file mode 100644 index 0000000000..6c48365e4e --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/delegate_test.go @@ -0,0 +1,359 @@ +package services + +import ( + "testing" + + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/cdn/v1/services" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleListCDNServiceSuccessfully(t) + + count := 0 + + err := List(fake.ServiceClient(), &os.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := os.ExtractServices(page) + if err != nil { + t.Errorf("Failed to extract services: %v", err) + return false, err + } + + expected := []os.Service{ + os.Service{ + ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Name: "mywebsite.com", + Domains: []os.Domain{ + os.Domain{ + Domain: "www.mywebsite.com", + }, + }, + Origins: []os.Origin{ + os.Origin{ + Origin: "mywebsite.com", + Port: 80, + SSL: false, + }, + }, + Caching: []os.CacheRule{ + os.CacheRule{ + Name: "default", + TTL: 3600, + }, + os.CacheRule{ + Name: "home", + TTL: 17200, + Rules: []os.TTLRule{ + os.TTLRule{ + Name: "index", + RequestURL: "/index.htm", + }, + }, + }, + os.CacheRule{ + Name: "images", + TTL: 12800, + Rules: []os.TTLRule{ + os.TTLRule{ + Name: "images", + RequestURL: "*.png", + }, + }, + }, + }, + Restrictions: []os.Restriction{ + os.Restriction{ + Name: "website only", + Rules: []os.RestrictionRule{ + os.RestrictionRule{ + Name: "mywebsite.com", + Referrer: "www.mywebsite.com", + }, + }, + }, + }, + FlavorID: "asia", + Status: "deployed", + Errors: []os.Error{}, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Rel: "self", + }, + gophercloud.Link{ + Href: "mywebsite.com.cdn123.poppycdn.net", + Rel: "access_url", + }, + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/asia", + Rel: "flavor", + }, + }, + }, + os.Service{ + ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1", + Name: "myothersite.com", + Domains: []os.Domain{ + os.Domain{ + Domain: "www.myothersite.com", + }, + }, + Origins: []os.Origin{ + os.Origin{ + Origin: "44.33.22.11", + Port: 80, + SSL: false, + }, + os.Origin{ + Origin: "77.66.55.44", + Port: 80, + SSL: false, + Rules: []os.OriginRule{ + os.OriginRule{ + Name: "videos", + RequestURL: "^/videos/*.m3u", + }, + }, + }, + }, + Caching: []os.CacheRule{ + os.CacheRule{ + Name: "default", + TTL: 3600, + }, + }, + Restrictions: []os.Restriction{}, + FlavorID: "europe", + Status: "deployed", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1", + Rel: "self", + }, + gophercloud.Link{ + Href: "myothersite.com.poppycdn.net", + Rel: "access_url", + }, + gophercloud.Link{ + Href: "https://www.poppycdn.io/v1.0/flavors/europe", + Rel: "flavor", + }, + }, + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleCreateCDNServiceSuccessfully(t) + + createOpts := os.CreateOpts{ + Name: "mywebsite.com", + Domains: []os.Domain{ + os.Domain{ + Domain: "www.mywebsite.com", + }, + os.Domain{ + Domain: "blog.mywebsite.com", + }, + }, + Origins: []os.Origin{ + os.Origin{ + Origin: "mywebsite.com", + Port: 80, + SSL: false, + }, + }, + Restrictions: []os.Restriction{ + os.Restriction{ + Name: "website only", + Rules: []os.RestrictionRule{ + os.RestrictionRule{ + Name: "mywebsite.com", + Referrer: "www.mywebsite.com", + }, + }, + }, + }, + Caching: []os.CacheRule{ + os.CacheRule{ + Name: "default", + TTL: 3600, + }, + }, + FlavorID: "cdn", + } + + expected := "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" + actual, err := Create(fake.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, expected, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleGetCDNServiceSuccessfully(t) + + expected := &os.Service{ + ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Name: "mywebsite.com", + Domains: []os.Domain{ + os.Domain{ + Domain: "www.mywebsite.com", + Protocol: "http", + }, + }, + Origins: []os.Origin{ + os.Origin{ + Origin: "mywebsite.com", + Port: 80, + SSL: false, + }, + }, + Caching: []os.CacheRule{ + os.CacheRule{ + Name: "default", + TTL: 3600, + }, + os.CacheRule{ + Name: "home", + TTL: 17200, + Rules: []os.TTLRule{ + os.TTLRule{ + Name: "index", + RequestURL: "/index.htm", + }, + }, + }, + os.CacheRule{ + Name: "images", + TTL: 12800, + Rules: []os.TTLRule{ + os.TTLRule{ + Name: "images", + RequestURL: "*.png", + }, + }, + }, + }, + Restrictions: []os.Restriction{ + os.Restriction{ + Name: "website only", + Rules: []os.RestrictionRule{ + os.RestrictionRule{ + Name: "mywebsite.com", + Referrer: "www.mywebsite.com", + }, + }, + }, + }, + FlavorID: "cdn", + Status: "deployed", + Errors: []os.Error{}, + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://global.cdn.api.rackspacecloud.com/v1.0/110011/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", + Rel: "self", + }, + gophercloud.Link{ + Href: "blog.mywebsite.com.cdn1.raxcdn.com", + Rel: "access_url", + }, + gophercloud.Link{ + Href: "https://global.cdn.api.rackspacecloud.com/v1.0/110011/flavors/cdn", + Rel: "flavor", + }, + }, + } + + actual, err := Get(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, expected, actual) +} + +func TestSuccessfulUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleUpdateCDNServiceSuccessfully(t) + + expected := "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" + ops := []os.Patch{ + // Append a single Domain + os.Append{Value: os.Domain{Domain: "appended.mocksite4.com"}}, + // Insert a single Domain + os.Insertion{ + Index: 4, + Value: os.Domain{Domain: "inserted.mocksite4.com"}, + }, + // Bulk addition + os.Append{ + Value: os.DomainList{ + os.Domain{Domain: "bulkadded1.mocksite4.com"}, + os.Domain{Domain: "bulkadded2.mocksite4.com"}, + }, + }, + // Replace a single Origin + os.Replacement{ + Index: 2, + Value: os.Origin{Origin: "44.33.22.11", Port: 80, SSL: false}, + }, + // Bulk replace Origins + os.Replacement{ + Index: 0, // Ignored + Value: os.OriginList{ + os.Origin{Origin: "44.33.22.11", Port: 80, SSL: false}, + os.Origin{Origin: "55.44.33.22", Port: 443, SSL: true}, + }, + }, + // Remove a single CacheRule + os.Removal{ + Index: 8, + Path: os.PathCaching, + }, + // Bulk removal + os.Removal{ + All: true, + Path: os.PathCaching, + }, + // Service name replacement + os.NameReplacement{ + NewName: "differentServiceName", + }, + } + + actual, err := Update(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", ops).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, expected, actual) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + os.HandleDeleteCDNServiceSuccessfully(t) + + err := Delete(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/doc.go new file mode 100644 index 0000000000..ee6e2a54fc --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/cdn/v1/services/doc.go @@ -0,0 +1,7 @@ +// Package services provides information and interaction with the services API +// resource in the Rackspace CDN service. This API resource allows for +// listing, creating, updating, retrieving, and deleting services. +// +// A service represents an application that has its content cached to the edge +// nodes. +package services diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go index 7421ff01ad..8f1f34f7b8 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go @@ -59,7 +59,7 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp &utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"}, } - chosen, endpoint, err := utils.ChooseVersion(client.IdentityBase, client.IdentityEndpoint, versions) + chosen, endpoint, err := utils.ChooseVersion(client, versions) if err != nil { return err } @@ -96,6 +96,11 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc return err } + if options.AllowReauth { + client.ReauthFunc = func() error { + return AuthenticateV2(client, options) + } + } client.TokenID = token.ID client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return os.V2EndpointURL(catalog, opts) @@ -176,3 +181,24 @@ func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpt } return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } + +// NewCDNV1 creates a ServiceClient that may be used to access the Rackspace v1 +// CDN service. +func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + eo.ApplyDefaults("rax:cdn") + url, err := client.EndpointLocator(eo) + if err != nil { + return nil, err + } + return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil +} + +// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service. +func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + eo.ApplyDefaults("orchestration") + url, err := client.EndpointLocator(eo) + if err != nil { + return nil, err + } + return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/networks/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/networks/requests.go index d3c973ecb2..3aefb0cdca 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/networks/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/networks/requests.go @@ -5,8 +5,6 @@ import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // List returns a Pager which allows you to iterate over a collection of @@ -23,10 +21,9 @@ func List(c *gophercloud.ServiceClient) pagination.Pager { // Get retrieves a specific network based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", getURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", getURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -81,11 +78,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { } // Send request to API - _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201, 202}, + _, res.Err = c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 201, 202}, }) return res } @@ -93,9 +89,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { // Delete accepts a unique ID and deletes the network associated with it. func Delete(c *gophercloud.ServiceClient, networkID string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(c, networkID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, res.Err = c.Request("DELETE", deleteURL(c, networkID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate.go index 4c7b24909a..173868edf1 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate.go @@ -16,6 +16,11 @@ func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.Cre return os.Create(client, opts) } +// Update requests an existing server to be updated with the supplied options. +func Update(client *gophercloud.ServiceClient, id string, opts os.UpdateOptsBuilder) os.UpdateResult { + return os.Update(client, id, opts) +} + // Delete requests that a server previously provisioned be removed from your account. func Delete(client *gophercloud.ServiceClient, id string) os.DeleteResult { return os.Delete(client, id) diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate_test.go index 7f414040f7..c3d9cc0897 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/delegate_test.go @@ -77,6 +77,28 @@ func TestGetServer(t *testing.T) { th.CheckDeepEquals(t, &GophercloudServer, actual) } +func TestUpdateServer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ "server": { "name": "test-server-updated" } }`) + + w.Header().Add("Content-Type", "application/json") + + fmt.Fprintf(w, UpdateOutput) + }) + + opts := os.UpdateOpts{ + Name: "test-server-updated", + } + actual, err := Update(client.ServiceClient(), "8c65cb68-0681-4c30-bc88-6b83a8a26aee", opts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &GophercloudUpdatedServer, actual) +} + func TestChangeAdminPassword(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/fixtures.go index b22a28998d..75cccd0418 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/servers/fixtures.go @@ -215,6 +215,77 @@ const GetOutput = ` } ` +// UpdateOutput is the recorded output of a Rackspace servers.Update request. +const UpdateOutput = ` +{ + "server": { + "OS-DCF:diskConfig": "AUTO", + "OS-EXT-STS:power_state": 1, + "OS-EXT-STS:task_state": null, + "OS-EXT-STS:vm_state": "active", + "accessIPv4": "1.2.4.8", + "accessIPv6": "2001:4800:6666:105:2a0f:c056:f594:7777", + "addresses": { + "private": [ + { + "addr": "10.20.40.80", + "version": 4 + } + ], + "public": [ + { + "addr": "1.2.4.8", + "version": 4 + }, + { + "addr": "2001:4800:6666:105:2a0f:c056:f594:7777", + "version": 6 + } + ] + }, + "created": "2014-10-21T14:42:16Z", + "flavor": { + "id": "performance1-1", + "links": [ + { + "href": "https://dfw.servers.api.rackspacecloud.com/111111/flavors/performance1-1", + "rel": "bookmark" + } + ] + }, + "hostId": "430d2ae02de0a7af77012c94778145eccf67e75b1fac0528aa10d4a7", + "id": "8c65cb68-0681-4c30-bc88-6b83a8a26aee", + "image": { + "id": "e19a734c-c7e6-443a-830c-242209c4d65d", + "links": [ + { + "href": "https://dfw.servers.api.rackspacecloud.com/111111/images/e19a734c-c7e6-443a-830c-242209c4d65d", + "rel": "bookmark" + } + ] + }, + "key_name": null, + "links": [ + { + "href": "https://dfw.servers.api.rackspacecloud.com/v2/111111/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", + "rel": "self" + }, + { + "href": "https://dfw.servers.api.rackspacecloud.com/111111/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", + "rel": "bookmark" + } + ], + "metadata": {}, + "name": "test-server-updated", + "progress": 100, + "status": "ACTIVE", + "tenant_id": "111111", + "updated": "2014-10-21T14:42:57Z", + "user_id": "14ae7bb21d81423694655f4dd30f2930" + } +} +` + // CreateOutput contains a sample of Rackspace's response to a Create call. const CreateOutput = ` { @@ -428,6 +499,70 @@ var GophercloudServer = os.Server{ AdminPass: "", } +// GophercloudUpdatedServer is the expected result from parsing UpdateOutput. +var GophercloudUpdatedServer = os.Server{ + ID: "8c65cb68-0681-4c30-bc88-6b83a8a26aee", + Name: "test-server-updated", + TenantID: "111111", + UserID: "14ae7bb21d81423694655f4dd30f2930", + HostID: "430d2ae02de0a7af77012c94778145eccf67e75b1fac0528aa10d4a7", + Updated: "2014-10-21T14:42:57Z", + Created: "2014-10-21T14:42:16Z", + AccessIPv4: "1.2.4.8", + AccessIPv6: "2001:4800:6666:105:2a0f:c056:f594:7777", + Progress: 100, + Status: "ACTIVE", + Image: map[string]interface{}{ + "id": "e19a734c-c7e6-443a-830c-242209c4d65d", + "links": []interface{}{ + map[string]interface{}{ + "href": "https://dfw.servers.api.rackspacecloud.com/111111/images/e19a734c-c7e6-443a-830c-242209c4d65d", + "rel": "bookmark", + }, + }, + }, + Flavor: map[string]interface{}{ + "id": "performance1-1", + "links": []interface{}{ + map[string]interface{}{ + "href": "https://dfw.servers.api.rackspacecloud.com/111111/flavors/performance1-1", + "rel": "bookmark", + }, + }, + }, + Addresses: map[string]interface{}{ + "private": []interface{}{ + map[string]interface{}{ + "addr": "10.20.40.80", + "version": float64(4.0), + }, + }, + "public": []interface{}{ + map[string]interface{}{ + "addr": "2001:4800:6666:105:2a0f:c056:f594:7777", + "version": float64(6.0), + }, + map[string]interface{}{ + "addr": "1.2.4.8", + "version": float64(4.0), + }, + }, + }, + Metadata: map[string]interface{}{}, + Links: []interface{}{ + map[string]interface{}{ + "href": "https://dfw.servers.api.rackspacecloud.com/v2/111111/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", + "rel": "self", + }, + map[string]interface{}{ + "href": "https://dfw.servers.api.rackspacecloud.com/111111/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", + "rel": "bookmark", + }, + }, + KeyName: "", + AdminPass: "", +} + // CreatedServer is the partial Server struct that can be parsed from CreateOutput. var CreatedServer = os.Server{ ID: "bb63327b-6a2f-34bc-b0ef-4b6d97ea637e", diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/virtualinterfaces/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/virtualinterfaces/requests.go index bfe3487861..3c81ef80f8 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/virtualinterfaces/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/virtualinterfaces/requests.go @@ -3,8 +3,6 @@ package virtualinterfaces import ( "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" - - "github.com/racker/perigee" ) // List returns a Pager which allows you to iterate over a collection of @@ -30,11 +28,10 @@ func Create(c *gophercloud.ServiceClient, instanceID, networkID string) CreateRe } // Send request to API - _, res.Err = perigee.Request("POST", createURL(c, instanceID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200, 201, 202}, + _, res.Err = c.Request("POST", createURL(c, instanceID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200, 201, 202}, }) return res } @@ -43,9 +40,8 @@ func Create(c *gophercloud.ServiceClient, instanceID, networkID string) CreateRe // instanceID. func Delete(c *gophercloud.ServiceClient, instanceID, interfaceID string) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", deleteURL(c, instanceID, interfaceID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{200, 204}, + _, res.Err = c.Request("DELETE", deleteURL(c, instanceID, interfaceID), gophercloud.RequestOpts{ + OkCodes: []int{200, 204}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/roles/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/roles/delegate.go index a6c01e4f2d..a6ee8515ce 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/roles/delegate.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/roles/delegate.go @@ -1,7 +1,6 @@ package roles import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" @@ -20,9 +19,8 @@ func List(client *gophercloud.ServiceClient) pagination.Pager { func AddUserRole(client *gophercloud.ServiceClient, userID, roleID string) UserRoleResult { var result UserRoleResult - _, result.Err = perigee.Request("PUT", userRoleURL(client, userID, roleID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200, 201}, + _, result.Err = client.Request("PUT", userRoleURL(client, userID, roleID), gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, }) return result @@ -34,9 +32,8 @@ func AddUserRole(client *gophercloud.ServiceClient, userID, roleID string) UserR func DeleteUserRole(client *gophercloud.ServiceClient, userID, roleID string) UserRoleResult { var result UserRoleResult - _, result.Err = perigee.Request("DELETE", userRoleURL(client, userID, roleID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{204}, + _, result.Err = client.Request("DELETE", userRoleURL(client, userID, roleID), gophercloud.RequestOpts{ + OkCodes: []int{204}, }) return result diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/users/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/users/delegate.go index ae2acde643..6135bec101 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/users/delegate.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/identity/v2/users/delegate.go @@ -3,7 +3,6 @@ package users import ( "errors" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" os "github.com/rackspace/gophercloud/openstack/identity/v2/users" "github.com/rackspace/gophercloud/pagination" @@ -116,11 +115,10 @@ func (opts UpdateOpts) ToUserUpdateMap() map[string]interface{} { func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { var result UpdateResult - _, result.Err = perigee.Request("POST", os.ResourceURL(client, id), perigee.Options{ - Results: &result.Body, - ReqBody: opts.ToUserUpdateMap(), - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("POST", os.ResourceURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + JSONBody: opts.ToUserUpdateMap(), + OkCodes: []int{200}, }) return result @@ -135,10 +133,9 @@ func Delete(client *gophercloud.ServiceClient, id string) os.DeleteResult { func ResetAPIKey(client *gophercloud.ServiceClient, id string) ResetAPIKeyResult { var result ResetAPIKeyResult - _, result.Err = perigee.Request("POST", resetAPIKeyURL(client, id), perigee.Options{ - Results: &result.Body, - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, result.Err = client.Request("POST", resetAPIKeyURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200}, }) return result diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/acl/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/acl/requests.go index e1e92ac631..94d98e34cb 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/acl/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/acl/requests.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -75,10 +74,9 @@ func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOp return res } - _, res.Err = perigee.Request("POST", rootURL(client, loadBalancerID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("POST", rootURL(client, loadBalancerID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -97,9 +95,8 @@ func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, itemIDs []int) url := rootURL(c, loadBalancerID) url += gophercloud.IDSliceToQueryString("id", itemIDs) - _, res.Err = perigee.Request("DELETE", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res @@ -108,9 +105,8 @@ func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, itemIDs []int) // Delete will remove a single network item from a load balancer's access list. func Delete(c *gophercloud.ServiceClient, lbID, itemID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, itemID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", resourceURL(c, lbID, itemID), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res } @@ -119,9 +115,8 @@ func Delete(c *gophercloud.ServiceClient, lbID, itemID int) DeleteResult { // effectively resetting it and allowing all traffic. func DeleteAll(c *gophercloud.ServiceClient, lbID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", rootURL(c, lbID), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/lbs/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/lbs/requests.go index 342f10790e..49a46f6d4e 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/lbs/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/lbs/requests.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/mitchellh/mapstructure" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" @@ -228,11 +227,10 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { return res } - _, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{202}, + _, res.Err = c.Request("POST", rootURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{202}, }) return res @@ -245,10 +243,9 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { func Get(c *gophercloud.ServiceClient, id int) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -272,9 +269,8 @@ func BulkDelete(c *gophercloud.ServiceClient, ids []int) DeleteResult { url := rootURL(c) url += gophercloud.IDSliceToQueryString("id", ids) - _, res.Err = perigee.Request("DELETE", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res @@ -284,9 +280,8 @@ func BulkDelete(c *gophercloud.ServiceClient, ids []int) DeleteResult { func Delete(c *gophercloud.ServiceClient, id int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", resourceURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res @@ -368,10 +363,9 @@ func Update(c *gophercloud.ServiceClient, id int, opts UpdateOptsBuilder) Update return res } - _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = c.Request("PUT", resourceURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -400,10 +394,9 @@ func ListAlgorithms(client *gophercloud.ServiceClient) pagination.Pager { func IsLoggingEnabled(client *gophercloud.ServiceClient, id int) (bool, error) { var body interface{} - _, err := perigee.Request("GET", loggingURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &body, - OkCodes: []int{200}, + _, err := client.Request("GET", loggingURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &body, + OkCodes: []int{200}, }) if err != nil { return false, err @@ -430,10 +423,9 @@ func EnableLogging(client *gophercloud.ServiceClient, id int) gophercloud.ErrRes reqBody := toConnLoggingMap(true) var res gophercloud.ErrResult - _, res.Err = perigee.Request("PUT", loggingURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("PUT", loggingURL(client, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -444,10 +436,9 @@ func DisableLogging(client *gophercloud.ServiceClient, id int) gophercloud.ErrRe reqBody := toConnLoggingMap(false) var res gophercloud.ErrResult - _, res.Err = perigee.Request("PUT", loggingURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("PUT", loggingURL(client, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -457,10 +448,9 @@ func DisableLogging(client *gophercloud.ServiceClient, id int) gophercloud.ErrRe func GetErrorPage(client *gophercloud.ServiceClient, id int) ErrorPageResult { var res ErrorPageResult - _, res.Err = perigee.Request("GET", errorPageURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = client.Request("GET", errorPageURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -474,11 +464,10 @@ func SetErrorPage(client *gophercloud.ServiceClient, id int, html string) ErrorP type stringMap map[string]string reqBody := map[string]stringMap{"errorpage": stringMap{"content": html}} - _, res.Err = perigee.Request("PUT", errorPageURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &res.Body, - ReqBody: &reqBody, - OkCodes: []int{200}, + _, res.Err = client.Request("PUT", errorPageURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + JSONBody: &reqBody, + OkCodes: []int{200}, }) return res @@ -488,9 +477,8 @@ func SetErrorPage(client *gophercloud.ServiceClient, id int, html string) ErrorP func DeleteErrorPage(client *gophercloud.ServiceClient, id int) gophercloud.ErrResult { var res gophercloud.ErrResult - _, res.Err = perigee.Request("DELETE", errorPageURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, res.Err = client.Request("DELETE", errorPageURL(client, id), gophercloud.RequestOpts{ + OkCodes: []int{200}, }) return res @@ -500,10 +488,9 @@ func DeleteErrorPage(client *gophercloud.ServiceClient, id int) gophercloud.ErrR func GetStats(client *gophercloud.ServiceClient, id int) StatsResult { var res StatsResult - _, res.Err = perigee.Request("GET", statsURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = client.Request("GET", statsURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -520,10 +507,9 @@ func GetStats(client *gophercloud.ServiceClient, id int) StatsResult { func IsContentCached(client *gophercloud.ServiceClient, id int) (bool, error) { var body interface{} - _, err := perigee.Request("GET", cacheURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - Results: &body, - OkCodes: []int{200}, + _, err := client.Request("GET", cacheURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &body, + OkCodes: []int{200}, }) if err != nil { return false, err @@ -550,10 +536,9 @@ func EnableCaching(client *gophercloud.ServiceClient, id int) gophercloud.ErrRes reqBody := toCachingMap(true) var res gophercloud.ErrResult - _, res.Err = perigee.Request("PUT", cacheURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("PUT", cacheURL(client, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -564,10 +549,9 @@ func DisableCaching(client *gophercloud.ServiceClient, id int) gophercloud.ErrRe reqBody := toCachingMap(false) var res gophercloud.ErrResult - _, res.Err = perigee.Request("PUT", cacheURL(client, id), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = client.Request("PUT", cacheURL(client, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/monitors/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/monitors/requests.go index cfc35d2ef7..917282c63b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/monitors/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/monitors/requests.go @@ -3,8 +3,6 @@ package monitors import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" ) @@ -143,10 +141,9 @@ func Update(c *gophercloud.ServiceClient, id int, opts UpdateOptsBuilder) Update return res } - _, res.Err = perigee.Request("PUT", rootURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = c.Request("PUT", rootURL(c, id), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -156,10 +153,9 @@ func Update(c *gophercloud.ServiceClient, id int, opts UpdateOptsBuilder) Update func Get(c *gophercloud.ServiceClient, id int) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", rootURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", rootURL(c, id), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -169,9 +165,8 @@ func Get(c *gophercloud.ServiceClient, id int) GetResult { func Delete(c *gophercloud.ServiceClient, id int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", rootURL(c, id), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", rootURL(c, id), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/fixtures.go index 0aea541036..7c85945caf 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/fixtures.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/fixtures.go @@ -163,7 +163,6 @@ func mockUpdateResponse(t *testing.T, lbID, nodeID int) { th.TestJSONRequest(t, r, ` { "node": { - "address": "1.2.3.4", "condition": "DRAINING", "weight": 10, "type": "SECONDARY" diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests.go index bfd0aed79f..86fe5d7c8c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -113,18 +112,17 @@ func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOp return res } - resp, err := perigee.Request("POST", rootURL(client, loadBalancerID), perigee.Options{ - MoreHeaders: client.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{202}, + resp, err := client.Request("POST", rootURL(client, loadBalancerID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{202}, }) if err != nil { res.Err = err return res } - pr, err := pagination.PageResultFrom(resp.HttpResponse) + pr, err := pagination.PageResultFrom(resp) if err != nil { res.Err = err return res @@ -147,9 +145,8 @@ func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) url := rootURL(c, loadBalancerID) url += gophercloud.IDSliceToQueryString("id", nodeIDs) - _, res.Err = perigee.Request("DELETE", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res @@ -159,10 +156,9 @@ func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", resourceURL(c, lbID, nodeID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", resourceURL(c, lbID, nodeID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -176,10 +172,6 @@ type UpdateOptsBuilder interface { // UpdateOpts represent the options for updating an existing node. type UpdateOpts struct { - // Optional - the IP address or CIDR for this back-end node. It can either be - // a private IP (ServiceNet) or a public IP. - Address string - // Optional - the condition of the node. See the consts in Results.go. Condition Condition @@ -194,9 +186,6 @@ type UpdateOpts struct { func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) { node := make(map[string]interface{}) - if opts.Address != "" { - node["address"] = opts.Address - } if opts.Condition != "" { node["condition"] = opts.Condition } @@ -224,10 +213,9 @@ func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuild return res } - _, res.Err = perigee.Request("PUT", resourceURL(c, lbID, nodeID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + _, res.Err = c.Request("PUT", resourceURL(c, lbID, nodeID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) return res @@ -236,9 +224,8 @@ func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuild // Delete is the operation responsible for permanently deleting a node. func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, nodeID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", resourceURL(c, lbID, nodeID), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests_test.go index f888a14586..003d347c07 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests_test.go @@ -149,7 +149,6 @@ func TestUpdate(t *testing.T) { mockUpdateResponse(t, lbID, nodeID) opts := UpdateOpts{ - Address: "1.2.3.4", Weight: gophercloud.IntToPointer(10), Condition: DRAINING, Type: SECONDARY, diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/sessions/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/sessions/requests.go index 9853ad1320..5572407edd 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/sessions/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/sessions/requests.go @@ -3,8 +3,6 @@ package sessions import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" ) @@ -44,11 +42,10 @@ func Enable(c *gophercloud.ServiceClient, lbID int, opts CreateOptsBuilder) Enab return res } - _, res.Err = perigee.Request("PUT", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{202}, + _, res.Err = c.Request("PUT", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{202}, }) return res @@ -59,10 +56,9 @@ func Enable(c *gophercloud.ServiceClient, lbID int, opts CreateOptsBuilder) Enab func Get(c *gophercloud.ServiceClient, lbID int) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -73,9 +69,8 @@ func Get(c *gophercloud.ServiceClient, lbID int) GetResult { func Disable(c *gophercloud.ServiceClient, lbID int) DisableResult { var res DisableResult - _, res.Err = perigee.Request("DELETE", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", rootURL(c, lbID), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/ssl/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/ssl/requests.go index 84b2712172..e9c6514286 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/ssl/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/ssl/requests.go @@ -3,8 +3,6 @@ package ssl import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -87,11 +85,10 @@ func Update(c *gophercloud.ServiceClient, lbID int, opts UpdateOptsBuilder) Upda return res } - _, res.Err = perigee.Request("PUT", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("PUT", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -102,10 +99,9 @@ func Update(c *gophercloud.ServiceClient, lbID int, opts UpdateOptsBuilder) Upda func Get(c *gophercloud.ServiceClient, lbID int) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -116,9 +112,8 @@ func Get(c *gophercloud.ServiceClient, lbID int) GetResult { func Delete(c *gophercloud.ServiceClient, lbID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, res.Err = c.Request("DELETE", rootURL(c, lbID), gophercloud.RequestOpts{ + OkCodes: []int{200}, }) return res @@ -185,11 +180,10 @@ func CreateCert(c *gophercloud.ServiceClient, lbID int, opts CreateCertOptsBuild return res } - _, res.Err = perigee.Request("POST", certURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("POST", certURL(c, lbID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -199,10 +193,9 @@ func CreateCert(c *gophercloud.ServiceClient, lbID int, opts CreateCertOptsBuild func GetCert(c *gophercloud.ServiceClient, lbID, certID int) GetCertResult { var res GetCertResult - _, res.Err = perigee.Request("GET", certResourceURL(c, lbID, certID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", certResourceURL(c, lbID, certID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -254,11 +247,10 @@ func UpdateCert(c *gophercloud.ServiceClient, lbID, certID int, opts UpdateCertO return res } - _, res.Err = perigee.Request("PUT", certResourceURL(c, lbID, certID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{202}, + _, res.Err = c.Request("PUT", certResourceURL(c, lbID, certID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{202}, }) return res @@ -269,9 +261,8 @@ func UpdateCert(c *gophercloud.ServiceClient, lbID, certID int, opts UpdateCertO func DeleteCert(c *gophercloud.ServiceClient, lbID, certID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", certResourceURL(c, lbID, certID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{200}, + _, res.Err = c.Request("DELETE", certResourceURL(c, lbID, certID), gophercloud.RequestOpts{ + OkCodes: []int{200}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/throttle/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/throttle/requests.go index 8c2e4be415..2680a892b6 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/throttle/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/throttle/requests.go @@ -3,8 +3,6 @@ package throttle import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" ) @@ -57,11 +55,10 @@ func Create(c *gophercloud.ServiceClient, lbID int, opts CreateOptsBuilder) Crea return res } - _, res.Err = perigee.Request("PUT", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{202}, + _, res.Err = c.Request("PUT", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{202}, }) return res @@ -72,10 +69,9 @@ func Create(c *gophercloud.ServiceClient, lbID int, opts CreateOptsBuilder) Crea func Get(c *gophercloud.ServiceClient, lbID int) GetResult { var res GetResult - _, res.Err = perigee.Request("GET", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res @@ -86,9 +82,8 @@ func Get(c *gophercloud.ServiceClient, lbID int) GetResult { func Delete(c *gophercloud.ServiceClient, lbID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", rootURL(c, lbID), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/vips/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/vips/requests.go index 42f0c1d071..d52a73afd4 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/vips/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/vips/requests.go @@ -3,8 +3,6 @@ package vips import ( "errors" - "github.com/racker/perigee" - "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -69,11 +67,10 @@ func Create(c *gophercloud.ServiceClient, lbID int, opts CreateOptsBuilder) Crea return res } - _, res.Err = perigee.Request("POST", rootURL(c, lbID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - Results: &res.Body, - OkCodes: []int{202}, + _, res.Err = c.Request("POST", rootURL(c, lbID), gophercloud.RequestOpts{ + JSONBody: &reqBody, + JSONResponse: &res.Body, + OkCodes: []int{202}, }) return res @@ -93,9 +90,8 @@ func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, vipIDs []int) url := rootURL(c, loadBalancerID) url += gophercloud.IDSliceToQueryString("id", vipIDs) - _, res.Err = perigee.Request("DELETE", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res @@ -104,9 +100,8 @@ func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, vipIDs []int) // Delete is the operation responsible for permanently deleting a VIP. func Delete(c *gophercloud.ServiceClient, lbID, vipID int) DeleteResult { var res DeleteResult - _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, vipID), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{202}, + _, res.Err = c.Request("DELETE", resourceURL(c, lbID, vipID), gophercloud.RequestOpts{ + OkCodes: []int{202}, }) return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/accounts/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/accounts/delegate_test.go index c568bd6e3b..a1ea98bb70 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/accounts/delegate_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/accounts/delegate_test.go @@ -8,20 +8,22 @@ import ( fake "github.com/rackspace/gophercloud/testhelper/client" ) -func TestGetAccounts(t *testing.T) { +func TestUpdateAccounts(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - os.HandleGetAccountSuccessfully(t) + + os.HandleUpdateAccountSuccessfully(t) options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}} res := Update(fake.ServiceClient(), options) th.CheckNoErr(t, res.Err) } -func TestUpdateAccounts(t *testing.T) { +func TestGetAccounts(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - os.HandleUpdateAccountSuccessfully(t) + + os.HandleGetAccountSuccessfully(t) expected := map[string]string{"Foo": "bar"} actual, err := Get(fake.ServiceClient()).ExtractMetadata() diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/bulk/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/bulk/requests.go index d252609d41..898b73b0bd 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/bulk/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/bulk/requests.go @@ -4,7 +4,6 @@ import ( "net/url" "strings" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" ) @@ -38,14 +37,13 @@ func Delete(c *gophercloud.ServiceClient, opts DeleteOptsBuilder) DeleteResult { reqBody := strings.NewReader(reqString) - resp, err := perigee.Request("DELETE", deleteURL(c), perigee.Options{ - ContentType: "text/plain", - MoreHeaders: c.AuthenticatedHeaders(), - OkCodes: []int{200}, - ReqBody: reqBody, - Results: &res.Body, + resp, err := c.Request("DELETE", deleteURL(c), gophercloud.RequestOpts{ + MoreHeaders: map[string]string{"Content-Type": "text/plain"}, + OkCodes: []int{200}, + JSONBody: reqBody, + JSONResponse: &res.Body, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/delegate.go index d7eef20255..89adb83965 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/delegate.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/delegate.go @@ -1,8 +1,6 @@ package cdncontainers import ( - "strconv" - "github.com/rackspace/gophercloud" os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers" "github.com/rackspace/gophercloud/pagination" @@ -38,34 +36,3 @@ func (opts ListOpts) ToContainerListParams() (bool, string, error) { func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager { return os.List(c, opts) } - -// Get is a function that retrieves the metadata of a container. To extract just -// the custom metadata, pass the GetResult response to the ExtractMetadata -// function. -func Get(c *gophercloud.ServiceClient, containerName string) os.GetResult { - return os.Get(c, containerName) -} - -// UpdateOpts is a structure that holds parameters for updating, creating, or -// deleting a container's metadata. -type UpdateOpts struct { - CDNEnabled bool `h:"X-Cdn-Enabled"` - LogRetention bool `h:"X-Log-Retention"` - TTL int `h:"X-Ttl"` -} - -// ToContainerUpdateMap formats a CreateOpts into a map of headers. -func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) { - h, err := gophercloud.BuildHeaders(opts) - if err != nil { - return nil, err - } - h["X-Cdn-Enabled"] = strconv.FormatBool(opts.CDNEnabled) - return h, nil -} - -// Update is a function that creates, updates, or deletes a container's -// metadata. -func Update(c *gophercloud.ServiceClient, containerName string, opts os.UpdateOptsBuilder) os.UpdateResult { - return os.Update(c, containerName, opts) -} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/requests.go index 0567833204..8e4abbe436 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/requests.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/requests.go @@ -1,7 +1,8 @@ package cdncontainers import ( - "github.com/racker/perigee" + "strconv" + "github.com/rackspace/gophercloud" ) @@ -48,11 +49,107 @@ func Enable(c *gophercloud.ServiceClient, containerName string, opts EnableOptsB } } - resp, err := perigee.Request("PUT", enableURL(c, containerName), perigee.Options{ + resp, err := c.Request("PUT", enableURL(c, containerName), gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{201, 202, 204}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header + res.Err = err + return res +} + +// Get is a function that retrieves the metadata of a container. To extract just +// the custom metadata, pass the GetResult response to the ExtractMetadata +// function. +func Get(c *gophercloud.ServiceClient, containerName string) GetResult { + var res GetResult + resp, err := c.Request("HEAD", getURL(c, containerName), gophercloud.RequestOpts{ + OkCodes: []int{200, 204}, + }) + res.Header = resp.Header + res.Err = err + return res +} + +// State is the state of an option. It is a pointer to a boolean to enable checking for +// a zero-value of nil instead of false, which is a valid option. +type State *bool + +var ( + iTrue = true + iFalse = false + + // Enabled is used for a true value for options in request bodies. + Enabled State = &iTrue + // Disabled is used for a false value for options in request bodies. + Disabled State = &iFalse +) + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToContainerUpdateMap() (map[string]string, error) +} + +// UpdateOpts is a structure that holds parameters for updating, creating, or +// deleting a container's metadata. +type UpdateOpts struct { + // Whether or not to CDN-enable a container. Prefer using XCDNEnabled, which + // is of type *bool underneath. + // TODO v2.0: change type to Enabled/Disabled (*bool) + CDNEnabled bool `h:"X-Cdn-Enabled"` + // Whether or not to enable log retention. Prefer using XLogRetention, which + // is of type *bool underneath. + // TODO v2.0: change type to Enabled/Disabled (*bool) + LogRetention bool `h:"X-Log-Retention"` + XCDNEnabled *bool + XLogRetention *bool + TTL int `h:"X-Ttl"` +} + +// ToContainerUpdateMap formats a CreateOpts into a map of headers. +func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) { + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, err + } + h["X-Cdn-Enabled"] = strconv.FormatBool(opts.CDNEnabled) + h["X-Log-Retention"] = strconv.FormatBool(opts.LogRetention) + + if opts.XCDNEnabled != nil { + h["X-Cdn-Enabled"] = strconv.FormatBool(*opts.XCDNEnabled) + } + + if opts.XLogRetention != nil { + h["X-Log-Retention"] = strconv.FormatBool(*opts.XLogRetention) + } + + return h, nil +} + +// Update is a function that creates, updates, or deletes a container's +// metadata. +func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + h := c.AuthenticatedHeaders() + + if opts != nil { + headers, err := opts.ToContainerUpdateMap() + if err != nil { + res.Err = err + return res + } + + for k, v := range headers { + h[k] = v + } + } + + resp, err := c.Request("POST", updateURL(c, containerName), gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{202, 204}, + }) + res.Header = resp.Header res.Err = err return res } diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/results.go index a5097ca7f6..cb0ad3096c 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/results.go @@ -1,8 +1,149 @@ package cdncontainers -import "github.com/rackspace/gophercloud" +import ( + "strings" + "time" -// EnableResult represents the result of a get operation. + "github.com/rackspace/gophercloud" +) + +// EnableHeader represents the headers returned in the response from an Enable request. +type EnableHeader struct { + CDNIosURI string `mapstructure:"X-Cdn-Ios-Uri"` + CDNSslURI string `mapstructure:"X-Cdn-Ssl-Uri"` + CDNStreamingURI string `mapstructure:"X-Cdn-Streaming-Uri"` + CDNUri string `mapstructure:"X-Cdn-Uri"` + ContentLength int `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + +// EnableResult represents the result of an Enable operation. type EnableResult struct { gophercloud.HeaderResult } + +// Extract will return extract an EnableHeader from the response to an Enable +// request. To obtain a map of headers, call the ExtractHeader method on the EnableResult. +func (er EnableResult) Extract() (EnableHeader, error) { + var eh EnableHeader + if er.Err != nil { + return eh, er.Err + } + + if err := gophercloud.DecodeHeader(er.Header, &eh); err != nil { + return eh, err + } + + if date, ok := er.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, er.Header["Date"][0]) + if err != nil { + return eh, err + } + eh.Date = t + } + + return eh, nil +} + +// GetHeader represents the headers returned in the response from a Get request. +type GetHeader struct { + CDNEnabled bool `mapstructure:"X-Cdn-Enabled"` + CDNIosURI string `mapstructure:"X-Cdn-Ios-Uri"` + CDNSslURI string `mapstructure:"X-Cdn-Ssl-Uri"` + CDNStreamingURI string `mapstructure:"X-Cdn-Streaming-Uri"` + CDNUri string `mapstructure:"X-Cdn-Uri"` + ContentLength int `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + LogRetention bool `mapstructure:"X-Log-Retention"` + TransID string `mapstructure:"X-Trans-Id"` + TTL int `mapstructure:"X-Ttl"` +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.HeaderResult +} + +// Extract will return a struct of headers returned from a call to Get. To obtain +// a map of headers, call the ExtractHeader method on the GetResult. +func (gr GetResult) Extract() (GetHeader, error) { + var gh GetHeader + if gr.Err != nil { + return gh, gr.Err + } + + if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil { + return gh, err + } + + if date, ok := gr.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, gr.Header["Date"][0]) + if err != nil { + return gh, err + } + gh.Date = t + } + + return gh, nil +} + +// ExtractMetadata is a function that takes a GetResult (of type *http.Response) +// and returns the custom metadata associated with the container. +func (gr GetResult) ExtractMetadata() (map[string]string, error) { + if gr.Err != nil { + return nil, gr.Err + } + metadata := make(map[string]string) + for k, v := range gr.Header { + if strings.HasPrefix(k, "X-Container-Meta-") { + key := strings.TrimPrefix(k, "X-Container-Meta-") + metadata[key] = v[0] + } + } + return metadata, nil +} + +// UpdateHeader represents the headers returned in the response from a Update request. +type UpdateHeader struct { + CDNIosURI string `mapstructure:"X-Cdn-Ios-Uri"` + CDNSslURI string `mapstructure:"X-Cdn-Ssl-Uri"` + CDNStreamingURI string `mapstructure:"X-Cdn-Streaming-Uri"` + CDNUri string `mapstructure:"X-Cdn-Uri"` + ContentLength int `mapstructure:"Content-Length"` + ContentType string `mapstructure:"Content-Type"` + Date time.Time `mapstructure:"-"` + TransID string `mapstructure:"X-Trans-Id"` +} + +// UpdateResult represents the result of an update operation. To extract the +// the headers from the HTTP response, you can invoke the 'ExtractHeader' +// method on the result struct. +type UpdateResult struct { + gophercloud.HeaderResult +} + +// Extract will return a struct of headers returned from a call to Update. To obtain +// a map of headers, call the ExtractHeader method on the UpdateResult. +func (ur UpdateResult) Extract() (UpdateHeader, error) { + var uh UpdateHeader + if ur.Err != nil { + return uh, ur.Err + } + + if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil { + return uh, err + } + + if date, ok := ur.Header["Date"]; ok && len(date) > 0 { + t, err := time.Parse(time.RFC1123, ur.Header["Date"][0]) + if err != nil { + return uh, err + } + uh.Date = t + } + + return uh, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/urls.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/urls.go index 80653f2762..541249a927 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/urls.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/urls.go @@ -5,3 +5,11 @@ import "github.com/rackspace/gophercloud" func enableURL(c *gophercloud.ServiceClient, containerName string) string { return c.ServiceURL(containerName) } + +func getURL(c *gophercloud.ServiceClient, container string) string { + return c.ServiceURL(container) +} + +func updateURL(c *gophercloud.ServiceClient, container string) string { + return getURL(c, container) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdnobjects/request.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdnobjects/request.go new file mode 100644 index 0000000000..540e0cd298 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdnobjects/request.go @@ -0,0 +1,15 @@ +package cdnobjects + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers" +) + +// CDNURL returns the unique CDN URI for the given container and object. +func CDNURL(c *gophercloud.ServiceClient, containerName, objectName string) (string, error) { + h, err := cdncontainers.Get(c, containerName).Extract() + if err != nil { + return "", err + } + return h.CDNUri + "/" + objectName, nil +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate.go index bd4a4f0835..028d66a0ab 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate.go @@ -88,3 +88,7 @@ func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts os func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts os.UpdateOptsBuilder) os.UpdateResult { return os.Update(c, containerName, objectName, opts) } + +func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts os.CreateTempURLOpts) (string, error) { + return os.CreateTempURL(c, containerName, objectName, opts) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate_test.go index 08831ec56a..8ab8029c37 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects/delegate_test.go @@ -66,14 +66,24 @@ func TestListObjectNames(t *testing.T) { func TestCreateObject(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - os.HandleCreateObjectSuccessfully(t) + os.HandleCreateTextObjectSuccessfully(t) content := bytes.NewBufferString("Did gyre and gimble in the wabe") - options := &os.CreateOpts{ContentType: "application/json"} + options := &os.CreateOpts{ContentType: "text/plain"} res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options) th.AssertNoErr(t, res.Err) } +func TestCreateObjectWithoutContentType(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleCreateTypelessObjectSuccessfully(t) + + content := bytes.NewBufferString("The sky was the color of television, tuned to a dead channel.") + res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &os.CreateOpts{}) + th.AssertNoErr(t, res.Err) +} + func TestCopyObject(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate.go new file mode 100644 index 0000000000..c834e5c7d3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate.go @@ -0,0 +1,11 @@ +package buildinfo + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo" +) + +// Get retreives build info data for the Heat deployment. +func Get(c *gophercloud.ServiceClient) os.GetResult { + return os.Get(c) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate_test.go new file mode 100644 index 0000000000..b25a690c8d --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate_test.go @@ -0,0 +1,21 @@ +package buildinfo + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestGetTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetSuccessfully(t, os.GetOutput) + + actual, err := Get(fake.ServiceClient()).Extract() + th.AssertNoErr(t, err) + + expected := os.GetExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/doc.go new file mode 100644 index 0000000000..183e8dfa76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/doc.go @@ -0,0 +1,2 @@ +// Package buildinfo provides build information about heat deployments. +package buildinfo diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate.go new file mode 100644 index 0000000000..08675deac7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate.go @@ -0,0 +1,27 @@ +package stackevents + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents" + "github.com/rackspace/gophercloud/pagination" +) + +// Find retreives stack events for the given stack name. +func Find(c *gophercloud.ServiceClient, stackName string) os.FindResult { + return os.Find(c, stackName) +} + +// List makes a request against the API to list resources for the given stack. +func List(c *gophercloud.ServiceClient, stackName, stackID string, opts os.ListOptsBuilder) pagination.Pager { + return os.List(c, stackName, stackID, opts) +} + +// ListResourceEvents makes a request against the API to list resources for the given stack. +func ListResourceEvents(c *gophercloud.ServiceClient, stackName, stackID, resourceName string, opts os.ListResourceEventsOptsBuilder) pagination.Pager { + return os.ListResourceEvents(c, stackName, stackID, resourceName, opts) +} + +// Get retreives data for the given stack resource. +func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName, eventID string) os.GetResult { + return os.Get(c, stackName, stackID, resourceName, eventID) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate_test.go new file mode 100644 index 0000000000..e1c0bc8dbc --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/delegate_test.go @@ -0,0 +1,72 @@ +package stackevents + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents" + "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() + os.HandleFindSuccessfully(t, os.FindOutput) + + actual, err := Find(fake.ServiceClient(), "postman_stack").Extract() + th.AssertNoErr(t, err) + + expected := os.FindExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleListSuccessfully(t, os.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 := os.ExtractEvents(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, os.ListExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListResourceEvents(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleListResourceEventsSuccessfully(t, os.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 := os.ExtractEvents(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, os.ListResourceEventsExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestGetEvent(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetSuccessfully(t, os.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 := os.GetExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/doc.go new file mode 100644 index 0000000000..dfd6ef6605 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents/doc.go @@ -0,0 +1,3 @@ +// Package stackevents provides operations for finding, listing, and retrieving +// stack events. +package stackevents diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate.go new file mode 100644 index 0000000000..cb7be28b78 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate.go @@ -0,0 +1,42 @@ +package stackresources + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources" + "github.com/rackspace/gophercloud/pagination" +) + +// Find retreives stack resources for the given stack name. +func Find(c *gophercloud.ServiceClient, stackName string) os.FindResult { + return os.Find(c, stackName) +} + +// List makes a request against the API to list resources for the given stack. +func List(c *gophercloud.ServiceClient, stackName, stackID string, opts os.ListOptsBuilder) pagination.Pager { + return os.List(c, stackName, stackID, opts) +} + +// Get retreives data for the given stack resource. +func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) os.GetResult { + return os.Get(c, stackName, stackID, resourceName) +} + +// Metadata retreives the metadata for the given stack resource. +func Metadata(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) os.MetadataResult { + return os.Metadata(c, stackName, stackID, resourceName) +} + +// ListTypes makes a request against the API to list resource types. +func ListTypes(c *gophercloud.ServiceClient) pagination.Pager { + return os.ListTypes(c) +} + +// Schema retreives the schema for the given resource type. +func Schema(c *gophercloud.ServiceClient, resourceType string) os.SchemaResult { + return os.Schema(c, resourceType) +} + +// Template retreives the template representation for the given resource type. +func Template(c *gophercloud.ServiceClient, resourceType string) os.TemplateResult { + return os.Template(c, resourceType) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate_test.go new file mode 100644 index 0000000000..18e9614151 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/delegate_test.go @@ -0,0 +1,108 @@ +package stackresources + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestFindResources(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleFindSuccessfully(t, os.FindOutput) + + actual, err := Find(fake.ServiceClient(), "hello_world").Extract() + th.AssertNoErr(t, err) + + expected := os.FindExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestListResources(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleListSuccessfully(t, os.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 := os.ExtractResources(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, os.ListExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestGetResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetSuccessfully(t, os.GetOutput) + + actual, err := Get(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() + th.AssertNoErr(t, err) + + expected := os.GetExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestResourceMetadata(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleMetadataSuccessfully(t, os.MetadataOutput) + + actual, err := Metadata(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() + th.AssertNoErr(t, err) + + expected := os.MetadataExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestListResourceTypes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleListTypesSuccessfully(t, os.ListTypesOutput) + + count := 0 + err := ListTypes(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := os.ExtractResourceTypes(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, os.ListTypesExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestGetResourceSchema(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetSchemaSuccessfully(t, os.GetSchemaOutput) + + actual, err := Schema(fake.ServiceClient(), "OS::Heat::AResourceName").Extract() + th.AssertNoErr(t, err) + + expected := os.GetSchemaExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestGetResourceTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetTemplateSuccessfully(t, os.GetTemplateOutput) + + actual, err := Template(fake.ServiceClient(), "OS::Heat::AResourceName").Extract() + th.AssertNoErr(t, err) + + expected := os.GetTemplateExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/doc.go new file mode 100644 index 0000000000..e4f8b08dcc --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources/doc.go @@ -0,0 +1,5 @@ +// Package stackresources provides operations for working with stack resources. +// A resource is a template artifact that represents some component of your +// desired architecture (a Cloud Server, a group of scaled Cloud Servers, a load +// balancer, some configuration management system, and so forth). +package stackresources diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate.go new file mode 100644 index 0000000000..f7e387f8f7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate.go @@ -0,0 +1,49 @@ +package stacks + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" +) + +// Create accepts an os.CreateOpts struct and creates a new stack using the values +// provided. +func Create(c *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult { + return os.Create(c, opts) +} + +// Adopt accepts an os.AdoptOpts struct and creates a new stack from existing stack +// resources using the values provided. +func Adopt(c *gophercloud.ServiceClient, opts os.AdoptOptsBuilder) os.AdoptResult { + return os.Adopt(c, opts) +} + +// List accepts an os.ListOpts struct and lists stacks based on the options provided. +func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager { + return os.List(c, opts) +} + +// Get retreives a stack based on the stack name and stack ID. +func Get(c *gophercloud.ServiceClient, stackName, stackID string) os.GetResult { + return os.Get(c, stackName, stackID) +} + +// Update accepts an os.UpdateOpts struct and updates a stack based on the options provided. +func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts os.UpdateOptsBuilder) os.UpdateResult { + return os.Update(c, stackName, stackID, opts) +} + +// Delete deletes a stack based on the stack name and stack ID provided. +func Delete(c *gophercloud.ServiceClient, stackName, stackID string) os.DeleteResult { + return os.Delete(c, stackName, stackID) +} + +// Preview provides a preview of a stack based on the options provided. +func Preview(c *gophercloud.ServiceClient, opts os.PreviewOptsBuilder) os.PreviewResult { + return os.Preview(c, opts) +} + +// Abandon abandons a stack, keeping the resources available. +func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) os.AbandonResult { + return os.Abandon(c, stackName, stackID) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate_test.go new file mode 100644 index 0000000000..a1fb393129 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/delegate_test.go @@ -0,0 +1,461 @@ +package stacks + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" + "github.com/rackspace/gophercloud/pagination" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestCreateStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleCreateSuccessfully(t, CreateOutput) + + createOpts := os.CreateOpts{ + Name: "stackcreated", + Timeout: 60, + Template: `{ + "outputs": { + "db_host": { + "value": { + "get_attr": [ + "db", + "hostname" + ] + } + } + }, + "heat_template_version": "2014-10-16", + "description": "HEAT template for creating a Cloud Database.\n", + "parameters": { + "db_name": { + "default": "wordpress", + "type": "string", + "description": "the name for the database", + "constraints": [ + { + "length": { + "max": 64, + "min": 1 + }, + "description": "must be between 1 and 64 characters" + }, + { + "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", + "description": "must begin with a letter and contain only alphanumeric characters." + } + ] + }, + "db_instance_name": { + "default": "Cloud_DB", + "type": "string", + "description": "the database instance name" + }, + "db_username": { + "default": "admin", + "hidden": true, + "type": "string", + "description": "database admin account username", + "constraints": [ + { + "length": { + "max": 16, + "min": 1 + }, + "description": "must be between 1 and 16 characters" + }, + { + "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", + "description": "must begin with a letter and contain only alphanumeric characters." + } + ] + }, + "db_volume_size": { + "default": 30, + "type": "number", + "description": "database volume size (in GB)", + "constraints": [ + { + "range": { + "max": 1024, + "min": 1 + }, + "description": "must be between 1 and 1024 GB" + } + ] + }, + "db_flavor": { + "default": "1GB Instance", + "type": "string", + "description": "database instance size", + "constraints": [ + { + "description": "must be a valid cloud database flavor", + "allowed_values": [ + "1GB Instance", + "2GB Instance", + "4GB Instance", + "8GB Instance", + "16GB Instance" + ] + } + ] + }, + "db_password": { + "default": "admin", + "hidden": true, + "type": "string", + "description": "database admin account password", + "constraints": [ + { + "length": { + "max": 41, + "min": 1 + }, + "description": "must be between 1 and 14 characters" + }, + { + "allowed_pattern": "[a-zA-Z0-9]*", + "description": "must contain only alphanumeric characters." + } + ] + } + }, + "resources": { + "db": { + "type": "OS::Trove::Instance", + "properties": { + "flavor": { + "get_param": "db_flavor" + }, + "size": { + "get_param": "db_volume_size" + }, + "users": [ + { + "password": { + "get_param": "db_password" + }, + "name": { + "get_param": "db_username" + }, + "databases": [ + { + "get_param": "db_name" + } + ] + } + ], + "name": { + "get_param": "db_instance_name" + }, + "databases": [ + { + "name": { + "get_param": "db_name" + } + } + ] + } + } + } + }`, + DisableRollback: os.Disable, + } + actual, err := Create(fake.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + + expected := CreateExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestAdoptStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleCreateSuccessfully(t, CreateOutput) + + adoptOpts := os.AdoptOpts{ + AdoptStackData: `{\"environment\":{\"parameters\":{}}, \"status\":\"COMPLETE\",\"name\": \"trovestack\",\n \"template\": {\n \"outputs\": {\n \"db_host\": {\n \"value\": {\n \"get_attr\": [\n \"db\",\n \"hostname\"\n ]\n }\n }\n },\n \"heat_template_version\": \"2014-10-16\",\n \"description\": \"HEAT template for creating a Cloud Database.\\n\",\n \"parameters\": {\n \"db_instance_name\": {\n \"default\": \"Cloud_DB\",\n \"type\": \"string\",\n \"description\": \"the database instance name\"\n },\n \"db_flavor\": {\n \"default\": \"1GB Instance\",\n \"type\": \"string\",\n \"description\": \"database instance size\",\n \"constraints\": [\n {\n \"description\": \"must be a valid cloud database flavor\",\n \"allowed_values\": [\n \"1GB Instance\",\n \"2GB Instance\",\n \"4GB Instance\",\n \"8GB Instance\",\n \"16GB Instance\"\n ]\n }\n ]\n },\n \"db_password\": {\n \"default\": \"admin\",\n \"hidden\": true,\n \"type\": \"string\",\n \"description\": \"database admin account password\",\n \"constraints\": [\n {\n \"length\": {\n \"max\": 41,\n \"min\": 1\n },\n \"description\": \"must be between 1 and 14 characters\"\n },\n {\n \"allowed_pattern\": \"[a-zA-Z0-9]*\",\n \"description\": \"must contain only alphanumeric characters.\"\n }\n ]\n },\n \"db_name\": {\n \"default\": \"wordpress\",\n \"type\": \"string\",\n \"description\": \"the name for the database\",\n \"constraints\": [\n {\n \"length\": {\n \"max\": 64,\n \"min\": 1\n },\n \"description\": \"must be between 1 and 64 characters\"\n },\n {\n \"allowed_pattern\": \"[a-zA-Z][a-zA-Z0-9]*\",\n \"description\": \"must begin with a letter and contain only alphanumeric characters.\"\n }\n ]\n },\n \"db_username\": {\n \"default\": \"admin\",\n \"hidden\": true,\n \"type\": \"string\",\n \"description\": \"database admin account username\",\n \"constraints\": [\n {\n \"length\": {\n \"max\": 16,\n \"min\": 1\n },\n \"description\": \"must be between 1 and 16 characters\"\n },\n {\n \"allowed_pattern\": \"[a-zA-Z][a-zA-Z0-9]*\",\n \"description\": \"must begin with a letter and contain only alphanumeric characters.\"\n }\n ]\n },\n \"db_volume_size\": {\n \"default\": 30,\n \"type\": \"number\",\n \"description\": \"database volume size (in GB)\",\n \"constraints\": [\n {\n \"range\": {\n \"max\": 1024,\n \"min\": 1\n },\n \"description\": \"must be between 1 and 1024 GB\"\n }\n ]\n }\n },\n \"resources\": {\n \"db\": {\n \"type\": \"OS::Trove::Instance\",\n \"properties\": {\n \"flavor\": {\n \"get_param\": \"db_flavor\"\n },\n \"databases\": [\n {\n \"name\": {\n \"get_param\": \"db_name\"\n }\n }\n ],\n \"users\": [\n {\n \"password\": {\n \"get_param\": \"db_password\"\n },\n \"name\": {\n \"get_param\": \"db_username\"\n },\n \"databases\": [\n {\n \"get_param\": \"db_name\"\n }\n ]\n }\n ],\n \"name\": {\n \"get_param\": \"db_instance_name\"\n },\n \"size\": {\n \"get_param\": \"db_volume_size\"\n }\n }\n }\n }\n },\n \"action\": \"CREATE\",\n \"id\": \"exxxxd-7xx5-4xxb-bxx2-cxxxxxx5\",\n \"resources\": {\n \"db\": {\n \"status\": \"COMPLETE\",\n \"name\": \"db\",\n \"resource_data\": {},\n \"resource_id\": \"exxxx2-9xx0-4xxxb-bxx2-dxxxxxx4\",\n \"action\": \"CREATE\",\n \"type\": \"OS::Trove::Instance\",\n \"metadata\": {}\n }\n }\n},`, + Name: "stackadopted", + Timeout: 60, + Template: `{ + "outputs": { + "db_host": { + "value": { + "get_attr": [ + "db", + "hostname" + ] + } + } + }, + "heat_template_version": "2014-10-16", + "description": "HEAT template for creating a Cloud Database.\n", + "parameters": { + "db_name": { + "default": "wordpress", + "type": "string", + "description": "the name for the database", + "constraints": [ + { + "length": { + "max": 64, + "min": 1 + }, + "description": "must be between 1 and 64 characters" + }, + { + "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", + "description": "must begin with a letter and contain only alphanumeric characters." + } + ] + }, + "db_instance_name": { + "default": "Cloud_DB", + "type": "string", + "description": "the database instance name" + }, + "db_username": { + "default": "admin", + "hidden": true, + "type": "string", + "description": "database admin account username", + "constraints": [ + { + "length": { + "max": 16, + "min": 1 + }, + "description": "must be between 1 and 16 characters" + }, + { + "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", + "description": "must begin with a letter and contain only alphanumeric characters." + } + ] + }, + "db_volume_size": { + "default": 30, + "type": "number", + "description": "database volume size (in GB)", + "constraints": [ + { + "range": { + "max": 1024, + "min": 1 + }, + "description": "must be between 1 and 1024 GB" + } + ] + }, + "db_flavor": { + "default": "1GB Instance", + "type": "string", + "description": "database instance size", + "constraints": [ + { + "description": "must be a valid cloud database flavor", + "allowed_values": [ + "1GB Instance", + "2GB Instance", + "4GB Instance", + "8GB Instance", + "16GB Instance" + ] + } + ] + }, + "db_password": { + "default": "admin", + "hidden": true, + "type": "string", + "description": "database admin account password", + "constraints": [ + { + "length": { + "max": 41, + "min": 1 + }, + "description": "must be between 1 and 14 characters" + }, + { + "allowed_pattern": "[a-zA-Z0-9]*", + "description": "must contain only alphanumeric characters." + } + ] + } + }, + "resources": { + "db": { + "type": "OS::Trove::Instance", + "properties": { + "flavor": { + "get_param": "db_flavor" + }, + "size": { + "get_param": "db_volume_size" + }, + "users": [ + { + "password": { + "get_param": "db_password" + }, + "name": { + "get_param": "db_username" + }, + "databases": [ + { + "get_param": "db_name" + } + ] + } + ], + "name": { + "get_param": "db_instance_name" + }, + "databases": [ + { + "name": { + "get_param": "db_name" + } + } + ] + } + } + } + }`, + DisableRollback: os.Disable, + } + actual, err := Adopt(fake.ServiceClient(), adoptOpts).Extract() + th.AssertNoErr(t, err) + + expected := CreateExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestListStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleListSuccessfully(t, os.FullListOutput) + + count := 0 + err := List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := os.ExtractStacks(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, os.ListExpected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestUpdateStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleUpdateSuccessfully(t) + + updateOpts := os.UpdateOpts{ + Template: ` + { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + }`, + } + err := Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleDeleteSuccessfully(t) + + err := Delete(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestPreviewStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandlePreviewSuccessfully(t, os.GetOutput) + + previewOpts := os.PreviewOpts{ + Name: "stackcreated", + Timeout: 60, + Template: ` + { + "stack_name": "postman_stack", + "template": { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type":"OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + } + }`, + DisableRollback: os.Disable, + } + actual, err := Preview(fake.ServiceClient(), previewOpts).Extract() + th.AssertNoErr(t, err) + + expected := os.PreviewExpected + th.AssertDeepEquals(t, expected, actual) +} + +/* +func TestAbandonStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleAbandonSuccessfully(t) + + //actual, err := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + //th.AssertNoErr(t, err) + res := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87") //.Extract() + th.AssertNoErr(t, res.Err) + t.Logf("actual: %+v", res) + + //expected := os.AbandonExpected + //th.AssertDeepEquals(t, expected, actual) +} +*/ diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/doc.go new file mode 100644 index 0000000000..19231b5137 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/doc.go @@ -0,0 +1,8 @@ +// Package stacks provides operation for working with Heat stacks. A stack is a +// group of resources (servers, load balancers, databases, and so forth) +// combined to fulfill a useful purpose. Based on a template, Heat orchestration +// engine creates an instantiated set of resources (a stack) to run the +// application framework or component specified (in the template). A stack is a +// running instance of a template. The result of creating a stack is a deployment +// of the application framework or component. +package stacks diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/fixtures.go new file mode 100644 index 0000000000..c9afeb156f --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks/fixtures.go @@ -0,0 +1,32 @@ +package stacks + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks" +) + +// CreateExpected represents the expected object from a Create request. +var CreateExpected = &os.CreatedStack{ + ID: "b663e18a-4767-4cdf-9db5-9c8cc13cc38a", + Links: []gophercloud.Link{ + gophercloud.Link{ + Href: "https://ord.orchestration.api.rackspacecloud.com/v1/864477/stacks/stackcreated/b663e18a-4767-4cdf-9db5-9c8cc13cc38a", + Rel: "self", + }, + }, +} + +// CreateOutput represents the response body from a Create request. +const CreateOutput = ` +{ + "stack": { + "id": "b663e18a-4767-4cdf-9db5-9c8cc13cc38a", + "links": [ + { + "href": "https://ord.orchestration.api.rackspacecloud.com/v1/864477/stacks/stackcreated/b663e18a-4767-4cdf-9db5-9c8cc13cc38a", + "rel": "self" + } + ] + } +} +` diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate.go new file mode 100644 index 0000000000..3b5d46e1c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate.go @@ -0,0 +1,16 @@ +package stacktemplates + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates" +) + +// Get retreives data for the given stack template. +func Get(c *gophercloud.ServiceClient, stackName, stackID string) os.GetResult { + return os.Get(c, stackName, stackID) +} + +// Validate validates the given stack template. +func Validate(c *gophercloud.ServiceClient, opts os.ValidateOptsBuilder) os.ValidateResult { + return os.Validate(c, opts) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate_test.go new file mode 100644 index 0000000000..d4006c476d --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/delegate_test.go @@ -0,0 +1,58 @@ +package stacktemplates + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates" + th "github.com/rackspace/gophercloud/testhelper" + fake "github.com/rackspace/gophercloud/testhelper/client" +) + +func TestGetTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleGetSuccessfully(t, os.GetOutput) + + actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + th.AssertNoErr(t, err) + + expected := os.GetExpected + th.AssertDeepEquals(t, expected, actual) +} + +func TestValidateTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleValidateSuccessfully(t, os.ValidateOutput) + + opts := os.ValidateOpts{ + Template: map[string]interface{}{ + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": map[string]interface{}{ + "flavor": map[string]interface{}{ + "default": "m1.tiny", + "type": "string", + }, + }, + "resources": map[string]interface{}{ + "hello_world": map[string]interface{}{ + "type": "OS::Nova::Server", + "properties": map[string]interface{}{ + "key_name": "heat_key", + "flavor": map[string]interface{}{ + "get_param": "flavor", + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", + }, + }, + }, + }, + } + actual, err := Validate(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + expected := os.ValidateExpected + th.AssertDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/doc.go new file mode 100644 index 0000000000..5af0bd62a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates/doc.go @@ -0,0 +1,8 @@ +// Package stacktemplates provides operations for working with Heat templates. +// A Cloud Orchestration template is a portable file, written in a user-readable +// language, that describes how a set of resources should be assembled and what +// software should be installed in order to produce a working stack. The template +// specifies what resources should be used, what attributes can be set, and other +// parameters that are critical to the successful, repeatable automation of a +// specific application stack. +package stacktemplates diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/results.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/results.go index 3fd50296f3..7c86ce4623 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/results.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/results.go @@ -3,6 +3,9 @@ package gophercloud import ( "encoding/json" "net/http" + "reflect" + + "github.com/mitchellh/mapstructure" ) /* @@ -82,6 +85,31 @@ func (hr HeaderResult) ExtractHeader() (http.Header, error) { return hr.Header, hr.Err } +// DecodeHeader is a function that decodes a header (usually of type map[string]interface{}) to +// another type (usually a struct). This function is used by the objectstorage package to give +// users access to response headers without having to query a map. A DecodeHookFunction is used, +// because OpenStack-based clients return header values as arrays (Go slices). +func DecodeHeader(from, to interface{}) error { + config := &mapstructure.DecoderConfig{ + DecodeHook: func(from, to reflect.Kind, data interface{}) (interface{}, error) { + if from == reflect.Slice { + return data.([]string)[0], nil + } + return data, nil + }, + Result: to, + WeaklyTypedInput: true, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + if err := decoder.Decode(from); err != nil { + return err + } + return nil +} + // RFC3339Milli describes a common time format used by some API responses. const RFC3339Milli = "2006-01-02T15:04:05.999999Z" From 5f2024034069906a9ba8606b171931be803c1944 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 11:15:24 -0500 Subject: [PATCH 2/3] Use --openstack-insecure to disable TLS checking. At your own risk! Signed-off-by: Ash Wilson --- drivers/openstack/client.go | 12 ++++++++++++ drivers/openstack/openstack.go | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/openstack/client.go b/drivers/openstack/client.go index 5051e8ad86..fd2392c435 100644 --- a/drivers/openstack/client.go +++ b/drivers/openstack/client.go @@ -1,6 +1,9 @@ package openstack import ( + "crypto/tls" + "net/http" + log "github.com/Sirupsen/logrus" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack" @@ -393,6 +396,7 @@ func (c *GenericClient) Authenticate(d *Driver) error { log.WithFields(log.Fields{ "AuthUrl": d.AuthUrl, + "Insecure": d.Insecure, "Username": d.Username, "TenantName": d.TenantName, "TenantID": d.TenantId, @@ -411,6 +415,14 @@ func (c *GenericClient) Authenticate(d *Driver) error { if err != nil { return err } + + if d.Insecure { + // Configure custom TLS settings. + config := &tls.Config{InsecureSkipVerify: true} + transport := &http.Transport{TLSClientConfig: config} + provider.HTTPClient.Transport = transport + } + c.Provider = provider return nil diff --git a/drivers/openstack/openstack.go b/drivers/openstack/openstack.go index b0969b60c6..6dbbdd0cf9 100644 --- a/drivers/openstack/openstack.go +++ b/drivers/openstack/openstack.go @@ -22,6 +22,7 @@ const ( type Driver struct { AuthUrl string + Insecure bool Username string Password string TenantName string @@ -54,6 +55,7 @@ type Driver struct { type CreateFlags struct { AuthUrl *string + Insecure *bool Username *string Password *string TenantName *string @@ -87,6 +89,10 @@ func GetCreateFlags() []cli.Flag { Usage: "OpenStack authentication URL", Value: "", }, + cli.BoolFlag{ + Name: "openstack-insecure", + Usage: "Disable TLS credential checking.", + }, cli.StringFlag{ EnvVar: "OS_USERNAME", Name: "openstack-username", @@ -203,6 +209,7 @@ func (d *Driver) DriverName() string { func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.AuthUrl = flags.String("openstack-auth-url") + d.Insecure = flags.Bool("openstack-insecure") d.Username = flags.String("openstack-username") d.Password = flags.String("openstack-password") d.TenantName = flags.String("openstack-tenant-name") From 96224409b86d1783a6347eec0a4d1961ca9809c4 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Fri, 20 Feb 2015 16:45:05 +0100 Subject: [PATCH 3/3] HTTP configuration it done too late The HTTP transport configuration should be done before the authentication request. Signed-off-by: Guillaume Giamarchi --- drivers/openstack/client.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/openstack/client.go b/drivers/openstack/client.go index fd2392c435..120de9337d 100644 --- a/drivers/openstack/client.go +++ b/drivers/openstack/client.go @@ -411,7 +411,7 @@ func (c *GenericClient) Authenticate(d *Driver) error { AllowReauth: true, } - provider, err := openstack.AuthenticatedClient(opts) + provider, err := openstack.NewClient(opts.IdentityEndpoint) if err != nil { return err } @@ -423,6 +423,11 @@ func (c *GenericClient) Authenticate(d *Driver) error { provider.HTTPClient.Transport = transport } + err = openstack.Authenticate(provider, opts) + if err != nil { + return err + } + c.Provider = provider return nil