Merge pull request #562 from smashwilson/openstack-insecure

Allow insecure connections in the Openstack driver.
This commit is contained in:
Evan Hazlett 2015-02-26 10:28:20 -05:00
commit 1e716a96ad
250 changed files with 14221 additions and 1810 deletions

9
Godeps/Godeps.json generated
View File

@ -123,15 +123,10 @@
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
},
{
"ImportPath": "github.com/racker/perigee",
"Comment": "v0.0.0-18-g0c00cb0",
"Rev": "0c00cb0a026b71034ebc8205263c77dad3577db5"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-232-g2e7ab37",
"Rev": "2e7ab378257b8723e02cbceac7410be4db286436"
"Comment": "v1.0.0-473-g7ca169d",
"Rev": "7ca169d371b29e3dbab9e631c3a6151896b06330"
},
{
"ImportPath": "github.com/smartystreets/go-aws-auth",

View File

@ -1,2 +0,0 @@
bin/*
pkg/*

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,120 +0,0 @@
# perigee
Perigee provides a REST client that, while it should be generic enough to use with most any RESTful API, is nonetheless optimized to the needs of the OpenStack APIs.
Perigee grew out of the need to refactor out common API access code from the [gorax](http://github.com/racker/gorax) project.
Several things influenced the name of the project.
Numerous elements of the OpenStack ecosystem are named after astronomical artifacts.
Additionally, perigee occurs when two orbiting bodies are closest to each other.
Perigee seemed appropriate for something aiming to bring OpenStack and other RESTful services closer to the end-user.
**This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want**
## Installation and Testing
To install:
```bash
go get github.com/racker/perigee
```
To run unit tests:
```bash
go test github.com/racker/perigee
```
## Contributing
The following guidelines are preliminary, as this project is just starting out.
However, this should serve as a working first-draft.
### Branching
The master branch must always be a valid build.
The `go get` command will not work otherwise.
Therefore, development must occur on a different branch.
When creating a feature branch, do so off the master branch:
```bash
git checkout master
git pull
git checkout -b featureBranch
git checkout -b featureBranch-wip # optional
```
Perform all your editing and testing in the WIP-branch.
Feel free to make as many commits as you see fit.
You may even open "WIP" pull requests from your feature branch to seek feedback.
WIP pull requests will **never** be merged, however.
To get code merged, you'll need to "squash" your changes into one or more clean commits in the feature branch.
These steps should be followed:
```bash
git checkout featureBranch
git merge --squash featureBranch-wip
git commit -a
git push origin featureBranch
```
You may now open a nice, clean, self-contained pull request from featureBranch to master.
The `git commit -a` command above will open a text editor so that
you may provide a comprehensive description of the changes.
In general, when submitting a pull request against master,
be sure to answer the following questions:
- What is the problem?
- Why is it a problem?
- What is your solution?
- How does your solution work? (Recommended for non-trivial changes.)
- Why should we use your solution over someone elses? (Recommended especially if multiple solutions being discussed.)
Remember that monster-sized pull requests are a bear to code-review,
so having helpful commit logs are an absolute must to review changes as quickly as possible.
Finally, (s)he who breaks master is ultimately responsible for fixing master.
### Source Representation
The Go community firmly believes in a consistent representation for all Go source code.
We do too.
Make sure all source code is passed through "go fmt" *before* you create your pull request.
Please note, however, that we fully acknowledge and recognize that we no longer rely upon punch-cards for representing source files.
Therefore, no 80-column limit exists.
However, if a line exceeds 132 columns, you may want to consider splitting the line.
### Unit and Integration Tests
Pull requests that include non-trivial code changes without accompanying unit tests will be flatly rejected.
While we have no way of enforcing this practice,
you can ensure your code is thoroughly tested by always [writing tests first by intention.](http://en.wikipedia.org/wiki/Test-driven_development)
When creating a pull request, if even one test fails, the PR will be rejected.
Make sure all unit tests pass.
Make sure all integration tests pass.
### Documentation
Private functions and methods which are obvious to anyone unfamiliar with gorax needn't be accompanied by documentation.
However, this is a code-smell; if submitting a PR, expect to justify your decision.
Public functions, regardless of how obvious, **must** have accompanying godoc-style documentation.
This is not to suggest you should provide a tome for each function, however.
Sometimes a link to more information is more appropriate, provided the link is stable, reliable, and pertinent.
Changing documentation often results in bizarre diffs in pull requests, due to text often spanning multiple lines.
To work around this, put [one logical thought or sentence on a single line.](http://rhodesmill.org/brandon/2012/one-sentence-per-line/)
While this looks weird in a plain-text editor,
remember that both godoc and HTML viewers will reflow text.
The source code and its comments should be easy to edit with minimal diff pollution.
Let software dedicated to presenting the documentation to human readers deal with its presentation.
## Examples
t.b.d.

View File

@ -1,269 +0,0 @@
// vim: ts=8 sw=8 noet ai
package perigee
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
)
// The UnexpectedResponseCodeError structure represents a mismatch in understanding between server and client in terms of response codes.
// Most often, this is due to an actual error condition (e.g., getting a 404 for a resource when you expect a 200).
// However, it needn't always be the case (e.g., getting a 204 (No Content) response back when a 200 is expected).
type UnexpectedResponseCodeError struct {
Url string
Expected []int
Actual int
Body []byte
}
func (err *UnexpectedResponseCodeError) Error() string {
return fmt.Sprintf("Expected HTTP response code %d when accessing URL(%s); got %d instead with the following body:\n%s", err.Expected, err.Url, err.Actual, string(err.Body))
}
// Request issues an HTTP request, marshaling parameters, and unmarshaling results, as configured in the provided Options parameter.
// The Response structure returned, if any, will include accumulated results recovered from the HTTP server.
// See the Response structure for more details.
func Request(method string, url string, opts Options) (*Response, error) {
var body io.Reader
var response Response
client := opts.CustomClient
if client == nil {
client = new(http.Client)
}
contentType := opts.ContentType
body = nil
if opts.ReqBody != nil {
if contentType == "" {
contentType = "application/json"
}
if contentType == "application/json" {
bodyText, err := json.Marshal(opts.ReqBody)
if err != nil {
return nil, err
}
body = strings.NewReader(string(bodyText))
if opts.DumpReqJson {
log.Printf("Making request:\n%#v\n", string(bodyText))
}
} else {
// assume opts.ReqBody implements the correct interface
body = opts.ReqBody.(io.Reader)
}
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
if contentType != "" {
req.Header.Add("Content-Type", contentType)
}
if opts.ContentLength > 0 {
req.ContentLength = opts.ContentLength
req.Header.Add("Content-Length", string(opts.ContentLength))
}
if opts.MoreHeaders != nil {
for k, v := range opts.MoreHeaders {
req.Header.Add(k, v)
}
}
if accept := req.Header.Get("Accept"); accept == "" {
accept = opts.Accept
if accept == "" {
accept = "application/json"
}
req.Header.Add("Accept", accept)
}
if opts.SetHeaders != nil {
err = opts.SetHeaders(req)
if err != nil {
return &response, err
}
}
httpResponse, err := client.Do(req)
if httpResponse != nil {
response.HttpResponse = *httpResponse
response.StatusCode = httpResponse.StatusCode
}
if err != nil {
return &response, err
}
// This if-statement is legacy code, preserved for backward compatibility.
if opts.StatusCode != nil {
*opts.StatusCode = httpResponse.StatusCode
}
acceptableResponseCodes := opts.OkCodes
if len(acceptableResponseCodes) != 0 {
if not_in(httpResponse.StatusCode, acceptableResponseCodes) {
b, _ := ioutil.ReadAll(httpResponse.Body)
httpResponse.Body.Close()
return &response, &UnexpectedResponseCodeError{
Url: url,
Expected: acceptableResponseCodes,
Actual: httpResponse.StatusCode,
Body: b,
}
}
}
if opts.Results != nil {
defer httpResponse.Body.Close()
jsonResult, err := ioutil.ReadAll(httpResponse.Body)
response.JsonResult = jsonResult
if err != nil {
return &response, err
}
err = json.Unmarshal(jsonResult, opts.Results)
// This if-statement is legacy code, preserved for backward compatibility.
if opts.ResponseJson != nil {
*opts.ResponseJson = jsonResult
}
}
return &response, err
}
// not_in returns false if, and only if, the provided needle is _not_
// in the given set of integers.
func not_in(needle int, haystack []int) bool {
for _, straw := range haystack {
if needle == straw {
return false
}
}
return true
}
// Post makes a POST request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Post(url string, opts Options) error {
r, err := Request("POST", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Get makes a GET request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Get(url string, opts Options) error {
r, err := Request("GET", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Delete makes a DELETE request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Delete(url string, opts Options) error {
r, err := Request("DELETE", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Put makes a PUT request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Put(url string, opts Options) error {
r, err := Request("PUT", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Options describes a set of optional parameters to the various request calls.
//
// The custom client can be used for a variety of purposes beyond selecting encrypted versus unencrypted channels.
// Transports can be defined to provide augmented logging, header manipulation, et. al.
//
// If the ReqBody field is provided, it will be embedded as a JSON object.
// Otherwise, provide nil.
//
// If JSON output is to be expected from the response,
// provide either a pointer to the container structure in Results,
// or a pointer to a nil-initialized pointer variable.
// The latter method will cause the unmarshaller to allocate the container type for you.
// If no response is expected, provide a nil Results value.
//
// The MoreHeaders map, if non-nil or empty, provides a set of headers to add to those
// already present in the request. At present, only Accepted and Content-Type are set
// by default.
//
// OkCodes provides a set of acceptable, positive responses.
//
// If provided, StatusCode specifies a pointer to an integer, which will receive the
// returned HTTP status code, successful or not. DEPRECATED; use the Response.StatusCode field instead for new software.
//
// ResponseJson, if specified, provides a means for returning the raw JSON. This is
// most useful for diagnostics. DEPRECATED; use the Response.JsonResult field instead for new software.
//
// DumpReqJson, if set to true, will cause the request to appear to stdout for debugging purposes.
// This attribute may be removed at any time in the future; DO NOT use this attribute in production software.
//
// Response, if set, provides a way to communicate the complete set of HTTP response, raw JSON, status code, and
// other useful attributes back to the caller. Note that the Request() method returns a Response structure as part
// of its public interface; you don't need to set the Response field here to use this structure. The Response field
// exists primarily for legacy or deprecated functions.
//
// SetHeaders allows the caller to provide code to set any custom headers programmatically. Typically, this
// facility can invoke, e.g., SetBasicAuth() on the request to easily set up authentication.
// Any error generated will terminate the request and will propegate back to the caller.
type Options struct {
CustomClient *http.Client
ReqBody interface{}
Results interface{}
MoreHeaders map[string]string
OkCodes []int
StatusCode *int `DEPRECATED`
DumpReqJson bool `UNSUPPORTED`
ResponseJson *[]byte `DEPRECATED`
Response **Response
ContentType string `json:"Content-Type,omitempty"`
ContentLength int64 `json:"Content-Length,omitempty"`
Accept string `json:"Accept,omitempty"`
SetHeaders func(r *http.Request) error
}
// Response contains return values from the various request calls.
//
// HttpResponse will return the http response from the request call.
// Note: HttpResponse.Body is always closed and will not be available from this return value.
//
// StatusCode specifies the returned HTTP status code, successful or not.
//
// If Results is specified in the Options:
// - JsonResult will contain the raw return from the request call
// This is most useful for diagnostics.
// - Result will contain the unmarshalled json either in the Result passed in
// or the unmarshaller will allocate the container type for you.
type Response struct {
HttpResponse http.Response
JsonResult []byte
Results interface{}
StatusCode int
}

View File

@ -1,226 +0,0 @@
package perigee
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestNormal(t *testing.T) {
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if response.StatusCode != 200 {
t.Fatalf("response code %d is not 200", response.StatusCode)
}
}
func TestOKCodes(t *testing.T) {
expectCode := 201
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(expectCode)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
options := Options{
OkCodes: []int{expectCode},
}
results, err := Request("GET", ts.URL, options)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if results.StatusCode != expectCode {
t.Fatalf("response code %d is not %d", results.StatusCode, expectCode)
}
}
func TestLocation(t *testing.T) {
newLocation := "http://www.example.com"
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
location, err := response.HttpResponse.Location()
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if location.String() != newLocation {
t.Fatalf("location returned \"%s\" is not \"%s\"", location.String(), newLocation)
}
}
func TestHeaders(t *testing.T) {
newLocation := "http://www.example.com"
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
location := response.HttpResponse.Header.Get("Location")
if location == "" {
t.Fatalf("Location should not empty")
}
if location != newLocation {
t.Fatalf("location returned \"%s\" is not \"%s\"", location, newLocation)
}
}
func TestCustomHeaders(t *testing.T) {
var contentType, accept, contentLength string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := map[string][]string(r.Header)
contentType = m["Content-Type"][0]
accept = m["Accept"][0]
contentLength = m["Content-Length"][0]
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{
ContentLength: 5,
ContentType: "x-application/vb",
Accept: "x-application/c",
ReqBody: strings.NewReader("Hello"),
})
if err != nil {
t.Fatalf(err.Error())
}
if contentType != "x-application/vb" {
t.Fatalf("I expected x-application/vb; got ", contentType)
}
if contentLength != "5" {
t.Fatalf("I expected 5 byte content length; got ", contentLength)
}
if accept != "x-application/c" {
t.Fatalf("I expected x-application/c; got ", accept)
}
}
func TestJson(t *testing.T) {
newLocation := "http://www.example.com"
jsonBytes := []byte(`{"foo": {"bar": "baz"}}`)
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write(jsonBytes)
})
ts := httptest.NewServer(handler)
defer ts.Close()
type Data struct {
Foo struct {
Bar string `json:"bar"`
} `json:"foo"`
}
var data Data
response, err := Request("GET", ts.URL, Options{Results: &data})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if bytes.Compare(jsonBytes, response.JsonResult) != 0 {
t.Fatalf("json returned \"%s\" is not \"%s\"", response.JsonResult, jsonBytes)
}
if data.Foo.Bar != "baz" {
t.Fatalf("Results returned %v", data)
}
}
func TestSetHeaders(t *testing.T) {
var wasCalled bool
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hi"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{
SetHeaders: func(r *http.Request) error {
wasCalled = true
return nil
},
})
if err != nil {
t.Fatal(err)
}
if !wasCalled {
t.Fatal("I expected header setter callback to be called, but it wasn't")
}
myError := fmt.Errorf("boo")
_, err = Request("GET", ts.URL, Options{
SetHeaders: func(r *http.Request) error {
return myError
},
})
if err != myError {
t.Fatal("I expected errors to propegate back to the caller.")
}
}
func TestBodilessMethodsAreSentWithoutContentHeaders(t *testing.T) {
var h map[string][]string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h = r.Header
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf(err.Error())
}
if len(h["Content-Type"]) != 0 {
t.Fatalf("I expected nothing for Content-Type but got ", h["Content-Type"])
}
if len(h["Content-Length"]) != 0 {
t.Fatalf("I expected nothing for Content-Length but got ", h["Content-Length"])
}
}

View File

@ -4,6 +4,8 @@ install:
go:
- 1.1
- 1.2
- 1.3
- 1.4
- tip
script: script/cibuild
after_success:
@ -12,3 +14,4 @@ after_success:
- go get github.com/mattn/goveralls
- export PATH=$PATH:$HOME/gopath/bin/
- goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
sudo: false

View File

@ -10,3 +10,4 @@ Contributors
| Ash Wilson | <ash.wilson@rackspace.com>
| Jamie Hannaford | <jamie.hannaford@rackspace.com>
| Don Schenck | don.schenck@rackspace.com>
| Joe Topjian | <joe@topjian.net>

View File

@ -1,5 +0,0 @@
{
"ImportPath": "github.com/rackspace/gophercloud",
"GoVersion": "go1.3.3",
"Deps": []
}

View File

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -17,7 +17,10 @@ func TestAccounts(t *testing.T) {
// Update an account's metadata.
updateres := accounts.Update(client, accounts.UpdateOpts{Metadata: metadata})
th.AssertNoErr(t, updateres.Err)
t.Logf("Update Account Response: %+v\n", updateres)
updateHeaders, err := updateres.Extract()
th.AssertNoErr(t, err)
t.Logf("Update Account Response Headers: %+v\n", updateHeaders)
// Defer the deletion of the metadata set above.
defer func() {
@ -29,11 +32,14 @@ func TestAccounts(t *testing.T) {
th.AssertNoErr(t, updateres.Err)
}()
// Retrieve account metadata.
getres := accounts.Get(client, nil)
th.AssertNoErr(t, getres.Err)
// Extract the custom metadata from the 'Get' response.
am, err := getres.ExtractMetadata()
res := accounts.Get(client, nil)
h, err := res.Extract()
th.AssertNoErr(t, err)
t.Logf("Get Account Response Headers: %+v\n", h)
am, err := res.ExtractMetadata()
th.AssertNoErr(t, err)
for k := range metadata {
if am[k] != metadata[strings.Title(k)] {

View File

@ -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)
}

View File

@ -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\" &gt; /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
}

View File

@ -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"
}
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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\" &gt; /root/hello-world.txt\n",
},
},
},
},
}
validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract()
th.AssertNoErr(t, err)
t.Logf("validated template: %+v\n", validatedTemplate)
}

View File

@ -0,0 +1,32 @@
// +build acceptance
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/rackspace/cdn/v1/base"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestBaseOps(t *testing.T) {
client := newClient(t)
t.Log("Retrieving Home Document")
testHomeDocumentGet(t, client)
t.Log("Pinging root URL")
testPing(t, client)
}
func testHomeDocumentGet(t *testing.T, client *gophercloud.ServiceClient) {
hd, err := base.Get(client).Extract()
th.AssertNoErr(t, err)
t.Logf("Retrieved home document: %+v", *hd)
}
func testPing(t *testing.T, client *gophercloud.ServiceClient) {
err := base.Ping(client).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Successfully pinged root URL")
}

View File

@ -0,0 +1,23 @@
// +build acceptance
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/rackspace"
th "github.com/rackspace/gophercloud/testhelper"
)
func newClient(t *testing.T) *gophercloud.ServiceClient {
ao, err := rackspace.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
client, err := rackspace.AuthenticatedClient(ao)
th.AssertNoErr(t, err)
c, err := rackspace.NewCDNV1(client, gophercloud.EndpointOpts{})
th.AssertNoErr(t, err)
return c
}

View File

@ -0,0 +1,47 @@
// +build acceptance
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/cdn/v1/flavors"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/cdn/v1/flavors"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestFlavor(t *testing.T) {
client := newClient(t)
t.Log("Listing Flavors")
id := testFlavorsList(t, client)
t.Log("Retrieving Flavor")
testFlavorGet(t, client, id)
}
func testFlavorsList(t *testing.T, client *gophercloud.ServiceClient) string {
var id string
err := flavors.List(client).EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := os.ExtractFlavors(page)
th.AssertNoErr(t, err)
for _, flavor := range flavorList {
t.Logf("Listing flavor: ID [%s] Providers [%+v]", flavor.ID, flavor.Providers)
id = flavor.ID
}
return true, nil
})
th.AssertNoErr(t, err)
return id
}
func testFlavorGet(t *testing.T, client *gophercloud.ServiceClient, id string) {
flavor, err := flavors.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Retrieved Flavor: %+v", *flavor)
}

View File

@ -0,0 +1,93 @@
// +build acceptance
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/cdn/v1/services"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/cdn/v1/services"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestService(t *testing.T) {
client := newClient(t)
t.Log("Creating Service")
loc := testServiceCreate(t, client, "test-site-1")
t.Logf("Created service at location: %s", loc)
defer testServiceDelete(t, client, loc)
t.Log("Updating Service")
testServiceUpdate(t, client, loc)
t.Log("Retrieving Service")
testServiceGet(t, client, loc)
t.Log("Listing Services")
testServiceList(t, client)
}
func testServiceCreate(t *testing.T, client *gophercloud.ServiceClient, name string) string {
createOpts := os.CreateOpts{
Name: name,
Domains: []os.Domain{
os.Domain{
Domain: "www." + name + ".com",
},
},
Origins: []os.Origin{
os.Origin{
Origin: name + ".com",
Port: 80,
SSL: false,
},
},
FlavorID: "cdn",
}
l, err := services.Create(client, createOpts).Extract()
th.AssertNoErr(t, err)
return l
}
func testServiceGet(t *testing.T, client *gophercloud.ServiceClient, id string) {
s, err := services.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Retrieved service: %+v", *s)
}
func testServiceUpdate(t *testing.T, client *gophercloud.ServiceClient, id string) {
opts := os.UpdateOpts{
os.Append{
Value: os.Domain{Domain: "newDomain.com", Protocol: "http"},
},
}
loc, err := services.Update(client, id, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Successfully updated service at location: %s", loc)
}
func testServiceList(t *testing.T, client *gophercloud.ServiceClient) {
err := services.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
serviceList, err := os.ExtractServices(page)
th.AssertNoErr(t, err)
for _, service := range serviceList {
t.Logf("Listing service: %+v", service)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func testServiceDelete(t *testing.T, client *gophercloud.ServiceClient, id string) {
err := services.Delete(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Successfully deleted service (%s)", id)
}

View File

@ -0,0 +1,32 @@
// +build acceptance
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
osServiceAssets "github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets"
"github.com/rackspace/gophercloud/rackspace/cdn/v1/serviceassets"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestServiceAsset(t *testing.T) {
client := newClient(t)
t.Log("Creating Service")
loc := testServiceCreate(t, client, "test-site-2")
t.Logf("Created service at location: %s", loc)
t.Log("Deleting Service Assets")
testServiceAssetDelete(t, client, loc)
}
func testServiceAssetDelete(t *testing.T, client *gophercloud.ServiceClient, url string) {
deleteOpts := osServiceAssets.DeleteOpts{
All: true,
}
err := serviceassets.Delete(client, url, deleteOpts).ExtractErr()
th.AssertNoErr(t, err)
t.Log("Successfully deleted all Service Assets")
}

View File

@ -41,6 +41,9 @@ func TestBootFromVolume(t *testing.T) {
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created server: %+v\n", server)
//defer deleteServer(t, client, server)
t.Logf("Deleting server [%s]...", name)
defer deleteServer(t, client, server)
getServer(t, client, server)
listServers(t, client)
}

View File

@ -100,6 +100,18 @@ func getServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Serve
logServer(t, details, -1)
}
func updateServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) {
t.Logf("> servers.Get")
opts := os.UpdateOpts{
Name: "updated-server",
}
updatedServer, err := servers.Update(client, server.ID, opts).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, "updated-server", updatedServer.Name)
logServer(t, updatedServer, -1)
}
func listServers(t *testing.T, client *gophercloud.ServiceClient) {
t.Logf("> servers.List")
@ -197,6 +209,7 @@ func TestServerOperations(t *testing.T) {
defer deleteServer(t, client, server)
getServer(t, client, server)
updateServer(t, client, server)
listServers(t, client)
changeAdminPassword(t, client, server)
rebootServer(t, client, server)

View File

@ -13,11 +13,11 @@ func TestAccounts(t *testing.T) {
c, err := createClient(t, false)
th.AssertNoErr(t, err)
updateres := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}})
th.AssertNoErr(t, updateres.Err)
t.Logf("Headers from Update Account request: %+v\n", updateres.Header)
updateHeaders, err := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).Extract()
th.AssertNoErr(t, err)
t.Logf("Update Account Response Headers: %+v\n", updateHeaders)
defer func() {
updateres = raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}})
updateres := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}})
th.AssertNoErr(t, updateres.Err)
metadata, err := raxAccounts.Get(c).ExtractMetadata()
th.AssertNoErr(t, err)
@ -25,8 +25,13 @@ func TestAccounts(t *testing.T) {
th.CheckEquals(t, metadata["White"], "")
}()
metadata, err := raxAccounts.Get(c).ExtractMetadata()
th.AssertNoErr(t, err)
getResp := raxAccounts.Get(c)
th.AssertNoErr(t, getResp.Err)
getHeaders, _ := getResp.Extract()
t.Logf("Get Account Response Headers: %+v\n", getHeaders)
metadata, _ := getResp.ExtractMetadata()
t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
th.CheckEquals(t, metadata["White"], "mountains")

View File

@ -26,10 +26,11 @@ func TestCDNContainers(t *testing.T) {
raxCDNClient, err := createClient(t, true)
th.AssertNoErr(t, err)
r := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900})
th.AssertNoErr(t, r.Err)
t.Logf("Headers from Enable CDN Container request: %+v\n", r.Header)
enableRes := raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900})
t.Logf("Header map from Enable CDN Container request: %+v\n", enableRes.Header)
enableHeader, err := enableRes.Extract()
th.AssertNoErr(t, err)
t.Logf("Headers from Enable CDN Container request: %+v\n", enableHeader)
t.Logf("Container Names available to the currently issued token:")
count := 0
@ -51,11 +52,15 @@ func TestCDNContainers(t *testing.T) {
t.Errorf("No CDN containers listed for your current token.")
}
updateres := raxCDNContainers.Update(raxCDNClient, "gophercloud-test", raxCDNContainers.UpdateOpts{CDNEnabled: false})
th.AssertNoErr(t, updateres.Err)
t.Logf("Headers from Update CDN Container request: %+v\n", updateres.Header)
metadata, err := raxCDNContainers.Get(raxCDNClient, "gophercloud-test").ExtractMetadata()
updateOpts := raxCDNContainers.UpdateOpts{XCDNEnabled: raxCDNContainers.Disabled, XLogRetention: raxCDNContainers.Enabled}
updateHeader, err := raxCDNContainers.Update(raxCDNClient, "gophercloud-test", updateOpts).Extract()
th.AssertNoErr(t, err)
t.Logf("Headers from Get CDN Container request (after update): %+v\n", metadata)
t.Logf("Headers from Update CDN Container request: %+v\n", updateHeader)
getRes := raxCDNContainers.Get(raxCDNClient, "gophercloud-test")
getHeader, err := getRes.Extract()
th.AssertNoErr(t, err)
t.Logf("Headers from Get CDN Container request (after update): %+v\n", getHeader)
metadata, err := getRes.ExtractMetadata()
t.Logf("Metadata from Get CDN Container request (after update): %+v\n", metadata)
}

View File

@ -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)
}

View File

@ -57,29 +57,34 @@ func TestContainers(t *testing.T) {
t.Errorf("No containers listed for your current token.")
}
createres := raxContainers.Create(c, "gophercloud-test", nil)
th.AssertNoErr(t, createres.Err)
createHeader, err := raxContainers.Create(c, "gophercloud-test", nil).Extract()
th.AssertNoErr(t, err)
t.Logf("Headers from Create Container request: %+v\n", createHeader)
defer func() {
res := raxContainers.Delete(c, "gophercloud-test")
th.AssertNoErr(t, res.Err)
deleteres := raxContainers.Delete(c, "gophercloud-test")
deleteHeader, err := deleteres.Extract()
th.AssertNoErr(t, err)
t.Logf("Headers from Delete Container request: %+v\n", deleteres.Header)
t.Logf("Headers from Delete Container request: %+v\n", deleteHeader)
}()
updateres := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}})
th.AssertNoErr(t, updateres.Err)
t.Logf("Headers from Update Account request: %+v\n", updateres.Header)
updateHeader, err := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).Extract()
th.AssertNoErr(t, err)
t.Logf("Headers from Update Container request: %+v\n", updateHeader)
defer func() {
res := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": ""}})
th.AssertNoErr(t, res.Err)
metadata, err := raxContainers.Get(c, "gophercloud-test").ExtractMetadata()
th.AssertNoErr(t, err)
t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
t.Logf("Metadata from Get Container request (after update reverted): %+v\n", metadata)
th.CheckEquals(t, metadata["White"], "")
}()
getres := raxContainers.Get(c, "gophercloud-test")
t.Logf("Headers from Get Account request (after update): %+v\n", getres.Header)
metadata, err := getres.ExtractMetadata()
getHeader, err := getres.Extract()
th.AssertNoErr(t, err)
t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
t.Logf("Headers from Get Container request (after update): %+v\n", getHeader)
metadata, err := getres.ExtractMetadata()
t.Logf("Metadata from Get Container request (after update): %+v\n", metadata)
th.CheckEquals(t, metadata["White"], "mountains")
}

View File

@ -21,6 +21,7 @@ func TestObjects(t *testing.T) {
th.AssertNoErr(t, res.Err)
defer func() {
t.Logf("Deleting container...")
res := raxContainers.Delete(c, "gophercloud-test")
th.AssertNoErr(t, res.Err)
}()
@ -29,7 +30,9 @@ func TestObjects(t *testing.T) {
options := &osObjects.CreateOpts{ContentType: "text/plain"}
createres := raxObjects.Create(c, "gophercloud-test", "o1", content, options)
th.AssertNoErr(t, createres.Err)
defer func() {
t.Logf("Deleting object o1...")
res := raxObjects.Delete(c, "gophercloud-test", "o1", nil)
th.AssertNoErr(t, res.Err)
}()
@ -80,6 +83,7 @@ func TestObjects(t *testing.T) {
copyres := raxObjects.Copy(c, "gophercloud-test", "o1", &raxObjects.CopyOpts{Destination: "gophercloud-test/o2"})
th.AssertNoErr(t, copyres.Err)
defer func() {
t.Logf("Deleting object o2...")
res := raxObjects.Delete(c, "gophercloud-test", "o2", nil)
th.AssertNoErr(t, res.Err)
}()
@ -99,7 +103,7 @@ func TestObjects(t *testing.T) {
metadata, err := raxObjects.Get(c, "gophercloud-test", "o2", nil).ExtractMetadata()
th.AssertNoErr(t, err)
t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
th.CheckEquals(t, metadata["White"], "")
th.CheckEquals(t, "", metadata["White"])
}()
getres := raxObjects.Get(c, "gophercloud-test", "o2", nil)
@ -108,5 +112,13 @@ func TestObjects(t *testing.T) {
metadata, err := getres.ExtractMetadata()
th.AssertNoErr(t, err)
t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
th.CheckEquals(t, metadata["White"], "mountains")
th.CheckEquals(t, "mountains", metadata["White"])
createTempURLOpts := osObjects.CreateTempURLOpts{
Method: osObjects.GET,
TTL: 600,
}
tempURL, err := raxObjects.CreateTempURL(c, "gophercloud-test", "o1", createTempURLOpts)
th.AssertNoErr(t, err)
t.Logf("TempURL for object (%s): %s", "o1", tempURL)
}

View File

@ -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)
}

View File

@ -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\" &gt; /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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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\" &gt; /root/hello-world.txt\n",
},
},
},
},
}
validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract()
th.AssertNoErr(t, err)
t.Logf("validated template: %+v\n", validatedTemplate)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"
}
]
}
}
`)
})

View File

@ -5,8 +5,6 @@ import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/racker/perigee"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
@ -85,11 +83,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes
return res
}
_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
MoreHeaders: client.AuthenticatedHeaders(),
ReqBody: &reqBody,
Results: &res.Body,
OkCodes: []int{200, 201},
_, res.Err = client.Request("POST", createURL(client), gophercloud.RequestOpts{
OkCodes: []int{200, 201},
JSONBody: &reqBody,
JSONResponse: &res.Body,
})
return res
}
@ -97,9 +94,8 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
MoreHeaders: client.AuthenticatedHeaders(),
OkCodes: []int{202, 204},
_, res.Err = client.Request("DELETE", deleteURL(client, id), gophercloud.RequestOpts{
OkCodes: []int{202, 204},
})
return res
}
@ -108,10 +104,9 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
Results: &res.Body,
MoreHeaders: client.AuthenticatedHeaders(),
OkCodes: []int{200},
_, res.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}
@ -207,11 +202,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
return res
}
_, res.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
MoreHeaders: client.AuthenticatedHeaders(),
OkCodes: []int{200},
ReqBody: &reqBody,
Results: &res.Body,
_, res.Err = client.Request("PUT", updateURL(client, id), gophercloud.RequestOpts{
OkCodes: []int{200},
JSONBody: &reqBody,
JSONResponse: &res.Body,
})
return res
}

View File

@ -56,6 +56,7 @@ func TestGet(t *testing.T) {
th.AssertEquals(t, v.Name, "vol-001")
th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
th.AssertEquals(t, v.Attachments[0]["device"], "/dev/vde")
}
func TestCreate(t *testing.T) {

View File

@ -16,7 +16,7 @@ type Volume struct {
Name string `mapstructure:"display_name"`
// Instances onto which the volume is attached.
Attachments []string `mapstructure:"attachments"`
Attachments []map[string]interface{} `mapstructure:"attachments"`
// This parameter is no longer used.
AvailabilityZone string `mapstructure:"availability_zone"`
@ -28,7 +28,7 @@ type Volume struct {
CreatedAt string `mapstructure:"created_at"`
// Human-readable description for the volume.
Description string `mapstructure:"display_discription"`
Description string `mapstructure:"display_description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `mapstructure:"volume_type"`

View File

@ -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

View File

@ -0,0 +1,4 @@
// Package base provides information and interaction with the base API
// resource in the OpenStack CDN service. This API resource allows for
// retrieving the Home Document and pinging the root URL.
package base

View File

@ -0,0 +1,53 @@
package base
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// HandleGetSuccessfully creates an HTTP handler at `/` on the test handler mux
// that responds with a `Get` response.
func HandleGetSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"resources": {
"rel/cdn": {
"href-template": "services{?marker,limit}",
"href-vars": {
"marker": "param/marker",
"limit": "param/limit"
},
"hints": {
"allow": [
"GET"
],
"formats": {
"application/json": {}
}
}
}
}
}
`)
})
}
// HandlePingSuccessfully creates an HTTP handler at `/ping` on the test handler
// mux that responds with a `Ping` response.
func HandlePingSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}

View File

@ -0,0 +1,24 @@
package base
import "github.com/rackspace/gophercloud"
// Get retrieves the home document, allowing the user to discover the
// entire API.
func Get(c *gophercloud.ServiceClient) GetResult {
var res GetResult
_, res.Err = c.Request("GET", getURL(c), gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}
// Ping retrieves a ping to the server.
func Ping(c *gophercloud.ServiceClient) PingResult {
var res PingResult
_, res.Err = c.Request("GET", pingURL(c), gophercloud.RequestOpts{
OkCodes: []int{204},
MoreHeaders: map[string]string{"Accept": ""},
})
return res
}

View File

@ -0,0 +1,43 @@
package base
import (
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func TestGetHomeDocument(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetSuccessfully(t)
actual, err := Get(fake.ServiceClient()).Extract()
th.CheckNoErr(t, err)
expected := HomeDocument{
"rel/cdn": map[string]interface{}{
"href-template": "services{?marker,limit}",
"href-vars": map[string]interface{}{
"marker": "param/marker",
"limit": "param/limit",
},
"hints": map[string]interface{}{
"allow": []string{"GET"},
"formats": map[string]interface{}{
"application/json": map[string]interface{}{},
},
},
},
}
th.CheckDeepEquals(t, expected, *actual)
}
func TestPing(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandlePingSuccessfully(t)
err := Ping(fake.ServiceClient()).ExtractErr()
th.CheckNoErr(t, err)
}

View File

@ -0,0 +1,35 @@
package base
import (
"errors"
"github.com/rackspace/gophercloud"
)
// HomeDocument is a resource that contains all the resources for the CDN API.
type HomeDocument map[string]interface{}
// GetResult represents the result of a Get operation.
type GetResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a home document resource.
func (r GetResult) Extract() (*HomeDocument, error) {
if r.Err != nil {
return nil, r.Err
}
submap, ok := r.Body.(map[string]interface{})["resources"]
if !ok {
return nil, errors.New("Unexpected HomeDocument structure")
}
casted := HomeDocument(submap.(map[string]interface{}))
return &casted, nil
}
// PingResult represents the result of a Ping operation.
type PingResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,11 @@
package base
import "github.com/rackspace/gophercloud"
func getURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL()
}
func pingURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("ping")
}

View File

@ -0,0 +1,6 @@
// Package flavors provides information and interaction with the flavors API
// resource in the OpenStack CDN service. This API resource allows for
// listing flavors and retrieving a specific flavor.
//
// A flavor is a mapping configuration to a CDN provider.
package flavors

View File

@ -0,0 +1,82 @@
package flavors
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// HandleListCDNFlavorsSuccessfully creates an HTTP handler at `/flavors` on the test handler mux
// that responds with a `List` response.
func HandleListCDNFlavorsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/flavors", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"flavors": [
{
"id": "europe",
"providers": [
{
"provider": "Fastly",
"links": [
{
"href": "http://www.fastly.com",
"rel": "provider_url"
}
]
}
],
"links": [
{
"href": "https://www.poppycdn.io/v1.0/flavors/europe",
"rel": "self"
}
]
}
]
}
`)
})
}
// HandleGetCDNFlavorSuccessfully creates an HTTP handler at `/flavors/{id}` on the test handler mux
// that responds with a `Get` response.
func HandleGetCDNFlavorSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/flavors/asia", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"id" : "asia",
"providers" : [
{
"provider" : "ChinaCache",
"links": [
{
"href": "http://www.chinacache.com",
"rel": "provider_url"
}
]
}
],
"links": [
{
"href": "https://www.poppycdn.io/v1.0/flavors/asia",
"rel": "self"
}
]
}
`)
})
}

View File

@ -0,0 +1,25 @@
package flavors
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a single page of CDN flavors.
func List(c *gophercloud.ServiceClient) pagination.Pager {
url := listURL(c)
createPage := func(r pagination.PageResult) pagination.Page {
return FlavorPage{pagination.SinglePageBase(r)}
}
return pagination.NewPager(c, url, createPage)
}
// Get retrieves a specific flavor based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = c.Request("GET", getURL(c, id), gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}

View File

@ -0,0 +1,90 @@
package flavors
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListCDNFlavorsSuccessfully(t)
count := 0
err := List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := ExtractFlavors(page)
if err != nil {
t.Errorf("Failed to extract flavors: %v", err)
return false, err
}
expected := []Flavor{
Flavor{
ID: "europe",
Providers: []Provider{
Provider{
Provider: "Fastly",
Links: []gophercloud.Link{
gophercloud.Link{
Href: "http://www.fastly.com",
Rel: "provider_url",
},
},
},
},
Links: []gophercloud.Link{
gophercloud.Link{
Href: "https://www.poppycdn.io/v1.0/flavors/europe",
Rel: "self",
},
},
},
}
th.CheckDeepEquals(t, expected, actual)
return true, nil
})
th.AssertNoErr(t, err)
th.CheckEquals(t, 1, count)
}
func TestGet(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetCDNFlavorSuccessfully(t)
expected := &Flavor{
ID: "asia",
Providers: []Provider{
Provider{
Provider: "ChinaCache",
Links: []gophercloud.Link{
gophercloud.Link{
Href: "http://www.chinacache.com",
Rel: "provider_url",
},
},
},
},
Links: []gophercloud.Link{
gophercloud.Link{
Href: "https://www.poppycdn.io/v1.0/flavors/asia",
Rel: "self",
},
},
}
actual, err := Get(fake.ServiceClient(), "asia").Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}

View File

@ -0,0 +1,71 @@
package flavors
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// Provider represents a provider for a particular flavor.
type Provider struct {
// Specifies the name of the provider. The name must not exceed 64 bytes in
// length and is limited to unicode, digits, underscores, and hyphens.
Provider string `mapstructure:"provider"`
// Specifies a list with an href where rel is provider_url.
Links []gophercloud.Link `mapstructure:"links"`
}
// Flavor represents a mapping configuration to a CDN provider.
type Flavor struct {
// Specifies the name of the flavor. The name must not exceed 64 bytes in
// length and is limited to unicode, digits, underscores, and hyphens.
ID string `mapstructure:"id"`
// Specifies the list of providers mapped to this flavor.
Providers []Provider `mapstructure:"providers"`
// Specifies the self-navigating JSON document paths.
Links []gophercloud.Link `mapstructure:"links"`
}
// FlavorPage is the page returned by a pager when traversing over a
// collection of CDN flavors.
type FlavorPage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a FlavorPage contains no Flavors.
func (r FlavorPage) IsEmpty() (bool, error) {
flavors, err := ExtractFlavors(r)
if err != nil {
return true, err
}
return len(flavors) == 0, nil
}
// ExtractFlavors extracts and returns Flavors. It is used while iterating over
// a flavors.List call.
func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
var response struct {
Flavors []Flavor `json:"flavors"`
}
err := mapstructure.Decode(page.(FlavorPage).Body, &response)
return response.Flavors, err
}
// GetResult represents the result of a get operation.
type GetResult struct {
gophercloud.Result
}
// Extract is a function that extracts a flavor from a GetResult.
func (r GetResult) Extract() (*Flavor, error) {
if r.Err != nil {
return nil, r.Err
}
var res Flavor
err := mapstructure.Decode(r.Body, &res)
return &res, err
}

View File

@ -0,0 +1,11 @@
package flavors
import "github.com/rackspace/gophercloud"
func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("flavors")
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("flavors", id)
}

View File

@ -0,0 +1,7 @@
// Package serviceassets provides information and interaction with the
// serviceassets API resource in the OpenStack CDN service. This API resource
// allows for deleting cached assets.
//
// A service distributes assets across the network. Service assets let you
// interrogate properties about these assets and perform certain actions on them.
package serviceassets

View File

@ -0,0 +1,19 @@
package serviceassets
import (
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// HandleDeleteCDNAssetSuccessfully creates an HTTP handler at `/services/{id}/assets` on the test handler mux
// that responds with a `Delete` response.
func HandleDeleteCDNAssetSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0/assets", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@ -0,0 +1,50 @@
package serviceassets
import (
"strings"
"github.com/rackspace/gophercloud"
)
// DeleteOptsBuilder allows extensions to add additional parameters to the Delete
// request.
type DeleteOptsBuilder interface {
ToCDNAssetDeleteParams() (string, error)
}
// DeleteOpts is a structure that holds options for deleting CDN service assets.
type DeleteOpts struct {
// If all is set to true, specifies that the delete occurs against all of the
// assets for the service.
All bool `q:"all"`
// Specifies the relative URL of the asset to be deleted.
URL string `q:"url"`
}
// ToCDNAssetDeleteParams formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToCDNAssetDeleteParams() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// Delete accepts a unique service ID or URL and deletes the CDN service asset associated with
// it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Delete(c *gophercloud.ServiceClient, idOrURL string, opts DeleteOptsBuilder) DeleteResult {
var url string
if strings.Contains(idOrURL, "/") {
url = idOrURL
} else {
url = deleteURL(c, idOrURL)
}
var res DeleteResult
_, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{
OkCodes: []int{202},
})
return res
}

View File

@ -0,0 +1,18 @@
package serviceassets
import (
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func TestDelete(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDeleteCDNAssetSuccessfully(t)
err := Delete(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", nil).ExtractErr()
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,8 @@
package serviceassets
import "github.com/rackspace/gophercloud"
// DeleteResult represents the result of a Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,7 @@
package serviceassets
import "github.com/rackspace/gophercloud"
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("services", id, "assets")
}

View File

@ -0,0 +1,7 @@
// Package services provides information and interaction with the services API
// resource in the OpenStack CDN service. This API resource allows for
// listing, creating, updating, retrieving, and deleting services.
//
// A service represents an application that has its content cached to the edge
// nodes.
package services

View File

@ -0,0 +1,7 @@
package services
import "fmt"
func no(str string) error {
return fmt.Errorf("Required parameter %s not provided", str)
}

View File

@ -0,0 +1,372 @@
package services
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// HandleListCDNServiceSuccessfully creates an HTTP handler at `/services` on the test handler mux
// that responds with a `List` response.
func HandleListCDNServiceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
r.ParseForm()
marker := r.Form.Get("marker")
switch marker {
case "":
fmt.Fprintf(w, `
{
"links": [
{
"rel": "next",
"href": "https://www.poppycdn.io/v1.0/services?marker=96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0&limit=20"
}
],
"services": [
{
"id": "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
"name": "mywebsite.com",
"domains": [
{
"domain": "www.mywebsite.com"
}
],
"origins": [
{
"origin": "mywebsite.com",
"port": 80,
"ssl": false
}
],
"caching": [
{
"name": "default",
"ttl": 3600
},
{
"name": "home",
"ttl": 17200,
"rules": [
{
"name": "index",
"request_url": "/index.htm"
}
]
},
{
"name": "images",
"ttl": 12800,
"rules": [
{
"name": "images",
"request_url": "*.png"
}
]
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mywebsite.com",
"referrer": "www.mywebsite.com"
}
]
}
],
"flavor_id": "asia",
"status": "deployed",
"errors" : [],
"links": [
{
"href": "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
"rel": "self"
},
{
"href": "mywebsite.com.cdn123.poppycdn.net",
"rel": "access_url"
},
{
"href": "https://www.poppycdn.io/v1.0/flavors/asia",
"rel": "flavor"
}
]
},
{
"id": "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1",
"name": "myothersite.com",
"domains": [
{
"domain": "www.myothersite.com"
}
],
"origins": [
{
"origin": "44.33.22.11",
"port": 80,
"ssl": false
},
{
"origin": "77.66.55.44",
"port": 80,
"ssl": false,
"rules": [
{
"name": "videos",
"request_url": "^/videos/*.m3u"
}
]
}
],
"caching": [
{
"name": "default",
"ttl": 3600
}
],
"restrictions": [
{}
],
"flavor_id": "europe",
"status": "deployed",
"links": [
{
"href": "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1",
"rel": "self"
},
{
"href": "myothersite.com.poppycdn.net",
"rel": "access_url"
},
{
"href": "https://www.poppycdn.io/v1.0/flavors/europe",
"rel": "flavor"
}
]
}
]
}
`)
case "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1":
fmt.Fprintf(w, `{
"services": []
}`)
default:
t.Fatalf("Unexpected marker: [%s]", marker)
}
})
}
// HandleCreateCDNServiceSuccessfully creates an HTTP handler at `/services` on the test handler mux
// that responds with a `Create` response.
func HandleCreateCDNServiceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"name": "mywebsite.com",
"domains": [
{
"domain": "www.mywebsite.com"
},
{
"domain": "blog.mywebsite.com"
}
],
"origins": [
{
"origin": "mywebsite.com",
"port": 80,
"ssl": false
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mywebsite.com",
"referrer": "www.mywebsite.com"
}
]
}
],
"caching": [
{
"name": "default",
"ttl": 3600
}
],
"flavor_id": "cdn"
}
`)
w.Header().Add("Location", "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0")
w.WriteHeader(http.StatusAccepted)
})
}
// HandleGetCDNServiceSuccessfully creates an HTTP handler at `/services/{id}` on the test handler mux
// that responds with a `Get` response.
func HandleGetCDNServiceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"id": "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
"name": "mywebsite.com",
"domains": [
{
"domain": "www.mywebsite.com",
"protocol": "http"
}
],
"origins": [
{
"origin": "mywebsite.com",
"port": 80,
"ssl": false
}
],
"caching": [
{
"name": "default",
"ttl": 3600
},
{
"name": "home",
"ttl": 17200,
"rules": [
{
"name": "index",
"request_url": "/index.htm"
}
]
},
{
"name": "images",
"ttl": 12800,
"rules": [
{
"name": "images",
"request_url": "*.png"
}
]
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mywebsite.com",
"referrer": "www.mywebsite.com"
}
]
}
],
"flavor_id": "cdn",
"status": "deployed",
"errors" : [],
"links": [
{
"href": "https://global.cdn.api.rackspacecloud.com/v1.0/110011/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
"rel": "self"
},
{
"href": "blog.mywebsite.com.cdn1.raxcdn.com",
"rel": "access_url"
},
{
"href": "https://global.cdn.api.rackspacecloud.com/v1.0/110011/flavors/cdn",
"rel": "flavor"
}
]
}
`)
})
}
// HandleUpdateCDNServiceSuccessfully creates an HTTP handler at `/services/{id}` on the test handler mux
// that responds with a `Update` response.
func HandleUpdateCDNServiceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PATCH")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
[
{
"op": "add",
"path": "/domains/-",
"value": {"domain": "appended.mocksite4.com"}
},
{
"op": "add",
"path": "/domains/4",
"value": {"domain": "inserted.mocksite4.com"}
},
{
"op": "add",
"path": "/domains",
"value": [
{"domain": "bulkadded1.mocksite4.com"},
{"domain": "bulkadded2.mocksite4.com"}
]
},
{
"op": "replace",
"path": "/origins/2",
"value": {"origin": "44.33.22.11", "port": 80, "ssl": false}
},
{
"op": "replace",
"path": "/origins",
"value": [
{"origin": "44.33.22.11", "port": 80, "ssl": false},
{"origin": "55.44.33.22", "port": 443, "ssl": true}
]
},
{
"op": "remove",
"path": "/caching/8"
},
{
"op": "remove",
"path": "/caching"
},
{
"op": "replace",
"path": "/name",
"value": "differentServiceName"
}
]
`)
w.Header().Add("Location", "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0")
w.WriteHeader(http.StatusAccepted)
})
}
// HandleDeleteCDNServiceSuccessfully creates an HTTP handler at `/services/{id}` on the test handler mux
// that responds with a `Delete` response.
func HandleDeleteCDNServiceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@ -0,0 +1,386 @@
package services
import (
"fmt"
"strings"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToCDNServiceListQuery() (string, error)
}
// ListOpts allows the filtering and sorting of paginated collections through
// the API. Marker and Limit are used for pagination.
type ListOpts struct {
Marker string `q:"marker"`
Limit int `q:"limit"`
}
// ToCDNServiceListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToCDNServiceListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List returns a Pager which allows you to iterate over a collection of
// CDN services. It accepts a ListOpts struct, which allows for pagination via
// marker and limit.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(c)
if opts != nil {
query, err := opts.ToCDNServiceListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPage := func(r pagination.PageResult) pagination.Page {
p := ServicePage{pagination.MarkerPageBase{PageResult: r}}
p.MarkerPageBase.Owner = p
return p
}
pager := pagination.NewPager(c, url, createPage)
return pager
}
// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
ToCDNServiceCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// REQUIRED. Specifies the name of the service. The minimum length for name is
// 3. The maximum length is 256.
Name string
// REQUIRED. Specifies a list of domains used by users to access their website.
Domains []Domain
// REQUIRED. Specifies a list of origin domains or IP addresses where the
// original assets are stored.
Origins []Origin
// REQUIRED. Specifies the CDN provider flavor ID to use. For a list of
// flavors, see the operation to list the available flavors. The minimum
// length for flavor_id is 1. The maximum length is 256.
FlavorID string
// OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
Caching []CacheRule
// OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache).
Restrictions []Restriction
}
// ToCDNServiceCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) {
s := make(map[string]interface{})
if opts.Name == "" {
return nil, no("Name")
}
s["name"] = opts.Name
if opts.Domains == nil {
return nil, no("Domains")
}
for _, domain := range opts.Domains {
if domain.Domain == "" {
return nil, no("Domains[].Domain")
}
}
s["domains"] = opts.Domains
if opts.Origins == nil {
return nil, no("Origins")
}
for _, origin := range opts.Origins {
if origin.Origin == "" {
return nil, no("Origins[].Origin")
}
if origin.Rules == nil && len(opts.Origins) > 1 {
return nil, no("Origins[].Rules")
}
for _, rule := range origin.Rules {
if rule.Name == "" {
return nil, no("Origins[].Rules[].Name")
}
if rule.RequestURL == "" {
return nil, no("Origins[].Rules[].RequestURL")
}
}
}
s["origins"] = opts.Origins
if opts.FlavorID == "" {
return nil, no("FlavorID")
}
s["flavor_id"] = opts.FlavorID
if opts.Caching != nil {
for _, cache := range opts.Caching {
if cache.Name == "" {
return nil, no("Caching[].Name")
}
if cache.Rules != nil {
for _, rule := range cache.Rules {
if rule.Name == "" {
return nil, no("Caching[].Rules[].Name")
}
if rule.RequestURL == "" {
return nil, no("Caching[].Rules[].RequestURL")
}
}
}
}
s["caching"] = opts.Caching
}
if opts.Restrictions != nil {
for _, restriction := range opts.Restrictions {
if restriction.Name == "" {
return nil, no("Restrictions[].Name")
}
if restriction.Rules != nil {
for _, rule := range restriction.Rules {
if rule.Name == "" {
return nil, no("Restrictions[].Rules[].Name")
}
}
}
}
s["restrictions"] = opts.Restrictions
}
return s, nil
}
// Create accepts a CreateOpts struct and creates a new CDN service using the
// values provided.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToCDNServiceCreateMap()
if err != nil {
res.Err = err
return res
}
// Send request to API
resp, err := c.Request("POST", createURL(c), gophercloud.RequestOpts{
JSONBody: &reqBody,
OkCodes: []int{202},
})
res.Header = resp.Header
res.Err = err
return res
}
// Get retrieves a specific service based on its URL or its unique ID. For
// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
var url string
if strings.Contains(idOrURL, "/") {
url = idOrURL
} else {
url = getURL(c, idOrURL)
}
var res GetResult
_, res.Err = c.Request("GET", url, gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}
// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
// or removed.
type Path struct {
baseElement string
}
func (p Path) renderRoot() string {
return "/" + p.baseElement
}
func (p Path) renderDash() string {
return fmt.Sprintf("/%s/-", p.baseElement)
}
func (p Path) renderIndex(index int64) string {
return fmt.Sprintf("/%s/%d", p.baseElement, index)
}
var (
// PathDomains indicates that an update operation is to be performed on a Domain.
PathDomains = Path{baseElement: "domains"}
// PathOrigins indicates that an update operation is to be performed on an Origin.
PathOrigins = Path{baseElement: "origins"}
// PathCaching indicates that an update operation is to be performed on a CacheRule.
PathCaching = Path{baseElement: "caching"}
)
type value interface {
toPatchValue() interface{}
appropriatePath() Path
renderRootOr(func(p Path) string) string
}
// Patch represents a single update to an existing Service. Multiple updates to a service can be
// submitted at the same time.
type Patch interface {
ToCDNServiceUpdateMap() map[string]interface{}
}
// Insertion is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to
// a Service at a fixed index. Use an Append instead to append the new value to the end of its
// collection. Pass it to the Update function as part of the Patch slice.
type Insertion struct {
Index int64
Value value
}
// ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the
// Update call.
func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
return map[string]interface{}{
"op": "add",
"path": i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }),
"value": i.Value.toPatchValue(),
}
}
// Append is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to a
// Service at the end of its respective collection. Use an Insertion instead to insert the value
// at a fixed index within the collection. Pass this to the Update function as part of its
// Patch slice.
type Append struct {
Value value
}
// ToCDNServiceUpdateMap converts an Append into a request body fragment suitable for the
// Update call.
func (a Append) ToCDNServiceUpdateMap() map[string]interface{} {
return map[string]interface{}{
"op": "add",
"path": a.Value.renderRootOr(func(p Path) string { return p.renderDash() }),
"value": a.Value.toPatchValue(),
}
}
// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
// in-place by index. Pass it to the Update function as part of the Patch slice.
type Replacement struct {
Value value
Index int64
}
// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
// Update call.
func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
return map[string]interface{}{
"op": "replace",
"path": r.Value.renderRootOr(func(p Path) string { return p.renderIndex(r.Index) }),
"value": r.Value.toPatchValue(),
}
}
// NameReplacement specifically updates the Service name. Pass it to the Update function as part
// of the Patch slice.
type NameReplacement struct {
NewName string
}
// ToCDNServiceUpdateMap converts a NameReplacement into a request body fragment suitable for the
// Update call.
func (r NameReplacement) ToCDNServiceUpdateMap() map[string]interface{} {
return map[string]interface{}{
"op": "replace",
"path": "/name",
"value": r.NewName,
}
}
// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
type Removal struct {
Path Path
Index int64
All bool
}
// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
// Update call.
func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
result := map[string]interface{}{"op": "remove"}
if r.All {
result["path"] = r.Path.renderRoot()
} else {
result["path"] = r.Path.renderIndex(r.Index)
}
return result
}
type UpdateOpts []Patch
// Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and
// updates an existing CDN service using the values provided. idOrURL can be either the service's
// URL or its ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) UpdateResult {
var url string
if strings.Contains(idOrURL, "/") {
url = idOrURL
} else {
url = updateURL(c, idOrURL)
}
reqBody := make([]map[string]interface{}, len(opts))
for i, patch := range opts {
reqBody[i] = patch.ToCDNServiceUpdateMap()
}
resp, err := c.Request("PATCH", url, gophercloud.RequestOpts{
JSONBody: &reqBody,
OkCodes: []int{202},
})
var result UpdateResult
result.Header = resp.Header
result.Err = err
return result
}
// Delete accepts a service's ID or its URL and deletes the CDN service
// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
var url string
if strings.Contains(idOrURL, "/") {
url = idOrURL
} else {
url = deleteURL(c, idOrURL)
}
var res DeleteResult
_, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{
OkCodes: []int{202},
})
return res
}

View File

@ -0,0 +1,358 @@
package services
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListCDNServiceSuccessfully(t)
count := 0
err := List(fake.ServiceClient(), &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := ExtractServices(page)
if err != nil {
t.Errorf("Failed to extract services: %v", err)
return false, err
}
expected := []Service{
Service{
ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
Name: "mywebsite.com",
Domains: []Domain{
Domain{
Domain: "www.mywebsite.com",
},
},
Origins: []Origin{
Origin{
Origin: "mywebsite.com",
Port: 80,
SSL: false,
},
},
Caching: []CacheRule{
CacheRule{
Name: "default",
TTL: 3600,
},
CacheRule{
Name: "home",
TTL: 17200,
Rules: []TTLRule{
TTLRule{
Name: "index",
RequestURL: "/index.htm",
},
},
},
CacheRule{
Name: "images",
TTL: 12800,
Rules: []TTLRule{
TTLRule{
Name: "images",
RequestURL: "*.png",
},
},
},
},
Restrictions: []Restriction{
Restriction{
Name: "website only",
Rules: []RestrictionRule{
RestrictionRule{
Name: "mywebsite.com",
Referrer: "www.mywebsite.com",
},
},
},
},
FlavorID: "asia",
Status: "deployed",
Errors: []Error{},
Links: []gophercloud.Link{
gophercloud.Link{
Href: "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
Rel: "self",
},
gophercloud.Link{
Href: "mywebsite.com.cdn123.poppycdn.net",
Rel: "access_url",
},
gophercloud.Link{
Href: "https://www.poppycdn.io/v1.0/flavors/asia",
Rel: "flavor",
},
},
},
Service{
ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1",
Name: "myothersite.com",
Domains: []Domain{
Domain{
Domain: "www.myothersite.com",
},
},
Origins: []Origin{
Origin{
Origin: "44.33.22.11",
Port: 80,
SSL: false,
},
Origin{
Origin: "77.66.55.44",
Port: 80,
SSL: false,
Rules: []OriginRule{
OriginRule{
Name: "videos",
RequestURL: "^/videos/*.m3u",
},
},
},
},
Caching: []CacheRule{
CacheRule{
Name: "default",
TTL: 3600,
},
},
Restrictions: []Restriction{},
FlavorID: "europe",
Status: "deployed",
Links: []gophercloud.Link{
gophercloud.Link{
Href: "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f1",
Rel: "self",
},
gophercloud.Link{
Href: "myothersite.com.poppycdn.net",
Rel: "access_url",
},
gophercloud.Link{
Href: "https://www.poppycdn.io/v1.0/flavors/europe",
Rel: "flavor",
},
},
},
}
th.CheckDeepEquals(t, expected, actual)
return true, nil
})
th.AssertNoErr(t, err)
if count != 1 {
t.Errorf("Expected 1 page, got %d", count)
}
}
func TestCreate(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleCreateCDNServiceSuccessfully(t)
createOpts := CreateOpts{
Name: "mywebsite.com",
Domains: []Domain{
Domain{
Domain: "www.mywebsite.com",
},
Domain{
Domain: "blog.mywebsite.com",
},
},
Origins: []Origin{
Origin{
Origin: "mywebsite.com",
Port: 80,
SSL: false,
},
},
Restrictions: []Restriction{
Restriction{
Name: "website only",
Rules: []RestrictionRule{
RestrictionRule{
Name: "mywebsite.com",
Referrer: "www.mywebsite.com",
},
},
},
},
Caching: []CacheRule{
CacheRule{
Name: "default",
TTL: 3600,
},
},
FlavorID: "cdn",
}
expected := "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
actual, err := Create(fake.ServiceClient(), createOpts).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, expected, actual)
}
func TestGet(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetCDNServiceSuccessfully(t)
expected := &Service{
ID: "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
Name: "mywebsite.com",
Domains: []Domain{
Domain{
Domain: "www.mywebsite.com",
Protocol: "http",
},
},
Origins: []Origin{
Origin{
Origin: "mywebsite.com",
Port: 80,
SSL: false,
},
},
Caching: []CacheRule{
CacheRule{
Name: "default",
TTL: 3600,
},
CacheRule{
Name: "home",
TTL: 17200,
Rules: []TTLRule{
TTLRule{
Name: "index",
RequestURL: "/index.htm",
},
},
},
CacheRule{
Name: "images",
TTL: 12800,
Rules: []TTLRule{
TTLRule{
Name: "images",
RequestURL: "*.png",
},
},
},
},
Restrictions: []Restriction{
Restriction{
Name: "website only",
Rules: []RestrictionRule{
RestrictionRule{
Name: "mywebsite.com",
Referrer: "www.mywebsite.com",
},
},
},
},
FlavorID: "cdn",
Status: "deployed",
Errors: []Error{},
Links: []gophercloud.Link{
gophercloud.Link{
Href: "https://global.cdn.api.rackspacecloud.com/v1.0/110011/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0",
Rel: "self",
},
gophercloud.Link{
Href: "blog.mywebsite.com.cdn1.raxcdn.com",
Rel: "access_url",
},
gophercloud.Link{
Href: "https://global.cdn.api.rackspacecloud.com/v1.0/110011/flavors/cdn",
Rel: "flavor",
},
},
}
actual, err := Get(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0").Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}
func TestSuccessfulUpdate(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleUpdateCDNServiceSuccessfully(t)
expected := "https://www.poppycdn.io/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
ops := UpdateOpts{
// Append a single Domain
Append{Value: Domain{Domain: "appended.mocksite4.com"}},
// Insert a single Domain
Insertion{
Index: 4,
Value: Domain{Domain: "inserted.mocksite4.com"},
},
// Bulk addition
Append{
Value: DomainList{
Domain{Domain: "bulkadded1.mocksite4.com"},
Domain{Domain: "bulkadded2.mocksite4.com"},
},
},
// Replace a single Origin
Replacement{
Index: 2,
Value: Origin{Origin: "44.33.22.11", Port: 80, SSL: false},
},
// Bulk replace Origins
Replacement{
Index: 0, // Ignored
Value: OriginList{
Origin{Origin: "44.33.22.11", Port: 80, SSL: false},
Origin{Origin: "55.44.33.22", Port: 443, SSL: true},
},
},
// Remove a single CacheRule
Removal{
Index: 8,
Path: PathCaching,
},
// Bulk removal
Removal{
All: true,
Path: PathCaching,
},
// Service name replacement
NameReplacement{
NewName: "differentServiceName",
},
}
actual, err := Update(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0", ops).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, expected, actual)
}
func TestDelete(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDeleteCDNServiceSuccessfully(t)
err := Delete(fake.ServiceClient(), "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0").ExtractErr()
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,316 @@
package services
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Domain represents a domain used by users to access their website.
type Domain struct {
// Specifies the domain used to access the assets on their website, for which
// a CNAME is given to the CDN provider.
Domain string `mapstructure:"domain" json:"domain"`
// Specifies the protocol used to access the assets on this domain. Only "http"
// or "https" are currently allowed. The default is "http".
Protocol string `mapstructure:"protocol" json:"protocol,omitempty"`
}
func (d Domain) toPatchValue() interface{} {
r := make(map[string]interface{})
r["domain"] = d.Domain
if d.Protocol != "" {
r["protocol"] = d.Protocol
}
return r
}
func (d Domain) appropriatePath() Path {
return PathDomains
}
func (d Domain) renderRootOr(render func(p Path) string) string {
return render(d.appropriatePath())
}
// DomainList provides a useful way to perform bulk operations in a single Patch.
type DomainList []Domain
func (list DomainList) toPatchValue() interface{} {
r := make([]interface{}, len(list))
for i, domain := range list {
r[i] = domain.toPatchValue()
}
return r
}
func (list DomainList) appropriatePath() Path {
return PathDomains
}
func (list DomainList) renderRootOr(_ func(p Path) string) string {
return list.appropriatePath().renderRoot()
}
// OriginRule represents a rule that defines when an origin should be accessed.
type OriginRule struct {
// Specifies the name of this rule.
Name string `mapstructure:"name" json:"name"`
// Specifies the request URL this rule should match for this origin to be used. Regex is supported.
RequestURL string `mapstructure:"request_url" json:"request_url"`
}
// Origin specifies a list of origin domains or IP addresses where the original assets are stored.
type Origin struct {
// Specifies the URL or IP address to pull origin content from.
Origin string `mapstructure:"origin" json:"origin"`
// Specifies the port used to access the origin. The default is port 80.
Port int `mapstructure:"port" json:"port,omitempty"`
// Specifies whether or not to use HTTPS to access the origin. The default
// is false.
SSL bool `mapstructure:"ssl" json:"ssl"`
// Specifies a collection of rules that define the conditions when this origin
// should be accessed. If there is more than one origin, the rules parameter is required.
Rules []OriginRule `mapstructure:"rules" json:"rules,omitempty"`
}
func (o Origin) toPatchValue() interface{} {
r := make(map[string]interface{})
r["origin"] = o.Origin
r["port"] = o.Port
r["ssl"] = o.SSL
if len(o.Rules) > 0 {
r["rules"] = make([]map[string]interface{}, len(o.Rules))
for index, rule := range o.Rules {
submap := r["rules"].([]map[string]interface{})[index]
submap["name"] = rule.Name
submap["request_url"] = rule.RequestURL
}
}
return r
}
func (o Origin) appropriatePath() Path {
return PathOrigins
}
func (o Origin) renderRootOr(render func(p Path) string) string {
return render(o.appropriatePath())
}
// OriginList provides a useful way to perform bulk operations in a single Patch.
type OriginList []Origin
func (list OriginList) toPatchValue() interface{} {
r := make([]interface{}, len(list))
for i, origin := range list {
r[i] = origin.toPatchValue()
}
return r
}
func (list OriginList) appropriatePath() Path {
return PathOrigins
}
func (list OriginList) renderRootOr(_ func(p Path) string) string {
return list.appropriatePath().renderRoot()
}
// TTLRule specifies a rule that determines if a TTL should be applied to an asset.
type TTLRule struct {
// Specifies the name of this rule.
Name string `mapstructure:"name" json:"name"`
// Specifies the request URL this rule should match for this TTL to be used. Regex is supported.
RequestURL string `mapstructure:"request_url" json:"request_url"`
}
// CacheRule specifies the TTL rules for the assets under this service.
type CacheRule struct {
// Specifies the name of this caching rule. Note: 'default' is a reserved name used for the default TTL setting.
Name string `mapstructure:"name" json:"name"`
// Specifies the TTL to apply.
TTL int `mapstructure:"ttl" json:"ttl"`
// Specifies a collection of rules that determine if this TTL should be applied to an asset.
Rules []TTLRule `mapstructure:"rules" json:"rules,omitempty"`
}
func (c CacheRule) toPatchValue() interface{} {
r := make(map[string]interface{})
r["name"] = c.Name
r["ttl"] = c.TTL
r["rules"] = make([]map[string]interface{}, len(c.Rules))
for index, rule := range c.Rules {
submap := r["rules"].([]map[string]interface{})[index]
submap["name"] = rule.Name
submap["request_url"] = rule.RequestURL
}
return r
}
func (c CacheRule) appropriatePath() Path {
return PathCaching
}
func (c CacheRule) renderRootOr(render func(p Path) string) string {
return render(c.appropriatePath())
}
// CacheRuleList provides a useful way to perform bulk operations in a single Patch.
type CacheRuleList []CacheRule
func (list CacheRuleList) toPatchValue() interface{} {
r := make([]interface{}, len(list))
for i, rule := range list {
r[i] = rule.toPatchValue()
}
return r
}
func (list CacheRuleList) appropriatePath() Path {
return PathCaching
}
func (list CacheRuleList) renderRootOr(_ func(p Path) string) string {
return list.appropriatePath().renderRoot()
}
// RestrictionRule specifies a rule that determines if this restriction should be applied to an asset.
type RestrictionRule struct {
// Specifies the name of this rule.
Name string `mapstructure:"name" json:"name"`
// Specifies the http host that requests must come from.
Referrer string `mapstructure:"referrer" json:"referrer,omitempty"`
}
// Restriction specifies a restriction that defines who can access assets (content from the CDN cache).
type Restriction struct {
// Specifies the name of this restriction.
Name string `mapstructure:"name" json:"name"`
// Specifies a collection of rules that determine if this TTL should be applied to an asset.
Rules []RestrictionRule `mapstructure:"rules" json:"rules"`
}
// Error specifies an error that occurred during the previous service action.
type Error struct {
// Specifies an error message detailing why there is an error.
Message string `mapstructure:"message"`
}
// Service represents a CDN service resource.
type Service struct {
// Specifies the service ID that represents distributed content. The value is
// a UUID, such as 96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0, that is generated by the server.
ID string `mapstructure:"id"`
// Specifies the name of the service.
Name string `mapstructure:"name"`
// Specifies a list of domains used by users to access their website.
Domains []Domain `mapstructure:"domains"`
// Specifies a list of origin domains or IP addresses where the original assets are stored.
Origins []Origin `mapstructure:"origins"`
// Specifies the TTL rules for the assets under this service. Supports wildcards for fine grained control.
Caching []CacheRule `mapstructure:"caching"`
// Specifies the restrictions that define who can access assets (content from the CDN cache).
Restrictions []Restriction `mapstructure:"restrictions" json:"restrictions,omitempty"`
// Specifies the CDN provider flavor ID to use. For a list of flavors, see the operation to list the available flavors.
FlavorID string `mapstructure:"flavor_id"`
// Specifies the current status of the service.
Status string `mapstructure:"status"`
// Specifies the list of errors that occurred during the previous service action.
Errors []Error `mapstructure:"errors"`
// Specifies the self-navigating JSON document paths.
Links []gophercloud.Link `mapstructure:"links"`
}
// ServicePage is the page returned by a pager when traversing over a
// collection of CDN services.
type ServicePage struct {
pagination.MarkerPageBase
}
// IsEmpty returns true if a ListResult contains no services.
func (r ServicePage) IsEmpty() (bool, error) {
services, err := ExtractServices(r)
if err != nil {
return true, err
}
return len(services) == 0, nil
}
// LastMarker returns the last service in a ListResult.
func (r ServicePage) LastMarker() (string, error) {
services, err := ExtractServices(r)
if err != nil {
return "", err
}
if len(services) == 0 {
return "", nil
}
return (services[len(services)-1]).ID, nil
}
// ExtractServices is a function that takes a ListResult and returns the services' information.
func ExtractServices(page pagination.Page) ([]Service, error) {
var response struct {
Services []Service `mapstructure:"services"`
}
err := mapstructure.Decode(page.(ServicePage).Body, &response)
return response.Services, err
}
// CreateResult represents the result of a Create operation.
type CreateResult struct {
gophercloud.Result
}
// Extract is a method that extracts the location of a newly created service.
func (r CreateResult) Extract() (string, error) {
if r.Err != nil {
return "", r.Err
}
if l, ok := r.Header["Location"]; ok && len(l) > 0 {
return l[0], nil
}
return "", nil
}
// GetResult represents the result of a get operation.
type GetResult struct {
gophercloud.Result
}
// Extract is a function that extracts a service from a GetResult.
func (r GetResult) Extract() (*Service, error) {
if r.Err != nil {
return nil, r.Err
}
var res Service
err := mapstructure.Decode(r.Body, &res)
return &res, err
}
// UpdateResult represents the result of a Update operation.
type UpdateResult struct {
gophercloud.Result
}
// Extract is a method that extracts the location of an updated service.
func (r UpdateResult) Extract() (string, error) {
if r.Err != nil {
return "", r.Err
}
if l, ok := r.Header["Location"]; ok && len(l) > 0 {
return l[0], nil
}
return "", nil
}
// DeleteResult represents the result of a Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,23 @@
package services
import "github.com/rackspace/gophercloud"
func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("services")
}
func createURL(c *gophercloud.ServiceClient) string {
return listURL(c)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("services", id)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return getURL(c, id)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return getURL(c, id)
}

View File

@ -68,7 +68,7 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp
&utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
}
chosen, endpoint, err := utils.ChooseVersion(client.IdentityBase, client.IdentityEndpoint, versions)
chosen, endpoint, err := utils.ChooseVersion(client, versions)
if err != nil {
return err
}
@ -107,6 +107,11 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
return err
}
if options.AllowReauth {
client.ReauthFunc = func() error {
return AuthenticateV2(client, options)
}
}
client.TokenID = token.ID
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V2EndpointURL(catalog, opts)
@ -133,6 +138,11 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
}
client.TokenID = token.ID
if options.AllowReauth {
client.ReauthFunc = func() error {
return AuthenticateV3(client, options)
}
}
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V3EndpointURL(v3Client, opts)
}
@ -203,3 +213,24 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("cdn")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("orchestration")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -0,0 +1,3 @@
// Package floatingip provides the ability to manage floating ips through
// nova-network
package floatingip

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -0,0 +1,3 @@
// Package volumeattach provides the ability to attach and detach volumes
// to instances
package volumeattach

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

Some files were not shown because too many files have changed in this diff Show More