Commit 2fc479d9 authored by NickrenREN's avatar NickrenREN

Remove cloud provider rackspace

parent 471b0beb
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["rackspace.go"],
deps = [
"//pkg/cloudprovider:go_default_library",
"//pkg/controller:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
"//vendor/github.com/rackspace/gophercloud/rackspace:go_default_library",
"//vendor/github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumes:go_default_library",
"//vendor/github.com/rackspace/gophercloud/rackspace/compute/v2/servers:go_default_library",
"//vendor/github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach:go_default_library",
"//vendor/gopkg.in/gcfg.v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["rackspace_test.go"],
library = ":go_default_library",
deps = ["//vendor/github.com/rackspace/gophercloud:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
Copyright 2014 The Kubernetes Authors.
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.
*/
package rackspace
import (
"os"
"strings"
"testing"
"time"
"github.com/rackspace/gophercloud"
)
func TestReadConfig(t *testing.T) {
_, err := readConfig(nil)
if err == nil {
t.Errorf("Should fail when no config is provided: %s", err)
}
cfg, err := readConfig(strings.NewReader(`
[Global]
auth-url = http://auth.url
username = user
[LoadBalancer]
create-monitor = yes
monitor-delay = 1m
monitor-timeout = 30s
monitor-max-retries = 3
`))
if err != nil {
t.Fatalf("Should succeed when a valid config is provided: %s", err)
}
if cfg.Global.AuthUrl != "http://auth.url" {
t.Errorf("incorrect authurl: %s", cfg.Global.AuthUrl)
}
if !cfg.LoadBalancer.CreateMonitor {
t.Errorf("incorrect lb.createmonitor: %t", cfg.LoadBalancer.CreateMonitor)
}
if cfg.LoadBalancer.MonitorDelay.Duration != 1*time.Minute {
t.Errorf("incorrect lb.monitordelay: %s", cfg.LoadBalancer.MonitorDelay)
}
if cfg.LoadBalancer.MonitorTimeout.Duration != 30*time.Second {
t.Errorf("incorrect lb.monitortimeout: %s", cfg.LoadBalancer.MonitorTimeout)
}
if cfg.LoadBalancer.MonitorMaxRetries != 3 {
t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries)
}
}
func TestToAuthOptions(t *testing.T) {
cfg := Config{}
cfg.Global.Username = "user"
// etc.
ao := cfg.toAuthOptions()
if !ao.AllowReauth {
t.Errorf("Will need to be able to reauthenticate")
}
if ao.Username != cfg.Global.Username {
t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username)
}
}
// This allows acceptance testing against an existing Rackspace
// install, using the standard OS_* Rackspace client environment
// variables.
// FIXME: it would be better to hermetically test against canned JSON
// requests/responses.
func configFromEnv() (cfg Config, ok bool) {
cfg.Global.AuthUrl = os.Getenv("OS_AUTH_URL")
cfg.Global.TenantId = os.Getenv("OS_TENANT_ID")
// Rax/nova _insists_ that we don't specify both tenant ID and name
if cfg.Global.TenantId == "" {
cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME")
}
cfg.Global.Username = os.Getenv("OS_USERNAME")
cfg.Global.Password = os.Getenv("OS_PASSWORD")
cfg.Global.ApiKey = os.Getenv("OS_API_KEY")
cfg.Global.Region = os.Getenv("OS_REGION_NAME")
cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID")
cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME")
ok = (cfg.Global.AuthUrl != "" &&
cfg.Global.Username != "" &&
(cfg.Global.Password != "" || cfg.Global.ApiKey != "") &&
(cfg.Global.TenantId != "" || cfg.Global.TenantName != "" ||
cfg.Global.DomainId != "" || cfg.Global.DomainName != ""))
return
}
func TestParseMetaData(t *testing.T) {
_, err := parseMetaData(strings.NewReader(""))
if err == nil {
t.Errorf("Should fail when invalid meta data is provided: %s", err)
}
id, err := parseMetaData(strings.NewReader(`
{
"UUID":"someuuid",
"name":"somename",
"project_id":"someprojectid"
}
`))
if err != nil {
t.Fatalf("Should succeed when valid meta data is provided: %s", err)
}
if id != "someuuid" {
t.Errorf("incorrect uuid: %s", id)
}
}
func TestNewRackspace(t *testing.T) {
cfg, ok := configFromEnv()
if !ok {
t.Skipf("No config found in environment")
}
_, err := newRackspace(cfg)
if err != nil {
t.Fatalf("Failed to construct/authenticate Rackspace: %s", err)
}
}
func TestZones(t *testing.T) {
os := Rackspace{
provider: &gophercloud.ProviderClient{
IdentityBase: "http://auth.url/",
},
region: "myRegion",
}
z, ok := os.Zones()
if !ok {
t.Fatalf("Zones() returned false")
}
zone, err := z.GetZone()
if err != nil {
t.Fatalf("GetZone() returned error: %s", err)
}
if zone.Region != "myRegion" {
t.Fatalf("GetZone() returned wrong region (%s)", zone.Region)
}
}
func TestInstanceIDFromProviderID(t *testing.T) {
testCases := []struct {
providerID string
instanceID string
fail bool
}{
{
providerID: ProviderName + "://7b9cf879-7146-417c-abfd-cb4272f0c935",
instanceID: "7b9cf879-7146-417c-abfd-cb4272f0c935",
fail: false,
},
{
providerID: "7b9cf879-7146-417c-abfd-cb4272f0c935",
instanceID: "",
fail: true,
},
{
providerID: "other-provider://7b9cf879-7146-417c-abfd-cb4272f0c935",
instanceID: "",
fail: true,
},
{
providerID: "",
instanceID: "",
fail: true,
},
}
for _, test := range testCases {
instanceID, err := instanceIDFromProviderID(test.providerID)
if (err != nil) != test.fail {
t.Errorf("%s yielded `err != nil` as %t. expected %t", test.providerID, (err != nil), test.fail)
}
if test.fail {
continue
}
if instanceID != test.instanceID {
t.Errorf("%s yielded %s. expected %s", test.providerID, instanceID, test.instanceID)
}
}
}
language: go
sudo: false
install:
- go get golang.org/x/crypto/ssh
- go get -v -tags 'fixtures acceptance' ./...
go:
- 1.4
- 1.5
- tip
env:
- COVERALLS_TOKEN=2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- go get github.com/pierrre/gotestcover
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
- $HOME/gopath/bin/gotestcover -v -tags=fixtures -coverprofile=cover.out ./...
after_success:
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"auth_options.go",
"auth_results.go",
"doc.go",
"endpoint_search.go",
"params.go",
"provider_client.go",
"results.go",
"service_client.go",
"util.go",
],
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/mitchellh/mapstructure:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack:all-srcs",
"//vendor/github.com/rackspace/gophercloud/pagination:all-srcs",
"//vendor/github.com/rackspace/gophercloud/rackspace:all-srcs",
"//vendor/github.com/rackspace/gophercloud/testhelper:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
Contributors
============
| Name | Email |
| ---- | ----- |
| Samuel A. Falvo II | <sam.falvo@rackspace.com>
| Glen Campbell | <glen.campbell@rackspace.com>
| Jesse Noller | <jesse.noller@rackspace.com>
| Jon Perritt | <jon.perritt@rackspace.com>
| Ash Wilson | <ash.wilson@rackspace.com>
| Jamie Hannaford | <jamie.hannaford@rackspace.com>
| Don Schenck | don.schenck@rackspace.com>
| Joe Topjian | <joe@topjian.net>
# Gophercloud: an OpenStack SDK for Go
[![Build Status](https://travis-ci.org/rackspace/gophercloud.svg?branch=master)](https://travis-ci.org/rackspace/gophercloud) [![Coverage Status](https://coveralls.io/repos/rackspace/gophercloud/badge.png)](https://coveralls.io/r/rackspace/gophercloud)
Gophercloud is a flexible SDK that allows you to consume and work with OpenStack
clouds in a simple and idiomatic way using golang. Many services are supported,
including Compute, Block Storage, Object Storage, Networking, and Identity.
Each service API is backed with getting started guides, code samples, reference
documentation, unit tests and acceptance tests.
## Useful links
* [Gophercloud homepage](http://gophercloud.io)
* [Reference documentation](http://godoc.org/github.com/rackspace/gophercloud)
* [Getting started guides](http://gophercloud.io/docs)
* [Effective Go](https://golang.org/doc/effective_go.html)
## How to install
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
is pointing to an appropriate directory where you want to install Gophercloud:
```bash
mkdir $HOME/go
export GOPATH=$HOME/go
```
To protect yourself against changes in your dependencies, we highly recommend choosing a
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
Gophercloud as a dependency like so:
```bash
go get github.com/rackspace/gophercloud
# Edit your code to import relevant packages from "github.com/rackspace/gophercloud"
godep save ./...
```
This will install all the source files you need into a `Godeps/_workspace` directory, which is
referenceable from your own source files when you use the `godep go` command.
## Getting started
### Credentials
Because you'll be hitting an API, you will need to retrieve your OpenStack
credentials and either store them as environment variables or in your local Go
files. The first method is recommended because it decouples credential
information from source code, allowing you to push the latter to your version
control system without any security risk.
You will need to retrieve the following:
* username
* password
* tenant name or tenant ID
* a valid Keystone identity URL
For users that have the OpenStack dashboard installed, there's a shortcut. If
you visit the `project/access_and_security` path in Horizon and click on the
"Download OpenStack RC File" button at the top right hand corner, you will
download a bash file that exports all of your access details to environment
variables. To execute the file, run `source admin-openrc.sh` and you will be
prompted for your password.
### Authentication
Once you have access to your credentials, you can begin plugging them into
Gophercloud. The next step is authentication, and this is handled by a base
"Provider" struct. To get one, you can either pass in your credentials
explicitly, or tell Gophercloud to use environment variables:
```go
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/utils"
)
// Option 1: Pass in the values yourself
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
// Option 2: Use a utility function to retrieve all your environment variables
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have the `opts` variable, you can pass it in and get back a
`ProviderClient` struct:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
The `ProviderClient` is the top-level client that all of your OpenStack services
derive from. The provider contains all of the authentication details that allow
your Go code to access the API - such as the base URL and token ID.
### Provision a server
Once we have a base Provider, we inject it as a dependency into each OpenStack
service. In order to work with the Compute API, we need a Compute service
client; which can be created like so:
```go
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
```
We then use this `client` for any Compute API operation we want. In our case,
we want to provision a new server - so we invoke the `Create` method and pass
in the flavor ID (hardware specification) and image ID (operating system) we're
interested in:
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
server, err := servers.Create(client, servers.CreateOpts{
Name: "My new server!",
FlavorRef: "flavor_id",
ImageRef: "image_id",
}).Extract()
```
If you are unsure about what images and flavors are, you can read our [Compute
Getting Started guide](http://gophercloud.io/docs/compute). The above code
sample creates a new server with the parameters, and embodies the new resource
in the `server` variable (a
[`servers.Server`](http://godoc.org/github.com/rackspace/gophercloud) struct).
### Next steps
Cool! You've handled authentication, got your `ProviderClient` and provisioned
a new server. You're now ready to use more OpenStack services.
* [Getting started with Compute](http://gophercloud.io/docs/compute)
* [Getting started with Object Storage](http://gophercloud.io/docs/object-storage)
* [Getting started with Networking](http://gophercloud.io/docs/networking)
* [Getting started with Block Storage](http://gophercloud.io/docs/block-storage)
* [Getting started with Identity](http://gophercloud.io/docs/identity)
## Contributing
Engaging the community and lowering barriers for contributors is something we
care a lot about. For this reason, we've taken the time to write a [contributing
guide](./CONTRIBUTING.md) for folks interested in getting involved in our project.
If you're not sure how you can get involved, feel free to submit an issue or
[contact us](https://developer.rackspace.com/support/). You don't need to be a
Go expert - all members of the community are welcome!
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues) or [contact us directly](https://developer.rackspace.com/support/).
# Upgrading to v1.0.0
With the arrival of this new major version increment, the unfortunate news is
that breaking changes have been introduced to existing services. The API
has been completely rewritten from the ground up to make the library more
extensible, maintainable and easy-to-use.
Below we've compiled upgrade instructions for the various services that
existed before. If you have a specific issue that is not addressed below,
please [submit an issue](/issues/new) or
[e-mail our support team](https://developer.rackspace.com/support/).
* [Authentication](#authentication)
* [Servers](#servers)
* [List servers](#list-servers)
* [Get server details](#get-server-details)
* [Create server](#create-server)
* [Resize server](#resize-server)
* [Reboot server](#reboot-server)
* [Update server](#update-server)
* [Rebuild server](#rebuild-server)
* [Change admin password](#change-admin-password)
* [Delete server](#delete-server)
* [Rescue server](#rescue-server)
* [Images and flavors](#images-and-flavors)
* [List images](#list-images)
* [List flavors](#list-flavors)
* [Create/delete image](#createdelete-image)
* [Other](#other)
* [List keypairs](#list-keypairs)
* [Create/delete keypair](#createdelete-keypair)
* [List IP addresses](#list-ip-addresses)
# Authentication
One of the major differences that this release introduces is the level of
sub-packaging to differentiate between services and providers. You now have
the option of authenticating with OpenStack and other providers (like Rackspace).
To authenticate with a vanilla OpenStack installation, you can either specify
your credentials like this:
```go
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
)
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
```
Or have them pulled in through environment variables, like this:
```go
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have your `AuthOptions` struct, you pass it in to get back a `Provider`,
like so:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
This provider is the top-level structure that all services are created from.
# Servers
Before you can interact with the Compute API, you need to retrieve a
`gophercloud.ServiceClient`. To do this:
```go
// Define your region, etc.
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client, err := openstack.NewComputeV2(provider, opts)
```
## List servers
All operations that involve API collections (servers, flavors, images) now use
the `pagination.Pager` interface. This interface represents paginated entities
that can be iterated over.
Once you have a Pager, you can then pass a callback function into its `EachPage`
method, and this will allow you to traverse over the collection and execute
arbitrary functionality. So, an example with list servers:
```go
import (
"fmt"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
// We have the option of filtering the server list. If we want the full
// collection, leave it as an empty struct or nil
opts := servers.ListOpts{Name: "server_1"}
// Retrieve a pager (i.e. a paginated collection)
pager := servers.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
serverList, err := servers.ExtractServers(page)
// `s' will be a servers.Server struct
for _, s := range serverList {
fmt.Printf("We have a server. ID=%s, Name=%s", s.ID, s.Name)
}
})
```
## Get server details
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// Get the HTTP result
response := servers.Get(client, "server_id")
// Extract a Server struct from the response
server, err := response.Extract()
```
## Create server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// Define our options
opts := servers.CreateOpts{
Name: "new_server",
FlavorRef: "flavorID",
ImageRef: "imageID",
}
// Get our response
response := servers.Create(client, opts)
// Extract
server, err := response.Extract()
```
## Change admin password
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
result := servers.ChangeAdminPassword(client, "server_id", "newPassword_&123")
```
## Resize server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
result := servers.Resize(client, "server_id", "new_flavor_id")
```
## Reboot server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// You have a choice of two reboot methods: servers.SoftReboot or servers.HardReboot
result := servers.Reboot(client, "server_id", servers.SoftReboot)
```
## Update server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
opts := servers.UpdateOpts{Name: "new_name"}
server, err := servers.Update(client, "server_id", opts).Extract()
```
## Rebuild server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// You have the option of specifying additional options
opts := RebuildOpts{
Name: "new_name",
AdminPass: "admin_password",
ImageID: "image_id",
Metadata: map[string]string{"owner": "me"},
}
result := servers.Rebuild(client, "server_id", opts)
// You can extract a servers.Server struct from the HTTP response
server, err := result.Extract()
```
## Delete server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
response := servers.Delete(client, "server_id")
```
## Rescue server
The server rescue extension for Compute is not currently supported.
# Images and flavors
## List images
As with listing servers (see above), you first retrieve a Pager, and then pass
in a callback over each page:
```go
import (
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
)
// We have the option of filtering the image list. If we want the full
// collection, leave it as an empty struct
opts := images.ListOpts{ChangesSince: "2014-01-01T01:02:03Z", Name: "Ubuntu 12.04"}
// Retrieve a pager (i.e. a paginated collection)
pager := images.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
for _, i := range imageList {
// "i" will be an images.Image
}
})
```
## List flavors
```go
import (
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
)
// We have the option of filtering the flavor list. If we want the full
// collection, leave it as an empty struct
opts := flavors.ListOpts{ChangesSince: "2014-01-01T01:02:03Z", MinRAM: 4}
// Retrieve a pager (i.e. a paginated collection)
pager := flavors.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := networks.ExtractFlavors(page)
for _, f := range flavorList {
// "f" will be a flavors.Flavor
}
})
```
## Create/delete image
Image management has been shifted to Glance, but unfortunately this service is
not supported as of yet. You can, however, list Compute images like so:
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/images"
// Retrieve a pager (i.e. a paginated collection)
pager := images.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
for _, i := range imageList {
// "i" will be an images.Image
}
})
```
# Other
## List keypairs
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
// Retrieve a pager (i.e. a paginated collection)
pager := keypairs.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
keyList, err := keypairs.ExtractKeyPairs(page)
for _, k := range keyList {
// "k" will be a keypairs.KeyPair
}
})
```
## Create/delete keypairs
To create a new keypair, you need to specify its name and, optionally, a
pregenerated OpenSSH-formatted public key.
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
opts := keypairs.CreateOpts{
Name: "new_key",
PublicKey: "...",
}
response := keypairs.Create(client, opts)
key, err := response.Extract()
```
To delete an existing keypair:
```go
response := keypairs.Delete(client, "keypair_id")
```
## List IP addresses
This operation is not currently supported.
package gophercloud
/*
AuthOptions stores information needed to authenticate to an OpenStack cluster.
You can populate one manually, or use a provider's AuthOptionsFromEnv() function
to read relevant information from the standard environment variables. Pass one
to a provider's AuthenticatedClient function to authenticate and obtain a
ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and
provider.
*/
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
IdentityEndpoint string
// Username is required if using Identity V2 API. Consult with your provider's
// control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed.
Username, UserID string
// Exactly one of Password or APIKey is required for the Identity V2 and V3
// APIs. Consult with your provider's control panel to discover your account's
// preferred method of authentication.
Password, APIKey string
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID, DomainName string
// The TenantID and TenantName fields are optional for the Identity V2 API.
// Some providers allow you to specify a TenantName instead of the TenantId.
// Some require both. Your provider's authentication policies will determine
// how these fields influence authentication.
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.
//
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
AllowReauth bool
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string
}
package gophercloud
import "time"
// AuthResults [deprecated] is a leftover type from the v0.x days. It was
// intended to describe common functionality among identity service results, but
// is not actually used anywhere.
type AuthResults interface {
// TokenID returns the token's ID value from the authentication response.
TokenID() (string, error)
// ExpiresAt retrieves the token's expiration time.
ExpiresAt() (time.Time, error)
}
/*
Package gophercloud provides a multi-vendor interface to OpenStack-compatible
clouds. The library has a three-level hierarchy: providers, services, and
resources.
Provider structs represent the service providers that offer and manage a
collection of services. Examples of providers include: OpenStack, Rackspace,
HP. These are defined like so:
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
provider, err := openstack.AuthenticatedClient(opts)
Service structs are specific to a provider and handle all of the logic and
operations for a particular OpenStack service. Examples of services include:
Compute, Object Storage, Block Storage. In order to define one, you need to
pass in the parent provider, like so:
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts)
Resource structs are the domain models that services make use of in order
to work with and represent the state of API resources:
server, err := servers.Get(client, "{serverId}").Extract()
Intermediate Result structs are returned for API operations, which allow
generic access to the HTTP headers, response body, and any errors associated
with the network transaction. To turn a result into a usable resource struct,
you must call the Extract method which is chained to the response, or an
Extract function from an applicable extension:
result := servers.Get(client, "{serverId}")
// Attempt to extract the disk configuration from the OS-DCF disk config
// extension:
config, err := diskconfig.ExtractGet(result)
All requests that enumerate a collection return a Pager struct that is used to
iterate through the results one page at a time. Use the EachPage method on that
Pager to handle each successive Page in a closure, then use the appropriate
extraction method from that request's package to interpret that Page as a slice
of results:
err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
s, err := servers.ExtractServers(page)
if err != nil {
return false, err
}
// Handle the []servers.Server slice.
// Return "false" or an error to prematurely stop fetching new pages.
return true, nil
})
This top-level package contains utility functions and data types that are used
throughout the provider and service packages. Of particular note for end users
are the AuthOptions and EndpointOpts structs.
*/
package gophercloud
package gophercloud
import "errors"
var (
// ErrServiceNotFound is returned when no service in a service catalog matches
// the provided EndpointOpts. This is generally returned by provider service
// factory methods like "NewComputeV2()" and can mean that a service is not
// enabled for your account.
ErrServiceNotFound = errors.New("No suitable service could be found in the service catalog.")
// ErrEndpointNotFound is returned when no available endpoints match the
// provided EndpointOpts. This is also generally returned by provider service
// factory methods, and usually indicates that a region was specified
// incorrectly.
ErrEndpointNotFound = errors.New("No suitable endpoint could be found in the service catalog.")
)
// Availability indicates to whom a specific service endpoint is accessible:
// the internet at large, internal networks only, or only to administrators.
// Different identity services use different terminology for these. Identity v2
// lists them as different kinds of URLs within the service catalog ("adminURL",
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
// endpoint's response.
type Availability string
const (
// AvailabilityAdmin indicates that an endpoint is only available to
// administrators.
AvailabilityAdmin Availability = "admin"
// AvailabilityPublic indicates that an endpoint is available to everyone on
// the internet.
AvailabilityPublic Availability = "public"
// AvailabilityInternal indicates that an endpoint is only available within
// the cluster's internal network.
AvailabilityInternal Availability = "internal"
)
// EndpointOpts specifies search criteria used by queries against an
// OpenStack service catalog. The options must contain enough information to
// unambiguously identify one, and only one, endpoint within the catalog.
//
// Usually, these are passed to service client factory functions in a provider
// package, like "rackspace.NewComputeV2()".
type EndpointOpts struct {
// Type [required] is the service type for the client (e.g., "compute",
// "object-store"). Generally, this will be supplied by the service client
// function, but a user-given value will be honored if provided.
Type string
// Name [optional] is the service name for the client (e.g., "nova") as it
// appears in the service catalog. Services can have the same Type but a
// different Name, which is why both Type and Name are sometimes needed.
Name string
// Region [required] is the geographic region in which the endpoint resides,
// generally specifying which datacenter should house your resources.
// Required only for services that span multiple regions.
Region string
// Availability [optional] is the visibility of the endpoint to be returned.
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
// or AvailabilityAdmin from this package.
//
// Availability is not required, and defaults to AvailabilityPublic. Not all
// providers or services offer all Availability options.
Availability Availability
}
/*
EndpointLocator is an internal function to be used by provider implementations.
It provides an implementation that locates a single endpoint from a service
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
provider then uses it to discover related ServiceClients.
*/
type EndpointLocator func(EndpointOpts) (string, error)
// ApplyDefaults is an internal method to be used by provider implementations.
//
// It sets EndpointOpts fields if not already set, including a default type.
// Currently, EndpointOpts.Availability defaults to the public endpoint.
func (eo *EndpointOpts) ApplyDefaults(t string) {
if eo.Type == "" {
eo.Type = t
}
if eo.Availability == "" {
eo.Availability = AvailabilityPublic
}
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"auth_env.go",
"client.go",
"endpoint_location.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/utils:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/diskconfig:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/flavors:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/images:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens:all-srcs",
"//vendor/github.com/rackspace/gophercloud/openstack/utils:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package openstack
import (
"fmt"
"os"
"github.com/rackspace/gophercloud"
)
var nilOptions = gophercloud.AuthOptions{}
// ErrNoAuthUrl, ErrNoUsername, and ErrNoPassword errors indicate of the required OS_AUTH_URL, OS_USERNAME, or OS_PASSWORD
// environment variables, respectively, remain undefined. See the AuthOptions() function for more details.
var (
ErrNoAuthURL = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME, OS_USERID, or OS_TOKEN needs to be set.")
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD or OS_TOKEN needs to be set.")
)
// AuthOptionsFromEnv fills out an AuthOptions structure from the environment
// variables: OS_AUTH_URL, OS_USERNAME, OS_USERID, OS_PASSWORD, OS_TENANT_ID,
// OS_TENANT_NAME, OS_DOMAIN_ID, OS_DOMAIN_NAME, OS_TOKEN. It checks that
// (1) OS_AUTH_URL is set, (2) OS_USERNAME, OS_USERID, or OS_TOKEN is set,
// (3) OS_PASSWORD or OS_TOKEN is set.
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
authURL := os.Getenv("OS_AUTH_URL")
username := os.Getenv("OS_USERNAME")
userID := os.Getenv("OS_USERID")
password := os.Getenv("OS_PASSWORD")
tenantID := os.Getenv("OS_TENANT_ID")
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
tokenID := os.Getenv("OS_TOKEN")
if authURL == "" {
return nilOptions, ErrNoAuthURL
}
if username == "" && userID == "" && tokenID == "" {
return nilOptions, ErrNoUsername
}
if password == "" && tokenID == "" {
return nilOptions, ErrNoPassword
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
TokenID: tokenID,
}
return ao, nil
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
"util.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package volumes provides information and interaction with volumes in the
// OpenStack Block Storage service. A volume is a detachable block storage
// device, akin to a USB hard drive. It can only be attached to one instance at
// a time.
package volumes
package volumes
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
// OPTIONAL
Availability string
// OPTIONAL
Description string
// OPTIONAL
Metadata map[string]string
// OPTIONAL
Name string
// REQUIRED
Size int
// OPTIONAL
SnapshotID, SourceVolID, ImageID string
// OPTIONAL
VolumeType string
}
// ToVolumeCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
v := make(map[string]interface{})
if opts.Size == 0 {
return nil, fmt.Errorf("Required CreateOpts field 'Size' not set.")
}
v["size"] = opts.Size
if opts.Availability != "" {
v["availability_zone"] = opts.Availability
}
if opts.Description != "" {
v["display_description"] = opts.Description
}
if opts.ImageID != "" {
v["imageRef"] = opts.ImageID
}
if opts.Metadata != nil {
v["metadata"] = opts.Metadata
}
if opts.Name != "" {
v["display_name"] = opts.Name
}
if opts.SourceVolID != "" {
v["source_volid"] = opts.SourceVolID
}
if opts.SnapshotID != "" {
v["snapshot_id"] = opts.SnapshotID
}
if opts.VolumeType != "" {
v["volume_type"] = opts.VolumeType
}
return map[string]interface{}{"volume": v}, nil
}
// Create will create a new Volume based on the values in CreateOpts. To extract
// the Volume object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToVolumeCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return res
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = client.Delete(deleteURL(client, id), nil)
return res
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
return res
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToVolumeListQuery() (string, error)
}
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
// admin-only option. Set it to true to see all tenant volumes.
AllTenants bool `q:"all_tenants"`
// List only volumes that contain Metadata.
Metadata map[string]string `q:"metadata"`
// List only volumes that have Name as the display name.
Name string `q:"name"`
// List only volumes that have a status of Status.
Status string `q:"status"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToVolumeListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPage := func(r pagination.PageResult) pagination.Page {
return ListResult{pagination.SinglePageBase(r)}
}
return pagination.NewPager(client, url, createPage)
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
// OPTIONAL
Name string
// OPTIONAL
Description string
// OPTIONAL
Metadata map[string]string
}
// ToVolumeUpdateMap assembles a request body based on the contents of an
// UpdateOpts.
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
v := make(map[string]interface{})
if opts.Description != "" {
v["display_description"] = opts.Description
}
if opts.Metadata != nil {
v["metadata"] = opts.Metadata
}
if opts.Name != "" {
v["display_name"] = opts.Name
}
return map[string]interface{}{"volume": v}, nil
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToVolumeUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Put(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
volumeCount := 0
volumeID := ""
if name == "" {
return "", fmt.Errorf("A volume name must be provided.")
}
pager := List(client, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
volumeList, err := ExtractVolumes(page)
if err != nil {
return false, err
}
for _, s := range volumeList {
if s.Name == name {
volumeCount++
volumeID = s.ID
}
}
return true, nil
})
switch volumeCount {
case 0:
return "", fmt.Errorf("Unable to find volume: %s", name)
case 1:
return volumeID, nil
default:
return "", fmt.Errorf("Found %d volumes matching %s", volumeCount, name)
}
}
package volumes
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Volume contains all the information associated with an OpenStack Volume.
type Volume struct {
// Current status of the volume.
Status string `mapstructure:"status"`
// Human-readable display name for the volume.
Name string `mapstructure:"display_name"`
// Instances onto which the volume is attached.
Attachments []map[string]interface{} `mapstructure:"attachments"`
// This parameter is no longer used.
AvailabilityZone string `mapstructure:"availability_zone"`
// Indicates whether this is a bootable volume.
Bootable string `mapstructure:"bootable"`
// The date when this volume was created.
CreatedAt string `mapstructure:"created_at"`
// Human-readable description for the volume.
Description string `mapstructure:"display_description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `mapstructure:"volume_type"`
// The ID of the snapshot from which the volume was created
SnapshotID string `mapstructure:"snapshot_id"`
// The ID of another block storage volume from which the current volume was created
SourceVolID string `mapstructure:"source_volid"`
// Arbitrary key-value pairs defined by the user.
Metadata map[string]string `mapstructure:"metadata"`
// Unique identifier for the volume.
ID string `mapstructure:"id"`
// Size of the volume in GB.
Size int `mapstructure:"size"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// ListResult is a pagination.pager that is returned from a call to the List function.
type ListResult struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a ListResult contains no Volumes.
func (r ListResult) IsEmpty() (bool, error) {
volumes, err := ExtractVolumes(r)
if err != nil {
return true, err
}
return len(volumes) == 0, nil
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(page pagination.Page) ([]Volume, error) {
var response struct {
Volumes []Volume `json:"volumes"`
}
err := mapstructure.Decode(page.(ListResult).Body, &response)
return response.Volumes, err
}
// UpdateResult contains the response body and error from an Update request.
type UpdateResult struct {
commonResult
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
Volume *Volume `json:"volume"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Volume, err
}
package volumes
import "github.com/rackspace/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("volumes")
}
func listURL(c *gophercloud.ServiceClient) string {
return createURL(c)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("volumes", id)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}
package volumes
import (
"github.com/rackspace/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package bootfromvolume
import (
"errors"
"strconv"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
// SourceType represents the type of medium being used to create the volume.
type SourceType string
const (
Volume SourceType = "volume"
Snapshot SourceType = "snapshot"
Image SourceType = "image"
Blank SourceType = "blank"
)
// BlockDevice is a structure with options for booting a server instance
// from a volume. The volume may be created from an image, snapshot, or another
// volume.
type BlockDevice struct {
// BootIndex [optional] is the boot index. It defaults to 0.
BootIndex int `json:"boot_index"`
// DeleteOnTermination [optional] specifies whether or not to delete the attached volume
// when the server is deleted. Defaults to `false`.
DeleteOnTermination bool `json:"delete_on_termination"`
// DestinationType [optional] is the type that gets created. Possible values are "volume"
// and "local".
DestinationType string `json:"destination_type"`
// GuestFormat [optional] specifies the format of the block device.
GuestFormat string `json:"guest_format"`
// SourceType [required] must be one of: "volume", "snapshot", "image".
SourceType SourceType `json:"source_type"`
// UUID [required] is the unique identifier for the volume, snapshot, or image (see above)
UUID string `json:"uuid"`
// VolumeSize [optional] is the size of the volume to create (in gigabytes).
VolumeSize int `json:"volume_size"`
}
// CreateOptsExt is a structure that extends the server `CreateOpts` structure
// by allowing for a block device mapping.
type CreateOptsExt struct {
servers.CreateOptsBuilder
BlockDevice []BlockDevice `json:"block_device_mapping_v2,omitempty"`
}
// ToServerCreateMap adds the block device mapping option to the base server
// creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
return nil, err
}
if len(opts.BlockDevice) == 0 {
return nil, errors.New("Required fields UUID and SourceType not set.")
}
serverMap := base["server"].(map[string]interface{})
blockDevice := make([]map[string]interface{}, len(opts.BlockDevice))
for i, bd := range opts.BlockDevice {
if string(bd.SourceType) == "" {
return nil, errors.New("SourceType must be one of: volume, image, snapshot.")
}
blockDevice[i] = make(map[string]interface{})
blockDevice[i]["source_type"] = bd.SourceType
blockDevice[i]["boot_index"] = strconv.Itoa(bd.BootIndex)
blockDevice[i]["delete_on_termination"] = strconv.FormatBool(bd.DeleteOnTermination)
if bd.VolumeSize > 0 {
blockDevice[i]["volume_size"] = strconv.Itoa(bd.VolumeSize)
}
if bd.UUID != "" {
blockDevice[i]["uuid"] = bd.UUID
}
if bd.DestinationType != "" {
blockDevice[i]["destination_type"] = bd.DestinationType
}
if bd.GuestFormat != "" {
blockDevice[i]["guest_format"] = bd.GuestFormat
}
}
serverMap["block_device_mapping_v2"] = blockDevice
return base, nil
}
// Create requests the creation of a server from the given block device mapping.
func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) servers.CreateResult {
var res servers.CreateResult
reqBody, err := opts.ToServerCreateMap()
if err != nil {
res.Err = err
return res
}
// Delete imageName and flavorName that come from ToServerCreateMap().
// As of Liberty, Boot From Volume is failing if they are passed.
delete(reqBody["server"].(map[string]interface{}), "imageName")
delete(reqBody["server"].(map[string]interface{}), "flavorName")
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return res
}
package bootfromvolume
import (
os "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
// CreateResult temporarily contains the response from a Create call.
type CreateResult struct {
os.CreateResult
}
package bootfromvolume
import "github.com/rackspace/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-volumes_boot")
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package diskconfig provides information and interaction with the Disk
// Config extension that works with the OpenStack Compute service.
package diskconfig
package diskconfig
import (
"errors"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
// DiskConfig represents one of the two possible settings for the DiskConfig option when creating,
// rebuilding, or resizing servers: Auto or Manual.
type DiskConfig string
const (
// Auto builds a server with a single partition the size of the target flavor disk and
// automatically adjusts the filesystem to fit the entire partition. Auto may only be used with
// images and servers that use a single EXT3 partition.
Auto DiskConfig = "AUTO"
// Manual builds a server using whatever partition scheme and filesystem are present in the source
// image. If the target flavor disk is larger, the remaining space is left unpartitioned. This
// enables images to have non-EXT3 filesystems, multiple partitions, and so on, and enables you
// to manage the disk configuration. It also results in slightly shorter boot times.
Manual DiskConfig = "MANUAL"
)
// ErrInvalidDiskConfig is returned if an invalid string is specified for a DiskConfig option.
var ErrInvalidDiskConfig = errors.New("DiskConfig must be either diskconfig.Auto or diskconfig.Manual.")
// Validate ensures that a DiskConfig contains an appropriate value.
func (config DiskConfig) validate() error {
switch config {
case Auto, Manual:
return nil
default:
return ErrInvalidDiskConfig
}
}
// CreateOptsExt adds a DiskConfig option to the base CreateOpts.
type CreateOptsExt struct {
servers.CreateOptsBuilder
// DiskConfig [optional] controls how the created server's disk is partitioned.
DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"`
}
// ToServerCreateMap adds the diskconfig option to the base server creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
return nil, err
}
if string(opts.DiskConfig) == "" {
return base, nil
}
serverMap := base["server"].(map[string]interface{})
serverMap["OS-DCF:diskConfig"] = string(opts.DiskConfig)
return base, nil
}
// RebuildOptsExt adds a DiskConfig option to the base RebuildOpts.
type RebuildOptsExt struct {
servers.RebuildOptsBuilder
// DiskConfig [optional] controls how the rebuilt server's disk is partitioned.
DiskConfig DiskConfig
}
// ToServerRebuildMap adds the diskconfig option to the base server rebuild options.
func (opts RebuildOptsExt) ToServerRebuildMap() (map[string]interface{}, error) {
err := opts.DiskConfig.validate()
if err != nil {
return nil, err
}
base, err := opts.RebuildOptsBuilder.ToServerRebuildMap()
if err != nil {
return nil, err
}
serverMap := base["rebuild"].(map[string]interface{})
serverMap["OS-DCF:diskConfig"] = string(opts.DiskConfig)
return base, nil
}
// ResizeOptsExt adds a DiskConfig option to the base server resize options.
type ResizeOptsExt struct {
servers.ResizeOptsBuilder
// DiskConfig [optional] controls how the resized server's disk is partitioned.
DiskConfig DiskConfig
}
// ToServerResizeMap adds the diskconfig option to the base server creation options.
func (opts ResizeOptsExt) ToServerResizeMap() (map[string]interface{}, error) {
err := opts.DiskConfig.validate()
if err != nil {
return nil, err
}
base, err := opts.ResizeOptsBuilder.ToServerResizeMap()
if err != nil {
return nil, err
}
serverMap := base["resize"].(map[string]interface{})
serverMap["OS-DCF:diskConfig"] = string(opts.DiskConfig)
return base, nil
}
package diskconfig
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination"
)
func commonExtract(result gophercloud.Result) (*DiskConfig, error) {
var resp struct {
Server struct {
DiskConfig string `mapstructure:"OS-DCF:diskConfig"`
} `mapstructure:"server"`
}
err := mapstructure.Decode(result.Body, &resp)
if err != nil {
return nil, err
}
config := DiskConfig(resp.Server.DiskConfig)
return &config, nil
}
// ExtractGet returns the disk configuration from a servers.Get call.
func ExtractGet(result servers.GetResult) (*DiskConfig, error) {
return commonExtract(result.Result)
}
// ExtractUpdate returns the disk configuration from a servers.Update call.
func ExtractUpdate(result servers.UpdateResult) (*DiskConfig, error) {
return commonExtract(result.Result)
}
// ExtractRebuild returns the disk configuration from a servers.Rebuild call.
func ExtractRebuild(result servers.RebuildResult) (*DiskConfig, error) {
return commonExtract(result.Result)
}
// ExtractDiskConfig returns the DiskConfig setting for a specific server acquired from an
// servers.ExtractServers call, while iterating through a Pager.
func ExtractDiskConfig(page pagination.Page, index int) (*DiskConfig, error) {
casted := page.(servers.ServerPage).Body
type server struct {
DiskConfig string `mapstructure:"OS-DCF:diskConfig"`
}
var response struct {
Servers []server `mapstructure:"servers"`
}
err := mapstructure.Decode(casted, &response)
if err != nil {
return nil, err
}
config := DiskConfig(response.Servers[index].DiskConfig)
return &config, nil
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package volumeattach provides the ability to attach and detach volumes
// to instances
package volumeattach
package volumeattach
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a Pager that allows you to iterate over a collection of VolumeAttachments.
func List(client *gophercloud.ServiceClient, serverId string) pagination.Pager {
return pagination.NewPager(client, listURL(client, serverId), func(r pagination.PageResult) pagination.Page {
return VolumeAttachmentsPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
// CreateOpts struct in this package does.
type CreateOptsBuilder interface {
ToVolumeAttachmentCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies volume attachment creation or import parameters.
type CreateOpts struct {
// Device is the device that the volume will attach to the instance as. Omit for "auto"
Device string
// VolumeID is the ID of the volume to attach to the instance
VolumeID string
}
// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) {
if opts.VolumeID == "" {
return nil, errors.New("Missing field required for volume attachment creation: VolumeID")
}
volumeAttachment := make(map[string]interface{})
volumeAttachment["volumeId"] = opts.VolumeID
if opts.Device != "" {
volumeAttachment["device"] = opts.Device
}
return map[string]interface{}{"volumeAttachment": volumeAttachment}, nil
}
// Create requests the creation of a new volume attachment on the server
func Create(client *gophercloud.ServiceClient, serverId string, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToVolumeAttachmentCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(createURL(client, serverId), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// Get returns public data about a previously created VolumeAttachment.
func Get(client *gophercloud.ServiceClient, serverId, aId string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, serverId, aId), &res.Body, nil)
return res
}
// Delete requests the deletion of a previous stored VolumeAttachment from the server.
func Delete(client *gophercloud.ServiceClient, serverId, aId string) DeleteResult {
var res DeleteResult
_, res.Err = client.Delete(deleteURL(client, serverId, aId), nil)
return res
}
package volumeattach
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// VolumeAttach controls the attachment of a volume to an instance.
type VolumeAttachment struct {
// ID is a unique id of the attachment
ID string `mapstructure:"id"`
// Device is what device the volume is attached as
Device string `mapstructure:"device"`
// VolumeID is the ID of the attached volume
VolumeID string `mapstructure:"volumeId"`
// ServerID is the ID of the instance that has the volume attached
ServerID string `mapstructure:"serverId"`
}
// VolumeAttachmentsPage stores a single, only page of VolumeAttachments
// results from a List call.
type VolumeAttachmentsPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a VolumeAttachmentsPage is empty.
func (page VolumeAttachmentsPage) IsEmpty() (bool, error) {
va, err := ExtractVolumeAttachments(page)
return len(va) == 0, err
}
// ExtractVolumeAttachments interprets a page of results as a slice of
// VolumeAttachments.
func ExtractVolumeAttachments(page pagination.Page) ([]VolumeAttachment, error) {
casted := page.(VolumeAttachmentsPage).Body
var response struct {
VolumeAttachments []VolumeAttachment `mapstructure:"volumeAttachments"`
}
err := mapstructure.WeakDecode(casted, &response)
return response.VolumeAttachments, err
}
type VolumeAttachmentResult struct {
gophercloud.Result
}
// Extract is a method that attempts to interpret any VolumeAttachment resource
// response as a VolumeAttachment struct.
func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
VolumeAttachment *VolumeAttachment `json:"volumeAttachment" mapstructure:"volumeAttachment"`
}
err := mapstructure.Decode(r.Body, &res)
return res.VolumeAttachment, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a VolumeAttachment.
type CreateResult struct {
VolumeAttachmentResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a VolumeAttachment.
type GetResult struct {
VolumeAttachmentResult
}
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
package volumeattach
import "github.com/rackspace/gophercloud"
const resourcePath = "os-volume_attachments"
func resourceURL(c *gophercloud.ServiceClient, serverId string) string {
return c.ServiceURL("servers", serverId, resourcePath)
}
func listURL(c *gophercloud.ServiceClient, serverId string) string {
return resourceURL(c, serverId)
}
func createURL(c *gophercloud.ServiceClient, serverId string) string {
return resourceURL(c, serverId)
}
func getURL(c *gophercloud.ServiceClient, serverId, aId string) string {
return c.ServiceURL("servers", serverId, resourcePath, aId)
}
func deleteURL(c *gophercloud.ServiceClient, serverId, aId string) string {
return getURL(c, serverId, aId)
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package flavors provides information and interaction with the flavor API
// resource in the OpenStack Compute service.
//
// A flavor is an available hardware configuration for a server. Each flavor
// has a unique combination of disk space, memory capacity and priority for CPU
// time.
package flavors
package flavors
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToFlavorListQuery() (string, error)
}
// ListOpts helps control the results returned by the List() function.
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
type ListOpts struct {
// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
ChangesSince string `q:"changes-since"`
// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
MinDisk int `q:"minDisk"`
MinRAM int `q:"minRam"`
// Marker and Limit control paging.
// Marker instructs List where to start listing from.
Marker string `q:"marker"`
// Limit instructs List to refrain from sending excessively large lists of flavors.
Limit int `q:"limit"`
}
// ToFlavorListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToFlavorListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// ListDetail instructs OpenStack to provide a list of flavors.
// You may provide criteria by which List curtails its results for easier processing.
// See ListOpts for more details.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToFlavorListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPage := func(r pagination.PageResult) pagination.Page {
return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
}
return pagination.NewPager(client, url, createPage)
}
// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
// Use ExtractFlavor to convert its result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
return res
}
// IDFromName is a convienience function that returns a flavor's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
flavorCount := 0
flavorID := ""
if name == "" {
return "", fmt.Errorf("A flavor name must be provided.")
}
pager := ListDetail(client, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := ExtractFlavors(page)
if err != nil {
return false, err
}
for _, f := range flavorList {
if f.Name == name {
flavorCount++
flavorID = f.ID
}
}
return true, nil
})
switch flavorCount {
case 0:
return "", fmt.Errorf("Unable to find flavor: %s", name)
case 1:
return flavorID, nil
default:
return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, name)
}
}
package flavors
import (
"errors"
"reflect"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// ErrCannotInterpret is returned by an Extract call if the response body doesn't have the expected structure.
var ErrCannotInterpet = errors.New("Unable to interpret a response body.")
// GetResult temporarily holds the response from a Get call.
type GetResult struct {
gophercloud.Result
}
// Extract provides access to the individual Flavor returned by the Get function.
func (gr GetResult) Extract() (*Flavor, error) {
if gr.Err != nil {
return nil, gr.Err
}
var result struct {
Flavor Flavor `mapstructure:"flavor"`
}
cfg := &mapstructure.DecoderConfig{
DecodeHook: defaulter,
Result: &result,
}
decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
return nil, err
}
err = decoder.Decode(gr.Body)
return &result.Flavor, err
}
// Flavor records represent (virtual) hardware configurations for server resources in a region.
type Flavor struct {
// 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.
ID string `mapstructure:"id"`
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
Disk int `mapstructure:"disk"`
RAM int `mapstructure:"ram"`
// The Name field provides a human-readable moniker for the flavor.
Name string `mapstructure:"name"`
RxTxFactor float64 `mapstructure:"rxtx_factor"`
// Swap indicates how much space is reserved for swap.
// If not provided, this field will be set to 0.
Swap int `mapstructure:"swap"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `mapstructure:"vcpus"`
}
// FlavorPage contains a single page of the response from a List call.
type FlavorPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines if a page contains any results.
func (p FlavorPage) IsEmpty() (bool, error) {
flavors, err := ExtractFlavors(p)
if err != nil {
return true, err
}
return len(flavors) == 0, nil
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (p FlavorPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"flavors_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
}
func defaulter(from, to reflect.Kind, v interface{}) (interface{}, error) {
if (from == reflect.String) && (to == reflect.Int) {
return 0, nil
}
return v, nil
}
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
casted := page.(FlavorPage).Body
var container struct {
Flavors []Flavor `mapstructure:"flavors"`
}
cfg := &mapstructure.DecoderConfig{
DecodeHook: defaulter,
Result: &container,
}
decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
return container.Flavors, err
}
err = decoder.Decode(casted)
if err != nil {
return container.Flavors, err
}
return container.Flavors, nil
}
package flavors
import (
"github.com/rackspace/gophercloud"
)
func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id)
}
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("flavors", "detail")
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package images provides information and interaction with the image API
// resource in the OpenStack Compute service.
//
// An image is a collection of files used to create or rebuild a server.
// Operators provide a number of pre-built OS images by default. You may also
// create custom images from cloud servers you have launched.
package images
package images
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToImageListQuery() (string, error)
}
// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
type ListOpts struct {
// When the image last changed status (in date-time format).
ChangesSince string `q:"changes-since"`
// The number of Images to return.
Limit int `q:"limit"`
// UUID of the Image at which to set a marker.
Marker string `q:"marker"`
// The name of the Image.
Name string `q:"name"`
// The name of the Server (in URL format).
Server string `q:"server"`
// The current status of the Image.
Status string `q:"status"`
// The value of the type of image (e.g. BASE, SERVER, ALL)
Type string `q:"type"`
}
// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// ListDetail enumerates the available images.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listDetailURL(client)
if opts != nil {
query, err := opts.ToImageListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPage := func(r pagination.PageResult) pagination.Page {
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
}
return pagination.NewPager(client, url, createPage)
}
// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to interpret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var result GetResult
_, result.Err = client.Get(getURL(client, id), &result.Body, nil)
return result
}
// Delete deletes the specified image ID.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var result DeleteResult
_, result.Err = client.Delete(deleteURL(client, id), nil)
return result
}
// IDFromName is a convienience function that returns an image's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
imageCount := 0
imageID := ""
if name == "" {
return "", fmt.Errorf("An image name must be provided.")
}
pager := ListDetail(client, &ListOpts{
Name: name,
})
pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := ExtractImages(page)
if err != nil {
return false, err
}
for _, i := range imageList {
if i.Name == name {
imageCount++
imageID = i.ID
}
}
return true, nil
})
switch imageCount {
case 0:
return "", fmt.Errorf("Unable to find image: %s", name)
case 1:
return imageID, nil
default:
return "", fmt.Errorf("Found %d images matching %s", imageCount, name)
}
}
package images
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// GetResult temporarily stores a Get response.
type GetResult struct {
gophercloud.Result
}
// DeleteResult represents the result of an image.Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// Extract interprets a GetResult as an Image.
func (gr GetResult) Extract() (*Image, error) {
if gr.Err != nil {
return nil, gr.Err
}
var decoded struct {
Image Image `mapstructure:"image"`
}
err := mapstructure.Decode(gr.Body, &decoded)
return &decoded.Image, err
}
// Image is used for JSON (un)marshalling.
// It provides a description of an OS image.
type Image struct {
// ID contains the image's unique identifier.
ID string
Created string
// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image.
MinDisk int
MinRAM int
// Name provides a human-readable moniker for the OS image.
Name string
// The Progress and Status fields indicate image-creation status.
// Any usable image will have 100% progress.
Progress int
Status string
Updated string
Metadata map[string]string
}
// ImagePage contains a single page of results from a List operation.
// Use ExtractImages to convert it into a slice of usable structs.
type ImagePage struct {
pagination.LinkedPageBase
}
// IsEmpty returns true if a page contains no Image results.
func (page ImagePage) IsEmpty() (bool, error) {
images, err := ExtractImages(page)
if err != nil {
return true, err
}
return len(images) == 0, nil
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (page ImagePage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"images_links"`
}
var r resp
err := mapstructure.Decode(page.Body, &r)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
}
// ExtractImages converts a page of List results into a slice of usable Image structs.
func ExtractImages(page pagination.Page) ([]Image, error) {
casted := page.(ImagePage).Body
var results struct {
Images []Image `mapstructure:"images"`
}
err := mapstructure.Decode(casted, &results)
return results.Images, err
}
package images
import "github.com/rackspace/gophercloud"
func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("images", "detail")
}
func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
"util.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/flavors:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/images:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package servers provides information and interaction with the server API
// resource in the OpenStack Compute service.
//
// A server is a virtual machine instance in the compute system. In order for
// one to be provisioned, a valid flavor and image are required.
package servers
package servers
import "github.com/rackspace/gophercloud"
func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("servers")
}
func listURL(client *gophercloud.ServiceClient) string {
return createURL(client)
}
func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("servers", "detail")
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id)
}
func getURL(client *gophercloud.ServiceClient, id string) string {
return deleteURL(client, id)
}
func updateURL(client *gophercloud.ServiceClient, id string) string {
return deleteURL(client, id)
}
func actionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "action")
}
func metadatumURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("servers", id, "metadata", key)
}
func metadataURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "metadata")
}
func listAddressesURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "ips")
}
func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string {
return client.ServiceURL("servers", id, "ips", network)
}
func passwordURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "os-server-password")
}
package servers
import "github.com/rackspace/gophercloud"
// WaitForStatus will continually poll a server until it successfully transitions to a specified
// status. It will do this for at most the number of seconds specified.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}
package openstack
import (
"fmt"
"github.com/rackspace/gophercloud"
tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
)
// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
// during the v2 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
var endpoints = make([]tokens2.Endpoint, 0, 1)
for _, entry := range catalog.Entries {
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
for _, endpoint := range entry.Endpoints {
if opts.Region == "" || endpoint.Region == opts.Region {
endpoints = append(endpoints, endpoint)
}
}
}
}
// Report an error if the options were ambiguous.
if len(endpoints) > 1 {
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
}
// Extract the appropriate URL from the matching Endpoint.
for _, endpoint := range endpoints {
switch opts.Availability {
case gophercloud.AvailabilityPublic:
return gophercloud.NormalizeURL(endpoint.PublicURL), nil
case gophercloud.AvailabilityInternal:
return gophercloud.NormalizeURL(endpoint.InternalURL), nil
case gophercloud.AvailabilityAdmin:
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
default:
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
}
}
// Report an error if there were no matching endpoints.
return "", gophercloud.ErrEndpointNotFound
}
// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
// Name if provided, and Region if provided.
var endpoints = make([]tokens3.Endpoint, 0, 1)
for _, entry := range catalog.Entries {
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
for _, endpoint := range entry.Endpoints {
if opts.Availability != gophercloud.AvailabilityAdmin &&
opts.Availability != gophercloud.AvailabilityPublic &&
opts.Availability != gophercloud.AvailabilityInternal {
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
}
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
(opts.Region == "" || endpoint.Region == opts.Region) {
endpoints = append(endpoints, endpoint)
}
}
}
}
// Report an error if the options were ambiguous.
if len(endpoints) > 1 {
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
}
// Extract the URL from the matching Endpoint.
for _, endpoint := range endpoints {
return gophercloud.NormalizeURL(endpoint.URL), nil
}
// Report an error if there were no matching endpoints.
return "", gophercloud.ErrEndpointNotFound
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package tenants provides information and interaction with the
// tenants API resource for the OpenStack Identity service.
//
// See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
// and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
// for more information.
package tenants
// +build fixtures
package tenants
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)
// ListOutput provides a single page of Tenant results.
const ListOutput = `
{
"tenants": [
{
"id": "1234",
"name": "Red Team",
"description": "The team that is red",
"enabled": true
},
{
"id": "9876",
"name": "Blue Team",
"description": "The team that is blue",
"enabled": false
}
]
}
`
// RedTeam is a Tenant fixture.
var RedTeam = Tenant{
ID: "1234",
Name: "Red Team",
Description: "The team that is red",
Enabled: true,
}
// BlueTeam is a Tenant fixture.
var BlueTeam = Tenant{
ID: "9876",
Name: "Blue Team",
Description: "The team that is blue",
Enabled: false,
}
// ExpectedTenantSlice is the slice of tenants expected to be returned from ListOutput.
var ExpectedTenantSlice = []Tenant{RedTeam, BlueTeam}
// HandleListTenantsSuccessfully creates an HTTP handler at `/tenants` on the test handler mux that
// responds with a list of two tenants.
func HandleListTenantsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/tenants", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "Accept", "application/json")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, ListOutput)
})
}
package tenants
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// ListOpts filters the Tenants that are returned by the List call.
type ListOpts struct {
// Marker is the ID of the last Tenant on the previous page.
Marker string `q:"marker"`
// Limit specifies the page size.
Limit int `q:"limit"`
}
// List enumerates the Tenants to which the current token has access.
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
createPage := func(r pagination.PageResult) pagination.Page {
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
}
url := listURL(client)
if opts != nil {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return pagination.Pager{Err: err}
}
url += q.String()
}
return pagination.NewPager(client, url, createPage)
}
package tenants
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// Tenant is a grouping of users in the identity service.
type Tenant struct {
// ID is a unique identifier for this tenant.
ID string `mapstructure:"id"`
// Name is a friendlier user-facing name for this tenant.
Name string `mapstructure:"name"`
// Description is a human-readable explanation of this Tenant's purpose.
Description string `mapstructure:"description"`
// Enabled indicates whether or not a tenant is active.
Enabled bool `mapstructure:"enabled"`
}
// TenantPage is a single page of Tenant results.
type TenantPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines whether or not a page of Tenants contains any results.
func (page TenantPage) IsEmpty() (bool, error) {
tenants, err := ExtractTenants(page)
if err != nil {
return false, err
}
return len(tenants) == 0, nil
}
// NextPageURL extracts the "next" link from the tenants_links section of the result.
func (page TenantPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"tenants_links"`
}
var r resp
err := mapstructure.Decode(page.Body, &r)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
}
// ExtractTenants returns a slice of Tenants contained in a single page of results.
func ExtractTenants(page pagination.Page) ([]Tenant, error) {
casted := page.(TenantPage).Body
var response struct {
Tenants []Tenant `mapstructure:"tenants"`
}
err := mapstructure.Decode(casted, &response)
return response.Tenants, err
}
package tenants
import "github.com/rackspace/gophercloud"
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tenants")
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"errors.go",
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
"//vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package tokens provides information and interaction with the token API
// resource for the OpenStack Identity service.
// For more information, see:
// http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
package tokens
package tokens
import (
"errors"
"fmt"
)
var (
// ErrUserIDProvided is returned if you attempt to authenticate with a UserID.
ErrUserIDProvided = unacceptedAttributeErr("UserID")
// ErrAPIKeyProvided is returned if you attempt to authenticate with an APIKey.
ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
// ErrDomainIDProvided is returned if you attempt to authenticate with a DomainID.
ErrDomainIDProvided = unacceptedAttributeErr("DomainID")
// ErrDomainNameProvided is returned if you attempt to authenticate with a DomainName.
ErrDomainNameProvided = unacceptedAttributeErr("DomainName")
// ErrUsernameRequired is returned if you attempt to authenticate without a Username.
ErrUsernameRequired = errors.New("You must supply a Username in your AuthOptions.")
// ErrPasswordRequired is returned if you don't provide a password.
ErrPasswordRequired = errors.New("Please supply a Password in your AuthOptions.")
)
func unacceptedAttributeErr(attribute string) error {
return fmt.Errorf("The base Identity V2 API does not accept authentication by %s", attribute)
}
// +build fixtures
package tokens
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
th "github.com/rackspace/gophercloud/testhelper"
thclient "github.com/rackspace/gophercloud/testhelper/client"
)
// ExpectedToken is the token that should be parsed from TokenCreationResponse.
var ExpectedToken = &Token{
ID: "aaaabbbbccccdddd",
ExpiresAt: time.Date(2014, time.January, 31, 15, 30, 58, 0, time.UTC),
Tenant: tenants.Tenant{
ID: "fc394f2ab2df4114bde39905f800dc57",
Name: "test",
Description: "There are many tenants. This one is yours.",
Enabled: true,
},
}
// ExpectedServiceCatalog is the service catalog that should be parsed from TokenCreationResponse.
var ExpectedServiceCatalog = &ServiceCatalog{
Entries: []CatalogEntry{
CatalogEntry{
Name: "inscrutablewalrus",
Type: "something",
Endpoints: []Endpoint{
Endpoint{
PublicURL: "http://something0:1234/v2/",
Region: "region0",
},
Endpoint{
PublicURL: "http://something1:1234/v2/",
Region: "region1",
},
},
},
CatalogEntry{
Name: "arbitrarypenguin",
Type: "else",
Endpoints: []Endpoint{
Endpoint{
PublicURL: "http://else0:4321/v3/",
Region: "region0",
},
},
},
},
}
// ExpectedUser is the token that should be parsed from TokenGetResponse.
var ExpectedUser = &User{
ID: "a530fefc3d594c4ba2693a4ecd6be74e",
Name: "apiserver",
Roles: []Role{{"member"}, {"service"}},
UserName: "apiserver",
}
// TokenCreationResponse is a JSON response that contains ExpectedToken and ExpectedServiceCatalog.
const TokenCreationResponse = `
{
"access": {
"token": {
"issued_at": "2014-01-30T15:30:58.000000Z",
"expires": "2014-01-31T15:30:58Z",
"id": "aaaabbbbccccdddd",
"tenant": {
"description": "There are many tenants. This one is yours.",
"enabled": true,
"id": "fc394f2ab2df4114bde39905f800dc57",
"name": "test"
}
},
"serviceCatalog": [
{
"endpoints": [
{
"publicURL": "http://something0:1234/v2/",
"region": "region0"
},
{
"publicURL": "http://something1:1234/v2/",
"region": "region1"
}
],
"type": "something",
"name": "inscrutablewalrus"
},
{
"endpoints": [
{
"publicURL": "http://else0:4321/v3/",
"region": "region0"
}
],
"type": "else",
"name": "arbitrarypenguin"
}
]
}
}
`
// TokenGetResponse is a JSON response that contains ExpectedToken and ExpectedUser.
const TokenGetResponse = `
{
"access": {
"token": {
"issued_at": "2014-01-30T15:30:58.000000Z",
"expires": "2014-01-31T15:30:58Z",
"id": "aaaabbbbccccdddd",
"tenant": {
"description": "There are many tenants. This one is yours.",
"enabled": true,
"id": "fc394f2ab2df4114bde39905f800dc57",
"name": "test"
}
},
"serviceCatalog": [],
"user": {
"id": "a530fefc3d594c4ba2693a4ecd6be74e",
"name": "apiserver",
"roles": [
{
"name": "member"
},
{
"name": "service"
}
],
"roles_links": [],
"username": "apiserver"
}
}
}`
// HandleTokenPost expects a POST against a /tokens handler, ensures that the request body has been
// constructed properly given certain auth options, and returns the result.
func HandleTokenPost(t *testing.T, requestJSON string) {
th.Mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
if requestJSON != "" {
th.TestJSONRequest(t, r, requestJSON)
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, TokenCreationResponse)
})
}
// HandleTokenGet expects a Get against a /tokens handler, ensures that the request body has been
// constructed properly given certain auth options, and returns the result.
func HandleTokenGet(t *testing.T, token string) {
th.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "Accept", "application/json")
th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, TokenGetResponse)
})
}
// IsSuccessful ensures that a CreateResult was successful and contains the correct token and
// service catalog.
func IsSuccessful(t *testing.T, result CreateResult) {
token, err := result.ExtractToken()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedToken, token)
serviceCatalog, err := result.ExtractServiceCatalog()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedServiceCatalog, serviceCatalog)
}
// GetIsSuccessful ensures that a GetResult was successful and contains the correct token and
// User Info.
func GetIsSuccessful(t *testing.T, result GetResult) {
token, err := result.ExtractToken()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedToken, token)
user, err := result.ExtractUser()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedUser, user)
}
package tokens
import (
"fmt"
"github.com/rackspace/gophercloud"
)
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
type AuthOptionsBuilder interface {
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
// missing or inconsistent.
ToTokenCreateMap() (map[string]interface{}, error)
}
// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
// interface.
type AuthOptions struct {
gophercloud.AuthOptions
}
// WrapOptions embeds a root AuthOptions struct in a package-specific one.
func WrapOptions(original gophercloud.AuthOptions) AuthOptions {
return AuthOptions{AuthOptions: original}
}
// ToTokenCreateMap converts AuthOptions into nested maps that can be serialized into a JSON
// request.
func (auth AuthOptions) ToTokenCreateMap() (map[string]interface{}, error) {
// Error out if an unsupported auth option is present.
if auth.UserID != "" {
return nil, ErrUserIDProvided
}
if auth.APIKey != "" {
return nil, ErrAPIKeyProvided
}
if auth.DomainID != "" {
return nil, ErrDomainIDProvided
}
if auth.DomainName != "" {
return nil, ErrDomainNameProvided
}
// Populate the request map.
authMap := make(map[string]interface{})
if auth.Username != "" {
if auth.Password != "" {
authMap["passwordCredentials"] = map[string]interface{}{
"username": auth.Username,
"password": auth.Password,
}
} else {
return nil, ErrPasswordRequired
}
} else if auth.TokenID != "" {
authMap["token"] = map[string]interface{}{
"id": auth.TokenID,
}
} else {
return nil, fmt.Errorf("You must provide either username/password or tenantID/token values.")
}
if auth.TenantID != "" {
authMap["tenantId"] = auth.TenantID
}
if auth.TenantName != "" {
authMap["tenantName"] = auth.TenantName
}
return map[string]interface{}{"auth": authMap}, nil
}
// Create authenticates to the identity service and attempts to acquire a Token.
// If successful, the CreateResult
// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
// which abstracts all of the gory details about navigating service catalogs and such.
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) CreateResult {
request, err := auth.ToTokenCreateMap()
if err != nil {
return CreateResult{gophercloud.Result{Err: err}}
}
var result CreateResult
_, result.Err = client.Post(CreateURL(client), request, &result.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
return result
}
// Validates and retrieves information for user's token.
func Get(client *gophercloud.ServiceClient, token string) GetResult {
var result GetResult
_, result.Err = client.Get(GetURL(client, token), &result.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
return result
}
package tokens
import (
"time"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
)
// Token provides only the most basic information related to an authentication token.
type Token struct {
// ID provides the primary means of identifying a user to the OpenStack API.
// OpenStack defines this field as an opaque value, so do not depend on its content.
// It is safe, however, to compare for equality.
ID string
// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the authentication token becomes invalid.
// After this point in time, future API requests made using this authentication token will respond with errors.
// Either the caller will need to reauthenticate manually, or more preferably, the caller should exploit automatic re-authentication.
// See the AuthOptions structure for more details.
ExpiresAt time.Time
// Tenant provides information about the tenant to which this token grants access.
Tenant tenants.Tenant
}
// Authorization need user info which can get from token authentication's response
type Role struct {
Name string `mapstructure:"name"`
}
type User struct {
ID string `mapstructure:"id"`
Name string `mapstructure:"name"`
UserName string `mapstructure:"username"`
Roles []Role `mapstructure:"roles"`
}
// Endpoint represents a single API endpoint offered by a service.
// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
// The significance of the Region field will depend upon your provider.
//
// In addition, the interface offered by the service will have version information associated with it
// through the VersionId, VersionInfo, and VersionList fields, if provided or supported.
//
// In all cases, fields which aren't supported by the provider and service combined will assume a zero-value ("").
type Endpoint struct {
TenantID string `mapstructure:"tenantId"`
PublicURL string `mapstructure:"publicURL"`
InternalURL string `mapstructure:"internalURL"`
AdminURL string `mapstructure:"adminURL"`
Region string `mapstructure:"region"`
VersionID string `mapstructure:"versionId"`
VersionInfo string `mapstructure:"versionInfo"`
VersionList string `mapstructure:"versionList"`
}
// CatalogEntry provides a type-safe interface to an Identity API V2 service catalog listing.
// Each class of service, such as cloud DNS or block storage services, will have a single
// CatalogEntry representing it.
//
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
// Otherwise, you'll tie the representation of the service to a specific provider.
type CatalogEntry struct {
// Name will contain the provider-specified name for the service.
Name string `mapstructure:"name"`
// Type will contain a type string if OpenStack defines a type for the service.
// Otherwise, for provider-specific services, the provider may assign their own type strings.
Type string `mapstructure:"type"`
// Endpoints will let the caller iterate over all the different endpoints that may exist for
// the service.
Endpoints []Endpoint `mapstructure:"endpoints"`
}
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
type ServiceCatalog struct {
Entries []CatalogEntry
}
// CreateResult defers the interpretation of a created token.
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
type CreateResult struct {
gophercloud.Result
}
// GetResult is the deferred response from a Get call, which is the same with a Created token.
// Use ExtractUser() to interpret it as a User.
type GetResult struct {
CreateResult
}
// ExtractToken returns the just-created Token from a CreateResult.
func (result CreateResult) ExtractToken() (*Token, error) {
if result.Err != nil {
return nil, result.Err
}
var response struct {
Access struct {
Token struct {
Expires string `mapstructure:"expires"`
ID string `mapstructure:"id"`
Tenant tenants.Tenant `mapstructure:"tenant"`
} `mapstructure:"token"`
} `mapstructure:"access"`
}
err := mapstructure.Decode(result.Body, &response)
if err != nil {
return nil, err
}
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, response.Access.Token.Expires)
if err != nil {
return nil, err
}
return &Token{
ID: response.Access.Token.ID,
ExpiresAt: expiresTs,
Tenant: response.Access.Token.Tenant,
}, nil
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
func (result CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
if result.Err != nil {
return nil, result.Err
}
var response struct {
Access struct {
Entries []CatalogEntry `mapstructure:"serviceCatalog"`
} `mapstructure:"access"`
}
err := mapstructure.Decode(result.Body, &response)
if err != nil {
return nil, err
}
return &ServiceCatalog{Entries: response.Access.Entries}, nil
}
// createErr quickly packs an error in a CreateResult.
func createErr(err error) CreateResult {
return CreateResult{gophercloud.Result{Err: err}}
}
// ExtractUser returns the User from a GetResult.
func (result GetResult) ExtractUser() (*User, error) {
if result.Err != nil {
return nil, result.Err
}
var response struct {
Access struct {
User User `mapstructure:"user"`
} `mapstructure:"access"`
}
err := mapstructure.Decode(result.Body, &response)
if err != nil {
return nil, err
}
return &response.Access.User, nil
}
package tokens
import "github.com/rackspace/gophercloud"
// CreateURL generates the URL used to create new Tokens.
func CreateURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tokens")
}
// GetURL generates the URL used to Validate Tokens.
func GetURL(client *gophercloud.ServiceClient, token string) string {
return client.ServiceURL("tokens", token)
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"errors.go",
"requests.go",
"results.go",
"urls.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mitchellh/mapstructure:go_default_library",
"//vendor/github.com/rackspace/gophercloud:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// Package tokens provides information and interaction with the token API
// resource for the OpenStack Identity service.
//
// For more information, see:
// http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
package tokens
package tokens
import (
"errors"
"fmt"
)
func unacceptedAttributeErr(attribute string) error {
return fmt.Errorf("The base Identity V3 API does not accept authentication by %s", attribute)
}
func redundantWithTokenErr(attribute string) error {
return fmt.Errorf("%s may not be provided when authenticating with a TokenID", attribute)
}
func redundantWithUserID(attribute string) error {
return fmt.Errorf("%s may not be provided when authenticating with a UserID", attribute)
}
var (
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
ErrTenantIDProvided = unacceptedAttributeErr("TenantID")
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
ErrTenantNameProvided = unacceptedAttributeErr("TenantName")
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
ErrUsernameWithToken = redundantWithTokenErr("Username")
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
ErrUserIDWithToken = redundantWithTokenErr("UserID")
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
ErrDomainIDWithToken = redundantWithTokenErr("DomainID")
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
ErrDomainNameWithToken = redundantWithTokenErr("DomainName")
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
ErrUsernameOrUserID = errors.New("Exactly one of Username and UserID must be provided for password authentication")
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
ErrDomainIDWithUserID = redundantWithUserID("DomainID")
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
ErrDomainNameWithUserID = redundantWithUserID("DomainName")
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
// It may also indicate that both a DomainID and a DomainName were provided at once.
ErrDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName to authenticate by Username")
// ErrMissingPassword indicates that no password and no token were provided and no token is available.
ErrMissingPassword = errors.New("You must provide a password or a token to authenticate")
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
ErrScopeDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName in a Scope with ProjectName")
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
ErrScopeProjectIDOrProjectName = errors.New("You must provide at most one of ProjectID or ProjectName in a Scope")
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
ErrScopeProjectIDAlone = errors.New("ProjectID must be supplied alone in a Scope")
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
ErrScopeDomainName = errors.New("DomainName must be supplied with a ProjectName or ProjectID in a Scope.")
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
ErrScopeEmpty = errors.New("You must provide either a Project or Domain in a Scope")
)
package tokens
import (
"time"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
)
// Endpoint represents a single API endpoint offered by a service.
// It matches either a public, internal or admin URL.
// If supported, it contains a region specifier, again if provided.
// The significance of the Region field will depend upon your provider.
type Endpoint struct {
ID string `mapstructure:"id"`
Region string `mapstructure:"region"`
Interface string `mapstructure:"interface"`
URL string `mapstructure:"url"`
}
// CatalogEntry provides a type-safe interface to an Identity API V3 service catalog listing.
// Each class of service, such as cloud DNS or block storage services, could have multiple
// CatalogEntry representing it (one by interface type, e.g public, admin or internal).
//
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
// Otherwise, you'll tie the representation of the service to a specific provider.
type CatalogEntry struct {
// Service ID
ID string `mapstructure:"id"`
// Name will contain the provider-specified name for the service.
Name string `mapstructure:"name"`
// Type will contain a type string if OpenStack defines a type for the service.
// Otherwise, for provider-specific services, the provider may assign their own type strings.
Type string `mapstructure:"type"`
// Endpoints will let the caller iterate over all the different endpoints that may exist for
// the service.
Endpoints []Endpoint `mapstructure:"endpoints"`
}
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
type ServiceCatalog struct {
Entries []CatalogEntry
}
// commonResult is the deferred result of a Create or a Get call.
type commonResult struct {
gophercloud.Result
}
// Extract is a shortcut for ExtractToken.
// This function is deprecated and still present for backward compatibility.
func (r commonResult) Extract() (*Token, error) {
return r.ExtractToken()
}
// ExtractToken interprets a commonResult as a Token.
func (r commonResult) ExtractToken() (*Token, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
Token struct {
ExpiresAt string `mapstructure:"expires_at"`
} `mapstructure:"token"`
}
var token Token
// Parse the token itself from the stored headers.
token.ID = r.Header.Get("X-Subject-Token")
err := mapstructure.Decode(r.Body, &response)
if err != nil {
return nil, err
}
// Attempt to parse the timestamp.
token.ExpiresAt, err = time.Parse(gophercloud.RFC3339Milli, response.Token.ExpiresAt)
return &token, err
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
func (result CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
if result.Err != nil {
return nil, result.Err
}
var response struct {
Token struct {
Entries []CatalogEntry `mapstructure:"catalog"`
} `mapstructure:"token"`
}
err := mapstructure.Decode(result.Body, &response)
if err != nil {
return nil, err
}
return &ServiceCatalog{Entries: response.Token.Entries}, nil
}
// CreateResult defers the interpretation of a created token.
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
type CreateResult struct {
commonResult
}
// createErr quickly creates a CreateResult that reports an error.
func createErr(err error) CreateResult {
return CreateResult{
commonResult: commonResult{Result: gophercloud.Result{Err: err}},
}
}
// GetResult is the deferred response from a Get call.
type GetResult struct {
commonResult
}
// RevokeResult is the deferred response from a Revoke call.
type RevokeResult struct {
commonResult
}
// Token is a string that grants a user access to a controlled set of services in an OpenStack provider.
// Each Token is valid for a set length of time.
type Token struct {
// ID is the issued token.
ID string
// ExpiresAt is the timestamp at which this token will no longer be accepted.
ExpiresAt time.Time
}
package tokens
import "github.com/rackspace/gophercloud"
func tokenURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("auth", "tokens")
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["choose_version.go"],
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/rackspace/gophercloud:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package utils
import (
"fmt"
"strings"
"github.com/rackspace/gophercloud"
)
// Version is a supported API version, corresponding to a vN package within the appropriate service.
type Version struct {
ID string
Suffix string
Priority int
}
var goodStatus = map[string]bool{
"current": true,
"supported": true,
"stable": true,
}
// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's
// published versions.
// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) {
type linkResp struct {
Href string `json:"href"`
Rel string `json:"rel"`
}
type valueResp struct {
ID string `json:"id"`
Status string `json:"status"`
Links []linkResp `json:"links"`
}
type versionsResp struct {
Values []valueResp `json:"values"`
}
type response struct {
Versions versionsResp `json:"versions"`
}
normalize := func(endpoint string) string {
if !strings.HasSuffix(endpoint, "/") {
return endpoint + "/"
}
return endpoint
}
identityEndpoint := normalize(client.IdentityEndpoint)
// If a full endpoint is specified, check version suffixes for a match first.
for _, v := range recognized {
if strings.HasSuffix(identityEndpoint, v.Suffix) {
return v, identityEndpoint, nil
}
}
var resp response
_, err := client.Request("GET", client.IdentityBase, gophercloud.RequestOpts{
JSONResponse: &resp,
OkCodes: []int{200, 300},
})
if err != nil {
return nil, "", err
}
byID := make(map[string]*Version)
for _, version := range recognized {
byID[version.ID] = version
}
var highest *Version
var endpoint string
for _, value := range resp.Versions.Values {
href := ""
for _, link := range value.Links {
if link.Rel == "self" {
href = normalize(link.Href)
}
}
if matching, ok := byID[value.ID]; ok {
// Prefer a version that exactly matches the provided endpoint.
if href == identityEndpoint {
if href == "" {
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
}
return matching, href, nil
}
// Otherwise, find the highest-priority version with a whitelisted status.
if goodStatus[strings.ToLower(value.Status)] {
if highest == nil || matching.Priority > highest.Priority {
highest = matching
endpoint = href
}
}
}
}
if highest == nil {
return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase)
}
if endpoint == "" {
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase)
}
return highest, endpoint, nil
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"http.go",
"linked.go",
"marker.go",
"null.go",
"pager.go",
"pkg.go",
"single.go",
],
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/rackspace/gophercloud:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package pagination
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/rackspace/gophercloud"
)
// PageResult stores the HTTP response that returned the current page of results.
type PageResult struct {
gophercloud.Result
url.URL
}
// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the
// results, interpreting it as JSON if the content type indicates.
func PageResultFrom(resp *http.Response) (PageResult, error) {
var parsedBody interface{}
defer resp.Body.Close()
rawBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return PageResult{}, err
}
if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
err = json.Unmarshal(rawBody, &parsedBody)
if err != nil {
return PageResult{}, err
}
} else {
parsedBody = rawBody
}
return PageResultFromParsed(resp, parsedBody), err
}
// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its
// body parsed as JSON (and closed).
func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
return PageResult{
Result: gophercloud.Result{
Body: body,
Header: resp.Header,
},
URL: *resp.Request.URL,
}
}
// Request performs an HTTP request and extracts the http.Response from the result.
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
return client.Request("GET", url, gophercloud.RequestOpts{
MoreHeaders: headers,
OkCodes: []int{200, 204},
})
}
package pagination
import "fmt"
// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result.
type LinkedPageBase struct {
PageResult
// LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer.
// If any link along the path is missing, an empty URL will be returned.
// If any link results in an unexpected value type, an error will be returned.
// When left as "nil", []string{"links", "next"} will be used as a default.
LinkPath []string
}
// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present.
// It assumes that the links are available in a "links" element of the top-level response object.
// If this is not the case, override NextPageURL on your result type.
func (current LinkedPageBase) NextPageURL() (string, error) {
var path []string
var key string
if current.LinkPath == nil {
path = []string{"links", "next"}
} else {
path = current.LinkPath
}
submap, ok := current.Body.(map[string]interface{})
if !ok {
return "", fmt.Errorf("Expected an object, but was %#v", current.Body)
}
for {
key, path = path[0], path[1:len(path)]
value, ok := submap[key]
if !ok {
return "", nil
}
if len(path) > 0 {
submap, ok = value.(map[string]interface{})
if !ok {
return "", fmt.Errorf("Expected an object, but was %#v", value)
}
} else {
if value == nil {
// Actual null element.
return "", nil
}
url, ok := value.(string)
if !ok {
return "", fmt.Errorf("Expected a string, but was %#v", value)
}
return url, nil
}
}
}
// GetBody returns the linked page's body. This method is needed to satisfy the
// Page interface.
func (current LinkedPageBase) GetBody() interface{} {
return current.Body
}
package pagination
// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager.
// For convenience, embed the MarkedPageBase struct.
type MarkerPage interface {
Page
// LastMarker returns the last "marker" value on this page.
LastMarker() (string, error)
}
// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters.
type MarkerPageBase struct {
PageResult
// Owner is a reference to the embedding struct.
Owner MarkerPage
}
// NextPageURL generates the URL for the page of results after this one.
func (current MarkerPageBase) NextPageURL() (string, error) {
currentURL := current.URL
mark, err := current.Owner.LastMarker()
if err != nil {
return "", err
}
q := currentURL.Query()
q.Set("marker", mark)
currentURL.RawQuery = q.Encode()
return currentURL.String(), nil
}
// GetBody returns the linked page's body. This method is needed to satisfy the
// Page interface.
func (current MarkerPageBase) GetBody() interface{} {
return current.Body
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment