From e6c16cd4f90678884a2d89aafa26406ff5dd230a Mon Sep 17 00:00:00 2001 From: Masahiro Sano Date: Mon, 2 Mar 2015 22:37:34 +0900 Subject: [PATCH] Update github.com/rackspace/gophercloud to HEAD --- Godeps/Godeps.json | 4 +- .../rackspace/gophercloud/CONTRIBUTORS.md | 1 + .../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/containers_test.go | 48 ++ .../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 +++ .../rackspace/compute/v2/volumeattach_test.go | 130 +++++ .../objectstorage/v1/cdnobjects_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 | 33 +- .../blockstorage/v1/volumes/requests_test.go | 27 + .../blockstorage/v1/volumes/results.go | 2 +- .../blockstorage/v1/volumetypes/requests.go | 21 +- .../openstack/cdn/v1/base/requests.go | 18 +- .../openstack/cdn/v1/flavors/requests.go | 8 +- .../cdn/v1/serviceassets/requests.go | 6 +- .../openstack/cdn/v1/services/requests.go | 31 +- .../rackspace/gophercloud/openstack/client.go | 22 +- .../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 | 118 ++-- .../compute/v2/servers/requests_test.go | 13 + .../openstack/compute/v2/servers/results.go | 3 + .../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 | 31 +- .../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/requests.go | 15 +- .../objectstorage/v1/containers/fixtures.go | 10 + .../objectstorage/v1/containers/requests.go | 21 +- .../v1/containers/requests_test.go | 24 + .../objectstorage/v1/objects/requests.go | 46 +- .../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 +- .../gophercloud/pagination/linked.go | 6 + .../gophercloud/pagination/linked_test.go | 13 + .../gophercloud/pagination/marker.go | 6 + .../gophercloud/pagination/marker_test.go | 13 + .../rackspace/gophercloud/pagination/pager.go | 109 ++++ .../gophercloud/pagination/single.go | 6 + .../gophercloud/pagination/single_test.go | 13 + .../rackspace/gophercloud/params.go | 11 + .../rackspace/gophercloud/params_test.go | 30 +- .../rackspace/gophercloud/provider_client.go | 195 +++++++ .../gophercloud/provider_client_test.go | 19 + .../blockstorage/v1/snapshots/delegate.go | 11 +- .../blockstorage/v1/snapshots/results.go | 4 +- .../rackspace/gophercloud/rackspace/client.go | 17 +- .../rackspace/compute/v2/networks/requests.go | 23 +- .../compute/v2/virtualinterfaces/requests.go | 16 +- .../compute/v2/volumeattach/delegate.go | 27 + .../compute/v2/volumeattach/delegate_test.go | 66 +++ .../rackspace/compute/v2/volumeattach/doc.go | 3 + .../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/requests.go | 36 +- .../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 +- .../objectstorage/v1/bulk/requests.go | 14 +- .../v1/cdncontainers/requests.go | 16 +- .../objectstorage/v1/cdnobjects/request.go | 15 + .../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 + 189 files changed, 10661 insertions(+), 909 deletions(-) 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/compute/v2/volumeattach_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/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/compute/v2/volumeattach/delegate.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/delegate_test.go create mode 100644 Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/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 ae37f15984a..c805e4b72a7 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -235,8 +235,8 @@ }, { "ImportPath": "github.com/rackspace/gophercloud", - "Comment": "v1.0.0-336-g2a6e319", - "Rev": "2a6e3190447abe5d000f951595ead1cf98df72d8" + "Comment": "v1.0.0-490-g32d0a89", + "Rev": "32d0a893a8ef70abe76dc5153e2925b39cbea7f7" }, { "ImportPath": "github.com/russross/blackfriday", diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md b/Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md index eb97094b73f..63beb30b20d 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/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 00000000000..ab7554b5a88 --- /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 00000000000..34634c9d2f8 --- /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 00000000000..80246b6481e --- /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 00000000000..206bf3313a8 --- /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 00000000000..fdca22e3fbc --- /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 00000000000..144aa0998f1 --- /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/containers_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go index d6832f1914c..8328a4fa6f1 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go @@ -87,3 +87,51 @@ func TestContainers(t *testing.T) { } } } + +func TestListAllContainers(t *testing.T) { + // Create a new client to execute the HTTP requests. See common.go for newClient body. + client := newClient(t) + + numContainers := 20 + + // Create a slice of random container names. + cNames := make([]string, numContainers) + for i := 0; i < numContainers; i++ { + cNames[i] = tools.RandomString("gophercloud-test-container-", 8) + } + + // Create numContainers containers. + for i := 0; i < len(cNames); i++ { + res := containers.Create(client, cNames[i], nil) + th.AssertNoErr(t, res.Err) + } + // Delete the numContainers containers after function completion. + defer func() { + for i := 0; i < len(cNames); i++ { + res := containers.Delete(client, cNames[i]) + th.AssertNoErr(t, res.Err) + } + }() + + // List all the numContainer names that were just created. To just list those, + // the 'prefix' parameter is used. + allPages, err := containers.List(client, &containers.ListOpts{Full: true, Limit: 5, Prefix: "gophercloud-test-container-"}).AllPages() + th.AssertNoErr(t, err) + containerInfoList, err := containers.ExtractInfo(allPages) + th.AssertNoErr(t, err) + for _, n := range containerInfoList { + t.Logf("Container: Name [%s] Count [%d] Bytes [%d]", + n.Name, n.Count, n.Bytes) + } + th.AssertEquals(t, numContainers, len(containerInfoList)) + + // List the info for all the numContainer containers that were created. + allPages, err = containers.List(client, &containers.ListOpts{Full: false, Limit: 2, Prefix: "gophercloud-test-container-"}).AllPages() + th.AssertNoErr(t, err) + containerNamesList, err := containers.ExtractNames(allPages) + th.AssertNoErr(t, err) + for _, n := range containerNamesList { + t.Logf("Container: Name [%s]", n) + } + th.AssertEquals(t, numContainers, len(containerNamesList)) +} 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 00000000000..05a5e1d67ec --- /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 00000000000..2c28dcbcc98 --- /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 00000000000..11cfc805342 --- /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 00000000000..e356c86aa9d --- /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 00000000000..b614f1cef5f --- /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 00000000000..01e76d61403 --- /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 00000000000..14d8f4437ae --- /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/compute/v2/volumeattach_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/volumeattach_test.go new file mode 100644 index 00000000000..9848e2eba59 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/volumeattach_test.go @@ -0,0 +1,130 @@ +// +build acceptance compute servers + +package v2 + +import ( + "os" + "testing" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/acceptance/tools" + "github.com/rackspace/gophercloud/openstack" + osVolumes "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" + osVolumeAttach "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" + osServers "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/rackspace" + "github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumes" + "github.com/rackspace/gophercloud/rackspace/compute/v2/servers" + "github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach" + th "github.com/rackspace/gophercloud/testhelper" +) + +func newBlockClient(t *testing.T) (*gophercloud.ServiceClient, error) { + ao, err := rackspace.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + client, err := rackspace.AuthenticatedClient(ao) + th.AssertNoErr(t, err) + + return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("RS_REGION_NAME"), + }) +} + +func createVAServer(t *testing.T, computeClient *gophercloud.ServiceClient, choices *serverOpts) (*osServers.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, osServers.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, &osVolumes.CreateOpts{ + Size: 80, + Name: "gophercloud-test-volume", + }).Extract() + th.AssertNoErr(t, err) + defer func() { + err = osVolumes.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, &osVolumeAttach.CreateOpts{ + VolumeID: volumeID, + }).Extract() + th.AssertNoErr(t, err) + defer func() { + err = osVolumes.WaitForStatus(blockClient, volumeID, "in-use", 60) + th.AssertNoErr(t, err) + err = volumeattach.Delete(computeClient, serverID, va.ID).ExtractErr() + th.AssertNoErr(t, err) + err = osVolumes.WaitForStatus(blockClient, volumeID, "available", 60) + th.AssertNoErr(t, err) + }() + t.Logf("Attached volume to server: %+v", va) +} + +func TestAttachVolume(t *testing.T) { + choices, err := optionsFromEnv() + 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 = osServers.WaitForStatus(computeClient, server.ID, "ACTIVE", 300); 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/rackspace/objectstorage/v1/cdnobjects_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go index 6e477ae7045..0c0ab8a1ed0 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/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 00000000000..42cc048e3b8 --- /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 00000000000..b9d51979d7f --- /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 00000000000..9e3fc084ad8 --- /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 00000000000..65926e78dc6 --- /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 00000000000..cfec4e9817e --- /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 00000000000..1f7b2171065 --- /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 19ce5d4a99d..9819e45f2d7 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 016bf374e31..f5a793c35ce 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 443f6960575..1b313a6080e 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 a01ad05a383..a1b8697c2a7 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 f4332de6578..e67ba105817 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 } @@ -157,6 +152,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa createPage := func(r pagination.PageResult) pagination.Page { return ListResult{pagination.SinglePageBase(r)} } + return pagination.NewPager(client, url, createPage) } @@ -207,11 +203,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 067f89bdd93..c484cf08df3 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 @@ -45,6 +45,32 @@ func TestList(t *testing.T) { } } +func TestListAll(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListResponse(t) + + allPages, err := List(client.ServiceClient(), &ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := ExtractVolumes(allPages) + th.AssertNoErr(t, err) + + expected := []Volume{ + Volume{ + ID: "289da7f8-6440-407c-9fb4-7db01ec49164", + Name: "vol-001", + }, + Volume{ + ID: "96c3bda7-c82a-4f50-be73-ca7621794835", + Name: "vol-002", + }, + } + + th.CheckDeepEquals(t, expected, actual) + +} + func TestGet(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -56,6 +82,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 aff60bca148..2fd4ef14eae 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"` 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 87e20f60037..6fedaa68968 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/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/cdn/v1/base/requests.go index 9d8632cbea6..b63dc95a71a 100644 --- 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 @@ -1,19 +1,14 @@ package base -import ( - "github.com/rackspace/gophercloud" - - "github.com/racker/perigee" -) +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 = perigee.Request("GET", getURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", getURL(c), gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -21,10 +16,9 @@ func Get(c *gophercloud.ServiceClient) GetResult { // Ping retrieves a ping to the server. func Ping(c *gophercloud.ServiceClient) PingResult { var res PingResult - _, res.Err = perigee.Request("GET", pingURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), + _, res.Err = c.Request("GET", pingURL(c), gophercloud.RequestOpts{ OkCodes: []int{204}, - OmitAccept: true, + MoreHeaders: map[string]string{"Accept": ""}, }) return res } 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 index 88ac891e8e6..138fd976319 100644 --- 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 @@ -1,7 +1,6 @@ package flavors import ( - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -18,10 +17,9 @@ func List(c *gophercloud.ServiceClient) pagination.Pager { // Get retrieves a specific flavor 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 } 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 index 5248ef20c12..a80aa0db28b 100644 --- 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 @@ -3,7 +3,6 @@ package serviceassets import ( "strings" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" ) @@ -44,9 +43,8 @@ func Delete(c *gophercloud.ServiceClient, idOrURL string, opts DeleteOptsBuilder } var res DeleteResult - _, 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 } 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 index ad1e1da462d..78a3087c98b 100644 --- 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 @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/pagination" ) @@ -178,12 +177,11 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { } // Send request to API - resp, err := perigee.Request("POST", createURL(c), perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + resp, err := c.Request("POST", createURL(c), gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) - res.Header = resp.HttpResponse.Header + res.Header = resp.Header res.Err = err return res } @@ -201,10 +199,9 @@ func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult { } var res GetResult - _, res.Err = perigee.Request("GET", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - Results: &res.Body, - OkCodes: []int{200}, + _, res.Err = c.Request("GET", url, gophercloud.RequestOpts{ + JSONResponse: &res.Body, + OkCodes: []int{200}, }) return res } @@ -359,13 +356,12 @@ func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) Updat reqBody[i] = patch.ToCDNServiceUpdateMap() } - resp, err := perigee.Request("PATCH", url, perigee.Options{ - MoreHeaders: c.AuthenticatedHeaders(), - ReqBody: &reqBody, - OkCodes: []int{202}, + resp, err := c.Request("PATCH", url, gophercloud.RequestOpts{ + JSONBody: &reqBody, + OkCodes: []int{202}, }) var result UpdateResult - result.Header = resp.HttpResponse.Header + result.Header = resp.Header result.Err = err return result } @@ -383,9 +379,8 @@ func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult { } var res DeleteResult - _, 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 } 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 9c12dcaae47..6818d9d734b 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) } @@ -214,3 +224,13 @@ func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) ( } 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 3ca6e12f195..dfd81c99a0d 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 5a976d11468..b64014f36b5 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 7d19741e10c..294bae35783 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 00000000000..f74f58ce837 --- /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 00000000000..26f32995fde --- /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 00000000000..d1540380108 --- /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 00000000000..ed2460edc67 --- /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 00000000000..be77fa17922 --- /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 00000000000..54198f85291 --- /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 00000000000..f73d6fb0f9f --- /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 7b372a355fb..287e4127c5a 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 ca76f686b8b..1c6ba394920 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 09503d715ec..8f0a7a032fd 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 f4760b6f2e8..dc53fbfac63 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 99c91b054a3..04b5909372e 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 00000000000..22f68d80e52 --- /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 00000000000..a7f03b322c8 --- /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 00000000000..79709fdbe40 --- /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 00000000000..e17f7e00637 --- /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 00000000000..26be39e4f70 --- /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 00000000000..9d9d1786db3 --- /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 00000000000..8ee0e42d456 --- /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 065a2ec4724..1d33f58ad56 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 bc61ddb9df3..9e9c3b1d4e8 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 01646058e6f..6125d530b30 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 be8ab73b75b..b7c1611d3c3 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" ) @@ -217,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 } @@ -229,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 } @@ -239,10 +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(), - OkCodes: []int{200, 203}, + _, result.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{ + JSONResponse: &result.Body, + OkCodes: []int{200, 203}, }) return result } @@ -284,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 } @@ -304,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 @@ -373,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 @@ -475,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 @@ -522,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 @@ -536,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 @@ -550,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 @@ -597,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 @@ -637,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 } @@ -648,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 } @@ -671,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 } @@ -710,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 } @@ -721,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 } @@ -731,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/requests_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests_test.go index 017e793ccd2..253a179191d 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests_test.go @@ -39,6 +39,19 @@ func TestListServers(t *testing.T) { } } +func TestListAllServers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleServerListSuccessfully(t) + + allPages, err := List(client.ServiceClient(), ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := ExtractServers(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ServerHerp, actual[0]) + th.CheckDeepEquals(t, ServerDerp, actual[1]) +} + func TestCreateServer(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() 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 d63d7c887aa..1b22f219b21 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 @@ -139,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. 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 152031ac350..bbdf76a46ce 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 87c923a2b46..db1ac8284fb 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 4ce395f2dd3..2afe62a6134 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 7bdb7cef2e8..3e09b2aef0d 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 1d9aaa873a2..c6820c700b2 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 5ca1031c415..bbd3c56231b 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 00000000000..3ec450a7b3d --- /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 00000000000..dd92bb20dbe --- /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 00000000000..69f3dcad134 --- /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 00000000000..f24e2835ea9 --- /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 00000000000..a8c76eef232 --- /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 00000000000..4dde53005a2 --- /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 00000000000..95081dfa031 --- /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 00000000000..b9d78652c32 --- /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 00000000000..a9a0c358d57 --- /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 00000000000..27ea9ae6143 --- /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 00000000000..0b29d39fd9e --- /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 00000000000..37801068bc6 --- /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 00000000000..36f89fa5c49 --- /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 00000000000..d772024b39f --- /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 00000000000..20b08791ed2 --- /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 d23f9e2b5ac..46f2b22cb16 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 19614be2ef1..d914a799bfc 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 e3a144171b1..12640dee1e5 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 25865841a48..023a04dc04a 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" ) @@ -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 e2b590ecb5a..de6f68862fc 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 ca8d33b8d5d..e7e6d944d05 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 ec929d63976..5b0bfd9a0bf 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 0c970ae6f2e..c07508bd7bc 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 edaebe82cd3..108acf670dc 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 dedbb252de9..b0db67e7d0f 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 06d273ef19e..01d550fc1af 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 cd7c663c2dc..63ac2901b7b 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/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts/requests.go index e6f5f9594cb..3e404c3b6bf 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/containers/fixtures.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers/fixtures.go index 9c84bce0a4c..e60735248fe 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 @@ -55,6 +55,14 @@ func HandleListContainerInfoSuccessfully(t *testing.T) { "name": "marktwain" } ]`) + case "janeausten": + fmt.Fprintf(w, `[ + { + "count": 1, + "bytes": 14, + "name": "marktwain" + } + ]`) case "marktwain": fmt.Fprintf(w, `[]`) default: @@ -77,6 +85,8 @@ func HandleListContainerNamesSuccessfully(t *testing.T) { switch marker { case "": fmt.Fprintf(w, "janeausten\nmarktwain\n") + case "janeausten": + fmt.Fprintf(w, "marktwain\n") case "marktwain": fmt.Fprintf(w, ``) default: 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 9f3b2af0a6f..a29d7da5d68 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 f650696e485..0ccd5a77868 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 @@ -29,6 +29,18 @@ func TestListContainerInfo(t *testing.T) { th.CheckEquals(t, count, 1) } +func TestListAllContainerInfo(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListContainerInfoSuccessfully(t) + + allPages, err := List(fake.ServiceClient(), &ListOpts{Full: true}).AllPages() + th.AssertNoErr(t, err) + actual, err := ExtractInfo(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedListInfo, actual) +} + func TestListContainerNames(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -51,6 +63,18 @@ func TestListContainerNames(t *testing.T) { th.CheckEquals(t, count, 1) } +func TestListAllContainerNames(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListContainerNamesSuccessfully(t) + + allPages, err := List(fake.ServiceClient(), &ListOpts{Full: false}).AllPages() + th.AssertNoErr(t, err) + actual, err := ExtractNames(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedListNames, actual) +} + func TestCreateContainer(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() 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 def1798a5b4..30ea94cc2d7 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 @@ -8,7 +8,6 @@ import ( "strings" "time" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts" "github.com/rackspace/gophercloud/pagination" @@ -131,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 } @@ -193,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() @@ -209,21 +208,14 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont url += query } - popts := perigee.Options{ - ReqBody: content, + ropts := gophercloud.RequestOpts{ + RawBody: content, MoreHeaders: h, OkCodes: []int{201, 202}, } - if contentType, explicit := h["Content-Type"]; explicit { - popts.ContentType = contentType - delete(h, "Content-Type") - } else { - popts.OmitContentType = true - } - - resp, err := perigee.Request("PUT", url, popts) - res.Header = resp.HttpResponse.Header + resp, err := c.Request("PUT", url, ropts) + res.Header = resp.Header res.Err = err return res } @@ -275,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 } @@ -318,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 } @@ -363,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 } @@ -420,11 +410,11 @@ 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 } 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 00000000000..f2db622d1fc --- /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 00000000000..f6454c86097 --- /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 00000000000..a2fc980d358 --- /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 00000000000..0700ab0afb8 --- /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 00000000000..55d6e0e7a43 --- /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 00000000000..183e8dfa76d --- /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 00000000000..20ea09b4425 --- /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 00000000000..379f34f30ba --- /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 00000000000..1e0fe230d66 --- /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 00000000000..683a434a053 --- /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 00000000000..2c873d02358 --- /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 00000000000..51cdd97473c --- /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 00000000000..016ae003b32 --- /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 00000000000..37eab1e763e --- /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 00000000000..a4da4d04e61 --- /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 00000000000..bf233ae2ad4 --- /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 00000000000..8b5eceb1704 --- /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 00000000000..e4f8b08dcc7 --- /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 00000000000..0b930f4841a --- /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 00000000000..2a66edc8c25 --- /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 00000000000..f1378785f89 --- /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 00000000000..13f5dd21fbe --- /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 00000000000..ef078d9c9b4 --- /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 00000000000..19231b5137e --- /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 00000000000..6d3e9597a21 --- /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 00000000000..c0388c366d1 --- /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 00000000000..1e32ca2a9e6 --- /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 00000000000..ff971e8b8bd --- /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 00000000000..3dd2bb32ead --- /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 00000000000..5af0bd62a11 --- /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 00000000000..71fa80891ea --- /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 00000000000..f57e226efbf --- /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 00000000000..d31c4ac9a2d --- /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 00000000000..ac2f24b80b4 --- /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 00000000000..c30b7ca1afe --- /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 a0d5b264688..b697ba8160a 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 9552696232c..388d6892cf4 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 1e108c80391..cabcccd79f3 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/pagination/linked.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked.go index 461fa499afc..e9bd8dec976 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked.go @@ -59,3 +59,9 @@ func (current LinkedPageBase) NextPageURL() (string, error) { } } } + +// GetBody returns the linked page's body. This method is needed to satisfy the +// Page interface. +func (current LinkedPageBase) GetBody() interface{} { + return current.Body +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked_test.go index 4d3248e6ac9..1ac0f73164f 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/linked_test.go @@ -105,3 +105,16 @@ func TestEnumerateLinked(t *testing.T) { t.Errorf("Expected 3 calls, but was %d", callCount) } } + +func TestAllPagesLinked(t *testing.T) { + pager := createLinked(t) + defer testhelper.TeardownHTTP() + + page, err := pager.AllPages() + testhelper.AssertNoErr(t, err) + + expected := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + actual, err := ExtractLinkedInts(page) + testhelper.AssertNoErr(t, err) + testhelper.CheckDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker.go index e7688c217b4..f355afc54b5 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker.go @@ -32,3 +32,9 @@ func (current MarkerPageBase) NextPageURL() (string, error) { return currentURL.String(), nil } + +// GetBody returns the linked page's body. This method is needed to satisfy the +// Page interface. +func (current MarkerPageBase) GetBody() interface{} { + return current.Body +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker_test.go index 3b1df1d68b9..f4d55be810b 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/marker_test.go @@ -111,3 +111,16 @@ func TestEnumerateMarker(t *testing.T) { testhelper.AssertNoErr(t, err) testhelper.AssertEquals(t, callCount, 3) } + +func TestAllPagesMarker(t *testing.T) { + pager := createMarkerPaged(t) + defer testhelper.TeardownHTTP() + + page, err := pager.AllPages() + testhelper.AssertNoErr(t, err) + + expected := []string{"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii"} + actual, err := ExtractMarkerStrings(page) + testhelper.AssertNoErr(t, err) + testhelper.CheckDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/pager.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/pager.go index 5c20e16c6cd..ea47c695dc3 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/pager.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/pager.go @@ -2,6 +2,10 @@ package pagination import ( "errors" + "fmt" + "net/http" + "reflect" + "strings" "github.com/rackspace/gophercloud" ) @@ -25,6 +29,9 @@ type Page interface { // IsEmpty returns true if this Page has no items in it. IsEmpty() (bool, error) + + // GetBody returns the Page Body. This is used in the `AllPages` method. + GetBody() interface{} } // Pager knows how to advance through a specific resource collection, one page at a time. @@ -113,3 +120,105 @@ func (p Pager) EachPage(handler func(Page) (bool, error)) error { } } } + +// AllPages returns all the pages from a `List` operation in a single page, +// allowing the user to retrieve all the pages at once. +func (p Pager) AllPages() (Page, error) { + // pagesSlice holds all the pages until they get converted into as Page Body. + var pagesSlice []interface{} + // body will contain the final concatenated Page body. + var body reflect.Value + + // Grab a test page to ascertain the page body type. + testPage, err := p.fetchNextPage(p.initialURL) + if err != nil { + return nil, err + } + // Store the page type so we can use reflection to create a new mega-page of + // that type. + pageType := reflect.TypeOf(testPage) + + // Switch on the page body type. Recognized types are `map[string]interface{}`, + // `[]byte`, and `[]interface{}`. + switch testPage.GetBody().(type) { + case map[string]interface{}: + // key is the map key for the page body if the body type is `map[string]interface{}`. + var key string + // Iterate over the pages to concatenate the bodies. + err := p.EachPage(func(page Page) (bool, error) { + b := page.GetBody().(map[string]interface{}) + for k := range b { + // If it's a linked page, we don't want the `links`, we want the other one. + if !strings.HasSuffix(k, "links") { + key = k + } + } + pagesSlice = append(pagesSlice, b[key].([]interface{})...) + return true, nil + }) + if err != nil { + return nil, err + } + // Set body to value of type `map[string]interface{}` + body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice))) + body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice)) + case []byte: + // Iterate over the pages to concatenate the bodies. + err := p.EachPage(func(page Page) (bool, error) { + b := page.GetBody().([]byte) + pagesSlice = append(pagesSlice, b) + // seperate pages with a comma + pagesSlice = append(pagesSlice, []byte{10}) + return true, nil + }) + if err != nil { + return nil, err + } + // Remove the trailing comma. + pagesSlice = pagesSlice[:len(pagesSlice)-1] + var b []byte + // Combine the slice of slices in to a single slice. + for _, slice := range pagesSlice { + b = append(b, slice.([]byte)...) + } + // Set body to value of type `bytes`. + body = reflect.New(reflect.TypeOf(b)).Elem() + body.SetBytes(b) + case []interface{}: + // Iterate over the pages to concatenate the bodies. + err := p.EachPage(func(page Page) (bool, error) { + b := page.GetBody().([]interface{}) + pagesSlice = append(pagesSlice, b...) + return true, nil + }) + if err != nil { + return nil, err + } + // Set body to value of type `[]interface{}` + body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice)) + for i, s := range pagesSlice { + body.Index(i).Set(reflect.ValueOf(s)) + } + default: + return nil, fmt.Errorf("Page body has unrecognized type.") + } + + // Each `Extract*` function is expecting a specific type of page coming back, + // otherwise the type assertion in those functions will fail. pageType is needed + // to create a type in this method that has the same type that the `Extract*` + // function is expecting and set the Body of that object to the concatenated + // pages. + page := reflect.New(pageType) + // Set the page body to be the concatenated pages. + page.Elem().FieldByName("Body").Set(body) + // Set any additional headers that were pass along. The `objectstorage` pacakge, + // for example, passes a Content-Type header. + h := make(http.Header) + for k, v := range p.Headers { + h.Add(k, v) + } + page.Elem().FieldByName("Header").Set(reflect.ValueOf(h)) + // Type assert the page to a Page interface so that the type assertion in the + // `Extract*` methods will work. + return page.Elem().Interface().(Page), err +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single.go index 4dd3c5c425b..f78d4ab5d92 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single.go @@ -7,3 +7,9 @@ type SinglePageBase PageResult func (current SinglePageBase) NextPageURL() (string, error) { return "", nil } + +// GetBody returns the single page's body. This method is needed to satisfy the +// Page interface. +func (current SinglePageBase) GetBody() interface{} { + return current.Body +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single_test.go index 8817d570f28..4af0fee69ab 100644 --- a/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single_test.go +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/single_test.go @@ -69,3 +69,16 @@ func TestEnumerateSinglePaged(t *testing.T) { testhelper.CheckNoErr(t, err) testhelper.CheckEquals(t, 1, callCount) } + +func TestAllPagesSingle(t *testing.T) { + pager := setupSinglePaged() + defer testhelper.TeardownHTTP() + + page, err := pager.AllPages() + testhelper.AssertNoErr(t, err) + + expected := []int{1, 2, 3} + actual, err := ExtractSingleInts(page) + testhelper.AssertNoErr(t, err) + testhelper.CheckDeepEquals(t, expected, actual) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/params.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/params.go index 948783b0738..4d0f1e6e028 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 4a2c9fe3413..2f40eec8122 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 7754c208121..f342a5e54e0 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,172 @@ 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 { + 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 b260246c5a3..8fd20f8f812 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 b338c36b71d..1cd1b6e30cc 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 0fab2828bc2..c81644c5ddd 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/client.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go index 45199a40326..8f1f34f7b8d 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) @@ -187,3 +192,13 @@ func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) ( } 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 d3c973ecb21..3aefb0cdca1 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/virtualinterfaces/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/virtualinterfaces/requests.go index bfe34878611..3c81ef80f8b 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/compute/v2/volumeattach/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/delegate.go new file mode 100644 index 00000000000..c6003e0e5b9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/delegate.go @@ -0,0 +1,27 @@ +package volumeattach + +import ( + "github.com/rackspace/gophercloud" + os "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" + "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 os.List(client, serverID) +} + +// Create requests the creation of a new volume attachment on the server +func Create(client *gophercloud.ServiceClient, serverID string, opts os.CreateOptsBuilder) os.CreateResult { + return os.Create(client, serverID, opts) +} + +// Get returns public data about a previously created VolumeAttachment. +func Get(client *gophercloud.ServiceClient, serverID, aID string) os.GetResult { + return os.Get(client, serverID, aID) +} + +// Delete requests the deletion of a previous stored VolumeAttachment from the server. +func Delete(client *gophercloud.ServiceClient, serverID, aID string) os.DeleteResult { + return os.Delete(client, serverID, aID) +} diff --git a/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/delegate_test.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/delegate_test.go new file mode 100644 index 00000000000..e26416cba0d --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/delegate_test.go @@ -0,0 +1,66 @@ +package volumeattach + +import ( + "testing" + + os "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" + "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() + os.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 := os.ExtractVolumeAttachments(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, os.ExpectedVolumeAttachmentSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.HandleCreateSuccessfully(t) + serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" + + actual, err := Create(client.ServiceClient(), serverId, os.CreateOpts{ + Device: "/dev/vdc", + VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", + }).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &os.CreatedVolumeAttachment, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.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, &os.SecondVolumeAttachment, actual) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + os.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/rackspace/compute/v2/volumeattach/doc.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/doc.go new file mode 100644 index 00000000000..2164908e3a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach/doc.go @@ -0,0 +1,3 @@ +// Package volumeattach provides the ability to attach and detach volume +// to instances to Rackspace servers +package volumeattach 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 a6c01e4f2d9..a6ee8515ceb 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 ae2acde643c..6135bec101a 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 e1e92ac6319..94d98e34cb1 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 342f10790ec..49a46f6d4e5 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 cfc35d2ef75..917282c63ba 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/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/requests.go index 77894aaa01a..86fe5d7c8ca 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 @@ -217,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 @@ -229,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/sessions/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/sessions/requests.go index 9853ad1320c..5572407edda 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 84b27121729..e9c65142862 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 8c2e4be415a..2680a892b68 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 42f0c1d071a..d52a73afd47 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/bulk/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/bulk/requests.go index d252609d418..898b73b0bd8 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/requests.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers/requests.go index 05b19399338..8e4abbe436c 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 @@ -3,7 +3,6 @@ package cdncontainers import ( "strconv" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" ) @@ -50,11 +49,11 @@ 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 } @@ -64,11 +63,10 @@ func Enable(c *gophercloud.ServiceClient, containerName string, opts EnableOptsB // 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 } @@ -147,11 +145,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 } 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 00000000000..540e0cd298a --- /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/orchestration/v1/buildinfo/delegate.go b/Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo/delegate.go new file mode 100644 index 00000000000..c834e5c7d39 --- /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 00000000000..b25a690c8df --- /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 00000000000..183e8dfa76d --- /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 00000000000..08675deac77 --- /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 00000000000..e1c0bc8dbcf --- /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 00000000000..dfd6ef6605e --- /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 00000000000..cb7be28b78a --- /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 00000000000..18e9614151a --- /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 00000000000..e4f8b08dcc7 --- /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 00000000000..f7e387f8f79 --- /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 00000000000..a1fb3931299 --- /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 00000000000..19231b5137e --- /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 00000000000..c9afeb156f6 --- /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 00000000000..3b5d46e1c9e --- /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 00000000000..d4006c476d5 --- /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 00000000000..5af0bd62a11 --- /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