mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-12-12 13:34:42 +00:00
Add github.com/rackspace/gophercloud package as a dependency.
Note this also pulls in: github.com/kr/text github.com/mitchellh/mapstructure github.com/racker/perigee github.com/tonnerre/golang-pretty
This commit is contained in:
16
Godeps/_workspace/src/github.com/rackspace/gophercloud/.editorconfig
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/rackspace/gophercloud/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# All files
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Golang
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
14
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
language: go
|
||||
install:
|
||||
- go get -v .
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- tip
|
||||
after_success:
|
||||
- go get code.google.com/p/go.tools/cmd/cover
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- export PATH=$PATH:$HOME/gopath/bin/
|
||||
- goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
|
||||
|
||||
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
Contributors
|
||||
============
|
||||
|
||||
Samuel A. Falvo II <sam.falvo@rackspace.com>
|
||||
Glen Campbell <glen.campbell@rackspace.com>
|
||||
Jesse Noller <jesse.noller@rackspace.com>
|
||||
191
Godeps/_workspace/src/github.com/rackspace/gophercloud/LICENSE
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/rackspace/gophercloud/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
Copyright 2012-2013 Rackspace, Inc.
|
||||
|
||||
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.
|
||||
|
||||
------
|
||||
|
||||
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
|
||||
44
Godeps/_workspace/src/github.com/rackspace/gophercloud/README.asciidoc
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/rackspace/gophercloud/README.asciidoc
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
== Gophercloud -- V0.1.0 image:https://secure.travis-ci.org/rackspace/gophercloud.png?branch=master["build status",link="https://travis-ci.org/rackspace/gophercloud"]
|
||||
|
||||
Gophercloud currently lets you authenticate with OpenStack providers to create and manage servers.
|
||||
We are working on extending the API to further include cloud files, block storage, DNS, databases, security groups, and other features.
|
||||
|
||||
WARNING: This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want. Yet.
|
||||
|
||||
=== Outstanding Features
|
||||
|
||||
1. Apache 2.0 License, making Gophercloud friendly to commercial and open-source enterprises alike.
|
||||
2. Gophercloud is one of the most actively maintained Go SDKs for OpenStack.
|
||||
3. Gophercloud supports Identity V2 and Nova V2 APIs. More coming soon!
|
||||
4. The up-coming Gophercloud 0.2.0 release supports API extensions, and makes writing support for new extensions easy.
|
||||
5. Gophercloud supports automatic reauthentication upon auth token timeout, if enabled by your software.
|
||||
6. Gophercloud is the only SDK implementation with actual acceptance-level integration tests.
|
||||
|
||||
=== What Does it Look Like?
|
||||
|
||||
The Gophercloud 0.1.0 and earlier APIs are now deprecated and obsolete.
|
||||
No new feature development will occur for 0.1.0 or 0.0.0.
|
||||
However, we will accept and provide bug fixes for these APIs.
|
||||
Please refer to the acceptance tests in the master brach for code examples using the v0.1.0 API.
|
||||
The most up to date documentation for version 0.1.x can be found at link:http://godoc.org/github.com/rackspace/gophercloud[our Godoc.org documentation].
|
||||
|
||||
We are working on a new API that provides much better support for extensions, pagination, and other features that proved difficult to implement before.
|
||||
This new API will be substantially more Go-idiomatic as well; one of the complaints received about 0.1.x and earlier is that it didn't "feel" right.
|
||||
To see what this new API is going to look like, you can look at the code examples up on the link:http://gophercloud.io/docs.html[Gophercloud website].
|
||||
If you're interested in tracking progress, note that features for version 0.2.0 will appear in the `v0.2.0` branch until merged to master.
|
||||
|
||||
=== How can I Contribute?
|
||||
|
||||
After using Gophercloud for a while, you might find that it lacks some useful feature, or that existing behavior seems buggy. We welcome contributions
|
||||
from our users for both missing functionality as well as for bug fixes. We encourage contributors to collaborate with the
|
||||
link:http://gophercloud.io/community.html[Gophercloud community.]
|
||||
|
||||
Finally, Gophercloud maintains its own link:http://gophercloud.io[announcements and updates blog.]
|
||||
Feel free to check back now and again to see what's new.
|
||||
|
||||
== License
|
||||
|
||||
Copyright (C) 2013, 2014 Rackspace, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
30
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/00-authentication.go
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/00-authentication.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
provider, username, _, apiKey := getCredentials()
|
||||
|
||||
if !strings.Contains(provider, "rackspace") {
|
||||
fmt.Fprintf(os.Stdout, "Skipping test because provider doesn't support API_KEYs\n")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
ApiKey: apiKey,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
22
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/01-authentication.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/01-authentication.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
func main() {
|
||||
provider, username, password, _ := getCredentials()
|
||||
|
||||
_, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
62
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/02-list-servers.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/02-list-servers.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
tryFullDetails(api)
|
||||
tryLinksOnly(api)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func tryLinksOnly(api gophercloud.CloudServersProvider) {
|
||||
servers, err := api.ListServersLinksOnly()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Id,Name")
|
||||
for _, s := range servers {
|
||||
if s.AccessIPv4 != "" {
|
||||
panic("IPv4 not expected")
|
||||
}
|
||||
|
||||
if s.Status != "" {
|
||||
panic("Status not expected")
|
||||
}
|
||||
|
||||
if s.Progress != 0 {
|
||||
panic("Progress not expected")
|
||||
}
|
||||
|
||||
fmt.Printf("%s,\"%s\"\n", s.Id, s.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tryFullDetails(api gophercloud.CloudServersProvider) {
|
||||
servers, err := api.ListServers()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Id,Name,AccessIPv4,Status,Progress")
|
||||
for _, s := range servers {
|
||||
fmt.Printf("%s,\"%s\",%s,%s,%d\n", s.Id, s.Name, s.AccessIPv4, s.Status, s.Progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/03-get-server-details.go
generated
vendored
Normal file
134
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/03-get-server-details.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
var id = flag.String("i", "", "Server ID to get info on. Defaults to first server in your account if unspecified.")
|
||||
var rgn = flag.String("r", "", "Datacenter region. Leave blank for default region.")
|
||||
var quiet = flag.Bool("quiet", false, "Run quietly, for acceptance testing. $? non-zero if issue.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
resultCode := 0
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
var (
|
||||
err error
|
||||
serverId string
|
||||
deleteAfterwards bool
|
||||
)
|
||||
|
||||
// Figure out which server to provide server details for.
|
||||
if *id == "" {
|
||||
deleteAfterwards, serverId, err = locateAServer(servers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if deleteAfterwards {
|
||||
defer servers.DeleteServerById(serverId)
|
||||
}
|
||||
} else {
|
||||
serverId = *id
|
||||
}
|
||||
|
||||
// Grab server details by ID, and provide a report.
|
||||
s, err := servers.ServerById(serverId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configs := []string{
|
||||
"Access IPv4: %s\n",
|
||||
"Access IPv6: %s\n",
|
||||
" Created: %s\n",
|
||||
" Flavor: %s\n",
|
||||
" Host ID: %s\n",
|
||||
" ID: %s\n",
|
||||
" Image: %s\n",
|
||||
" Name: %s\n",
|
||||
" Progress: %s\n",
|
||||
" Status: %s\n",
|
||||
" Tenant ID: %s\n",
|
||||
" Updated: %s\n",
|
||||
" User ID: %s\n",
|
||||
}
|
||||
|
||||
values := []string{
|
||||
s.AccessIPv4,
|
||||
s.AccessIPv6,
|
||||
s.Created,
|
||||
s.Flavor.Id,
|
||||
s.HostId,
|
||||
s.Id,
|
||||
s.Image.Id,
|
||||
s.Name,
|
||||
fmt.Sprintf("%d", s.Progress),
|
||||
s.Status,
|
||||
s.TenantId,
|
||||
s.Updated,
|
||||
s.UserId,
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Server info:")
|
||||
for i, _ := range configs {
|
||||
fmt.Printf(configs[i], values[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Negative test -- We should absolutely never panic for a server that doesn't exist.
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
_, err := servers.ServerById(randomString("garbage", 32))
|
||||
if err == nil {
|
||||
fmt.Printf("Expected a 404 response when looking for a server known not to exist\n")
|
||||
resultCode = 1
|
||||
}
|
||||
perigeeError, ok := err.(*perigee.UnexpectedResponseCodeError)
|
||||
if !ok {
|
||||
fmt.Printf("Unexpected error type\n")
|
||||
resultCode = 1
|
||||
} else {
|
||||
if perigeeError.Actual != 404 {
|
||||
fmt.Printf("Expected a 404 error code\n")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
os.Exit(resultCode)
|
||||
}
|
||||
|
||||
// locateAServer queries the set of servers owned by the user. If at least one
|
||||
// exists, the first found is picked, and its ID is returned. Otherwise, a new
|
||||
// server will be created, and its ID returned.
|
||||
//
|
||||
// deleteAfter will be true if the caller should schedule a call to DeleteServerById()
|
||||
// to clean up.
|
||||
func locateAServer(servers gophercloud.CloudServersProvider) (deleteAfter bool, id string, err error) {
|
||||
ss, err := servers.ListServers()
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
if len(ss) > 0 {
|
||||
// We could just cheat and dump the server details from ss[0].
|
||||
// But, that tests ListServers(), and not ServerById(). So, we
|
||||
// elect not to cheat.
|
||||
return false, ss[0].Id, nil
|
||||
}
|
||||
|
||||
serverId, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
err = waitForServerState(servers, serverId, "ACTIVE")
|
||||
return true, serverId, err
|
||||
}
|
||||
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/04-create-server.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/04-create-server.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var region, serverName, imageRef, flavorRef *string
|
||||
var adminPass = flag.String("a", "", "Administrator password (auto-assigned if none)")
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance tests. $? non-zero if error.")
|
||||
|
||||
func configure() {
|
||||
region = flag.String("r", "", "Region in which to create the server. Leave blank for provider-default region.")
|
||||
serverName = flag.String("n", randomString("ACPTTEST--", 16), "Server name (what you see in the control panel)")
|
||||
imageRef = flag.String("i", "", "ID of image to deploy onto the server")
|
||||
flavorRef = flag.String("f", "", "Flavor of server to deploy image upon")
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
configure()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
_, err := createServer(servers, *imageRef, *flavorRef, *serverName, *adminPass)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
allServers, err := servers.ListServers()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Printf("ID,Name,Status,Progress\n")
|
||||
for _, i := range allServers {
|
||||
fmt.Printf("%s,\"%s\",%s,%d\n", i.Id, i.Name, i.Status, i.Progress)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/05-list-images.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/05-list-images.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
images, err := servers.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("ID,Name,MinRam,MinDisk")
|
||||
for _, image := range images {
|
||||
fmt.Printf("%s,\"%s\",%d,%d\n", image.Id, image.Name, image.MinRam, image.MinDisk)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/06-list-flavors.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/06-list-flavors.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
flavors, err := servers.ListFlavors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("ID,Name,MinRam,MinDisk")
|
||||
for _, f := range flavors {
|
||||
fmt.Printf("%s,\"%s\",%d,%d\n", f.Id, f.Name, f.Ram, f.Disk)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/07-change-admin-password.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/07-change-admin-password.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
var serverId = flag.String("i", "", "ID of server whose admin password is to be changed.")
|
||||
var newPass = flag.String("p", "", "New password for the server.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
// If user doesn't explicitly provide a server ID, create one dynamically.
|
||||
if *serverId == "" {
|
||||
var err error
|
||||
*serverId, err = createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(api, *serverId, "ACTIVE")
|
||||
}
|
||||
|
||||
// If no password is provided, create one dynamically.
|
||||
if *newPass == "" {
|
||||
*newPass = randomString("", 16)
|
||||
}
|
||||
|
||||
// Submit the request for changing the admin password.
|
||||
// Note that we don't verify this actually completes;
|
||||
// doing so is beyond the scope of the SDK, and should be
|
||||
// the responsibility of your specific OpenStack provider.
|
||||
err := api.SetAdminPassword(*serverId, *newPass)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Password change request submitted.")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/08-reauthentication.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/08-reauthentication.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Invoke withIdentity such that re-auth is enabled.
|
||||
withIdentity(true, func(auth gophercloud.AccessProvider) {
|
||||
token1 := auth.AuthToken()
|
||||
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
// Just to confirm everything works, we should be able to list images without error.
|
||||
_, err := servers.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Revoke our current authentication token.
|
||||
auth.Revoke(auth.AuthToken())
|
||||
|
||||
// Attempt to list images again. This should _succeed_, because we enabled re-authentication.
|
||||
_, err = servers.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// However, our new authentication token should differ.
|
||||
token2 := auth.AuthToken()
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Old authentication token: ", token1)
|
||||
fmt.Println("New authentication token: ", token2)
|
||||
}
|
||||
|
||||
if token1 == token2 {
|
||||
panic("Tokens should differ")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
102
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/09-resize-server.go
generated
vendored
Normal file
102
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/09-resize-server.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"time"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
// These tests are going to take some time to complete.
|
||||
// So, we'll do two tests at the same time to help amortize test time.
|
||||
done := make(chan bool)
|
||||
go resizeRejectTest(api, done)
|
||||
go resizeAcceptTest(api, done)
|
||||
_ = <-done
|
||||
_ = <-done
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Done.")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Perform the resize test, but reject the resize request.
|
||||
func resizeRejectTest(api gophercloud.CloudServersProvider, done chan bool) {
|
||||
withServer(api, func(id string) {
|
||||
newFlavorId := findAlternativeFlavor()
|
||||
err := api.ResizeServer(id, randomString("ACPTTEST", 24), newFlavorId, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
waitForServerState(api, id, "VERIFY_RESIZE")
|
||||
|
||||
err = api.RevertResize(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
done <- true
|
||||
}
|
||||
|
||||
// Perform the resize test, but accept the resize request.
|
||||
func resizeAcceptTest(api gophercloud.CloudServersProvider, done chan bool) {
|
||||
withServer(api, func(id string) {
|
||||
newFlavorId := findAlternativeFlavor()
|
||||
err := api.ResizeServer(id, randomString("ACPTTEST", 24), newFlavorId, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
waitForServerState(api, id, "VERIFY_RESIZE")
|
||||
|
||||
err = api.ConfirmResize(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
done <- true
|
||||
}
|
||||
|
||||
func withServer(api gophercloud.CloudServersProvider, f func(string)) {
|
||||
id, err := createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
s, err := api.ServerById(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if s.Status == "ACTIVE" {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
f(id)
|
||||
|
||||
// I've learned that resizing an instance can fail if a delete request
|
||||
// comes in prior to its completion. This ends up leaving the server
|
||||
// in an error state, and neither the resize NOR the delete complete.
|
||||
// This is a bug in OpenStack, as far as I'm concerned, but thankfully,
|
||||
// there's an easy work-around -- just wait for your server to return to
|
||||
// active state first!
|
||||
waitForServerState(api, id, "ACTIVE")
|
||||
err = api.DeleteServerById(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/10-reboot-server.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/10-reboot-server.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
serverId, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Soft-rebooting server")
|
||||
servers.RebootServer(serverId, false)
|
||||
waitForServerState(servers, serverId, "REBOOT")
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Hard-rebooting server")
|
||||
servers.RebootServer(serverId, true)
|
||||
waitForServerState(servers, serverId, "HARD_REBOOT")
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Done")
|
||||
servers.DeleteServerById(serverId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/11-rescue-unrescue-server.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/11-rescue-unrescue-server.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
defer servers.DeleteServerById(id)
|
||||
|
||||
log("Rescuing server")
|
||||
adminPass, err := servers.RescueServer(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log(" Admin password = " + adminPass)
|
||||
if len(adminPass) < 1 {
|
||||
panic("Empty admin password")
|
||||
}
|
||||
waitForServerState(servers, id, "RESCUE")
|
||||
|
||||
log("Unrescuing server")
|
||||
err = servers.UnrescueServer(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/12-update-server.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/12-update-server.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
defer servers.DeleteServerById(id)
|
||||
|
||||
log("Updating name of server")
|
||||
newName := randomString("ACPTTEST", 32)
|
||||
newDetails, err := servers.UpdateServer(id, gophercloud.NewServerSettings{
|
||||
Name: newName,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if newDetails.Name != newName {
|
||||
panic("Name change didn't appear to take")
|
||||
}
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/13-rebuild-server.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/13-rebuild-server.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
defer servers.DeleteServerById(id)
|
||||
|
||||
log("Rebuilding server")
|
||||
newDetails, err := servers.RebuildServer(id, gophercloud.NewServer{
|
||||
Name: randomString("ACPTTEST", 32),
|
||||
ImageRef: findAlternativeImage(),
|
||||
FlavorRef: findAlternativeFlavor(),
|
||||
AdminPass: randomString("", 16),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, newDetails.Id, "ACTIVE")
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
66
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/14-list-addresses.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/14-list-addresses.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(api, id, "ACTIVE")
|
||||
defer api.DeleteServerById(id)
|
||||
|
||||
tryAllAddresses(id, api)
|
||||
tryAddressesByNetwork("private", id, api)
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func tryAllAddresses(id string, api gophercloud.CloudServersProvider) {
|
||||
log("Getting list of all addresses...")
|
||||
addresses, err := api.ListAddresses(id)
|
||||
if (err != nil) && (err != gophercloud.WarnUnauthoritative) {
|
||||
panic(err)
|
||||
}
|
||||
if err == gophercloud.WarnUnauthoritative {
|
||||
log("Uh oh -- got a response back, but it's not authoritative for some reason.")
|
||||
}
|
||||
if !*quiet {
|
||||
fmt.Println("Addresses:")
|
||||
fmt.Printf("%+v\n", addresses)
|
||||
}
|
||||
}
|
||||
|
||||
func tryAddressesByNetwork(networkLabel string, id string, api gophercloud.CloudServersProvider) {
|
||||
log("Getting list of addresses on", networkLabel, "network...")
|
||||
network, err := api.ListAddressesByNetwork(id, networkLabel)
|
||||
if (err != nil) && (err != gophercloud.WarnUnauthoritative) {
|
||||
panic(err)
|
||||
}
|
||||
if err == gophercloud.WarnUnauthoritative {
|
||||
log("Uh oh -- got a response back, but it's not authoritative for some reason.")
|
||||
}
|
||||
for _, addr := range network[networkLabel] {
|
||||
log("Address:", addr.Addr, " IPv", addr.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func log(s ...interface{}) {
|
||||
if !*quiet {
|
||||
fmt.Println(s...)
|
||||
}
|
||||
}
|
||||
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/15-list-keypairs.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/15-list-keypairs.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
keypairs, err := servers.ListKeyPairs()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("name,fingerprint,publickey")
|
||||
for _, key := range keypairs {
|
||||
fmt.Printf("%s,%s,%s\n", key.Name, key.FingerPrint, key.PublicKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/16-create-delete-keypair.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/16-create-delete-keypair.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
name := randomString("ACPTTEST", 16)
|
||||
kp := gophercloud.NewKeyPair{
|
||||
Name: name,
|
||||
}
|
||||
keypair, err := servers.CreateKeyPair(kp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !*quiet {
|
||||
fmt.Printf("%s,%s,%s\n", keypair.Name, keypair.FingerPrint, keypair.PublicKey)
|
||||
}
|
||||
|
||||
keypair, err = servers.ShowKeyPair(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !*quiet {
|
||||
fmt.Printf("%s,%s,%s\n", keypair.Name, keypair.FingerPrint, keypair.PublicKey)
|
||||
}
|
||||
|
||||
err = servers.DeleteKeyPair(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/17-create-delete-image.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/17-create-delete-image.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
serverId, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Creating image")
|
||||
name := randomString("ACPTTEST", 16)
|
||||
createImage := gophercloud.CreateImage{
|
||||
Name: name,
|
||||
}
|
||||
imageId, err := servers.CreateImage(serverId, createImage)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForImageState(servers, imageId, "ACTIVE")
|
||||
|
||||
log("Deleting server")
|
||||
servers.DeleteServerById(serverId)
|
||||
|
||||
log("Deleting image")
|
||||
servers.DeleteImageById(imageId)
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/18-osutil-authentication.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/18-osutil-authentication.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/osutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
provider, authOptions, err := osutil.AuthOptions()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = gophercloud.Authenticate(provider, authOptions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
58
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/19-list-addresses-0.1.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/19-list-addresses-0.1.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(api, id, "ACTIVE")
|
||||
defer api.DeleteServerById(id)
|
||||
|
||||
tryAllAddresses(id, api)
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func tryAllAddresses(id string, api gophercloud.CloudServersProvider) {
|
||||
log("Getting the server instance")
|
||||
s, err := api.ServerById(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log("Getting the complete set of pools")
|
||||
ps, err := s.AllAddressPools()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log("Listing IPs for each pool")
|
||||
for k, v := range ps {
|
||||
log(fmt.Sprintf(" Pool %s", k))
|
||||
for _, a := range v {
|
||||
log(fmt.Sprintf(" IP: %s, Version: %d", a.Addr, a.Version))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func log(s ...interface{}) {
|
||||
if !*quiet {
|
||||
fmt.Println(s...)
|
||||
}
|
||||
}
|
||||
48
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/99-delete-server.go
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/99-delete-server.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet operation for acceptance tests. $? non-zero if problem.")
|
||||
var region = flag.String("r", "", "Datacenter region. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
// Grab a listing of all servers.
|
||||
ss, err := servers.ListServers()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// And for each one that starts with the ACPTTEST prefix, delete it.
|
||||
// These are likely left-overs from previously running acceptance tests.
|
||||
// Note that 04-create-servers.go is intended to leak servers by intention,
|
||||
// so as to test this code. :)
|
||||
n := 0
|
||||
for _, s := range ss {
|
||||
if len(s.Name) < 8 {
|
||||
continue
|
||||
}
|
||||
if s.Name[0:8] == "ACPTTEST" {
|
||||
err := servers.DeleteServerById(s.Id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Printf("%d servers removed.\n", n)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
239
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/libargs.go
generated
vendored
Normal file
239
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/libargs.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getCredentials will verify existence of needed credential information
|
||||
// provided through environment variables. This function will not return
|
||||
// if at least one piece of required information is missing.
|
||||
func getCredentials() (provider, username, password, apiKey string) {
|
||||
provider = os.Getenv("SDK_PROVIDER")
|
||||
username = os.Getenv("SDK_USERNAME")
|
||||
password = os.Getenv("SDK_PASSWORD")
|
||||
apiKey = os.Getenv("SDK_API_KEY")
|
||||
var authURL = os.Getenv("OS_AUTH_URL")
|
||||
|
||||
if (provider == "") || (username == "") || (password == "") {
|
||||
fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n")
|
||||
fmt.Fprintf(os.Stderr, " SDK_PROVIDER=\"%s\"\n", provider)
|
||||
fmt.Fprintf(os.Stderr, " SDK_USERNAME=\"%s\"\n", username)
|
||||
fmt.Fprintf(os.Stderr, " SDK_PASSWORD=\"%s\"\n", password)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if strings.Contains(provider, "rackspace") && (authURL != "") {
|
||||
provider = authURL + "/v2.0/tokens"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// randomString generates a string of given length, but random content.
|
||||
// All content will be within the ASCII graphic character set.
|
||||
// (Implementation from Even Shaw's contribution on
|
||||
// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
|
||||
func randomString(prefix string, n int) string {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return prefix + string(bytes)
|
||||
}
|
||||
|
||||
// aSuitableImage finds a minimal image for use in dynamically creating servers.
|
||||
// If none can be found, this function will panic.
|
||||
func aSuitableImage(api gophercloud.CloudServersProvider) string {
|
||||
images, err := api.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO(sfalvo):
|
||||
// Works for Rackspace, might not work for your provider!
|
||||
// Need to figure out why ListImages() provides 0 values for
|
||||
// Ram and Disk fields.
|
||||
//
|
||||
// Until then, just return Ubuntu 12.04 LTS.
|
||||
for i := 0; i < len(images); i++ {
|
||||
if strings.Contains(images[i].Name, "Ubuntu 12.04 LTS") {
|
||||
return images[i].Id
|
||||
}
|
||||
}
|
||||
panic("Image for Ubuntu 12.04 LTS not found.")
|
||||
}
|
||||
|
||||
// aSuitableFlavor finds the minimum flavor capable of running the test image
|
||||
// chosen by aSuitableImage. If none can be found, this function will panic.
|
||||
func aSuitableFlavor(api gophercloud.CloudServersProvider) string {
|
||||
flavors, err := api.ListFlavors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO(sfalvo):
|
||||
// Works for Rackspace, might not work for your provider!
|
||||
// Need to figure out why ListFlavors() provides 0 values for
|
||||
// Ram and Disk fields.
|
||||
//
|
||||
// Until then, just return Ubuntu 12.04 LTS.
|
||||
for i := 0; i < len(flavors); i++ {
|
||||
if flavors[i].Id == "2" {
|
||||
return flavors[i].Id
|
||||
}
|
||||
}
|
||||
panic("Flavor 2 (512MB 1-core 20GB machine) not found.")
|
||||
}
|
||||
|
||||
// createServer creates a new server in a manner compatible with acceptance testing.
|
||||
// In particular, it ensures that the name of the server always starts with "ACPTTEST--",
|
||||
// which the delete servers acceptance test relies on to identify servers to delete.
|
||||
// Passing in empty image and flavor references will force the use of reasonable defaults.
|
||||
// An empty name string will result in a dynamically created name prefixed with "ACPTTEST--".
|
||||
// A blank admin password will cause a password to be automatically generated; however,
|
||||
// at present no means of recovering this password exists, as no acceptance tests yet require
|
||||
// this data.
|
||||
func createServer(servers gophercloud.CloudServersProvider, imageRef, flavorRef, name, adminPass string) (string, error) {
|
||||
if imageRef == "" {
|
||||
imageRef = aSuitableImage(servers)
|
||||
}
|
||||
|
||||
if flavorRef == "" {
|
||||
flavorRef = aSuitableFlavor(servers)
|
||||
}
|
||||
|
||||
if len(name) < 1 {
|
||||
name = randomString("ACPTTEST", 16)
|
||||
}
|
||||
|
||||
if (len(name) < 8) || (name[0:8] != "ACPTTEST") {
|
||||
name = fmt.Sprintf("ACPTTEST--%s", name)
|
||||
}
|
||||
|
||||
newServer, err := servers.CreateServer(gophercloud.NewServer{
|
||||
Name: name,
|
||||
ImageRef: imageRef,
|
||||
FlavorRef: flavorRef,
|
||||
AdminPass: adminPass,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return newServer.Id, nil
|
||||
}
|
||||
|
||||
// findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different
|
||||
// than what aSuitableFlavor() returns. If none could be found, this function will panic.
|
||||
func findAlternativeFlavor() string {
|
||||
return "3" // 1GB image, up from 512MB image
|
||||
}
|
||||
|
||||
// findAlternativeImage locates an image to resize or rebuild a server with. It is guaranteed to be
|
||||
// different than what aSuitableImage() returns. If none could be found, this function will panic.
|
||||
func findAlternativeImage() string {
|
||||
return "c6f9c411-e708-4952-91e5-62ded5ea4d3e"
|
||||
}
|
||||
|
||||
// withIdentity authenticates the user against the provider's identity service, and provides an
|
||||
// accessor for additional services.
|
||||
func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
|
||||
_, _, _, apiKey := getCredentials()
|
||||
if len(apiKey) == 0 {
|
||||
withPasswordIdentity(ar, f)
|
||||
} else {
|
||||
withAPIKeyIdentity(ar, f)
|
||||
}
|
||||
}
|
||||
|
||||
func withPasswordIdentity(ar bool, f func(gophercloud.AccessProvider)) {
|
||||
provider, username, password, _ := getCredentials()
|
||||
acc, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
AllowReauth: ar,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f(acc)
|
||||
}
|
||||
|
||||
func withAPIKeyIdentity(ar bool, f func(gophercloud.AccessProvider)) {
|
||||
provider, username, _, apiKey := getCredentials()
|
||||
acc, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
ApiKey: apiKey,
|
||||
AllowReauth: ar,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f(acc)
|
||||
}
|
||||
|
||||
// withServerApi acquires the cloud servers API.
|
||||
func withServerApi(acc gophercloud.AccessProvider, f func(gophercloud.CloudServersProvider)) {
|
||||
api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{
|
||||
Name: "cloudServersOpenStack",
|
||||
VersionId: "2",
|
||||
UrlChoice: gophercloud.PublicURL,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f(api)
|
||||
}
|
||||
|
||||
// waitForServerState polls, every 10 seconds, for a given server to appear in the indicated state.
|
||||
// This call will block forever if it never appears in the desired state, so if a timeout is required,
|
||||
// make sure to call this function in a goroutine.
|
||||
func waitForServerState(api gophercloud.CloudServersProvider, id, state string) error {
|
||||
for {
|
||||
s, err := api.ServerById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Status == state {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
panic("Impossible")
|
||||
}
|
||||
|
||||
// waitForImageState polls, every 10 seconds, for a given image to appear in the indicated state.
|
||||
// This call will block forever if it never appears in the desired state, so if a timeout is required,
|
||||
// make sure to call this function in a goroutine.
|
||||
func waitForImageState(api gophercloud.CloudServersProvider, id, state string) error {
|
||||
for {
|
||||
s, err := api.ImageById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Status == state {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
panic("Impossible")
|
||||
}
|
||||
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/api_fetch.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/api_fetch.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package gophercloud
|
||||
|
||||
import(
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
//The default generic openstack api
|
||||
var OpenstackApi = map[string]interface{}{
|
||||
"Type": "compute",
|
||||
"UrlChoice": PublicURL,
|
||||
}
|
||||
|
||||
// Api for use with rackspace
|
||||
var RackspaceApi = map[string]interface{}{
|
||||
"Name": "cloudServersOpenStack",
|
||||
"VersionId": "2",
|
||||
"UrlChoice": PublicURL,
|
||||
}
|
||||
|
||||
|
||||
//Populates an ApiCriteria struct with the api values
|
||||
//from one of the api maps
|
||||
func PopulateApi(variant string) (ApiCriteria, error){
|
||||
var Api ApiCriteria
|
||||
var variantMap map[string]interface{}
|
||||
|
||||
switch variant {
|
||||
case "":
|
||||
variantMap = OpenstackApi
|
||||
|
||||
case "openstack":
|
||||
variantMap = OpenstackApi
|
||||
|
||||
case "rackspace":
|
||||
variantMap = RackspaceApi
|
||||
|
||||
default:
|
||||
var err = fmt.Errorf(
|
||||
"PopulateApi: Unknown variant %# v; legal values: \"openstack\", \"rackspace\"", variant)
|
||||
return Api, err
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(variantMap,&Api)
|
||||
if err != nil{
|
||||
return Api,err
|
||||
}
|
||||
return Api, err
|
||||
}
|
||||
257
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate.go
generated
vendored
Normal file
257
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// AuthOptions lets anyone calling Authenticate() supply the required access credentials.
|
||||
// At present, only Identity V2 API support exists; therefore, only Username, Password,
|
||||
// and optionally, TenantId are provided. If future Identity API versions become available,
|
||||
// alternative fields unique to those versions may appear here.
|
||||
type AuthOptions struct {
|
||||
// Username and Password are required if using Identity V2 API.
|
||||
// Consult with your provider's control panel to discover your
|
||||
// account's username and password.
|
||||
Username, Password string
|
||||
|
||||
// ApiKey used for providers that support Api Key authentication
|
||||
ApiKey string
|
||||
|
||||
// The TenantId field is optional for the Identity V2 API.
|
||||
TenantId string
|
||||
|
||||
// The TenantName can be specified instead of the TenantId
|
||||
TenantName string
|
||||
|
||||
// AllowReauth should be set to true if you grant permission for Gophercloud to cache
|
||||
// your credentials in memory, and to allow Gophercloud to attempt to 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.
|
||||
AllowReauth bool
|
||||
}
|
||||
|
||||
// AuthContainer provides a JSON encoding wrapper for passing credentials to the Identity
|
||||
// service. You will not work with this structure directly.
|
||||
type AuthContainer struct {
|
||||
Auth Auth `json:"auth"`
|
||||
}
|
||||
|
||||
// Auth provides a JSON encoding wrapper for passing credentials to the Identity
|
||||
// service. You will not work with this structure directly.
|
||||
type Auth struct {
|
||||
PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty"`
|
||||
ApiKeyCredentials *ApiKeyCredentials `json:"RAX-KSKEY:apiKeyCredentials,omitempty"`
|
||||
TenantId string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
}
|
||||
|
||||
// PasswordCredentials provides a JSON encoding wrapper for passing credentials to the Identity
|
||||
// service. You will not work with this structure directly.
|
||||
type PasswordCredentials struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ApiKeyCredentials struct {
|
||||
Username string `json:"username"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
// Access encapsulates the API token and its relevant fields, as well as the
|
||||
// services catalog that Identity API returns once authenticated.
|
||||
type Access struct {
|
||||
Token Token
|
||||
ServiceCatalog []CatalogEntry
|
||||
User User
|
||||
provider Provider `json:"-"`
|
||||
options AuthOptions `json:"-"`
|
||||
context *Context `json:"-"`
|
||||
}
|
||||
|
||||
// Token encapsulates an authentication token and when it expires. It also includes
|
||||
// tenant information if available.
|
||||
type Token struct {
|
||||
Id, Expires string
|
||||
Tenant Tenant
|
||||
}
|
||||
|
||||
// Tenant encapsulates tenant authentication information. If, after authentication,
|
||||
// no tenant information is supplied, both Id and Name will be "".
|
||||
type Tenant struct {
|
||||
Id, Name string
|
||||
}
|
||||
|
||||
// User encapsulates the user credentials, and provides visibility in what
|
||||
// the user can do through its role assignments.
|
||||
type User struct {
|
||||
Id, Name string
|
||||
XRaxDefaultRegion string `json:"RAX-AUTH:defaultRegion"`
|
||||
Roles []Role
|
||||
}
|
||||
|
||||
// Role encapsulates a permission that a user can rely on.
|
||||
type Role struct {
|
||||
Description, Id, Name string
|
||||
}
|
||||
|
||||
// CatalogEntry encapsulates a service catalog record.
|
||||
type CatalogEntry struct {
|
||||
Name, Type string
|
||||
Endpoints []EntryEndpoint
|
||||
}
|
||||
|
||||
// EntryEndpoint encapsulates how to get to the API of some service.
|
||||
type EntryEndpoint struct {
|
||||
Region, TenantId string
|
||||
PublicURL, InternalURL string
|
||||
VersionId, VersionInfo, VersionList string
|
||||
}
|
||||
|
||||
type AuthError struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (ae *AuthError) Error() string {
|
||||
switch ae.StatusCode {
|
||||
case 401:
|
||||
return "Auth failed. Bad credentials."
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("Auth failed. Status code is: %s.", ae.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
func getAuthCredentials(options AuthOptions) Auth {
|
||||
if options.ApiKey == "" {
|
||||
return Auth{
|
||||
PasswordCredentials: &PasswordCredentials{
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
},
|
||||
TenantId: options.TenantId,
|
||||
TenantName: options.TenantName,
|
||||
}
|
||||
} else {
|
||||
return Auth{
|
||||
ApiKeyCredentials: &ApiKeyCredentials{
|
||||
Username: options.Username,
|
||||
ApiKey: options.ApiKey,
|
||||
},
|
||||
TenantId: options.TenantId,
|
||||
TenantName: options.TenantName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// papersPlease contains the common logic between authentication and re-authentication.
|
||||
// The name, obviously a joke on the process of authentication, was chosen because
|
||||
// of how many other entities exist in the program containing the word Auth or Authorization.
|
||||
// I didn't need another one.
|
||||
func (c *Context) papersPlease(p Provider, options AuthOptions) (*Access, error) {
|
||||
var access *Access
|
||||
access = new(Access)
|
||||
|
||||
if (options.Username == "") || (options.Password == "" && options.ApiKey == "") {
|
||||
return nil, ErrCredentials
|
||||
}
|
||||
|
||||
resp, err := perigee.Request("POST", p.AuthEndpoint, perigee.Options{
|
||||
CustomClient: c.httpClient,
|
||||
ReqBody: &AuthContainer{
|
||||
Auth: getAuthCredentials(options),
|
||||
},
|
||||
Results: &struct {
|
||||
Access **Access `json:"access"`
|
||||
}{
|
||||
&access,
|
||||
},
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
access.options = options
|
||||
access.provider = p
|
||||
access.context = c
|
||||
|
||||
default:
|
||||
err = &AuthError {
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return access, err
|
||||
}
|
||||
|
||||
// Authenticate() grants access to the OpenStack-compatible provider API.
|
||||
//
|
||||
// Providers are identified through a unique key string.
|
||||
// See the RegisterProvider() method for more details.
|
||||
//
|
||||
// The supplied AuthOptions instance allows the client to specify only those credentials
|
||||
// relevant for the authentication request. At present, support exists for OpenStack
|
||||
// Identity V2 API only; support for V3 will become available as soon as documentation for it
|
||||
// becomes readily available.
|
||||
//
|
||||
// For Identity V2 API requirements, you must provide at least the Username and Password
|
||||
// options. The TenantId field is optional, and defaults to "".
|
||||
func (c *Context) Authenticate(provider string, options AuthOptions) (*Access, error) {
|
||||
p, err := c.ProviderByName(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.papersPlease(p, options)
|
||||
}
|
||||
|
||||
// Reauthenticate attempts to reauthenticate using the configured access credentials, if
|
||||
// allowed. This method takes no action unless your AuthOptions has the AllowReauth flag
|
||||
// set to true.
|
||||
func (a *Access) Reauthenticate() error {
|
||||
var other *Access
|
||||
var err error
|
||||
|
||||
if a.options.AllowReauth {
|
||||
other, err = a.context.papersPlease(a.provider, a.options)
|
||||
if err == nil {
|
||||
*a = *other
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// See AccessProvider interface definition for details.
|
||||
func (a *Access) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
|
||||
ep := FindFirstEndpointByCriteria(a.ServiceCatalog, ac)
|
||||
urls := []string{ep.PublicURL, ep.InternalURL}
|
||||
return urls[ac.UrlChoice]
|
||||
}
|
||||
|
||||
// See AccessProvider interface definition for details.
|
||||
func (a *Access) AuthToken() string {
|
||||
return a.Token.Id
|
||||
}
|
||||
|
||||
// See AccessProvider interface definition for details.
|
||||
func (a *Access) Revoke(tok string) error {
|
||||
url := a.provider.AuthEndpoint + "/" + tok
|
||||
err := perigee.Delete(url, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": a.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{204},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See ServiceCatalogerForIdentityV2 interface definition for details.
|
||||
// Note that the raw slice is returend; be careful not to alter the fields of any members,
|
||||
// for other components of Gophercloud may depend upon them.
|
||||
// If this becomes a problem in the future,
|
||||
// a future revision may return a deep-copy of the service catalog instead.
|
||||
func (a *Access) V2ServiceCatalog() []CatalogEntry {
|
||||
return a.ServiceCatalog
|
||||
}
|
||||
264
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate_test.go
generated
vendored
Normal file
264
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate_test.go
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const SUCCESSFUL_RESPONSE = `{
|
||||
"access": {
|
||||
"serviceCatalog": [{
|
||||
"endpoints": [{
|
||||
"publicURL": "https://ord.servers.api.rackspacecloud.com/v2/12345",
|
||||
"region": "ORD",
|
||||
"tenantId": "12345",
|
||||
"versionId": "2",
|
||||
"versionInfo": "https://ord.servers.api.rackspacecloud.com/v2",
|
||||
"versionList": "https://ord.servers.api.rackspacecloud.com/"
|
||||
},{
|
||||
"publicURL": "https://dfw.servers.api.rackspacecloud.com/v2/12345",
|
||||
"region": "DFW",
|
||||
"tenantId": "12345",
|
||||
"versionId": "2",
|
||||
"versionInfo": "https://dfw.servers.api.rackspacecloud.com/v2",
|
||||
"versionList": "https://dfw.servers.api.rackspacecloud.com/"
|
||||
}],
|
||||
"name": "cloudServersOpenStack",
|
||||
"type": "compute"
|
||||
},{
|
||||
"endpoints": [{
|
||||
"publicURL": "https://ord.databases.api.rackspacecloud.com/v1.0/12345",
|
||||
"region": "ORD",
|
||||
"tenantId": "12345"
|
||||
}],
|
||||
"name": "cloudDatabases",
|
||||
"type": "rax:database"
|
||||
}],
|
||||
"token": {
|
||||
"expires": "2012-04-13T13:15:00.000-05:00",
|
||||
"id": "aaaaa-bbbbb-ccccc-dddd"
|
||||
},
|
||||
"user": {
|
||||
"RAX-AUTH:defaultRegion": "DFW",
|
||||
"id": "161418",
|
||||
"name": "demoauthor",
|
||||
"roles": [{
|
||||
"description": "User Admin Role.",
|
||||
"id": "3",
|
||||
"name": "identity:user-admin"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestAuthProvider(t *testing.T) {
|
||||
tt := newTransport().WithResponse(SUCCESSFUL_RESPONSE)
|
||||
c := TestContext().UseCustomClient(&http.Client{
|
||||
Transport: tt,
|
||||
})
|
||||
|
||||
_, err := c.Authenticate("", AuthOptions{})
|
||||
if err == nil {
|
||||
t.Error("Expected error for empty provider string")
|
||||
return
|
||||
}
|
||||
_, err = c.Authenticate("unknown-provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err == nil {
|
||||
t.Error("Expected error for unknown service provider")
|
||||
return
|
||||
}
|
||||
|
||||
err = c.RegisterProvider("provider", Provider{AuthEndpoint: "/"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_, err = c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if tt.called != 1 {
|
||||
t.Error("Expected transport to be called once.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantIdEncoding(t *testing.T) {
|
||||
tt := newTransport().WithResponse(SUCCESSFUL_RESPONSE)
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{
|
||||
Transport: tt,
|
||||
}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "/"})
|
||||
|
||||
tt.IgnoreTenantId()
|
||||
_, err := c.Authenticate("provider", AuthOptions{
|
||||
Username: "u",
|
||||
Password: "p",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if tt.tenantIdFound {
|
||||
t.Error("Tenant ID should not have been encoded")
|
||||
return
|
||||
}
|
||||
|
||||
tt.ExpectTenantId()
|
||||
_, err = c.Authenticate("provider", AuthOptions{
|
||||
Username: "u",
|
||||
Password: "p",
|
||||
TenantId: "t",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !tt.tenantIdFound {
|
||||
t.Error("Tenant ID should have been encoded")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserNameAndPassword(t *testing.T) {
|
||||
c := TestContext().
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"}).
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)})
|
||||
|
||||
credentials := []AuthOptions{
|
||||
{},
|
||||
{Username: "u"},
|
||||
{Password: "p"},
|
||||
}
|
||||
for i, auth := range credentials {
|
||||
_, err := c.Authenticate("provider", auth)
|
||||
if err == nil {
|
||||
t.Error("Expected error from missing credentials (%d)", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserNameAndApiKey(t *testing.T) {
|
||||
c := TestContext().
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"}).
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)})
|
||||
|
||||
credentials := []AuthOptions{
|
||||
{},
|
||||
{Username: "u"},
|
||||
{ApiKey: "a"},
|
||||
}
|
||||
for i, auth := range credentials {
|
||||
_, err := c.Authenticate("provider", auth)
|
||||
if err == nil {
|
||||
t.Error("Expected error from missing credentials (%d)", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.Authenticate("provider", AuthOptions{Username: "u", ApiKey: "a"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenAcquisition(t *testing.T) {
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
|
||||
|
||||
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
tok := acc.Token
|
||||
if (tok.Id == "") || (tok.Expires == "") {
|
||||
t.Error("Expected a valid token for successful login; got %s, %s", tok.Id, tok.Expires)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceCatalogAcquisition(t *testing.T) {
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
|
||||
|
||||
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
svcs := acc.ServiceCatalog
|
||||
if len(svcs) < 2 {
|
||||
t.Error("Expected 2 service catalog entries; got %d", len(svcs))
|
||||
return
|
||||
}
|
||||
|
||||
types := map[string]bool{
|
||||
"compute": true,
|
||||
"rax:database": true,
|
||||
}
|
||||
for _, entry := range svcs {
|
||||
if !types[entry.Type] {
|
||||
t.Error("Expected to find type %s.", entry.Type)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAcquisition(t *testing.T) {
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
|
||||
|
||||
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
u := acc.User
|
||||
if u.Id != "161418" {
|
||||
t.Error("Expected user ID of 16148; got", u.Id)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticationNeverReauths(t *testing.T) {
|
||||
tt := newTransport().WithError(401)
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: tt}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost"})
|
||||
|
||||
_, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err == nil {
|
||||
t.Error("Expected an error from a 401 Unauthorized response")
|
||||
return
|
||||
}
|
||||
|
||||
rc, _ := ActualResponseCode(err)
|
||||
if rc != 401 {
|
||||
t.Error("Expected a 401 error code")
|
||||
return
|
||||
}
|
||||
|
||||
err = tt.VerifyCalls(t, 1)
|
||||
if err != nil {
|
||||
// Test object already flagged.
|
||||
return
|
||||
}
|
||||
}
|
||||
24
Godeps/_workspace/src/github.com/rackspace/gophercloud/common_types.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/rackspace/gophercloud/common_types.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package gophercloud
|
||||
|
||||
// Link is used for JSON (un)marshalling.
|
||||
// It provides RESTful links to a resource.
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Rel string `json:"rel"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// FileConfig structures represent a blob of data which must appear at a
|
||||
// a specific location in a server's filesystem. The file contents are
|
||||
// base-64 encoded.
|
||||
type FileConfig struct {
|
||||
Path string `json:"path"`
|
||||
Contents string `json:"contents"`
|
||||
}
|
||||
|
||||
// NetworkConfig structures represent an affinity between a server and a
|
||||
// specific, uniquely identified network. Networks are identified through
|
||||
// universally unique IDs.
|
||||
type NetworkConfig struct {
|
||||
Uuid string `json:"uuid"`
|
||||
}
|
||||
150
Godeps/_workspace/src/github.com/rackspace/gophercloud/context.go
generated
vendored
Normal file
150
Godeps/_workspace/src/github.com/rackspace/gophercloud/context.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"fmt"
|
||||
"github.com/tonnerre/golang-pretty"
|
||||
)
|
||||
|
||||
// Provider structures exist for each tangible provider of OpenStack service.
|
||||
// For example, Rackspace, Hewlett-Packard, and NASA might have their own instance of this structure.
|
||||
//
|
||||
// At a minimum, a provider must expose an authentication endpoint.
|
||||
type Provider struct {
|
||||
AuthEndpoint string
|
||||
}
|
||||
|
||||
// ReauthHandlerFunc functions are responsible for somehow performing the task of
|
||||
// reauthentication.
|
||||
type ReauthHandlerFunc func(AccessProvider) error
|
||||
|
||||
// Context structures encapsulate Gophercloud-global state in a manner which
|
||||
// facilitates easier unit testing. As a user of this SDK, you'll never
|
||||
// have to use this structure, except when contributing new code to the SDK.
|
||||
type Context struct {
|
||||
// providerMap serves as a directory of supported providers.
|
||||
providerMap map[string]Provider
|
||||
|
||||
// httpClient refers to the current HTTP client interface to use.
|
||||
httpClient *http.Client
|
||||
|
||||
// reauthHandler provides the functionality needed to re-authenticate
|
||||
// if that feature is enabled. Note: in order to allow for automatic
|
||||
// re-authentication, the Context object will need to remember your
|
||||
// username, password, and tenant ID as provided in the initial call
|
||||
// to Authenticate(). If you do not desire this, you'll need to handle
|
||||
// reauthentication yourself through other means. Two methods exist:
|
||||
// the first approach is to just handle errors yourself at the application
|
||||
// layer, and the other is through a custom reauthentication handler
|
||||
// set through the WithReauthHandler() method.
|
||||
reauthHandler ReauthHandlerFunc
|
||||
}
|
||||
|
||||
// TestContext yields a new Context instance, pre-initialized with a barren
|
||||
// state suitable for per-unit-test customization. This configuration consists
|
||||
// of:
|
||||
//
|
||||
// * An empty provider map.
|
||||
//
|
||||
// * An HTTP client built by the net/http package (see http://godoc.org/net/http#Client).
|
||||
func TestContext() *Context {
|
||||
return &Context{
|
||||
providerMap: make(map[string]Provider),
|
||||
httpClient: &http.Client{},
|
||||
reauthHandler: func(acc AccessProvider) error {
|
||||
return acc.Reauthenticate()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// UseCustomClient configures the context to use a customized HTTP client
|
||||
// instance. By default, TestContext() will return a Context which uses
|
||||
// the net/http package's default client instance.
|
||||
func (c *Context) UseCustomClient(hc *http.Client) *Context {
|
||||
c.httpClient = hc
|
||||
return c
|
||||
}
|
||||
|
||||
// RegisterProvider allows a unit test to register a mythical provider convenient for testing.
|
||||
// If the provider structure lacks adequate configuration, or the configuration given has some
|
||||
// detectable error, an ErrConfiguration error will result.
|
||||
func (c *Context) RegisterProvider(name string, p Provider) error {
|
||||
if p.AuthEndpoint == "" {
|
||||
return ErrConfiguration
|
||||
}
|
||||
|
||||
c.providerMap[name] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithProvider offers convenience for unit tests.
|
||||
func (c *Context) WithProvider(name string, p Provider) *Context {
|
||||
err := c.RegisterProvider(name, p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ProviderByName will locate a provider amongst those previously registered, if it exists.
|
||||
// If the named provider has not been registered, an ErrProvider error will result.
|
||||
//
|
||||
// You may also specify a custom Identity API URL.
|
||||
// Any provider name that contains the characters "://", in that order, will be treated as a custom Identity API URL.
|
||||
// Custom URLs, important for private cloud deployments, overrides all provider configurations.
|
||||
func (c *Context) ProviderByName(name string) (p Provider, err error) {
|
||||
for provider, descriptor := range c.providerMap {
|
||||
if name == provider {
|
||||
return descriptor, nil
|
||||
}
|
||||
}
|
||||
if strings.Contains(name, "://") {
|
||||
p = Provider{
|
||||
AuthEndpoint: name,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
return Provider{}, ErrProvider
|
||||
}
|
||||
|
||||
func getServiceCatalogFromAccessProvider(provider AccessProvider) ([]CatalogEntry) {
|
||||
access, found := provider.(*Access)
|
||||
if found {
|
||||
return access.ServiceCatalog
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiates a Cloud Servers API for the provider given.
|
||||
func (c *Context) ServersApi(provider AccessProvider, criteria ApiCriteria) (CloudServersProvider, error) {
|
||||
url := provider.FirstEndpointUrlByCriteria(criteria)
|
||||
if url == "" {
|
||||
var err = fmt.Errorf(
|
||||
"Missing endpoint, or insufficient privileges to access endpoint; criteria = %# v; serviceCatalog = %# v",
|
||||
pretty.Formatter(criteria),
|
||||
pretty.Formatter(getServiceCatalogFromAccessProvider(provider)))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcp := &genericServersProvider{
|
||||
endpoint: url,
|
||||
context: c,
|
||||
access: provider,
|
||||
}
|
||||
|
||||
return gcp, nil
|
||||
}
|
||||
|
||||
// WithReauthHandler configures the context to handle reauthentication attempts using the supplied
|
||||
// funtion. By default, reauthentication happens by invoking Authenticate(), which is unlikely to be
|
||||
// useful in a unit test.
|
||||
//
|
||||
// Do not confuse this function with WithReauth()! Although they work together to support reauthentication,
|
||||
// WithReauth() actually contains the decision-making logic to determine when to perform a reauth,
|
||||
// while WithReauthHandler() is used to configure what a reauth actually entails.
|
||||
func (c *Context) WithReauthHandler(f ReauthHandlerFunc) *Context {
|
||||
c.reauthHandler = f
|
||||
return c
|
||||
}
|
||||
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/context_test.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/context_test.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProviderRegistry(t *testing.T) {
|
||||
c := TestContext()
|
||||
|
||||
_, err := c.ProviderByName("aProvider")
|
||||
if err == nil {
|
||||
t.Error("Expected error when looking for a provider by non-existant name")
|
||||
return
|
||||
}
|
||||
|
||||
err = c.RegisterProvider("aProvider", Provider{})
|
||||
if err != ErrConfiguration {
|
||||
t.Error("Unexpected error/nil when registering a provider w/out an auth endpoint\n %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = c.RegisterProvider("aProvider", Provider{AuthEndpoint: "http://localhost/auth"})
|
||||
_, err = c.ProviderByName("aProvider")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
39
Godeps/_workspace/src/github.com/rackspace/gophercloud/errors.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/rackspace/gophercloud/errors.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrNotImplemented should be used only while developing new SDK features.
|
||||
// No established function or method will ever produce this error.
|
||||
var ErrNotImplemented = fmt.Errorf("Not implemented")
|
||||
|
||||
// ErrProvider errors occur when attempting to reference an unsupported
|
||||
// provider. More often than not, this error happens due to a typo in
|
||||
// the name.
|
||||
var ErrProvider = fmt.Errorf("Missing or incorrect provider")
|
||||
|
||||
// ErrCredentials errors happen when attempting to authenticate using a
|
||||
// set of credentials not recognized by the Authenticate() method.
|
||||
// For example, not providing a username or password when attempting to
|
||||
// authenticate against an Identity V2 API.
|
||||
var ErrCredentials = fmt.Errorf("Missing or incomplete credentials")
|
||||
|
||||
// ErrConfiguration errors happen when attempting to add a new provider, and
|
||||
// the provider added lacks a correct or consistent configuration.
|
||||
// For example, all providers must expose at least an Identity V2 API
|
||||
// for authentication; if this endpoint isn't specified, you may receive
|
||||
// this error when attempting to register it against a context.
|
||||
var ErrConfiguration = fmt.Errorf("Missing or incomplete configuration")
|
||||
|
||||
// ErrError errors happen when you attempt to discover the response code
|
||||
// responsible for a previous request bombing with an error, but pass in an
|
||||
// error interface which doesn't belong to the web client.
|
||||
var ErrError = fmt.Errorf("Attempt to solicit actual HTTP response code from error entity which doesn't know")
|
||||
|
||||
// WarnUnauthoritative warnings happen when a service believes its response
|
||||
// to be correct, but is not in a position of knowing for sure at the moment.
|
||||
// For example, the service could be responding with cached data that has
|
||||
// exceeded its time-to-live setting, but which has not yet received an official
|
||||
// update from an authoritative source.
|
||||
var WarnUnauthoritative = fmt.Errorf("Unauthoritative data")
|
||||
55
Godeps/_workspace/src/github.com/rackspace/gophercloud/flavors.go
generated
vendored
Normal file
55
Godeps/_workspace/src/github.com/rackspace/gophercloud/flavors.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// See CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListFlavors() ([]Flavor, error) {
|
||||
var fs []Flavor
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/flavors/detail"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ Flavors *[]Flavor }{&fs},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return fs, err
|
||||
}
|
||||
|
||||
// FlavorLink provides a reference to a flavor by either ID or by direct URL.
|
||||
// Some services use just the ID, others use just the URL.
|
||||
// This structure provides a common means of expressing both in a single field.
|
||||
type FlavorLink struct {
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
}
|
||||
|
||||
// Flavor records represent (virtual) hardware configurations for server resources in a region.
|
||||
//
|
||||
// The Id field contains the flavor's unique identifier.
|
||||
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
|
||||
//
|
||||
// The Disk and Ram fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
|
||||
//
|
||||
// The Name field provides a human-readable moniker for the flavor.
|
||||
//
|
||||
// Swap indicates how much space is reserved for swap.
|
||||
// If not provided, this field will be set to 0.
|
||||
//
|
||||
// VCpus indicates how many (virtual) CPUs are available for this flavor.
|
||||
type Flavor struct {
|
||||
OsFlvDisabled bool `json:"OS-FLV-DISABLED:disabled"`
|
||||
Disk int `json:"disk"`
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
Name string `json:"name"`
|
||||
Ram int `json:"ram"`
|
||||
RxTxFactor float64 `json:"rxtx_factor"`
|
||||
Swap int `json:"swap"`
|
||||
VCpus int `json:"vcpus"`
|
||||
}
|
||||
88
Godeps/_workspace/src/github.com/rackspace/gophercloud/floating_ips.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/rackspace/gophercloud/floating_ips.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
func (gsp *genericServersProvider) ListFloatingIps() ([]FloatingIp, error) {
|
||||
var fips []FloatingIp
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-floating-ips"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct {
|
||||
FloatingIps *[]FloatingIp `json:"floating_ips"`
|
||||
}{&fips},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return fips, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) CreateFloatingIp(pool string) (FloatingIp, error) {
|
||||
fip := new(FloatingIp)
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-floating-ips"
|
||||
return perigee.Post(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
ReqBody: map[string]string{
|
||||
"pool": pool,
|
||||
},
|
||||
Results: &struct {
|
||||
FloatingIp **FloatingIp `json:"floating_ip"`
|
||||
}{&fip},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
if fip.Ip == "" {
|
||||
return *fip, errors.New("Error creating floating IP")
|
||||
}
|
||||
|
||||
return *fip, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) AssociateFloatingIp(serverId string, ip FloatingIp) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, serverId)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
ReqBody: map[string](map[string]string){
|
||||
"addFloatingIp": map[string]string{"address": ip.Ip},
|
||||
},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) DeleteFloatingIp(ip FloatingIp) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-floating-ips/%d", gsp.endpoint, ip.Id)
|
||||
return perigee.Delete(ep, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type FloatingIp struct {
|
||||
Id int `json:"id"`
|
||||
Pool string `json:"pool"`
|
||||
Ip string `json:"ip"`
|
||||
FixedIp string `json:"fixed_ip"`
|
||||
InstanceId string `json:"instance_id"`
|
||||
}
|
||||
67
Godeps/_workspace/src/github.com/rackspace/gophercloud/global_context.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/rackspace/gophercloud/global_context.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// globalContext is the, well, "global context."
|
||||
// Most of this SDK is written in a manner to facilitate easier testing,
|
||||
// which doesn't require all the configuration a real-world application would require.
|
||||
// However, for real-world deployments, applications should be able to rely on a consistent configuration of providers, etc.
|
||||
var globalContext *Context
|
||||
|
||||
// providers is the set of supported providers.
|
||||
var providers = map[string]Provider{
|
||||
"rackspace-us": {
|
||||
AuthEndpoint: "https://identity.api.rackspacecloud.com/v2.0/tokens",
|
||||
},
|
||||
"rackspace-uk": {
|
||||
AuthEndpoint: "https://lon.identity.api.rackspacecloud.com/v2.0/tokens",
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize the global context to sane configuration.
|
||||
// The Go runtime ensures this function is called before main(),
|
||||
// thus guaranteeing proper configuration before your application ever runs.
|
||||
func init() {
|
||||
globalContext = TestContext()
|
||||
for name, descriptor := range providers {
|
||||
globalContext.RegisterProvider(name, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate() grants access to the OpenStack-compatible provider API.
|
||||
//
|
||||
// Providers are identified through a unique key string.
|
||||
// Specifying an unsupported provider will result in an ErrProvider error.
|
||||
// However, you may also specify a custom Identity API URL.
|
||||
// Any provider name that contains the characters "://", in that order, will be treated as a custom Identity API URL.
|
||||
// Custom URLs, important for private cloud deployments, overrides all provider configurations.
|
||||
//
|
||||
// The supplied AuthOptions instance allows the client to specify only those credentials
|
||||
// relevant for the authentication request. At present, support exists for OpenStack
|
||||
// Identity V2 API only; support for V3 will become available as soon as documentation for it
|
||||
// becomes readily available.
|
||||
//
|
||||
// For Identity V2 API requirements, you must provide at least the Username and Password
|
||||
// options. The TenantId field is optional, and defaults to "".
|
||||
func Authenticate(provider string, options AuthOptions) (*Access, error) {
|
||||
return globalContext.Authenticate(provider, options)
|
||||
}
|
||||
|
||||
// Instantiates a Cloud Servers object for the provider given.
|
||||
func ServersApi(acc AccessProvider, criteria ApiCriteria) (CloudServersProvider, error) {
|
||||
return globalContext.ServersApi(acc, criteria)
|
||||
}
|
||||
|
||||
// ActualResponseCode inspects a returned error, and discovers the actual response actual
|
||||
// response code that caused the error to be raised.
|
||||
func ActualResponseCode(e error) (int, error) {
|
||||
if err, typeOk := e.(*perigee.UnexpectedResponseCodeError); typeOk {
|
||||
return err.Actual, nil
|
||||
} else if err, typeOk := e.(*AuthError); typeOk{
|
||||
return err.StatusCode, nil
|
||||
}
|
||||
|
||||
return 0, ErrError
|
||||
}
|
||||
106
Godeps/_workspace/src/github.com/rackspace/gophercloud/images.go
generated
vendored
Normal file
106
Godeps/_workspace/src/github.com/rackspace/gophercloud/images.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// See the CloudImagesProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListImages() ([]Image, error) {
|
||||
var is []Image
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/images/detail"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ Images *[]Image }{&is},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return is, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) ImageById(id string) (*Image, error) {
|
||||
var is *Image
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/images/" + id
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ Image **Image }{&is},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return is, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) DeleteImageById(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/images/" + id
|
||||
_, err := perigee.Request("DELETE", url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ImageLink provides a reference to a image by either ID or by direct URL.
|
||||
// Some services use just the ID, others use just the URL.
|
||||
// This structure provides a common means of expressing both in a single field.
|
||||
type ImageLink struct {
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
}
|
||||
|
||||
// Image is used for JSON (un)marshalling.
|
||||
// It provides a description of an OS image.
|
||||
//
|
||||
// The Id field contains the image's unique identifier.
|
||||
// For example, this identifier will be useful for specifying which operating system to install on a new server instance.
|
||||
//
|
||||
// The MinDisk and MinRam fields specify the minimum resources a server must provide to be able to install the image.
|
||||
//
|
||||
// The Name field provides a human-readable moniker for the OS image.
|
||||
//
|
||||
// The Progress and Status fields indicate image-creation status.
|
||||
// Any usable image will have 100% progress.
|
||||
//
|
||||
// The Updated field indicates the last time this image was changed.
|
||||
//
|
||||
// OsDcfDiskConfig indicates the server's boot volume configuration.
|
||||
// Valid values are:
|
||||
// AUTO
|
||||
// ----
|
||||
// The server is built with a single partition the size of the target flavor disk.
|
||||
// The file system is automatically adjusted to fit the entire partition.
|
||||
// This keeps things simple and automated.
|
||||
// AUTO is valid only for images and servers with a single partition that use the EXT3 file system.
|
||||
// This is the default setting for applicable Rackspace base images.
|
||||
//
|
||||
// MANUAL
|
||||
// ------
|
||||
// The server is built using whatever partition scheme and file system is in the source image.
|
||||
// If the target flavor disk is larger,
|
||||
// the remaining disk space is left unpartitioned.
|
||||
// This enables images to have non-EXT3 file systems, multiple partitions, and so on,
|
||||
// and enables you to manage the disk configuration.
|
||||
//
|
||||
type Image struct {
|
||||
Created string `json:"created"`
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
MinDisk int `json:"minDisk"`
|
||||
MinRam int `json:"minRam"`
|
||||
Name string `json:"name"`
|
||||
Progress int `json:"progress"`
|
||||
Status string `json:"status"`
|
||||
Updated string `json:"updated"`
|
||||
OsDcfDiskConfig string `json:"OS-DCF:diskConfig"`
|
||||
}
|
||||
247
Godeps/_workspace/src/github.com/rackspace/gophercloud/interfaces.go
generated
vendored
Normal file
247
Godeps/_workspace/src/github.com/rackspace/gophercloud/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
package gophercloud
|
||||
|
||||
import "net/url"
|
||||
|
||||
// AccessProvider instances encapsulate a Keystone authentication interface.
|
||||
type AccessProvider interface {
|
||||
// FirstEndpointUrlByCriteria searches through the service catalog for the first
|
||||
// matching entry endpoint fulfilling the provided criteria. If nothing found,
|
||||
// return "". Otherwise, return either the public or internal URL for the
|
||||
// endpoint, depending on both its existence and the setting of the ApiCriteria.UrlChoice
|
||||
// field.
|
||||
FirstEndpointUrlByCriteria(ApiCriteria) string
|
||||
|
||||
// AuthToken provides a copy of the current authentication token for the user's credentials.
|
||||
// Note that AuthToken() will not automatically refresh an expired token.
|
||||
AuthToken() string
|
||||
|
||||
// Revoke allows you to terminate any program's access to the OpenStack API by token ID.
|
||||
Revoke(string) error
|
||||
|
||||
// Reauthenticate attempts to acquire a new authentication token, if the feature is enabled by
|
||||
// AuthOptions.AllowReauth.
|
||||
Reauthenticate() error
|
||||
}
|
||||
|
||||
// ServiceCatalogerIdentityV2 interface provides direct access to the service catalog as offered by the Identity V2 API.
|
||||
// We regret we need to fracture the namespace of what should otherwise be a simple concept; however,
|
||||
// the OpenStack community saw fit to render V3's service catalog completely incompatible with V2.
|
||||
type ServiceCatalogerForIdentityV2 interface {
|
||||
V2ServiceCatalog() []CatalogEntry
|
||||
}
|
||||
|
||||
// CloudServersProvider instances encapsulate a Cloud Servers API, should one exist in the service catalog
|
||||
// for your provider.
|
||||
type CloudServersProvider interface {
|
||||
// Servers
|
||||
|
||||
// ListServers provides a complete list of servers hosted by the user
|
||||
// in a given region. This function differs from ListServersLinksOnly()
|
||||
// in that it returns all available details for each server returned.
|
||||
ListServers() ([]Server, error)
|
||||
|
||||
// ListServersByFilters provides a list of servers hosted by the user in a
|
||||
// given region. This function let you requests servers by certain URI
|
||||
// paramaters defined by the API endpoint. This is sometimes more suitable
|
||||
// if you have many servers and you only want to pick servers on certain
|
||||
// criterias. An example usage could be :
|
||||
//
|
||||
// filter := url.Values{}
|
||||
// filter.Set("name", "MyServer")
|
||||
// filter.Set("status", "ACTIVE")
|
||||
//
|
||||
// filteredServers, err := c.ListServersByFilters(filter)
|
||||
//
|
||||
// Here, filteredServers only contains servers whose name started with
|
||||
// "MyServer" and are in "ACTIVE" status.
|
||||
ListServersByFilter(filter url.Values) ([]Server, error)
|
||||
|
||||
// ListServers provides a complete list of servers hosted by the user
|
||||
// in a given region. This function differs from ListServers() in that
|
||||
// it returns only IDs and links to each server returned.
|
||||
//
|
||||
// This function should be used only under certain circumstances.
|
||||
// It's most useful for checking to see if a server with a given ID exists,
|
||||
// or that you have permission to work with that server. It's also useful
|
||||
// when the cost of retrieving the server link list plus the overhead of manually
|
||||
// invoking ServerById() for each of the servers you're interested in is less than
|
||||
// just calling ListServers() to begin with. This may be a consideration, for
|
||||
// example, with mobile applications.
|
||||
//
|
||||
// In other cases, you probably should just call ListServers() and cache the
|
||||
// results to conserve overall bandwidth and reduce your access rate on the API.
|
||||
ListServersLinksOnly() ([]Server, error)
|
||||
|
||||
// ServerById will retrieve a detailed server description given the unique ID
|
||||
// of a server. The ID can be returned by either ListServers() or by ListServersLinksOnly().
|
||||
ServerById(id string) (*Server, error)
|
||||
|
||||
// CreateServer requests a new server to be created by the cloud server provider.
|
||||
// The user must pass in a pointer to an initialized NewServerContainer structure.
|
||||
// Please refer to the NewServerContainer documentation for more details.
|
||||
//
|
||||
// If the NewServer structure's AdminPass is empty (""), a password will be
|
||||
// automatically generated by your OpenStack provider, and returned through the
|
||||
// AdminPass field of the result. Take care, however; this will be the only time
|
||||
// this happens. No other means exists in the public API to acquire a password
|
||||
// for a pre-existing server. If you lose it, you'll need to call SetAdminPassword()
|
||||
// to set a new one.
|
||||
CreateServer(ns NewServer) (*NewServer, error)
|
||||
|
||||
// DeleteServerById requests that the server with the assigned ID be removed
|
||||
// from your account. The delete happens asynchronously.
|
||||
DeleteServerById(id string) error
|
||||
|
||||
// SetAdminPassword requests that the server with the specified ID have its
|
||||
// administrative password changed. For Linux, BSD, or other POSIX-like
|
||||
// system, this password corresponds to the root user. For Windows machines,
|
||||
// the Administrator password will be affected instead.
|
||||
SetAdminPassword(id string, pw string) error
|
||||
|
||||
// ResizeServer can be a short-hand for RebuildServer where only the size of the server
|
||||
// changes. Note that after the resize operation is requested, you will need to confirm
|
||||
// the resize has completed for changes to take effect permanently. Changes will assume
|
||||
// to be confirmed even without an explicit confirmation after 24 hours from the initial
|
||||
// request.
|
||||
ResizeServer(id, newName, newFlavor, newDiskConfig string) error
|
||||
|
||||
// RevertResize will reject a server's resized configuration, thus
|
||||
// rolling back to the original server.
|
||||
RevertResize(id string) error
|
||||
|
||||
// ConfirmResizeServer will acknowledge a server's resized configuration.
|
||||
ConfirmResize(id string) error
|
||||
|
||||
// RebootServer requests that the server with the specified ID be rebooted.
|
||||
// Two reboot mechanisms exist.
|
||||
//
|
||||
// - Hard. This will physically power-cycle the unit.
|
||||
// - Soft. This will attempt to use the server's software-based mechanisms to restart
|
||||
// the machine. E.g., "shutdown -r now" on Linux.
|
||||
RebootServer(id string, hard bool) error
|
||||
|
||||
// RescueServer requests that the server with the specified ID be placed into
|
||||
// a state of maintenance. The server instance is replaced with a new instance,
|
||||
// of the same flavor and image. This new image will have the boot volume of the
|
||||
// original machine mounted as a secondary device, so that repair and administration
|
||||
// may occur. Use UnrescueServer() to restore the server to its previous state.
|
||||
// Note also that many providers will impose a time limit for how long a server may
|
||||
// exist in rescue mode! Consult the API documentation for your provider for
|
||||
// details.
|
||||
RescueServer(id string) (string, error)
|
||||
|
||||
// UnrescueServer requests that a server in rescue state be placed into its nominal
|
||||
// operating state.
|
||||
UnrescueServer(id string) error
|
||||
|
||||
// UpdateServer alters one or more fields of the identified server's Server record.
|
||||
// However, not all fields may be altered. Presently, only Name, AccessIPv4, and
|
||||
// AccessIPv6 fields may be altered. If unspecified, or set to an empty or zero
|
||||
// value, the corresponding field remains unaltered.
|
||||
//
|
||||
// This function returns the new set of server details if successful.
|
||||
UpdateServer(id string, newValues NewServerSettings) (*Server, error)
|
||||
|
||||
// RebuildServer reprovisions a server to the specifications given by the
|
||||
// NewServer structure. The following fields are guaranteed to be recognized:
|
||||
//
|
||||
// Name (required) AccessIPv4
|
||||
// imageRef (required) AccessIPv6
|
||||
// AdminPass (required) Metadata
|
||||
// Personality
|
||||
//
|
||||
// Other providers may reserve the right to act on additional fields.
|
||||
RebuildServer(id string, ns NewServer) (*Server, error)
|
||||
|
||||
// CreateImage will create a new image from the specified server id returning the id of the new image.
|
||||
CreateImage(id string, ci CreateImage) (string, error)
|
||||
|
||||
// Addresses
|
||||
|
||||
// ListAddresses yields the list of available addresses for the server.
|
||||
// This information is also returned by ServerById() in the Server.Addresses
|
||||
// field. However, if you have a lot of servers and all you need are addresses,
|
||||
// this function might be more efficient.
|
||||
ListAddresses(id string) (AddressSet, error)
|
||||
|
||||
// ListAddressesByNetwork yields the list of available addresses for a given server id and networkLabel.
|
||||
// Example: ListAddressesByNetwork("234-4353-4jfrj-43j2s", "private")
|
||||
ListAddressesByNetwork(id, networkLabel string) (NetworkAddress, error)
|
||||
|
||||
// ListFloatingIps yields the list of all floating IP addresses allocated to the current project.
|
||||
ListFloatingIps() ([]FloatingIp, error)
|
||||
|
||||
// CreateFloatingIp allocates a new IP from the named pool to the current project.
|
||||
CreateFloatingIp(pool string) (FloatingIp, error)
|
||||
|
||||
// DeleteFloatingIp returns the specified IP from the current project to the pool.
|
||||
DeleteFloatingIp(ip FloatingIp) error
|
||||
|
||||
// AssociateFloatingIp associates the given floating IP to the given server id.
|
||||
AssociateFloatingIp(serverId string, ip FloatingIp) error
|
||||
|
||||
// Images
|
||||
|
||||
// ListImages yields the list of available operating system images. This function
|
||||
// returns full details for each image, if available.
|
||||
ListImages() ([]Image, error)
|
||||
|
||||
// ImageById yields details about a specific image.
|
||||
ImageById(id string) (*Image, error)
|
||||
|
||||
// DeleteImageById will delete the specific image.
|
||||
DeleteImageById(id string) error
|
||||
|
||||
// Flavors
|
||||
|
||||
// ListFlavors yields the list of available system flavors. This function
|
||||
// returns full details for each flavor, if available.
|
||||
ListFlavors() ([]Flavor, error)
|
||||
|
||||
// KeyPairs
|
||||
|
||||
// ListKeyPairs yields the list of available keypairs.
|
||||
ListKeyPairs() ([]KeyPair, error)
|
||||
|
||||
// CreateKeyPairs will create or generate a new keypair.
|
||||
CreateKeyPair(nkp NewKeyPair) (KeyPair, error)
|
||||
|
||||
// DeleteKeyPair wil delete a keypair.
|
||||
DeleteKeyPair(name string) error
|
||||
|
||||
// ShowKeyPair will yield the named keypair.
|
||||
ShowKeyPair(name string) (KeyPair, error)
|
||||
|
||||
// ListSecurityGroups provides a listing of security groups for the tenant.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
ListSecurityGroups() ([]SecurityGroup, error)
|
||||
|
||||
// CreateSecurityGroup lets a tenant create a new security group.
|
||||
// Only the SecurityGroup fields which are specified will be marshalled to the API.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error)
|
||||
|
||||
// ListSecurityGroupsByServerId provides a list of security groups which apply to the indicated server.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error)
|
||||
|
||||
// SecurityGroupById returns a security group corresponding to the provided ID number.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
SecurityGroupById(id int) (*SecurityGroup, error)
|
||||
|
||||
// DeleteSecurityGroupById disposes of a security group corresponding to the provided ID number.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
DeleteSecurityGroupById(id int) error
|
||||
|
||||
// ListDefaultSGRules lists default security group rules.
|
||||
// This method only works if the provider supports the os-security-groups-default-rules extension.
|
||||
ListDefaultSGRules() ([]SGRule, error)
|
||||
|
||||
// CreateDefaultSGRule creates a default security group rule.
|
||||
// This method only works if the provider supports the os-security-groups-default-rules extension.
|
||||
CreateDefaultSGRule(SGRule) (*SGRule, error)
|
||||
|
||||
// GetSGRule obtains information for a specified security group rule.
|
||||
// This method only works if the provider supports the os-security-groups-default-rules extension.
|
||||
GetSGRule(string) (*SGRule, error)
|
||||
}
|
||||
98
Godeps/_workspace/src/github.com/rackspace/gophercloud/keypairs.go
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/rackspace/gophercloud/keypairs.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// See the CloudImagesProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListKeyPairs() ([]KeyPair, error) {
|
||||
type KeyPairs struct {
|
||||
KeyPairs []struct {
|
||||
KeyPair KeyPair `json:"keypair"`
|
||||
} `json:"keypairs"`
|
||||
}
|
||||
|
||||
var kp KeyPairs
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &kp,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// Flatten out the list of keypairs
|
||||
var keypairs []KeyPair
|
||||
for _, k := range kp.KeyPairs {
|
||||
keypairs = append(keypairs, k.KeyPair)
|
||||
}
|
||||
return keypairs, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) CreateKeyPair(nkp NewKeyPair) (KeyPair, error) {
|
||||
var kp KeyPair
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs"
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
KeyPair *NewKeyPair `json:"keypair"`
|
||||
}{&nkp},
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ KeyPair *KeyPair }{&kp},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
})
|
||||
return kp, err
|
||||
}
|
||||
|
||||
// See the CloudImagesProvider interface for details.
|
||||
func (gsp *genericServersProvider) DeleteKeyPair(name string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs/" + name
|
||||
return perigee.Delete(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) ShowKeyPair(name string) (KeyPair, error) {
|
||||
var kp KeyPair
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs/" + name
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ KeyPair *KeyPair }{&kp},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return kp, err
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
FingerPrint string `json:"fingerprint"`
|
||||
Name string `json:"name"`
|
||||
PrivateKey string `json:"private_key,omitempty"`
|
||||
PublicKey string `json:"public_key"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
type NewKeyPair struct {
|
||||
Name string `json:"name"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}
|
||||
64
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/auth.go
generated
vendored
Normal file
64
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/auth.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
nilOptions = gophercloud.AuthOptions{}
|
||||
|
||||
// ErrNoAuthUrl errors occur when the value of the OS_AUTH_URL environment variable cannot be determined.
|
||||
ErrNoAuthUrl = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
|
||||
|
||||
// ErrNoUsername errors occur when the value of the OS_USERNAME environment variable cannot be determined.
|
||||
ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME needs to be set.")
|
||||
|
||||
// ErrNoPassword errors occur when the value of the OS_PASSWORD environment variable cannot be determined.
|
||||
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD or OS_API_KEY needs to be set.")
|
||||
)
|
||||
|
||||
// AuthOptions fills out a gophercloud.AuthOptions structure with the settings found on the various OpenStack
|
||||
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
|
||||
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
|
||||
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
|
||||
//
|
||||
// The value of OS_AUTH_URL will be returned directly to the caller, for subsequent use in
|
||||
// gophercloud.Authenticate()'s Provider parameter. This function will not interpret the value of OS_AUTH_URL,
|
||||
// so as a convenient extention, you may set OS_AUTH_URL to, e.g., "rackspace-uk", or any other Gophercloud-recognized
|
||||
// provider shortcuts. For broad compatibility, especially with local installations, you should probably
|
||||
// avoid the temptation to do this.
|
||||
func AuthOptions() (string, gophercloud.AuthOptions, error) {
|
||||
provider := os.Getenv("OS_AUTH_URL")
|
||||
username := os.Getenv("OS_USERNAME")
|
||||
password := os.Getenv("OS_PASSWORD")
|
||||
tenantId := os.Getenv("OS_TENANT_ID")
|
||||
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||
|
||||
if provider == "" {
|
||||
return "", nilOptions, ErrNoAuthUrl
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return "", nilOptions, ErrNoUsername
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
return "", nilOptions, ErrNoPassword
|
||||
}
|
||||
|
||||
ao := gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
TenantId: tenantId,
|
||||
TenantName: tenantName,
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(provider, "/tokens") {
|
||||
provider += "/tokens"
|
||||
}
|
||||
|
||||
return provider, ao, nil
|
||||
}
|
||||
9
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/region.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/region.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package osutil
|
||||
|
||||
import "os"
|
||||
|
||||
// Region provides a means of querying the OS_REGION_NAME environment variable.
|
||||
// At present, you may also use os.Getenv("OS_REGION_NAME") as well.
|
||||
func Region() string {
|
||||
return os.Getenv("OS_REGION_NAME")
|
||||
}
|
||||
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/package.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/package.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// Gophercloud provides a multi-vendor interface to OpenStack-compatible clouds which attempts to follow
|
||||
// established Go community coding standards and social norms.
|
||||
//
|
||||
// Unless you intend on contributing code to the SDK, you will almost certainly never have to use any
|
||||
// Context structures or any of its methods. Contextual methods exist for easier unit testing only.
|
||||
// Stick with the global functions unless you know exactly what you're doing, and why.
|
||||
package gophercloud
|
||||
36
Godeps/_workspace/src/github.com/rackspace/gophercloud/reauth.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/rackspace/gophercloud/reauth.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// WithReauth wraps a Perigee request fragment with logic to perform re-authentication
|
||||
// if it's deemed necessary.
|
||||
//
|
||||
// Do not confuse this function with WithReauth()! Although they work together to support reauthentication,
|
||||
// WithReauth() actually contains the decision-making logic to determine when to perform a reauth,
|
||||
// while WithReauthHandler() is used to configure what a reauth actually entails.
|
||||
func (c *Context) WithReauth(ap AccessProvider, f func() error) error {
|
||||
err := f()
|
||||
cause, ok := err.(*perigee.UnexpectedResponseCodeError)
|
||||
if ok && cause.Actual == 401 {
|
||||
err = c.reauthHandler(ap)
|
||||
if err == nil {
|
||||
err = f()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// This is like WithReauth above but returns a perigee Response object
|
||||
func (c *Context) ResponseWithReauth(ap AccessProvider, f func() (*perigee.Response, error)) (*perigee.Response, error) {
|
||||
response, err := f()
|
||||
cause, ok := err.(*perigee.UnexpectedResponseCodeError)
|
||||
if ok && cause.Actual == 401 {
|
||||
err = c.reauthHandler(ap)
|
||||
if err == nil {
|
||||
response, err = f()
|
||||
}
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
133
Godeps/_workspace/src/github.com/rackspace/gophercloud/reauth_test.go
generated
vendored
Normal file
133
Godeps/_workspace/src/github.com/rackspace/gophercloud/reauth_test.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This reauth-handler does nothing, and returns no error.
|
||||
func doNothing(_ AccessProvider) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOtherErrorsPropegate(t *testing.T) {
|
||||
calls := 0
|
||||
c := TestContext().WithReauthHandler(doNothing)
|
||||
|
||||
err := c.WithReauth(nil, func() error {
|
||||
calls++
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 404,
|
||||
}
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected MyError to be returned; got nil instead.")
|
||||
return
|
||||
}
|
||||
if _, ok := err.(*perigee.UnexpectedResponseCodeError); !ok {
|
||||
t.Error("Expected UnexpectedResponseCodeError; got %#v", err)
|
||||
return
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Errorf("Expected the body to be invoked once; found %d calls instead", calls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Test401ErrorCausesBodyInvokation2ndTime(t *testing.T) {
|
||||
calls := 0
|
||||
c := TestContext().WithReauthHandler(doNothing)
|
||||
|
||||
err := c.WithReauth(nil, func() error {
|
||||
calls++
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected MyError to be returned; got nil instead.")
|
||||
return
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Errorf("Expected the body to be invoked once; found %d calls instead", calls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestReauthAttemptShouldHappen(t *testing.T) {
|
||||
calls := 0
|
||||
c := TestContext().WithReauthHandler(func(_ AccessProvider) error {
|
||||
calls++
|
||||
return nil
|
||||
})
|
||||
c.WithReauth(nil, func() error {
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
|
||||
if calls != 1 {
|
||||
t.Errorf("Expected Reauthenticator to be called once; found %d instead", calls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type MyError struct{}
|
||||
|
||||
func (*MyError) Error() string {
|
||||
return "MyError instance"
|
||||
}
|
||||
|
||||
func TestReauthErrorShouldPropegate(t *testing.T) {
|
||||
c := TestContext().WithReauthHandler(func(_ AccessProvider) error {
|
||||
return &MyError{}
|
||||
})
|
||||
|
||||
err := c.WithReauth(nil, func() error {
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
|
||||
if _, ok := err.(*MyError); !ok {
|
||||
t.Errorf("Expected a MyError; got %#v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type MyAccess struct{}
|
||||
|
||||
func (my *MyAccess) FirstEndpointUrlByCriteria(ApiCriteria) string {
|
||||
return ""
|
||||
}
|
||||
func (my *MyAccess) AuthToken() string {
|
||||
return ""
|
||||
}
|
||||
func (my *MyAccess) Revoke(string) error {
|
||||
return nil
|
||||
}
|
||||
func (my *MyAccess) Reauthenticate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestReauthHandlerUsesSameAccessProvider(t *testing.T) {
|
||||
fakeAccess := &MyAccess{}
|
||||
c := TestContext().WithReauthHandler(func(acc AccessProvider) error {
|
||||
if acc != fakeAccess {
|
||||
t.Errorf("Expected acc = fakeAccess")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.WithReauth(fakeAccess, func() error {
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
}
|
||||
26
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/create-environment.sh
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/create-environment.sh
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script helps new contributors set up their local workstation for
|
||||
# gophercloud development and contributions.
|
||||
|
||||
# Create the environment
|
||||
export GOPATH=$HOME/go/gophercloud
|
||||
mkdir -p $GOPATH
|
||||
|
||||
# Download gophercloud into that environment
|
||||
go get github.com/rackspace/gophercloud
|
||||
cd $GOPATH/src/github.com/rackspace/gophercloud
|
||||
git checkout master
|
||||
|
||||
# Write out the env.sh convenience file.
|
||||
cd $GOPATH
|
||||
cat <<EOF >env.sh
|
||||
#!/bin/bash
|
||||
export GOPATH=$(pwd)
|
||||
export GOPHERCLOUD=$GOPATH/src/github.com/rackspace/gophercloud
|
||||
EOF
|
||||
chmod a+x env.sh
|
||||
|
||||
# Make changes immediately available as a convenience.
|
||||
. ./env.sh
|
||||
|
||||
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/test-all.sh
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/test-all.sh
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script is responsible for executing all the acceptance tests found in
|
||||
# the acceptance/ directory.
|
||||
|
||||
# Find where _this_ script is running from.
|
||||
SCRIPTS=$(dirname $0)
|
||||
SCRIPTS=$(cd $SCRIPTS; pwd)
|
||||
|
||||
# Locate the acceptance test / examples directory.
|
||||
ACCEPTANCE=$(cd $SCRIPTS/../acceptance; pwd)
|
||||
|
||||
# Go workspace path
|
||||
WS=$(cd $SCRIPTS/..; pwd)
|
||||
|
||||
# In order to run Go code interactively, we need the GOPATH environment
|
||||
# to be set.
|
||||
if [ "x$GOPATH" == "x" ]; then
|
||||
export GOPATH=$WS
|
||||
echo "WARNING: You didn't have your GOPATH environment variable set."
|
||||
echo " I'm assuming $GOPATH as its value."
|
||||
fi
|
||||
|
||||
# Run all acceptance tests sequentially.
|
||||
# If any test fails, we fail fast.
|
||||
LIBS=$(ls $ACCEPTANCE/lib*.go)
|
||||
for T in $(ls -1 $ACCEPTANCE/[0-9][0-9]*.go); do
|
||||
if ! [ -x $T ]; then
|
||||
CMD="go run $T $LIBS -quiet"
|
||||
echo "$CMD ..."
|
||||
if ! $CMD ; then
|
||||
echo "- FAILED. Try re-running w/out the -quiet option to see output."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
807
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers.go
generated
vendored
Normal file
807
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers.go
generated
vendored
Normal file
@@ -0,0 +1,807 @@
|
||||
// TODO(sfalvo): Remove Rackspace-specific Server structure fields and refactor them into a provider-specific access method.
|
||||
// Be sure to update godocs accordingly.
|
||||
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// genericServersProvider structures provide the implementation for generic OpenStack-compatible
|
||||
// CloudServersProvider interfaces.
|
||||
type genericServersProvider struct {
|
||||
// endpoint refers to the provider's API endpoint base URL. This will be used to construct
|
||||
// and issue queries.
|
||||
endpoint string
|
||||
|
||||
// Test context (if any) in which to issue requests.
|
||||
context *Context
|
||||
|
||||
// access associates this API provider with a set of credentials,
|
||||
// which may be automatically renewed if they near expiration.
|
||||
access AccessProvider
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gcp *genericServersProvider) ListServersByFilter(filter url.Values) ([]Server, error) {
|
||||
var ss []Server
|
||||
|
||||
err := gcp.context.WithReauth(gcp.access, func() error {
|
||||
url := gcp.endpoint + "/servers/detail?" + filter.Encode()
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gcp.context.httpClient,
|
||||
Results: &struct{ Servers *[]Server }{&ss},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gcp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gcp *genericServersProvider) ListServersLinksOnly() ([]Server, error) {
|
||||
var ss []Server
|
||||
|
||||
err := gcp.context.WithReauth(gcp.access, func() error {
|
||||
url := gcp.endpoint + "/servers"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gcp.context.httpClient,
|
||||
Results: &struct{ Servers *[]Server }{&ss},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gcp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gcp *genericServersProvider) ListServers() ([]Server, error) {
|
||||
var ss []Server
|
||||
|
||||
err := gcp.context.WithReauth(gcp.access, func() error {
|
||||
url := gcp.endpoint + "/servers/detail"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gcp.context.httpClient,
|
||||
Results: &struct{ Servers *[]Server }{&ss},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gcp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// Compatibility with v0.0.x -- we "map" our public and private
|
||||
// addresses into a legacy structure field for the benefit of
|
||||
// earlier software.
|
||||
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
for _, s := range ss {
|
||||
err = mapstructure.Decode(s.RawAddresses, &s.Addresses)
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
}
|
||||
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ServerById(id string) (*Server, error) {
|
||||
var s *Server
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/servers/" + id
|
||||
return perigee.Get(url, perigee.Options{
|
||||
Results: &struct{ Server **Server }{&s},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
})
|
||||
|
||||
// Compatibility with v0.0.x -- we "map" our public and private
|
||||
// addresses into a legacy structure field for the benefit of
|
||||
// earlier software.
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
err = mapstructure.Decode(s.RawAddresses, &s.Addresses)
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateServer(ns NewServer) (*NewServer, error) {
|
||||
var s *NewServer
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := gsp.endpoint + "/servers"
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Server *NewServer `json:"server"`
|
||||
}{&ns},
|
||||
Results: &struct{ Server **NewServer }{&s},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) DeleteServerById(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/servers/" + id
|
||||
return perigee.Delete(url, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{204},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) SetAdminPassword(id, pw string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
ChangePassword struct {
|
||||
AdminPass string `json:"adminPass"`
|
||||
} `json:"changePassword"`
|
||||
}{
|
||||
struct {
|
||||
AdminPass string `json:"adminPass"`
|
||||
}{pw},
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ResizeServer(id, newName, newFlavor, newDiskConfig string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
rr := ResizeRequest{
|
||||
Name: newName,
|
||||
FlavorRef: newFlavor,
|
||||
DiskConfig: newDiskConfig,
|
||||
}
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Resize ResizeRequest `json:"resize"`
|
||||
}{rr},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) RevertResize(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
RevertResize *int `json:"revertResize"`
|
||||
}{nil},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ConfirmResize(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
ConfirmResize *int `json:"confirmResize"`
|
||||
}{nil},
|
||||
OkCodes: []int{204},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) RebootServer(id string, hard bool) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
types := map[bool]string{false: "SOFT", true: "HARD"}
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Reboot struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"reboot"`
|
||||
}{
|
||||
struct {
|
||||
Type string `json:"type"`
|
||||
}{types[hard]},
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) RescueServer(id string) (string, error) {
|
||||
var pw *string
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Rescue string `json:"rescue"`
|
||||
}{"none"},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
AdminPass **string `json:"adminPass"`
|
||||
}{&pw},
|
||||
})
|
||||
})
|
||||
return *pw, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) UnrescueServer(id string) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Unrescue *int `json:"unrescue"`
|
||||
}{nil},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) UpdateServer(id string, changes NewServerSettings) (*Server, error) {
|
||||
var svr *Server
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s", gsp.endpoint, id)
|
||||
return perigee.Put(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Server NewServerSettings `json:"server"`
|
||||
}{changes},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
Server **Server `json:"server"`
|
||||
}{&svr},
|
||||
})
|
||||
})
|
||||
return svr, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) RebuildServer(id string, ns NewServer) (*Server, error) {
|
||||
var s *Server
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Rebuild *NewServer `json:"rebuild"`
|
||||
}{&ns},
|
||||
Results: &struct{ Server **Server }{&s},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListAddresses(id string) (AddressSet, error) {
|
||||
var pas *AddressSet
|
||||
var statusCode int
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/ips", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
Results: &struct{ Addresses **AddressSet }{&pas},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200, 203},
|
||||
StatusCode: &statusCode,
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if statusCode == 203 {
|
||||
err = WarnUnauthoritative
|
||||
}
|
||||
}
|
||||
|
||||
return *pas, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListAddressesByNetwork(id, networkLabel string) (NetworkAddress, error) {
|
||||
pas := make(NetworkAddress)
|
||||
var statusCode int
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/ips/%s", gsp.endpoint, id, networkLabel)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
Results: &pas,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200, 203},
|
||||
StatusCode: &statusCode,
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if statusCode == 203 {
|
||||
err = WarnUnauthoritative
|
||||
}
|
||||
}
|
||||
|
||||
return pas, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateImage(id string, ci CreateImage) (string, error) {
|
||||
response, err := gsp.context.ResponseWithReauth(gsp.access, func() (*perigee.Response, error) {
|
||||
ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Request("POST", ep, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
CreateImage *CreateImage `json:"createImage"`
|
||||
}{&ci},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200, 202},
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
location, err := response.HttpResponse.Location()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return the last element of the location which is the image id
|
||||
locationArr := strings.Split(location.Path, "/")
|
||||
return locationArr[len(locationArr)-1], err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListSecurityGroups() ([]SecurityGroup, error) {
|
||||
var sgs []SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroups *[]SecurityGroup `json:"security_groups"`
|
||||
}{&sgs},
|
||||
})
|
||||
})
|
||||
return sgs, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error) {
|
||||
var actual *SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
ReqBody: struct {
|
||||
AddSecurityGroup SecurityGroup `json:"security_group"`
|
||||
}{desired},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroup **SecurityGroup `json:"security_group"`
|
||||
}{&actual},
|
||||
})
|
||||
})
|
||||
return actual, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error) {
|
||||
var sgs []SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/os-security-groups", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroups *[]SecurityGroup `json:"security_groups"`
|
||||
}{&sgs},
|
||||
})
|
||||
})
|
||||
return sgs, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) SecurityGroupById(id int) (*SecurityGroup, error) {
|
||||
var actual *SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroup **SecurityGroup `json:"security_group"`
|
||||
}{&actual},
|
||||
})
|
||||
})
|
||||
return actual, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) DeleteSecurityGroupById(id int) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id)
|
||||
return perigee.Delete(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListDefaultSGRules() ([]SGRule, error) {
|
||||
var sgrs []SGRule
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-group-default-rules", gsp.endpoint)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct{ Security_group_default_rules *[]SGRule }{&sgrs},
|
||||
})
|
||||
})
|
||||
return sgrs, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateDefaultSGRule(r SGRule) (*SGRule, error) {
|
||||
var sgr *SGRule
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-group-default-rules", gsp.endpoint)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct{ Security_group_default_rule **SGRule }{&sgr},
|
||||
ReqBody: struct {
|
||||
Security_group_default_rule SGRule `json:"security_group_default_rule"`
|
||||
}{r},
|
||||
})
|
||||
})
|
||||
return sgr, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) GetSGRule(id string) (*SGRule, error) {
|
||||
var sgr *SGRule
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-group-default-rules/%s", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct{ Security_group_default_rule **SGRule }{&sgr},
|
||||
})
|
||||
})
|
||||
return sgr, err
|
||||
}
|
||||
|
||||
// SecurityGroup provides a description of a security group, including all its rules.
|
||||
type SecurityGroup struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Id int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Rules []SGRule `json:"rules,omitempty"`
|
||||
TenantId string `json:"tenant_id,omitempty"`
|
||||
}
|
||||
|
||||
// SGRule encapsulates a single rule which applies to a security group.
|
||||
// This definition is just a guess, based on the documentation found in another extension here: http://docs.openstack.org/api/openstack-compute/2/content/GET_os-security-group-default-rules-v2_listSecGroupDefaultRules_v2__tenant_id__os-security-group-rules_ext-os-security-group-default-rules.html
|
||||
type SGRule struct {
|
||||
FromPort int `json:"from_port,omitempty"`
|
||||
Id int `json:"id,omitempty"`
|
||||
IpProtocol string `json:"ip_protocol,omitempty"`
|
||||
IpRange map[string]interface{} `json:"ip_range,omitempty"`
|
||||
ToPort int `json:"to_port,omitempty"`
|
||||
}
|
||||
|
||||
// RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
|
||||
type RaxBandwidth struct {
|
||||
AuditPeriodEnd string `json:"audit_period_end"`
|
||||
AuditPeriodStart string `json:"audit_period_start"`
|
||||
BandwidthInbound int64 `json:"bandwidth_inbound"`
|
||||
BandwidthOutbound int64 `json:"bandwidth_outbound"`
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
// A VersionedAddress denotes either an IPv4 or IPv6 (depending on version indicated)
|
||||
// address.
|
||||
type VersionedAddress struct {
|
||||
Addr string `json:"addr"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
// An AddressSet provides a set of public and private IP addresses for a resource.
|
||||
// Each address has a version to identify if IPv4 or IPv6.
|
||||
type AddressSet struct {
|
||||
Public []VersionedAddress `json:"public"`
|
||||
Private []VersionedAddress `json:"private"`
|
||||
}
|
||||
|
||||
type NetworkAddress map[string][]VersionedAddress
|
||||
|
||||
// Server records represent (virtual) hardware instances (not configurations) accessible by the user.
|
||||
//
|
||||
// The AccessIPv4 / AccessIPv6 fields provides IP addresses for the server in the IPv4 or IPv6 format, respectively.
|
||||
//
|
||||
// Addresses provides addresses for any attached isolated networks.
|
||||
// The version field indicates whether the IP address is version 4 or 6.
|
||||
// Note: only public and private pools appear here.
|
||||
// To get the complete set, use the AllAddressPools() method instead.
|
||||
//
|
||||
// Created tells when the server entity was created.
|
||||
//
|
||||
// The Flavor field includes the flavor ID and flavor links.
|
||||
//
|
||||
// The compute provisioning algorithm has an anti-affinity property that
|
||||
// attempts to spread customer VMs across hosts.
|
||||
// Under certain situations,
|
||||
// VMs from the same customer might be placed on the same host.
|
||||
// The HostId field represents the host your server runs on and
|
||||
// can be used to determine this scenario if it is relevant to your application.
|
||||
// Note that HostId is unique only per account; it is not globally unique.
|
||||
//
|
||||
// Id provides the server's unique identifier.
|
||||
// This field must be treated opaquely.
|
||||
//
|
||||
// Image indicates which image is installed on the server.
|
||||
//
|
||||
// Links provides one or more means of accessing the server.
|
||||
//
|
||||
// Metadata provides a small key-value store for application-specific information.
|
||||
//
|
||||
// Name provides a human-readable name for the server.
|
||||
//
|
||||
// Progress indicates how far along it is towards being provisioned.
|
||||
// 100 represents complete, while 0 represents just beginning.
|
||||
//
|
||||
// Status provides an indication of what the server's doing at the moment.
|
||||
// A server will be in ACTIVE state if it's ready for use.
|
||||
//
|
||||
// OsDcfDiskConfig indicates the server's boot volume configuration.
|
||||
// Valid values are:
|
||||
// AUTO
|
||||
// ----
|
||||
// The server is built with a single partition the size of the target flavor disk.
|
||||
// The file system is automatically adjusted to fit the entire partition.
|
||||
// This keeps things simple and automated.
|
||||
// AUTO is valid only for images and servers with a single partition that use the EXT3 file system.
|
||||
// This is the default setting for applicable Rackspace base images.
|
||||
//
|
||||
// MANUAL
|
||||
// ------
|
||||
// The server is built using whatever partition scheme and file system is in the source image.
|
||||
// If the target flavor disk is larger,
|
||||
// the remaining disk space is left unpartitioned.
|
||||
// This enables images to have non-EXT3 file systems, multiple partitions, and so on,
|
||||
// and enables you to manage the disk configuration.
|
||||
//
|
||||
// RaxBandwidth provides measures of the server's inbound and outbound bandwidth per interface.
|
||||
//
|
||||
// OsExtStsPowerState provides an indication of the server's power.
|
||||
// This field appears to be a set of flag bits:
|
||||
//
|
||||
// ... 4 3 2 1 0
|
||||
// +--//--+---+---+---+---+
|
||||
// | .... | 0 | S | 0 | I |
|
||||
// +--//--+---+---+---+---+
|
||||
// | |
|
||||
// | +--- 0=Instance is down.
|
||||
// | 1=Instance is up.
|
||||
// |
|
||||
// +----------- 0=Server is switched ON.
|
||||
// 1=Server is switched OFF.
|
||||
// (note reverse logic.)
|
||||
//
|
||||
// Unused bits should be ignored when read, and written as 0 for future compatibility.
|
||||
//
|
||||
// OsExtStsTaskState and OsExtStsVmState work together
|
||||
// to provide visibility in the provisioning process for the instance.
|
||||
// Consult Rackspace documentation at
|
||||
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ch_extensions.html#ext_status
|
||||
// for more details. It's too lengthy to include here.
|
||||
type Server struct {
|
||||
AccessIPv4 string `json:"accessIPv4"`
|
||||
AccessIPv6 string `json:"accessIPv6"`
|
||||
Addresses AddressSet
|
||||
Created string `json:"created"`
|
||||
Flavor FlavorLink `json:"flavor"`
|
||||
HostId string `json:"hostId"`
|
||||
Id string `json:"id"`
|
||||
Image ImageLink `json:"image"`
|
||||
Links []Link `json:"links"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
Name string `json:"name"`
|
||||
Progress int `json:"progress"`
|
||||
Status string `json:"status"`
|
||||
TenantId string `json:"tenant_id"`
|
||||
Updated string `json:"updated"`
|
||||
UserId string `json:"user_id"`
|
||||
OsDcfDiskConfig string `json:"OS-DCF:diskConfig"`
|
||||
RaxBandwidth []RaxBandwidth `json:"rax-bandwidth:bandwidth"`
|
||||
OsExtStsPowerState int `json:"OS-EXT-STS:power_state"`
|
||||
OsExtStsTaskState string `json:"OS-EXT-STS:task_state"`
|
||||
OsExtStsVmState string `json:"OS-EXT-STS:vm_state"`
|
||||
|
||||
RawAddresses map[string]interface{} `json:"addresses"`
|
||||
}
|
||||
|
||||
// AllAddressPools returns a complete set of address pools available on the server.
|
||||
// The name of each pool supported keys the map.
|
||||
// The value of the map contains the addresses provided in the corresponding pool.
|
||||
func (s *Server) AllAddressPools() (map[string][]VersionedAddress, error) {
|
||||
pools := make(map[string][]VersionedAddress, 0)
|
||||
for pool, subtree := range s.RawAddresses {
|
||||
addresses := make([]VersionedAddress, 0)
|
||||
err := mapstructure.Decode(subtree, &addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[pool] = addresses
|
||||
}
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
// NewServerSettings structures record those fields of the Server structure to change
|
||||
// when updating a server (see UpdateServer method).
|
||||
type NewServerSettings struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||
}
|
||||
|
||||
// NewServer structures are used for both requests and responses.
|
||||
// The fields discussed below are relevent for server-creation purposes.
|
||||
//
|
||||
// The Name field contains the desired name of the server.
|
||||
// Note that (at present) Rackspace permits more than one server with the same name;
|
||||
// however, software should not depend on this.
|
||||
// Not only will Rackspace support thank you, so will your own devops engineers.
|
||||
// A name is required.
|
||||
//
|
||||
// The ImageRef field contains the ID of the desired software image to place on the server.
|
||||
// This ID must be found in the image slice returned by the Images() function.
|
||||
// This field is required.
|
||||
//
|
||||
// The FlavorRef field contains the ID of the server configuration desired for deployment.
|
||||
// This ID must be found in the flavor slice returned by the Flavors() function.
|
||||
// This field is required.
|
||||
//
|
||||
// For OsDcfDiskConfig, refer to the Image or Server structure documentation.
|
||||
// This field defaults to "AUTO" if not explicitly provided.
|
||||
//
|
||||
// Metadata contains a small key/value association of arbitrary data.
|
||||
// Neither Rackspace nor OpenStack places significance on this field in any way.
|
||||
// This field defaults to an empty map if not provided.
|
||||
//
|
||||
// Personality specifies the contents of certain files in the server's filesystem.
|
||||
// The files and their contents are mapped through a slice of FileConfig structures.
|
||||
// If not provided, all filesystem entities retain their image-specific configuration.
|
||||
//
|
||||
// Networks specifies an affinity for the server's various networks and interfaces.
|
||||
// Networks are identified through UUIDs; see NetworkConfig structure documentation for more details.
|
||||
// If not provided, network affinity is determined automatically.
|
||||
//
|
||||
// The AdminPass field may be used to provide a root- or administrator-password
|
||||
// during the server provisioning process.
|
||||
// If not provided, a random password will be automatically generated and returned in this field.
|
||||
//
|
||||
// The following fields are intended to be used to communicate certain results about the server being provisioned.
|
||||
// When attempting to create a new server, these fields MUST not be provided.
|
||||
// They'll be filled in by the response received from the Rackspace APIs.
|
||||
//
|
||||
// The Id field contains the server's unique identifier.
|
||||
// The identifier's scope is best assumed to be bound by the user's account, unless other arrangements have been made with Rackspace.
|
||||
//
|
||||
// The SecurityGroup field allows the user to specify a security group at launch.
|
||||
//
|
||||
// Any Links provided are used to refer to the server specifically by URL.
|
||||
// These links are useful for making additional REST calls not explicitly supported by Gorax.
|
||||
type NewServer struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ImageRef string `json:"imageRef,omitempty"`
|
||||
FlavorRef string `json:"flavorRef,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Personality []FileConfig `json:"personality,omitempty"`
|
||||
Networks []NetworkConfig `json:"networks,omitempty"`
|
||||
AdminPass string `json:"adminPass,omitempty"`
|
||||
KeyPairName string `json:"key_name,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
OsDcfDiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
|
||||
SecurityGroup []map[string]interface{} `json:"security_groups,omitempty"`
|
||||
ConfigDrive bool `json:"config_drive"`
|
||||
UserData string `json:"user_data"`
|
||||
}
|
||||
|
||||
// ResizeRequest structures are used internally to encode to JSON the parameters required to resize a server instance.
|
||||
// Client applications will not use this structure (no API accepts an instance of this structure).
|
||||
// See the Region method ResizeServer() for more details on how to resize a server.
|
||||
type ResizeRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
FlavorRef string `json:"flavorRef"`
|
||||
DiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
|
||||
}
|
||||
|
||||
type CreateImage struct {
|
||||
Name string `json:"name"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers_test.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers_test.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testAccess struct {
|
||||
public, internal string
|
||||
calledFirstEndpointByCriteria int
|
||||
}
|
||||
|
||||
func (ta *testAccess) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
|
||||
ta.calledFirstEndpointByCriteria++
|
||||
urls := []string{ta.public, ta.internal}
|
||||
return urls[ac.UrlChoice]
|
||||
}
|
||||
|
||||
func (ta *testAccess) AuthToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ta *testAccess) Revoke(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ta *testAccess) Reauthenticate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGetServersApi(t *testing.T) {
|
||||
c := TestContext().UseCustomClient(&http.Client{Transport: newTransport().WithResponse("Hello")})
|
||||
|
||||
acc := &testAccess{
|
||||
public: "http://localhost:8080",
|
||||
internal: "http://localhost:8086",
|
||||
}
|
||||
|
||||
_, err := c.ServersApi(acc, ApiCriteria{
|
||||
Name: "cloudComputeOpenStack",
|
||||
Region: "dfw",
|
||||
VersionId: "2",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if acc.calledFirstEndpointByCriteria != 1 {
|
||||
t.Error("Expected FirstEndpointByCriteria to be called")
|
||||
return
|
||||
}
|
||||
}
|
||||
75
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ApiCriteria provides one or more criteria for the SDK to look for appropriate endpoints.
|
||||
// Fields left unspecified or otherwise set to their zero-values are assumed to not be
|
||||
// relevant, and do not participate in the endpoint search.
|
||||
//
|
||||
// Name specifies the desired service catalog entry name.
|
||||
// Type specifies the desired service catalog entry type.
|
||||
// Region specifies the desired endpoint region.
|
||||
// If unset, Gophercloud will try to use the region set in the
|
||||
// OS_REGION_NAME environment variable. If that's not set,
|
||||
// region comparison will not occur. If OS_REGION_NAME is set
|
||||
// and IgnoreEnvVars is also set, OS_REGION_NAME will be ignored.
|
||||
// VersionId specifies the desired version of the endpoint.
|
||||
// Note that this field is matched exactly, and is (at present)
|
||||
// opaque to Gophercloud. Thus, requesting a version 2
|
||||
// endpoint will _not_ match a version 3 endpoint.
|
||||
// The UrlChoice field inidicates whether or not gophercloud
|
||||
// should use the public or internal endpoint URL if a
|
||||
// candidate endpoint is found.
|
||||
// IgnoreEnvVars instructs Gophercloud to ignore helpful environment variables.
|
||||
type ApiCriteria struct {
|
||||
Name string
|
||||
Type string
|
||||
Region string
|
||||
VersionId string
|
||||
UrlChoice int
|
||||
IgnoreEnvVars bool
|
||||
}
|
||||
|
||||
// The choices available for UrlChoice. See the ApiCriteria structure for details.
|
||||
const (
|
||||
PublicURL = iota
|
||||
InternalURL
|
||||
)
|
||||
|
||||
// Given a set of criteria to match on, locate the first candidate endpoint
|
||||
// in the provided service catalog.
|
||||
//
|
||||
// If nothing found, the result will be a zero-valued EntryEndpoint (all URLs
|
||||
// set to "").
|
||||
func FindFirstEndpointByCriteria(entries []CatalogEntry, ac ApiCriteria) EntryEndpoint {
|
||||
rgn := strings.ToUpper(ac.Region)
|
||||
if (rgn == "") && !ac.IgnoreEnvVars {
|
||||
rgn = os.Getenv("OS_REGION_NAME")
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if (ac.Name != "") && (ac.Name != entry.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (ac.Type != "") && (ac.Type != entry.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, endpoint := range entry.Endpoints {
|
||||
if (rgn != "") && (rgn != strings.ToUpper(endpoint.Region)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (ac.VersionId != "") && (ac.VersionId != endpoint.VersionId) {
|
||||
continue
|
||||
}
|
||||
|
||||
return endpoint
|
||||
}
|
||||
}
|
||||
return EntryEndpoint{}
|
||||
}
|
||||
190
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog_test.go
generated
vendored
Normal file
190
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog_test.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFFEBCViaEnvVariable exercises only those calls where a region
|
||||
// parameter is required, but is provided by an environment variable.
|
||||
func TestFFEBCViaEnvVariable(t *testing.T) {
|
||||
changeRegion("RGN")
|
||||
|
||||
endpoint := FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Name: "test"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("If provided, the Region qualifier must exclude endpoints with missing or mismatching regions.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Regions are case insensitive.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Missing version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "3"),
|
||||
ApiCriteria{Name: "test", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Mismatched version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "2"),
|
||||
ApiCriteria{Name: "test", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("All search criteria met; endpoint expected.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestFFEBCViaRegionOption exercises only those calls where a region
|
||||
// parameter is specified explicitly. The region option overrides
|
||||
// any defined OS_REGION_NAME environment setting.
|
||||
func TestFFEBCViaRegionOption(t *testing.T) {
|
||||
changeRegion("Starfleet Command")
|
||||
|
||||
endpoint := FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Name: "test", Region: "RGN"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("If provided, the Region qualifier must exclude endpoints with missing or mismatching regions.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test", Region: "RGN"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Regions are case insensitive.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test", Region: "RGN", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Missing version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "3"),
|
||||
ApiCriteria{Name: "test", Region: "RGN", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Mismatched version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "2"),
|
||||
ApiCriteria{Name: "test", Region: "RGN", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("All search criteria met; endpoint expected.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestFFEBCWithoutRegion exercises only those calls where a region
|
||||
// is irrelevant. Just to make sure, though, we enforce Gophercloud
|
||||
// from paying any attention to OS_REGION_NAME if it happens to be set.
|
||||
func TestFindFirstEndpointByCriteria(t *testing.T) {
|
||||
endpoint := FindFirstEndpointByCriteria([]CatalogEntry{}, ApiCriteria{Name: "test", IgnoreEnvVars: true})
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Not expecting to find anything in an empty service catalog.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
[]CatalogEntry{
|
||||
{Name: "test"},
|
||||
},
|
||||
ApiCriteria{Name: "test", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Even though we have a matching entry, no endpoints exist")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Name: "test", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Looking for an endpoint by name but without region or version ID should match first entry endpoint.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Type: "compute", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Looking for an endpoint by type but without region or version ID should match first entry endpoint.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Type: "identity", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Returned mismatched type.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "ord", "2"),
|
||||
ApiCriteria{Name: "test", VersionId: "2", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Sometimes, you might not care what region your stuff is in.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func catalog(name, entry_type, url, region, version string) []CatalogEntry {
|
||||
return []CatalogEntry{
|
||||
{
|
||||
Name: name,
|
||||
Type: entry_type,
|
||||
Endpoints: []EntryEndpoint{
|
||||
{
|
||||
PublicURL: url,
|
||||
Region: region,
|
||||
VersionId: version,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func changeRegion(r string) {
|
||||
err := os.Setenv("OS_REGION_NAME", r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
103
Godeps/_workspace/src/github.com/rackspace/gophercloud/transport_double_test.go
generated
vendored
Normal file
103
Godeps/_workspace/src/github.com/rackspace/gophercloud/transport_double_test.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type transport struct {
|
||||
called int
|
||||
response string
|
||||
expectTenantId bool
|
||||
tenantIdFound bool
|
||||
status int
|
||||
}
|
||||
|
||||
func (t *transport) RoundTrip(req *http.Request) (rsp *http.Response, err error) {
|
||||
var authContainer *AuthContainer
|
||||
|
||||
t.called++
|
||||
|
||||
headers := make(http.Header)
|
||||
headers.Add("Content-Type", "application/xml; charset=UTF-8")
|
||||
|
||||
body := ioutil.NopCloser(strings.NewReader(t.response))
|
||||
|
||||
if t.status == 0 {
|
||||
t.status = 200
|
||||
}
|
||||
statusMsg := "OK"
|
||||
if (t.status < 200) || (299 < t.status) {
|
||||
statusMsg = "Error"
|
||||
}
|
||||
|
||||
rsp = &http.Response{
|
||||
Status: fmt.Sprintf("%d %s", t.status, statusMsg),
|
||||
StatusCode: t.status,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: headers,
|
||||
Body: body,
|
||||
ContentLength: -1,
|
||||
TransferEncoding: nil,
|
||||
Close: true,
|
||||
Trailer: nil,
|
||||
Request: req,
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(bytes, &authContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.tenantIdFound = (authContainer.Auth.TenantId != "")
|
||||
|
||||
if t.tenantIdFound != t.expectTenantId {
|
||||
rsp.Status = "500 Internal Server Error"
|
||||
rsp.StatusCode = 500
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newTransport() *transport {
|
||||
return &transport{}
|
||||
}
|
||||
|
||||
func (t *transport) IgnoreTenantId() *transport {
|
||||
t.expectTenantId = false
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) ExpectTenantId() *transport {
|
||||
t.expectTenantId = true
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) WithResponse(r string) *transport {
|
||||
t.response = r
|
||||
t.status = 200
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) WithError(code int) *transport {
|
||||
t.response = fmt.Sprintf("Error %d", code)
|
||||
t.status = code
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) VerifyCalls(test *testing.T, n int) error {
|
||||
if t.called != n {
|
||||
err := fmt.Errorf("Expected Transport to be called %d times; found %d instead", n, t.called)
|
||||
test.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user