update gophercloud to newest version

This commit is contained in:
zengchen1024 2017-10-31 14:55:46 +08:00
parent b02c3a269c
commit 41b8f21ce5
110 changed files with 4960 additions and 996 deletions

@ -1 +1 @@
Subproject commit 2bf16b94fdd9b01557c4d076e567fe5cbbe5a961
Subproject commit c7551a666c4fee120cc314dce91ba3d0663a86f3

View File

@ -1 +1,2 @@
**/*.swp
.idea

View File

@ -12,6 +12,8 @@ go:
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
before_script:
- go vet ./...
script:
- ./script/coverage
- ./script/format

View File

@ -10,7 +10,26 @@ to a remote API.
> be certain cases where this does not happen; always double-check to make sure
> you have no stragglers left behind.
### Step 1. Set environment variables
### Step 1. Creating a Testing Environment
Running tests on an existing OpenStack cloud can be risky. Malformed tests,
especially ones which require Admin privileges, can cause damage to the
environment. Additionally, you may incur bandwidth and service charges for
the resources used, as mentioned in the note above.
Therefore, it is usually best to first practice running acceptance tests in
an isolated test environment. Two options to easily create a testing
environment are [DevStack](https://docs.openstack.org/devstack/latest/)
and [PackStack](https://www.rdoproject.org/install/packstack/).
The following blog posts detail how to create reusable PackStack environments.
These posts were written with Gophercloud in mind:
* http://terrarum.net/blog/building-openstack-environments.html
* http://terrarum.net/blog/building-openstack-environments-2.html
* http://terrarum.net/blog/building-openstack-environments-3.html
### Step 2. Set environment variables
A lot of tests rely on environment variables for configuration - so you will need
to set them before running the suite. If you're testing against pure OpenStack APIs,
@ -43,14 +62,22 @@ to set them manually.
|`OS_FLAVOR_ID`|The ID of the flavor you want your server to be based on|
|`OS_FLAVOR_ID_RESIZE`|The ID of the flavor you want your server to be resized to|
|`OS_POOL_NAME`|The Pool from where to obtain Floating IPs|
|`OS_NETWORK_NAME`|The network to launch instances on|
|`OS_NETWORK_NAME`|The internal/private network to launch instances on|
|`OS_EXTGW_ID`|The external/public network|
#### Database
|Name|Description|
|---|---|
|`OS_DB_DATASTORE_TYPE`|The Datastore type to use. Example: `mariadb`|
|`OS_DB_DATASTORE_VERSION`|The Datastore version to use. Example: `mariadb-10`|
#### Shared file systems
|Name|Description|
|---|---|
|`OS_SHARE_NETWORK_ID`| The share network ID to use when creating shares|
### 2. Run the test suite
### 3. Run the test suite
From the root directory, run:
@ -78,7 +105,7 @@ $ gophercloudtest TestFlavors compute/v2
$ gophercloudtest Test compute/v2
```
### 3. Notes
### 4. Notes
#### Compute Tests

View File

@ -10,6 +10,7 @@ import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/blockstorage/noauth"
)
// AcceptanceTestChoices contains image and flavor selections for use by the acceptance tests.
@ -35,6 +36,12 @@ type AcceptanceTestChoices struct {
// ShareNetworkID is the Manila Share network ID
ShareNetworkID string
// DBDatastoreType is the datastore type for DB tests.
DBDatastoreType string
// DBDatastoreTypeID is the datastore type version for DB tests.
DBDatastoreVersion string
}
// AcceptanceTestChoicesFromEnv populates a ComputeChoices struct from environment variables.
@ -47,6 +54,8 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) {
floatingIPPoolName := os.Getenv("OS_POOL_NAME")
externalNetworkID := os.Getenv("OS_EXTGW_ID")
shareNetworkID := os.Getenv("OS_SHARE_NETWORK_ID")
dbDatastoreType := os.Getenv("OS_DB_DATASTORE_TYPE")
dbDatastoreVersion := os.Getenv("OS_DB_DATASTORE_VERSION")
missing := make([]string, 0, 3)
if imageID == "" {
@ -95,6 +104,8 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) {
NetworkName: networkName,
ExternalNetworkID: externalNetworkID,
ShareNetworkID: shareNetworkID,
DBDatastoreType: dbDatastoreType,
DBDatastoreVersion: dbDatastoreVersion,
}, nil
}
@ -136,22 +147,20 @@ func NewBlockStorageV2Client() (*gophercloud.ServiceClient, error) {
})
}
// NewSharedFileSystemV2Client returns a *ServiceClient for making calls
// to the OpenStack Shared File System v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
// NewBlockStorageV2NoAuthClient returns a noauth *ServiceClient for
// making calls to the OpenStack Block Storage v2 API. An error will be
// returned if client creation was not possible.
func NewBlockStorageV2NoAuthClient() (*gophercloud.ServiceClient, error) {
client, err := noauth.NewClient(gophercloud.AuthOptions{
Username: os.Getenv("OS_USERNAME"),
TenantName: os.Getenv("OS_TENANT_NAME"),
})
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewSharedFileSystemV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
return noauth.NewBlockStorageV2(client, noauth.EndpointOpts{
CinderEndpoint: os.Getenv("CINDER_ENDPOINT"),
})
}
@ -174,6 +183,25 @@ func NewComputeV2Client() (*gophercloud.ServiceClient, error) {
})
}
// NewDBV1Client returns a *ServiceClient for making calls
// to the OpenStack Database v1 API. An error will be returned
// if authentication or client creation was not possible.
func NewDBV1Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewDBV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewDNSV2Client returns a *ServiceClient for making calls
// to the OpenStack Compute v2 API. An error will be returned
// if authentication or client creation was not possible.
@ -341,3 +369,22 @@ func NewObjectStorageV1Client() (*gophercloud.ServiceClient, error) {
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewSharedFileSystemV2Client returns a *ServiceClient for making calls
// to the OpenStack Shared File System v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewSharedFileSystemV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}

View File

@ -0,0 +1,142 @@
// Package noauth contains common functions for creating block storage based
// resources for use in acceptance tests. See the `*_test.go` files for
// example usages.
package noauth
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
)
// CreateVolume will create a volume with a random name and size of 1GB. An
// error will be returned if the volume was unable to be created.
func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) {
if testing.Short() {
t.Skip("Skipping test that requires volume creation in short mode.")
}
volumeName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create volume: %s", volumeName)
createOpts := volumes.CreateOpts{
Size: 1,
Name: volumeName,
}
volume, err := volumes.Create(client, createOpts).Extract()
if err != nil {
return volume, err
}
err = volumes.WaitForStatus(client, volume.ID, "available", 60)
if err != nil {
return volume, err
}
return volume, nil
}
// CreateVolumeFromImage will create a volume from with a random name and size of
// 1GB. An error will be returned if the volume was unable to be created.
func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) {
if testing.Short() {
t.Skip("Skipping test that requires volume creation in short mode.")
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
volumeName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create volume: %s", volumeName)
createOpts := volumes.CreateOpts{
Size: 1,
Name: volumeName,
ImageID: choices.ImageID,
}
volume, err := volumes.Create(client, createOpts).Extract()
if err != nil {
return volume, err
}
err = volumes.WaitForStatus(client, volume.ID, "available", 60)
if err != nil {
return volume, err
}
return volume, nil
}
// DeleteVolume will delete a volume. A fatal error will occur if the volume
// failed to be deleted. This works best when used as a deferred function.
func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
err := volumes.Delete(client, volume.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete volume %s: %v", volume.ID, err)
}
t.Logf("Deleted volume: %s", volume.ID)
}
// CreateSnapshot will create a snapshot of the specified volume.
// Snapshot will be assigned a random name and description.
func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) {
if testing.Short() {
t.Skip("Skipping test that requires snapshot creation in short mode.")
}
snapshotName := tools.RandomString("ACPTTEST", 16)
snapshotDescription := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create snapshot: %s", snapshotName)
createOpts := snapshots.CreateOpts{
VolumeID: volume.ID,
Name: snapshotName,
Description: snapshotDescription,
}
snapshot, err := snapshots.Create(client, createOpts).Extract()
if err != nil {
return snapshot, err
}
err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60)
if err != nil {
return snapshot, err
}
return snapshot, nil
}
// DeleteSnapshot will delete a snapshot. A fatal error will occur if the
// snapshot failed to be deleted.
func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) {
err := snapshots.Delete(client, snapshot.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err)
}
// Volumes can't be deleted until their snapshots have been,
// so block up to 120 seconds for the snapshot to delete.
err = gophercloud.WaitFor(120, func() (bool, error) {
_, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("Error waiting for snapshot to delete: %v", err)
}
t.Logf("Deleted snapshot: %s", snapshot.ID)
}

View File

@ -0,0 +1,3 @@
// The noauth package contains acceptance tests for the Openstack Cinder standalone service.
package noauth

View File

@ -0,0 +1,58 @@
// +build acceptance blockstorage
package noauth
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots"
)
func TestSnapshotsList(t *testing.T) {
client, err := clients.NewBlockStorageV2NoAuthClient()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := snapshots.List(client, snapshots.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve snapshots: %v", err)
}
allSnapshots, err := snapshots.ExtractSnapshots(allPages)
if err != nil {
t.Fatalf("Unable to extract snapshots: %v", err)
}
for _, snapshot := range allSnapshots {
tools.PrintResource(t, snapshot)
}
}
func TestSnapshotsCreateDelete(t *testing.T) {
client, err := clients.NewBlockStorageV2NoAuthClient()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
volume, err := CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer DeleteVolume(t, client, volume)
snapshot, err := CreateSnapshot(t, client, volume)
if err != nil {
t.Fatalf("Unable to create snapshot: %v", err)
}
defer DeleteSnapshot(t, client, snapshot)
newSnapshot, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve snapshot: %v", err)
}
tools.PrintResource(t, newSnapshot)
}

View File

@ -0,0 +1,52 @@
// +build acceptance blockstorage
package noauth
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
)
func TestVolumesList(t *testing.T) {
client, err := clients.NewBlockStorageV2NoAuthClient()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := volumes.List(client, volumes.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve volumes: %v", err)
}
allVolumes, err := volumes.ExtractVolumes(allPages)
if err != nil {
t.Fatalf("Unable to extract volumes: %v", err)
}
for _, volume := range allVolumes {
tools.PrintResource(t, volume)
}
}
func TestVolumesCreateDestroy(t *testing.T) {
client, err := clients.NewBlockStorageV2NoAuthClient()
if err != nil {
t.Fatalf("Unable to create blockstorage client: %v", err)
}
volume, err := CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer DeleteVolume(t, client, volume)
newVolume, err := volumes.Get(client, volume.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve volume: %v", err)
}
tools.PrintResource(t, newVolume)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
@ -479,3 +480,39 @@ func TestServersActionSuspend(t *testing.T) {
t.Fatal(err)
}
}
func TestServersActionLock(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
t.Logf("Attempting to Lock server %s", server.ID)
err = lockunlock.Lock(client, server.ID).ExtractErr()
if err != nil {
t.Fatal(err)
}
err = servers.Delete(client, server.ID).ExtractErr()
if err == nil {
t.Fatalf("Should not have been able to delete the server")
}
err = lockunlock.Unlock(client, server.ID).ExtractErr()
if err != nil {
t.Fatal(err)
}
err = WaitForComputeStatus(client, server, "ACTIVE")
if err != nil {
t.Fatal(err)
}
}

View File

@ -1,70 +0,0 @@
// +build acceptance db
package v1
import (
"os"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
th "github.com/gophercloud/gophercloud/testhelper"
)
func newClient(t *testing.T) *gophercloud.ServiceClient {
ao, err := openstack.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
client, err := openstack.AuthenticatedClient(ao)
th.AssertNoErr(t, err)
c, err := openstack.NewDBV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
th.AssertNoErr(t, err)
return c
}
type context struct {
test *testing.T
client *gophercloud.ServiceClient
instanceID string
DBIDs []string
users []string
}
func newContext(t *testing.T) context {
return context{
test: t,
client: newClient(t),
}
}
func (c context) Logf(msg string, args ...interface{}) {
if len(args) > 0 {
c.test.Logf(msg, args...)
} else {
c.test.Log(msg)
}
}
func (c context) AssertNoErr(err error) {
th.AssertNoErr(c.test, err)
}
func (c context) WaitUntilActive(id string) {
err := gophercloud.WaitFor(60, func() (bool, error) {
inst, err := instances.Get(c.client, id).Extract()
if err != nil {
return false, err
}
if inst.Status == "ACTIVE" {
return true, nil
}
return false, nil
})
c.AssertNoErr(err)
}

View File

@ -1,45 +0,0 @@
// +build acceptance db
package v1
import (
db "github.com/gophercloud/gophercloud/openstack/db/v1/databases"
"github.com/gophercloud/gophercloud/pagination"
)
func (c context) createDBs() {
opts := db.BatchCreateOpts{
db.CreateOpts{Name: "db1"},
db.CreateOpts{Name: "db2"},
db.CreateOpts{Name: "db3"},
}
err := db.Create(c.client, c.instanceID, opts).ExtractErr()
c.AssertNoErr(err)
c.Logf("Created three databases on instance %s: db1, db2, db3", c.instanceID)
}
func (c context) listDBs() {
c.Logf("Listing databases on instance %s", c.instanceID)
err := db.List(c.client, c.instanceID).EachPage(func(page pagination.Page) (bool, error) {
dbList, err := db.ExtractDBs(page)
c.AssertNoErr(err)
for _, db := range dbList {
c.Logf("DB: %#v", db)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) deleteDBs() {
for _, id := range []string{"db1", "db2", "db3"} {
err := db.Delete(c.client, c.instanceID, id).ExtractErr()
c.AssertNoErr(err)
c.Logf("Deleted DB %s", id)
}
}

View File

@ -0,0 +1,55 @@
// +build acceptance db
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/databases"
)
// Because it takes so long to create an instance,
// all tests will be housed in a single function.
func TestDatabases(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
client, err := clients.NewDBV1Client()
if err != nil {
t.Fatalf("Unable to create a DB client: %v", err)
}
// Create and Get an instance.
instance, err := CreateInstance(t, client)
if err != nil {
t.Fatalf("Unable to create instance: %v", err)
}
defer DeleteInstance(t, client, instance.ID)
// Create a database.
err = CreateDatabase(t, client, instance.ID)
if err != nil {
t.Fatalf("Unable to create database: %v", err)
}
// List all databases.
allPages, err := databases.List(client, instance.ID).AllPages()
if err != nil {
t.Fatalf("Unable to list databases: %v", err)
}
allDatabases, err := databases.ExtractDBs(allPages)
if err != nil {
t.Fatalf("Unable to extract databases: %v", err)
}
for _, db := range allDatabases {
tools.PrintResource(t, db)
}
defer DeleteDatabase(t, client, instance.ID, allDatabases[0].Name)
}

View File

@ -0,0 +1,145 @@
// Package v2 contains common functions for creating db resources for use
// in acceptance tests. See the `*_test.go` files for example usages.
package v1
import (
"fmt"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/databases"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
"github.com/gophercloud/gophercloud/openstack/db/v1/users"
)
// CreateDatabase will create a database with a randomly generated name.
// An error will be returned if the database was unable to be created.
func CreateDatabase(t *testing.T, client *gophercloud.ServiceClient, instanceID string) error {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create database: %s", name)
createOpts := databases.BatchCreateOpts{
databases.CreateOpts{
Name: name,
},
}
return databases.Create(client, instanceID, createOpts).ExtractErr()
}
// CreateInstance will create an instance with a randomly generated name.
// The flavor of the instance will be the value of the OS_FLAVOR_ID
// environment variable. The Datastore will be pulled from the
// OS_DATASTORE_TYPE_ID environment variable.
// An error will be returned if the instance was unable to be created.
func CreateInstance(t *testing.T, client *gophercloud.ServiceClient) (*instances.Instance, error) {
if testing.Short() {
t.Skip("Skipping test that requires instance creation in short mode.")
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
return nil, err
}
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create instance: %s", name)
createOpts := instances.CreateOpts{
FlavorRef: choices.FlavorID,
Size: 1,
Name: name,
Datastore: &instances.DatastoreOpts{
Type: choices.DBDatastoreType,
Version: choices.DBDatastoreVersion,
},
}
instance, err := instances.Create(client, createOpts).Extract()
if err != nil {
return instance, err
}
if err := WaitForInstanceStatus(client, instance, "ACTIVE"); err != nil {
return instance, err
}
return instances.Get(client, instance.ID).Extract()
}
// CreateUser will create a user with a randomly generated name.
// An error will be returned if the user was unable to be created.
func CreateUser(t *testing.T, client *gophercloud.ServiceClient, instanceID string) error {
name := tools.RandomString("ACPTTEST", 8)
password := tools.RandomString("", 8)
t.Logf("Attempting to create user: %s", name)
createOpts := users.BatchCreateOpts{
users.CreateOpts{
Name: name,
Password: password,
},
}
return users.Create(client, instanceID, createOpts).ExtractErr()
}
// DeleteDatabase deletes a database. A fatal error will occur if the database
// failed to delete. This works best when used as a deferred function.
func DeleteDatabase(t *testing.T, client *gophercloud.ServiceClient, instanceID, name string) {
t.Logf("Attempting to delete database: %s", name)
err := databases.Delete(client, instanceID, name).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete database %s: %s", name, err)
}
t.Logf("Deleted database: %s", name)
}
// DeleteInstance deletes an instance. A fatal error will occur if the instance
// failed to delete. This works best when used as a deferred function.
func DeleteInstance(t *testing.T, client *gophercloud.ServiceClient, id string) {
t.Logf("Attempting to delete instance: %s", id)
err := instances.Delete(client, id).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete instance %s: %s", id, err)
}
t.Logf("Deleted instance: %s", id)
}
// DeleteUser deletes a user. A fatal error will occur if the user
// failed to delete. This works best when used as a deferred function.
func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, instanceID, name string) {
t.Logf("Attempting to delete user: %s", name)
err := users.Delete(client, instanceID, name).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete users %s: %s", name, err)
}
t.Logf("Deleted users: %s", name)
}
// WaitForInstanceState will poll an instance's status until it either matches
// the specified status or the status becomes ERROR.
func WaitForInstanceStatus(
client *gophercloud.ServiceClient, instance *instances.Instance, status string) error {
return tools.WaitFor(func() (bool, error) {
latest, err := instances.Get(client, instance.ID).Extract()
if err != nil {
return false, err
}
if latest.Status == status {
return true, nil
}
if latest.Status == "ERROR" {
return false, fmt.Errorf("Instance in ERROR state")
}
return false, nil
})
}

View File

@ -1,31 +0,0 @@
// +build acceptance db
package v1
import (
"github.com/gophercloud/gophercloud/openstack/db/v1/flavors"
"github.com/gophercloud/gophercloud/pagination"
)
func (c context) listFlavors() {
c.Logf("Listing flavors")
err := flavors.List(c.client).EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := flavors.ExtractFlavors(page)
c.AssertNoErr(err)
for _, f := range flavorList {
c.Logf("Flavor: ID [%s] Name [%s] RAM [%d]", f.ID, f.Name, f.RAM)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) getFlavor() {
flavor, err := flavors.Get(c.client, "1").Extract()
c.Logf("Getting flavor %s", flavor.ID)
c.AssertNoErr(err)
}

View File

@ -0,0 +1,58 @@
// +build acceptance db
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/flavors"
)
func TestFlavorsList(t *testing.T) {
client, err := clients.NewDBV1Client()
if err != nil {
t.Fatalf("Unable to create a DB client: %v", err)
}
allPages, err := flavors.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve flavors: %v", err)
}
allFlavors, err := flavors.ExtractFlavors(allPages)
if err != nil {
t.Fatalf("Unable to extract flavors: %v", err)
}
for _, flavor := range allFlavors {
tools.PrintResource(t, &flavor)
}
}
func TestFlavorsGet(t *testing.T) {
client, err := clients.NewDBV1Client()
if err != nil {
t.Fatalf("Unable to create a DB client: %v", err)
}
allPages, err := flavors.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve flavors: %v", err)
}
allFlavors, err := flavors.ExtractFlavors(allPages)
if err != nil {
t.Fatalf("Unable to extract flavors: %v", err)
}
if len(allFlavors) > 0 {
flavor, err := flavors.Get(client, allFlavors[0].StrID).Extract()
if err != nil {
t.Fatalf("Unable to get flavor: %v", err)
}
tools.PrintResource(t, flavor)
}
}

View File

@ -1,138 +0,0 @@
// +build acceptance db
package v1
import (
"os"
"testing"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
"github.com/gophercloud/gophercloud/pagination"
th "github.com/gophercloud/gophercloud/testhelper"
)
const envDSType = "DATASTORE_TYPE_ID"
func TestRunner(t *testing.T) {
c := newContext(t)
// FLAVOR tests
c.listFlavors()
c.getFlavor()
// INSTANCE tests
c.createInstance()
c.listInstances()
c.getInstance()
c.isRootEnabled()
c.enableRootUser()
c.isRootEnabled()
c.restartInstance()
//c.resizeInstance()
//c.resizeVol()
// DATABASE tests
c.createDBs()
c.listDBs()
// USER tests
c.createUsers()
c.listUsers()
// TEARDOWN
c.deleteUsers()
c.deleteDBs()
c.deleteInstance()
}
func (c context) createInstance() {
if os.Getenv(envDSType) == "" {
c.test.Fatalf("%s must be set as an environment var", envDSType)
}
opts := instances.CreateOpts{
FlavorRef: "2",
Size: 5,
Name: tools.RandomString("gopher_db", 5),
Datastore: &instances.DatastoreOpts{Type: os.Getenv(envDSType)},
}
instance, err := instances.Create(c.client, opts).Extract()
th.AssertNoErr(c.test, err)
c.Logf("Restarting %s. Waiting...", instance.ID)
c.WaitUntilActive(instance.ID)
c.Logf("Created Instance %s", instance.ID)
c.instanceID = instance.ID
}
func (c context) listInstances() {
c.Logf("Listing instances")
err := instances.List(c.client).EachPage(func(page pagination.Page) (bool, error) {
instanceList, err := instances.ExtractInstances(page)
c.AssertNoErr(err)
for _, i := range instanceList {
c.Logf("Instance: ID [%s] Name [%s] Status [%s] VolSize [%d] Datastore Type [%s]",
i.ID, i.Name, i.Status, i.Volume.Size, i.Datastore.Type)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) getInstance() {
instance, err := instances.Get(c.client, c.instanceID).Extract()
c.AssertNoErr(err)
c.Logf("Getting instance: %s", instance.ID)
}
func (c context) deleteInstance() {
err := instances.Delete(c.client, c.instanceID).ExtractErr()
c.AssertNoErr(err)
c.Logf("Deleted instance %s", c.instanceID)
}
func (c context) enableRootUser() {
_, err := instances.EnableRootUser(c.client, c.instanceID).Extract()
c.AssertNoErr(err)
c.Logf("Enabled root user on %s", c.instanceID)
}
func (c context) isRootEnabled() {
enabled, err := instances.IsRootEnabled(c.client, c.instanceID)
c.AssertNoErr(err)
c.Logf("Is root enabled? %d", enabled)
}
func (c context) restartInstance() {
id := c.instanceID
err := instances.Restart(c.client, id).ExtractErr()
c.AssertNoErr(err)
c.Logf("Restarting %s. Waiting...", id)
c.WaitUntilActive(id)
c.Logf("Restarted %s", id)
}
func (c context) resizeInstance() {
id := c.instanceID
err := instances.Resize(c.client, id, "3").ExtractErr()
c.AssertNoErr(err)
c.Logf("Resizing %s. Waiting...", id)
c.WaitUntilActive(id)
c.Logf("Resized %s with flavorRef %s", id, "2")
}
func (c context) resizeVol() {
id := c.instanceID
err := instances.ResizeVolume(c.client, id, 4).ExtractErr()
c.AssertNoErr(err)
c.Logf("Resizing volume of %s. Waiting...", id)
c.WaitUntilActive(id)
c.Logf("Resized the volume of %s to %d GB", id, 2)
}

View File

@ -0,0 +1,71 @@
// +build acceptance db
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
)
// Because it takes so long to create an instance,
// all tests will be housed in a single function.
func TestInstances(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
client, err := clients.NewDBV1Client()
if err != nil {
t.Fatalf("Unable to create a DB client: %v", err)
}
// Create and Get an instance.
instance, err := CreateInstance(t, client)
if err != nil {
t.Fatalf("Unable to create instance: %v", err)
}
defer DeleteInstance(t, client, instance.ID)
tools.PrintResource(t, &instance)
// List all instances.
allPages, err := instances.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list instances: %v", err)
}
allInstances, err := instances.ExtractInstances(allPages)
if err != nil {
t.Fatalf("Unable to extract instances: %v", err)
}
for _, instance := range allInstances {
tools.PrintResource(t, instance)
}
// Enable root user.
_, err = instances.EnableRootUser(client, instance.ID).Extract()
if err != nil {
t.Fatalf("Unable to enable root user: %v", err)
}
enabled, err := instances.IsRootEnabled(client, instance.ID).Extract()
if err != nil {
t.Fatalf("Unable to check if root user is enabled: %v", err)
}
t.Logf("Root user is enabled: %t", enabled)
// Restart
err = instances.Restart(client, instance.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to restart instance: %v", err)
}
err = WaitForInstanceStatus(client, instance, "ACTIVE")
if err != nil {
t.Fatalf("Unable to restart instance: %v", err)
}
}

View File

@ -1,70 +0,0 @@
// +build acceptance db
package v1
import (
"github.com/gophercloud/gophercloud/acceptance/tools"
db "github.com/gophercloud/gophercloud/openstack/db/v1/databases"
u "github.com/gophercloud/gophercloud/openstack/db/v1/users"
"github.com/gophercloud/gophercloud/pagination"
)
func (c context) createUsers() {
users := []string{
tools.RandomString("user_", 5),
tools.RandomString("user_", 5),
tools.RandomString("user_", 5),
}
db1 := db.CreateOpts{Name: "db1"}
db2 := db.CreateOpts{Name: "db2"}
db3 := db.CreateOpts{Name: "db3"}
opts := u.BatchCreateOpts{
u.CreateOpts{
Name: users[0],
Password: tools.RandomString("", 5),
Databases: db.BatchCreateOpts{db1, db2, db3},
},
u.CreateOpts{
Name: users[1],
Password: tools.RandomString("", 5),
Databases: db.BatchCreateOpts{db1, db2},
},
u.CreateOpts{
Name: users[2],
Password: tools.RandomString("", 5),
Databases: db.BatchCreateOpts{db3},
},
}
err := u.Create(c.client, c.instanceID, opts).ExtractErr()
c.AssertNoErr(err)
c.Logf("Created three users on instance %s: %s, %s, %s", c.instanceID, users[0], users[1], users[2])
c.users = users
}
func (c context) listUsers() {
c.Logf("Listing databases on instance %s", c.instanceID)
err := db.List(c.client, c.instanceID).EachPage(func(page pagination.Page) (bool, error) {
dbList, err := db.ExtractDBs(page)
c.AssertNoErr(err)
for _, db := range dbList {
c.Logf("DB: %#v", db)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) deleteUsers() {
for _, id := range c.DBIDs {
err := db.Delete(c.client, c.instanceID, id).ExtractErr()
c.AssertNoErr(err)
c.Logf("Deleted DB %s", id)
}
}

View File

@ -0,0 +1,54 @@
// +build acceptance db
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/users"
)
// Because it takes so long to create an instance,
// all tests will be housed in a single function.
func TestUsers(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
client, err := clients.NewDBV1Client()
if err != nil {
t.Fatalf("Unable to create a DB client: %v", err)
}
// Create and Get an instance.
instance, err := CreateInstance(t, client)
if err != nil {
t.Fatalf("Unable to create instance: %v", err)
}
defer DeleteInstance(t, client, instance.ID)
// Create a user.
err = CreateUser(t, client, instance.ID)
if err != nil {
t.Fatalf("Unable to create user: %v", err)
}
// List all users.
allPages, err := users.List(client, instance.ID).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
for _, user := range allUsers {
tools.PrintResource(t, user)
}
defer DeleteUser(t, client, instance.ID, allUsers[0].Name)
}

View File

@ -0,0 +1,96 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/domains"
)
func TestDomainsList(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
var iTrue bool = true
listOpts := domains.ListOpts{
Enabled: &iTrue,
}
allPages, err := domains.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list domains: %v", err)
}
allDomains, err := domains.ExtractDomains(allPages)
if err != nil {
t.Fatalf("Unable to extract domains: %v", err)
}
for _, domain := range allDomains {
tools.PrintResource(t, domain)
}
}
func TestDomainsGet(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allPages, err := domains.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list domains: %v", err)
}
allDomains, err := domains.ExtractDomains(allPages)
if err != nil {
t.Fatalf("Unable to extract domains: %v", err)
}
domain := allDomains[0]
p, err := domains.Get(client, domain.ID).Extract()
if err != nil {
t.Fatalf("Unable to get domain: %v", err)
}
tools.PrintResource(t, p)
}
func TestDomainsCRUD(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
var iTrue bool = true
createOpts := domains.CreateOpts{
Description: "Testing Domain",
Enabled: &iTrue,
}
domain, err := CreateDomain(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create domain: %v", err)
}
defer DeleteDomain(t, client, domain.ID)
tools.PrintResource(t, domain)
var iFalse bool = false
updateOpts := domains.UpdateOpts{
Description: "Staging Test Domain",
Enabled: &iFalse,
}
newDomain, err := domains.Update(client, domain.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update domain: %v", err)
}
tools.PrintResource(t, newDomain)
}

View File

@ -0,0 +1,79 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
)
func TestGroupCRUD(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
createOpts := groups.CreateOpts{
Name: "testgroup",
DomainID: "default",
Extra: map[string]interface{}{
"email": "testgroup@example.com",
},
}
// Create Group in the default domain
group, err := CreateGroup(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create group: %v", err)
}
defer DeleteGroup(t, client, group.ID)
tools.PrintResource(t, group)
tools.PrintResource(t, group.Extra)
updateOpts := groups.UpdateOpts{
Description: "Test Users",
Extra: map[string]interface{}{
"email": "thetestgroup@example.com",
},
}
newGroup, err := groups.Update(client, group.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update group: %v", err)
}
tools.PrintResource(t, newGroup)
tools.PrintResource(t, newGroup.Extra)
listOpts := groups.ListOpts{
DomainID: "default",
}
// List all Groups in default domain
allPages, err := groups.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list groups: %v", err)
}
allGroups, err := groups.ExtractGroups(allPages)
if err != nil {
t.Fatalf("Unable to extract groups: %v", err)
}
for _, g := range allGroups {
tools.PrintResource(t, g)
tools.PrintResource(t, g.Extra)
}
// Get the recently created group by ID
p, err := groups.Get(client, group.ID).Extract()
if err != nil {
t.Fatalf("Unable to get group: %v", err)
}
tools.PrintResource(t, p)
}

View File

@ -5,7 +5,10 @@ import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/domains"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/openstack/identity/v3/roles"
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
)
@ -36,7 +39,7 @@ func CreateProject(t *testing.T, client *gophercloud.ServiceClient, c *projects.
return project, nil
}
// CreateUser will create a project with a random name.
// CreateUser will create a user with a random name.
// It takes an optional createOpts parameter since creating a user
// has so many options. An error will be returned if the user was
// unable to be created.
@ -63,6 +66,87 @@ func CreateUser(t *testing.T, client *gophercloud.ServiceClient, c *users.Create
return user, nil
}
// CreateGroup will create a group with a random name.
// It takes an optional createOpts parameter since creating a group
// has so many options. An error will be returned if the group was
// unable to be created.
func CreateGroup(t *testing.T, client *gophercloud.ServiceClient, c *groups.CreateOpts) (*groups.Group, error) {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create group: %s", name)
var createOpts groups.CreateOpts
if c != nil {
createOpts = *c
} else {
createOpts = groups.CreateOpts{}
}
createOpts.Name = name
group, err := groups.Create(client, createOpts).Extract()
if err != nil {
return group, err
}
t.Logf("Successfully created group %s with ID %s", name, group.ID)
return group, nil
}
// CreateDomain will create a domain with a random name.
// It takes an optional createOpts parameter since creating a domain
// has many options. An error will be returned if the domain was
// unable to be created.
func CreateDomain(t *testing.T, client *gophercloud.ServiceClient, c *domains.CreateOpts) (*domains.Domain, error) {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create domain: %s", name)
var createOpts domains.CreateOpts
if c != nil {
createOpts = *c
} else {
createOpts = domains.CreateOpts{}
}
createOpts.Name = name
domain, err := domains.Create(client, createOpts).Extract()
if err != nil {
return domain, err
}
t.Logf("Successfully created domain %s with ID %s", name, domain.ID)
return domain, nil
}
// CreateRole will create a role with a random name.
// It takes an optional createOpts parameter since creating a role
// has so many options. An error will be returned if the role was
// unable to be created.
func CreateRole(t *testing.T, client *gophercloud.ServiceClient, c *roles.CreateOpts) (*roles.Role, error) {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create role: %s", name)
var createOpts roles.CreateOpts
if c != nil {
createOpts = *c
} else {
createOpts = roles.CreateOpts{}
}
createOpts.Name = name
role, err := roles.Create(client, createOpts).Extract()
if err != nil {
return role, err
}
t.Logf("Successfully created role %s with ID %s", name, role.ID)
return role, nil
}
// DeleteProject will delete a project by ID. A fatal error will occur if
// the project ID failed to be deleted. This works best when using it as
// a deferred function.
@ -81,8 +165,82 @@ func DeleteProject(t *testing.T, client *gophercloud.ServiceClient, projectID st
func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
err := users.Delete(client, userID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete user %s: %v", userID, err)
t.Fatalf("Unable to delete user with ID %s: %v", userID, err)
}
t.Logf("Deleted user: %s", userID)
t.Logf("Deleted user with ID: %s", userID)
}
// DeleteGroup will delete a group by ID. A fatal error will occur if
// the group failed to be deleted. This works best when using it as
// a deferred function.
func DeleteGroup(t *testing.T, client *gophercloud.ServiceClient, groupID string) {
err := groups.Delete(client, groupID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete group %s: %v", groupID, err)
}
t.Logf("Deleted group: %s", groupID)
}
// DeleteDomain will delete a domain by ID. A fatal error will occur if
// the project ID failed to be deleted. This works best when using it as
// a deferred function.
func DeleteDomain(t *testing.T, client *gophercloud.ServiceClient, domainID string) {
err := domains.Delete(client, domainID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete domain %s: %v", domainID, err)
}
t.Logf("Deleted domain: %s", domainID)
}
// DeleteRole will delete a role by ID. A fatal error will occur if
// the role failed to be deleted. This works best when using it as
// a deferred function.
func DeleteRole(t *testing.T, client *gophercloud.ServiceClient, roleID string) {
err := roles.Delete(client, roleID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete role %s: %v", roleID, err)
}
t.Logf("Deleted role: %s", roleID)
}
// UnassignRole will delete a role assigned to a user/group on a project/domain
// A fatal error will occur if it fails to delete the assignment.
// This works best when using it as a deferred function.
func UnassignRole(t *testing.T, client *gophercloud.ServiceClient, roleID string, opts *roles.UnassignOpts) {
err := roles.Unassign(client, roleID, *opts).ExtractErr()
if err != nil {
t.Fatalf("Unable to unassign a role %v on context %+v: %v", roleID, *opts, err)
}
t.Logf("Unassigned the role %v on context %+v", roleID, *opts)
}
// FindRole finds all roles that the current authenticated client has access
// to and returns the first one found. An error will be returned if the lookup
// was unsuccessful.
func FindRole(t *testing.T, client *gophercloud.ServiceClient) (*roles.Role, error) {
t.Log("Attempting to find a role")
var role *roles.Role
allPages, err := roles.List(client, nil).AllPages()
if err != nil {
return nil, err
}
allRoles, err := roles.ExtractRoles(allPages)
if err != nil {
return nil, err
}
for _, r := range allRoles {
role = &r
break
}
t.Logf("Successfully found a role %s with ID %s", role.Name, role.ID)
return role, nil
}

View File

@ -0,0 +1,328 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/domains"
"github.com/gophercloud/gophercloud/openstack/identity/v3/roles"
)
func TestRolesList(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
listOpts := roles.ListOpts{
DomainID: "default",
}
allPages, err := roles.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list roles: %v", err)
}
allRoles, err := roles.ExtractRoles(allPages)
if err != nil {
t.Fatalf("Unable to extract roles: %v", err)
}
for _, role := range allRoles {
tools.PrintResource(t, role)
}
}
func TestRolesGet(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
role, err := FindRole(t, client)
if err != nil {
t.Fatalf("Unable to find a role: %v", err)
}
p, err := roles.Get(client, role.ID).Extract()
if err != nil {
t.Fatalf("Unable to get role: %v", err)
}
tools.PrintResource(t, p)
}
func TestRoleCRUD(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
createOpts := roles.CreateOpts{
Name: "testrole",
DomainID: "default",
Extra: map[string]interface{}{
"description": "test role description",
},
}
// Create Role in the default domain
role, err := CreateRole(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create role: %v", err)
}
defer DeleteRole(t, client, role.ID)
tools.PrintResource(t, role)
tools.PrintResource(t, role.Extra)
updateOpts := roles.UpdateOpts{
Extra: map[string]interface{}{
"description": "updated test role description",
},
}
newRole, err := roles.Update(client, role.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update role: %v", err)
}
tools.PrintResource(t, newRole)
tools.PrintResource(t, newRole.Extra)
}
func TestRoleAssignToUserOnProject(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an indentity client: %v", err)
}
project, err := CreateProject(t, client, nil)
if err != nil {
t.Fatal("Unable to create a project")
}
defer DeleteProject(t, client, project.ID)
role, err := FindRole(t, client)
if err != nil {
t.Fatalf("Unable to get a role: %v", err)
}
user, err := CreateUser(t, client, nil)
if err != nil {
t.Fatalf("Unable to create user: %v", err)
}
defer DeleteUser(t, client, user.ID)
t.Logf("Attempting to assign a role %s to a user %s on a project %s", role.Name, user.Name, project.Name)
err = roles.Assign(client, role.ID, roles.AssignOpts{
UserID: user.ID,
ProjectID: project.ID,
}).ExtractErr()
if err != nil {
t.Fatalf("Unable to assign a role to a user on a project: %v", err)
}
t.Logf("Successfully assigned a role %s to a user %s on a project %s", role.Name, user.Name, project.Name)
defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{
UserID: user.ID,
ProjectID: project.ID,
})
allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{
RoleID: role.ID,
ScopeProjectID: project.ID,
UserID: user.ID,
}).AllPages()
if err != nil {
t.Fatalf("Unable to list role assignments: %v", err)
}
allRoleAssignments, err := roles.ExtractRoleAssignments(allPages)
if err != nil {
t.Fatalf("Unable to extract role assignments: %v", err)
}
t.Logf("Role assignments of user %s on project %s:", user.Name, project.Name)
for _, roleAssignment := range allRoleAssignments {
tools.PrintResource(t, roleAssignment)
}
}
func TestRoleAssignToUserOnDomain(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an indentity client: %v", err)
}
domain, err := CreateDomain(t, client, &domains.CreateOpts{
Enabled: gophercloud.Disabled,
})
if err != nil {
t.Fatal("Unable to create a domain")
}
defer DeleteDomain(t, client, domain.ID)
role, err := FindRole(t, client)
if err != nil {
t.Fatalf("Unable to get a role: %v", err)
}
user, err := CreateUser(t, client, nil)
if err != nil {
t.Fatalf("Unable to create user: %v", err)
}
defer DeleteUser(t, client, user.ID)
t.Logf("Attempting to assign a role %s to a user %s on a domain %s", role.Name, user.Name, domain.Name)
err = roles.Assign(client, role.ID, roles.AssignOpts{
UserID: user.ID,
DomainID: domain.ID,
}).ExtractErr()
if err != nil {
t.Fatalf("Unable to assign a role to a user on a domain: %v", err)
}
t.Logf("Successfully assigned a role %s to a user %s on a domain %s", role.Name, user.Name, domain.Name)
defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{
UserID: user.ID,
DomainID: domain.ID,
})
allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{
RoleID: role.ID,
ScopeDomainID: domain.ID,
UserID: user.ID,
}).AllPages()
if err != nil {
t.Fatalf("Unable to list role assignments: %v", err)
}
allRoleAssignments, err := roles.ExtractRoleAssignments(allPages)
if err != nil {
t.Fatalf("Unable to extract role assignments: %v", err)
}
t.Logf("Role assignments of user %s on domain %s:", user.Name, domain.Name)
for _, roleAssignment := range allRoleAssignments {
tools.PrintResource(t, roleAssignment)
}
}
func TestRoleAssignToGroupOnDomain(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an indentity client: %v", err)
}
domain, err := CreateDomain(t, client, &domains.CreateOpts{
Enabled: gophercloud.Disabled,
})
if err != nil {
t.Fatal("Unable to create a domain")
}
defer DeleteDomain(t, client, domain.ID)
role, err := FindRole(t, client)
if err != nil {
t.Fatalf("Unable to get a role: %v", err)
}
group, err := CreateGroup(t, client, nil)
if err != nil {
t.Fatalf("Unable to create group: %v", err)
}
defer DeleteGroup(t, client, group.ID)
t.Logf("Attempting to assign a role %s to a group %s on a domain %s", role.Name, group.Name, domain.Name)
err = roles.Assign(client, role.ID, roles.AssignOpts{
GroupID: group.ID,
DomainID: domain.ID,
}).ExtractErr()
if err != nil {
t.Fatalf("Unable to assign a role to a group on a domain: %v", err)
}
t.Logf("Successfully assigned a role %s to a group %s on a domain %s", role.Name, group.Name, domain.Name)
defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{
GroupID: group.ID,
DomainID: domain.ID,
})
allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{
RoleID: role.ID,
ScopeDomainID: domain.ID,
GroupID: group.ID,
}).AllPages()
if err != nil {
t.Fatalf("Unable to list role assignments: %v", err)
}
allRoleAssignments, err := roles.ExtractRoleAssignments(allPages)
if err != nil {
t.Fatalf("Unable to extract role assignments: %v", err)
}
t.Logf("Role assignments of group %s on domain %s:", group.Name, domain.Name)
for _, roleAssignment := range allRoleAssignments {
tools.PrintResource(t, roleAssignment)
}
}
func TestRoleAssignToGroupOnProject(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an indentity client: %v", err)
}
project, err := CreateProject(t, client, nil)
if err != nil {
t.Fatal("Unable to create a project")
}
defer DeleteProject(t, client, project.ID)
role, err := FindRole(t, client)
if err != nil {
t.Fatalf("Unable to get a role: %v", err)
}
group, err := CreateGroup(t, client, nil)
if err != nil {
t.Fatalf("Unable to create group: %v", err)
}
defer DeleteGroup(t, client, group.ID)
t.Logf("Attempting to assign a role %s to a group %s on a project %s", role.Name, group.Name, project.Name)
err = roles.Assign(client, role.ID, roles.AssignOpts{
GroupID: group.ID,
ProjectID: project.ID,
}).ExtractErr()
if err != nil {
t.Fatalf("Unable to assign a role to a group on a project: %v", err)
}
t.Logf("Successfully assigned a role %s to a group %s on a project %s", role.Name, group.Name, project.Name)
defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{
GroupID: group.ID,
ProjectID: project.ID,
})
allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{
RoleID: role.ID,
ScopeProjectID: project.ID,
GroupID: group.ID,
}).AllPages()
if err != nil {
t.Fatalf("Unable to list role assignments: %v", err)
}
allRoleAssignments, err := roles.ExtractRoleAssignments(allPages)
if err != nil {
t.Fatalf("Unable to extract role assignments: %v", err)
}
t.Logf("Role assignments of group %s on project %s:", group.Name, project.Name)
for _, roleAssignment := range allRoleAssignments {
tools.PrintResource(t, roleAssignment)
}
}

View File

@ -8,6 +8,7 @@ import (
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
)
@ -154,3 +155,68 @@ func TestUsersListGroups(t *testing.T) {
tools.PrintResource(t, group.Extra)
}
}
func TestUsersListProjects(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allUserPages, err := users.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allUserPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
user := allUsers[0]
allProjectPages, err := users.ListProjects(client, user.ID).AllPages()
if err != nil {
t.Fatalf("Unable to list projects: %v", err)
}
allProjects, err := projects.ExtractProjects(allProjectPages)
if err != nil {
t.Fatalf("Unable to extract projects: %v", err)
}
for _, project := range allProjects {
tools.PrintResource(t, project)
}
}
func TestUsersListInGroup(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allGroupPages, err := groups.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list groups: %v", err)
}
allGroups, err := groups.ExtractGroups(allGroupPages)
if err != nil {
t.Fatalf("Unable to extract groups: %v", err)
}
group := allGroups[0]
allUserPages, err := users.ListInGroup(client, group.ID, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allUserPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
for _, user := range allUsers {
tools.PrintResource(t, user)
tools.PrintResource(t, user.Extra)
}
}

View File

@ -28,8 +28,8 @@ func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*ne
}
createOpts := external.CreateOptsExt{
networkCreateOpts,
&isExternal,
CreateOptsBuilder: networkCreateOpts,
External: &isExternal,
}
network, err := networks.Create(client, createOpts).Extract()

View File

@ -46,8 +46,10 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou
t.Logf("Attempting to create external router: %s", routerName)
adminStateUp := true
enableSNAT := false
gatewayInfo := routers.GatewayInfo{
NetworkID: choices.ExternalNetworkID,
NetworkID: choices.ExternalNetworkID,
EnableSNAT: &enableSNAT,
}
createOpts := routers.CreateOpts{

View File

@ -34,7 +34,7 @@ func TestLayer3RouterList(t *testing.T) {
}
}
func TestLayer3RouterCreateDelete(t *testing.T) {
func TestLayer3ExternalRouterCreateDelete(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)

View File

@ -0,0 +1,26 @@
/*
Package volumetenants provides the ability to extend a volume result with
tenant/project information. Example:
type VolumeWithTenant struct {
volumes.Volume
volumetenants.VolumeTenantExt
}
var allVolumes []VolumeWithTenant
allPages, err := volumes.List(client, nil).AllPages()
if err != nil {
panic("Unable to retrieve volumes: %s", err)
}
err = volumes.ExtractVolumesInto(allPages, &allVolumes)
if err != nil {
panic("Unable to extract volumes: %s", err)
}
for _, volume := range allVolumes {
fmt.Println(volume.TenantID)
}
*/
package volumetenants

View File

@ -1,12 +1,7 @@
package volumetenants
// VolumeExt is an extension to the base Volume object
type VolumeExt struct {
// VolumeTenantExt is an extension to the base Volume object
type VolumeTenantExt struct {
// TenantID is the id of the project that owns the volume.
TenantID string `json:"os-vol-tenant-attr:tenant_id"`
}
// UnmarshalJSON to override default
func (r *VolumeExt) UnmarshalJSON(b []byte) error {
return nil
}

View File

@ -0,0 +1,17 @@
/*
Package noauth creates a "noauth" *gophercloud.ServiceClient for use in Cinder
environments configured with the noauth authentication middleware.
Example of Creating a noauth Service Client
provider, err := noauth.NewClient(gophercloud.AuthOptions{
Username: os.Getenv("OS_USERNAME"),
TenantName: os.Getenv("OS_TENANT_NAME"),
})
client, err := noauth.NewBlockStorageV2(provider, noauth.EndpointOpts{
CinderEndpoint: os.Getenv("CINDER_ENDPOINT"),
})
An example of a CinderEndpoint would be: http://example.com:8776/v2,
*/
package noauth

View File

@ -0,0 +1,55 @@
package noauth
import (
"fmt"
"strings"
"github.com/gophercloud/gophercloud"
)
// EndpointOpts specifies a "noauth" Cinder Endpoint.
type EndpointOpts struct {
// CinderEndpoint [required] is currently only used with "noauth" Cinder.
// A cinder endpoint with "auth_strategy=noauth" is necessary, for example:
// http://example.com:8776/v2.
CinderEndpoint string
}
// NewClient prepares an unauthenticated ProviderClient instance.
func NewClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
if options.Username == "" {
options.Username = "admin"
}
if options.TenantName == "" {
options.TenantName = "admin"
}
client := &gophercloud.ProviderClient{
TokenID: fmt.Sprintf("%s:%s", options.Username, options.TenantName),
}
return client, nil
}
func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) {
sc := new(gophercloud.ServiceClient)
if eo.CinderEndpoint == "" {
return nil, fmt.Errorf("CinderEndpoint is required")
}
token := strings.Split(client.TokenID, ":")
if len(token) != 2 {
return nil, fmt.Errorf("Malformed noauth token")
}
endpoint := fmt.Sprintf("%s%s", gophercloud.NormalizeURL(eo.CinderEndpoint), token[1])
sc.Endpoint = gophercloud.NormalizeURL(endpoint)
sc.ProviderClient = client
return sc, nil
}
// NewBlockStorageV2 creates a ServiceClient that may be used to access a
// "noauth" block storage service.
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo)
}

View File

@ -0,0 +1,2 @@
// noauth unit tests
package testing

View File

@ -0,0 +1,19 @@
package testing
// NoAuthResult is the expected result of the noauth Service Client
type NoAuthResult struct {
TokenID string
Endpoint string
}
var naTestResult = NoAuthResult{
TokenID: "user:test",
Endpoint: "http://cinder:8776/v2/test/",
}
var naResult = NoAuthResult{
TokenID: "admin:admin",
Endpoint: "http://cinder:8776/v2/admin/",
}
var errorResult = "CinderEndpoint is required"

View File

@ -0,0 +1,38 @@
package testing
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/blockstorage/noauth"
th "github.com/gophercloud/gophercloud/testhelper"
)
func TestNoAuth(t *testing.T) {
ao := gophercloud.AuthOptions{
Username: "user",
TenantName: "test",
}
provider, err := noauth.NewClient(ao)
th.AssertNoErr(t, err)
noauthClient, err := noauth.NewBlockStorageV2(provider, noauth.EndpointOpts{
CinderEndpoint: "http://cinder:8776/v2",
})
th.AssertNoErr(t, err)
th.AssertEquals(t, naTestResult.Endpoint, noauthClient.Endpoint)
th.AssertEquals(t, naTestResult.TokenID, noauthClient.TokenID)
ao2 := gophercloud.AuthOptions{}
provider2, err := noauth.NewClient(ao2)
th.AssertNoErr(t, err)
noauthClient2, err := noauth.NewBlockStorageV2(provider2, noauth.EndpointOpts{
CinderEndpoint: "http://cinder:8776/v2/",
})
th.AssertNoErr(t, err)
th.AssertEquals(t, naResult.Endpoint, noauthClient2.Endpoint)
th.AssertEquals(t, naResult.TokenID, noauthClient2.TokenID)
errTest, err := noauth.NewBlockStorageV2(provider2, noauth.EndpointOpts{})
_ = errTest
th.AssertEquals(t, errorResult, err.Error())
}

View File

@ -102,7 +102,7 @@ func TestListAllWithExtensions(t *testing.T) {
type VolumeWithExt struct {
volumes.Volume
volumetenants.VolumeExt
volumetenants.VolumeTenantExt
}
allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages()
@ -244,7 +244,7 @@ func TestGetWithExtensions(t *testing.T) {
var s struct {
volumes.Volume
volumetenants.VolumeExt
volumetenants.VolumeTenantExt
}
err := volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s)
th.AssertNoErr(t, err)

View File

@ -4,6 +4,8 @@ import (
"fmt"
"net/url"
"reflect"
"regexp"
"strings"
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
@ -12,8 +14,13 @@ import (
)
const (
v20 = "v2.0"
v30 = "v3.0"
// v2 represents Keystone v2.
// It should never increase beyond 2.0.
v2 = "v2.0"
// v3 represents Keystone v3.
// The version can be anything from v3 to v3.x.
v3 = "v3"
)
/*
@ -35,24 +42,25 @@ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
if err != nil {
return nil, err
}
hadPath := u.Path != ""
u.Path, u.RawQuery, u.Fragment = "", "", ""
base := u.String()
u.RawQuery, u.Fragment = "", ""
var base string
versionRe := regexp.MustCompile("v[0-9.]+/?")
if version := versionRe.FindString(u.Path); version != "" {
base = strings.Replace(u.String(), version, "", -1)
} else {
base = u.String()
}
endpoint = gophercloud.NormalizeURL(endpoint)
base = gophercloud.NormalizeURL(base)
if hadPath {
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: endpoint,
}, nil
}
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: "",
IdentityEndpoint: endpoint,
}, nil
}
/*
@ -92,8 +100,8 @@ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider
// supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
{ID: v30, Priority: 30, Suffix: "/v3/"},
{ID: v2, Priority: 20, Suffix: "/v2.0/"},
{ID: v3, Priority: 30, Suffix: "/v3/"},
}
chosen, endpoint, err := utils.ChooseVersion(client, versions)
@ -102,9 +110,9 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp
}
switch chosen.ID {
case v20:
case v2:
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
case v30:
case v3:
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
default:
// The switch statement must be out of date from the versions list.
@ -241,6 +249,13 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
}
}
// Ensure endpoint still has a suffix of v3.
// This is because EndpointLocator might have found a versionless
// endpoint and requests will fail unless targeted at /v3.
if !strings.HasSuffix(endpoint, "v3/") {
endpoint = endpoint + "v3/"
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,

View File

@ -69,7 +69,7 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
}
// Delete will permanently delete a rule the project's default security group.
func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(resourceURL(client, id), nil)
return
}

View File

@ -65,3 +65,9 @@ func (r commonResult) Extract() (*DefaultRule, error) {
err := r.ExtractInto(&s)
return &s.DefaultRule, err
}
// DeleteResult is the response from a delete operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,13 @@
/*
Package evacuate provides functionality to evacuates servers that have been
provisioned by the OpenStack Compute service from a failed host to a new host.
Example to Evacuate a Server from a Host
serverID := "b16ba811-199d-4ffd-8839-ba96c1185a67"
err := evacuate.Evacuate(computeClient, serverID, evacuate.EvacuateOpts{}).ExtractErr()
if err != nil {
panic(err)
}
*/
package evacuate

View File

@ -0,0 +1,41 @@
package evacuate
import (
"github.com/gophercloud/gophercloud"
)
// EvacuateOptsBuilder allows extensions to add additional parameters to the
// the Evacuate request.
type EvacuateOptsBuilder interface {
ToEvacuateMap() (map[string]interface{}, error)
}
// EvacuateOpts specifies Evacuate action parameters.
type EvacuateOpts struct {
// The name of the host to which the server is evacuated
Host string `json:"host,omitempty"`
// Indicates whether server is on shared storage
OnSharedStorage bool `json:"onSharedStorage"`
// An administrative password to access the evacuated server
AdminPass string `json:"adminPass,omitempty"`
}
// ToServerGroupCreateMap constructs a request body from CreateOpts.
func (opts EvacuateOpts) ToEvacuateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "evacuate")
}
// Evacuate will Evacuate a failed instance to another host.
func Evacuate(client *gophercloud.ServiceClient, id string, opts EvacuateOptsBuilder) (r EvacuateResult) {
b, err := opts.ToEvacuateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}

View File

@ -0,0 +1,23 @@
package evacuate
import (
"github.com/gophercloud/gophercloud"
)
// EvacuateResult is the response from an Evacuate operation.
//Call its ExtractAdminPass method to retrieve the admin password of the instance.
//The admin password will be an empty string if the cloud is not configured to inject admin passwords..
type EvacuateResult struct {
gophercloud.Result
}
func (r EvacuateResult) ExtractAdminPass() (string, error) {
var s struct {
AdminPass string `json:"adminPass"`
}
err := r.ExtractInto(&s)
if err != nil && err.Error() == "EOF" {
return "", nil
}
return s.AdminPass, err
}

View File

@ -0,0 +1,2 @@
// compute_extensions_evacuate_v2
package testing

View File

@ -0,0 +1,83 @@
package testing
import (
"fmt"
"net/http"
"testing"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func mockEvacuateResponse(t *testing.T, id string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, `
{
"evacuate": {
"adminPass": "MySecretPass",
"host": "derp",
"onSharedStorage": false
}
}
`)
w.WriteHeader(http.StatusOK)
})
}
func mockEvacuateResponseWithHost(t *testing.T, id string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, `
{
"evacuate": {
"host": "derp",
"onSharedStorage": false
}
}
`)
w.WriteHeader(http.StatusOK)
})
}
func mockEvacuateResponseWithNoOpts(t *testing.T, id string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, `
{
"evacuate": {
"onSharedStorage": false
}
}
`)
w.WriteHeader(http.StatusOK)
})
}
const EvacuateResponse = `
{
"adminPass": "MySecretPass"
}
`
func mockEvacuateAdminpassResponse(t *testing.T, id string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, `
{
"evacuate": {
"onSharedStorage": false
}
}
`)
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, EvacuateResponse)
})
}

View File

@ -0,0 +1,60 @@
package testing
import (
"testing"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func TestEvacuate(t *testing.T) {
const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67"
th.SetupHTTP()
defer th.TeardownHTTP()
mockEvacuateResponse(t, serverID)
_, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{
Host: "derp",
AdminPass: "MySecretPass",
OnSharedStorage: false,
}).ExtractAdminPass()
th.AssertNoErr(t, err)
}
func TestEvacuateWithHost(t *testing.T) {
const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67"
th.SetupHTTP()
defer th.TeardownHTTP()
mockEvacuateResponseWithHost(t, serverID)
_, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{
Host: "derp",
}).ExtractAdminPass()
th.AssertNoErr(t, err)
}
func TestEvacuateWithNoOpts(t *testing.T) {
const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67"
th.SetupHTTP()
defer th.TeardownHTTP()
mockEvacuateResponseWithNoOpts(t, serverID)
_, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}).ExtractAdminPass()
th.AssertNoErr(t, err)
}
func TestEvacuateAdminpassResponse(t *testing.T) {
const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67"
th.SetupHTTP()
defer th.TeardownHTTP()
mockEvacuateAdminpassResponse(t, serverID)
actual, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}).ExtractAdminPass()
th.CheckEquals(t, "MySecretPass", actual)
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,9 @@
package evacuate
import (
"github.com/gophercloud/gophercloud"
)
func actionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "action")
}

View File

@ -0,0 +1,19 @@
/*
Package lockunlock provides functionality to lock and unlock servers that
have been provisioned by the OpenStack Compute service.
Example to Lock and Unlock a Server
serverID := "47b6b7b7-568d-40e4-868c-d5c41735532e"
err := lockunlock.Lock(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
err = lockunlock.Unlock(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
*/
package lockunlock

View File

@ -0,0 +1,19 @@
package lockunlock
import "github.com/gophercloud/gophercloud"
func actionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "action")
}
// Lock is the operation responsible for locking a Compute server.
func Lock(client *gophercloud.ServiceClient, id string) (r LockResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"lock": nil}, nil, nil)
return
}
// Unlock is the operation responsible for unlocking a Compute server.
func Unlock(client *gophercloud.ServiceClient, id string) (r UnlockResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unlock": nil}, nil, nil)
return
}

View File

@ -0,0 +1,16 @@
package lockunlock
import (
"github.com/gophercloud/gophercloud"
)
// LockResult and UnlockResult are the responses from a Lock and Unlock
// operations respectively. Call their ExtractErr methods to determine if the
// requests suceeded or failed.
type LockResult struct {
gophercloud.ErrResult
}
type UnlockResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,2 @@
// unlocklock unit tests
package testing

View File

@ -0,0 +1,27 @@
package testing
import (
"net/http"
"testing"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func mockStartServerResponse(t *testing.T, id string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, `{"lock": null}`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockStopServerResponse(t *testing.T, id string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, `{"unlock": null}`)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@ -0,0 +1,31 @@
package testing
import (
"testing"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
const serverID = "{serverId}"
func TestLock(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
mockStartServerResponse(t, serverID)
err := lockunlock.Lock(client.ServiceClient(), serverID).ExtractErr()
th.AssertNoErr(t, err)
}
func TestUnlock(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
mockStopServerResponse(t, serverID)
err := lockunlock.Unlock(client.ServiceClient(), serverID).ExtractErr()
th.AssertNoErr(t, err)
}

View File

@ -5,7 +5,7 @@ import (
)
// MigrateResult is the response from a Migrate operation. Call its ExtractErr
// method to determine if the suceeded or failed.
// method to determine if the request suceeded or failed.
type MigrateResult struct {
gophercloud.ErrResult
}

View File

@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string {
}
// Pause is the operation responsible for pausing a Compute server.
func Pause(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Pause(client *gophercloud.ServiceClient, id string) (r PauseResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"pause": nil}, nil, nil)
return
}
// Unpause is the operation responsible for unpausing a Compute server.
func Unpause(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Unpause(client *gophercloud.ServiceClient, id string) (r UnpauseResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unpause": nil}, nil, nil)
return
}

View File

@ -0,0 +1,15 @@
package pauseunpause
import "github.com/gophercloud/gophercloud"
// PauseResult is the response from a Pause operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type PauseResult struct {
gophercloud.ErrResult
}
// UnpauseResult is the response from an Unpause operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type UnpauseResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,13 @@
/*
Package resetstate provides functionality to reset the state of a server that has
been provisioned by the OpenStack Compute service.
Example to Reset a Server
serverID := "47b6b7b7-568d-40e4-868c-d5c41735532e"
err := resetstate.ResetState(client, id, resetstate.StateActive).ExtractErr()
if err != nil {
panic(err)
}
*/
package resetstate

View File

@ -0,0 +1,23 @@
package resetstate
import (
"github.com/gophercloud/gophercloud"
)
// ServerState refers to the states usable in ResetState Action
type ServerState string
const (
// StateActive returns the state of the server as active
StateActive ServerState = "active"
// StateError returns the state of the server as error
StateError ServerState = "error"
)
// ResetState will reset the state of a server
func ResetState(client *gophercloud.ServiceClient, id string, state ServerState) (r ResetResult) {
stateMap := map[string]interface{}{"state": state}
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-resetState": stateMap}, nil, nil)
return
}

View File

@ -0,0 +1,11 @@
package resetstate
import (
"github.com/gophercloud/gophercloud"
)
// ResetResult is the response of a ResetState operation. Call its ExtractErr
// method to determine if the request suceeded or failed.
type ResetResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1 @@
package testing

View File

@ -0,0 +1,19 @@
package testing
import (
"fmt"
"net/http"
"testing"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func mockResetStateResponse(t *testing.T, id string, state string) {
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, fmt.Sprintf(`{"os-resetState": {"state": "%s"}}`, state))
w.WriteHeader(http.StatusAccepted)
})
}

View File

@ -0,0 +1,21 @@
package testing
import (
"testing"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67"
func TestResetState(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
mockResetStateResponse(t, serverID, "active")
err := resetstate.ResetState(client.ServiceClient(), serverID, "active").ExtractErr()
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,9 @@
package resetstate
import (
"github.com/gophercloud/gophercloud"
)
func actionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "action")
}

View File

@ -1 +1,112 @@
/*
Package secgroups provides the ability to manage security groups through the
Nova API.
This API has been deprecated and will be removed from a future release of the
Nova API service.
For environments that support this extension, this package can be used
regardless of if either Neutron or nova-network is used as the cloud's network
service.
Example to List Security Groups
allPages, err := secroups.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allSecurityGroups, err := secgroups.ExtractSecurityGroups(allPages)
if err != nil {
panic(err)
}
for _, sg := range allSecurityGroups {
fmt.Printf("%+v\n", sg)
}
Example to List Security Groups by Server
serverID := "aab3ad01-9956-4623-a29b-24afc89a7d36"
allPages, err := secroups.ListByServer(computeClient, serverID).AllPages()
if err != nil {
panic(err)
}
allSecurityGroups, err := secgroups.ExtractSecurityGroups(allPages)
if err != nil {
panic(err)
}
for _, sg := range allSecurityGroups {
fmt.Printf("%+v\n", sg)
}
Example to Create a Security Group
createOpts := secgroups.CreateOpts{
Name: "group_name",
Description: "A Security Group",
}
sg, err := secgroups.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Create a Security Group Rule
sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141"
createOpts := secgroups.CreateRuleOpts{
ParentGroupID: sgID,
FromPort: 22,
ToPort: 22,
IPProtocol: "tcp",
CIDR: "0.0.0.0/0",
}
rule, err := secgroups.CreateRule(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Add a Security Group to a Server
serverID := "aab3ad01-9956-4623-a29b-24afc89a7d36"
sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141"
err := secgroups.AddServer(computeClient, serverID, sgID).ExtractErr()
if err != nil {
panic(err)
}
Example to Remove a Security Group from a Server
serverID := "aab3ad01-9956-4623-a29b-24afc89a7d36"
sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141"
err := secgroups.RemoveServer(computeClient, serverID, sgID).ExtractErr()
if err != nil {
panic(err)
}
Example to Delete a Security Group
sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141"
err := secgroups.Delete(computeClient, sgID).ExtractErr()
if err != nil {
panic(err)
}
Example to Delete a Security Group Rule
ruleID := "6221fe3e-383d-46c9-a3a6-845e66c1e8b4"
err := secgroups.DeleteRule(computeClient, ruleID).ExtractErr()
if err != nil {
panic(err)
}
*/
package secgroups

View File

@ -36,12 +36,13 @@ type GroupOpts struct {
// CreateOpts is the struct responsible for creating a security group.
type CreateOpts GroupOpts
// CreateOptsBuilder builds the create options into a serializable format.
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToSecGroupCreateMap() (map[string]interface{}, error)
}
// ToSecGroupCreateMap builds the create options into a serializable format.
// ToSecGroupCreateMap builds a request body from CreateOpts.
func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
@ -62,12 +63,13 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
// UpdateOpts is the struct responsible for updating an existing security group.
type UpdateOpts GroupOpts
// UpdateOptsBuilder builds the update options into a serializable format.
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToSecGroupUpdateMap() (map[string]interface{}, error)
}
// ToSecGroupUpdateMap builds the update options into a serializable format.
// ToSecGroupUpdateMap builds a request body from UpdateOpts.
func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
@ -93,7 +95,7 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
}
// Delete will permanently delete a security group from the project.
func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(resourceURL(client, id), nil)
return
}
@ -101,31 +103,41 @@ func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResu
// CreateRuleOpts represents the configuration for adding a new rule to an
// existing security group.
type CreateRuleOpts struct {
// the ID of the group that this rule will be added to.
// ID is the ID of the group that this rule will be added to.
ParentGroupID string `json:"parent_group_id" required:"true"`
// the lower bound of the port range that will be opened.
// FromPort is the lower bound of the port range that will be opened.
// Use -1 to allow all ICMP traffic.
FromPort int `json:"from_port"`
// the upper bound of the port range that will be opened.
// ToPort is the upper bound of the port range that will be opened.
// Use -1 to allow all ICMP traffic.
ToPort int `json:"to_port"`
// the protocol type that will be allowed, e.g. TCP.
// IPProtocol the protocol type that will be allowed, e.g. TCP.
IPProtocol string `json:"ip_protocol" required:"true"`
// ONLY required if FromGroupID is blank. This represents the IP range that
// will be the source of network traffic to your security group. Use
// 0.0.0.0/0 to allow all IP addresses.
// CIDR is the network CIDR to allow traffic from.
// This is ONLY required if FromGroupID is blank. This represents the IP
// range that will be the source of network traffic to your security group.
// Use 0.0.0.0/0 to allow all IP addresses.
CIDR string `json:"cidr,omitempty" or:"FromGroupID"`
// ONLY required if CIDR is blank. This value represents the ID of a group
// that forwards traffic to the parent group. So, instead of accepting
// FromGroupID represents another security group to allow access.
// This is ONLY required if CIDR is blank. This value represents the ID of a
// group that forwards traffic to the parent group. So, instead of accepting
// network traffic from an entire IP range, you can instead refine the
// inbound source by an existing security group.
FromGroupID string `json:"group_id,omitempty" or:"CIDR"`
}
// CreateRuleOptsBuilder builds the create rule options into a serializable format.
// CreateRuleOptsBuilder allows extensions to add additional parameters to the
// CreateRule request.
type CreateRuleOptsBuilder interface {
ToRuleCreateMap() (map[string]interface{}, error)
}
// ToRuleCreateMap builds the create rule options into a serializable format.
// ToRuleCreateMap builds a request body from CreateRuleOpts.
func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group_rule")
}
@ -146,7 +158,7 @@ func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) (
}
// DeleteRule will permanently delete a rule from a security group.
func DeleteRule(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func DeleteRule(client *gophercloud.ServiceClient, id string) (r DeleteRuleResult) {
_, r.Err = client.Delete(resourceRuleURL(client, id), nil)
return
}
@ -159,13 +171,13 @@ func actionMap(prefix, groupName string) map[string]map[string]string {
// AddServer will associate a server and a security group, enforcing the
// rules of the group on the server.
func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) {
func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r AddServerResult) {
_, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &r.Body, nil)
return
}
// RemoveServer will disassociate a server from a security group.
func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) {
func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r RemoveServerResult) {
_, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &r.Body, nil)
return
}

View File

@ -59,19 +59,19 @@ type Rule struct {
// numeric ID. For the sake of consistency, we always cast it to a string.
ID string `json:"-"`
// The lower bound of the port range which this security group should open up
// The lower bound of the port range which this security group should open up.
FromPort int `json:"from_port"`
// The upper bound of the port range which this security group should open up
// The upper bound of the port range which this security group should open up.
ToPort int `json:"to_port"`
// The IP protocol (e.g. TCP) which the security group accepts
// The IP protocol (e.g. TCP) which the security group accepts.
IPProtocol string `json:"ip_protocol"`
// The CIDR IP range whose traffic can be received
// The CIDR IP range whose traffic can be received.
IPRange IPRange `json:"ip_range"`
// The security group ID to which this rule belongs
// The security group ID to which this rule belongs.
ParentGroupID string `json:"parent_group_id"`
// Not documented.
@ -126,13 +126,15 @@ type SecurityGroupPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a page of Security Groups contains any results.
// IsEmpty determines whether or not a page of Security Groups contains any
// results.
func (page SecurityGroupPage) IsEmpty() (bool, error) {
users, err := ExtractSecurityGroups(page)
return len(users) == 0, err
}
// ExtractSecurityGroups returns a slice of SecurityGroups contained in a single page of results.
// ExtractSecurityGroups returns a slice of SecurityGroups contained in a
// single page of results.
func ExtractSecurityGroups(r pagination.Page) ([]SecurityGroup, error) {
var s struct {
SecurityGroups []SecurityGroup `json:"security_groups"`
@ -145,17 +147,20 @@ type commonResult struct {
gophercloud.Result
}
// CreateResult represents the result of a create operation.
// CreateResult represents the result of a create operation. Call its Extract
// method to interpret the result as a SecurityGroup.
type CreateResult struct {
commonResult
}
// GetResult represents the result of a get operation.
// GetResult represents the result of a get operation. Call its Extract
// method to interpret the result as a SecurityGroup.
type GetResult struct {
commonResult
}
// UpdateResult represents the result of an update operation.
// UpdateResult represents the result of an update operation. Call its Extract
// method to interpret the result as a SecurityGroup.
type UpdateResult struct {
commonResult
}
@ -170,6 +175,7 @@ func (r commonResult) Extract() (*SecurityGroup, error) {
}
// CreateRuleResult represents the result when adding rules to a security group.
// Call its Extract method to interpret the result as a Rule.
type CreateRuleResult struct {
gophercloud.Result
}
@ -182,3 +188,27 @@ func (r CreateRuleResult) Extract() (*Rule, error) {
err := r.ExtractInto(&s)
return s.Rule, err
}
// DeleteResult is the response from delete operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// DeleteRuleResult is the response from a DeleteRule operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type DeleteRuleResult struct {
gophercloud.ErrResult
}
// AddServerResult is the response from an AddServer operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type AddServerResult struct {
gophercloud.ErrResult
}
// RemoveServerResult is the response from a RemoveServer operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type RemoveServerResult struct {
gophercloud.ErrResult
}

View File

@ -1,2 +1,40 @@
// Package servergroups provides the ability to manage server groups
/*
Package servergroups provides the ability to manage server groups.
Example to List Server Groups
allpages, err := servergroups.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allServerGroups, err := servergroups.ExtractServerGroups(allPages)
if err != nil {
panic(err)
}
for _, sg := range allServerGroups {
fmt.Printf("%#v\n", sg)
}
Example to Create a Server Group
createOpts := servergroups.CreateOpts{
Name: "my_sg",
Policies: []string{"anti-affinity"},
}
sg, err := servergroups.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Server Group
sgID := "7a6f29ad-e34d-4368-951a-58a08f11cfb7"
err := servergroups.Delete(computeClient, sgID).ExtractErr()
if err != nil {
panic(err)
}
*/
package servergroups

View File

@ -5,23 +5,25 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// List returns a Pager that allows you to iterate over a collection of ServerGroups.
// List returns a Pager that allows you to iterate over a collection of
// ServerGroups.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
return ServerGroupPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notably, the
// CreateOpts struct in this package does.
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToServerGroupCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies a Server Group allocation request
// CreateOpts specifies Server Group creation parameters.
type CreateOpts struct {
// Name is the name of the server group
Name string `json:"name" required:"true"`
// Policies are the server group policies
Policies []string `json:"policies" required:"true"`
}
@ -31,7 +33,7 @@ func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error)
return gophercloud.BuildRequestBody(opts, "server_group")
}
// Create requests the creation of a new Server Group
// Create requests the creation of a new Server Group.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToServerGroupCreateMap()
if err != nil {

View File

@ -5,7 +5,7 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// A ServerGroup creates a policy for instance placement in the cloud
// A ServerGroup creates a policy for instance placement in the cloud.
type ServerGroup struct {
// ID is the unique ID of the Server Group.
ID string `json:"id"`
@ -14,17 +14,26 @@ type ServerGroup struct {
Name string `json:"name"`
// Polices are the group policies.
//
// Normally a single policy is applied:
//
// "affinity" will place all servers within the server group on the
// same compute node.
//
// "anti-affinity" will place servers within the server group on different
// compute nodes.
Policies []string `json:"policies"`
// Members are the members of the server group.
Members []string `json:"members"`
// Metadata includes a list of all user-specified key-value pairs attached to the Server Group.
// Metadata includes a list of all user-specified key-value pairs attached
// to the Server Group.
Metadata map[string]interface{}
}
// ServerGroupPage stores a single, only page of ServerGroups
// results from a List call.
// ServerGroupPage stores a single page of all ServerGroups results from a
// List call.
type ServerGroupPage struct {
pagination.SinglePageBase
}
@ -59,20 +68,20 @@ func (r ServerGroupResult) Extract() (*ServerGroup, error) {
return s.ServerGroup, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a ServerGroup.
// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a ServerGroup.
type CreateResult struct {
ServerGroupResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a ServerGroup.
// GetResult is the response from a Get operation. Call its Extract method to
// interpret it as a ServerGroup.
type GetResult struct {
ServerGroupResult
}
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
// DeleteResult is the response from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string {
}
// Start is the operation responsible for starting a Compute server.
func Start(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Start(client *gophercloud.ServiceClient, id string) (r StartResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil)
return
}
// Stop is the operation responsible for stopping a Compute server.
func Stop(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Stop(client *gophercloud.ServiceClient, id string) (r StopResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil)
return
}

View File

@ -0,0 +1,15 @@
package startstop
import "github.com/gophercloud/gophercloud"
// StartResult is the response from a Start operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type StartResult struct {
gophercloud.ErrResult
}
// StopResult is the response from Stop operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type StopResult struct {
gophercloud.ErrResult
}

View File

@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string {
}
// Suspend is the operation responsible for suspending a Compute server.
func Suspend(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Suspend(client *gophercloud.ServiceClient, id string) (r SuspendResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"suspend": nil}, nil, nil)
return
}
// Resume is the operation responsible for resuming a Compute server.
func Resume(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Resume(client *gophercloud.ServiceClient, id string) (r UnsuspendResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"resume": nil}, nil, nil)
return
}

View File

@ -0,0 +1,15 @@
package suspendresume
import "github.com/gophercloud/gophercloud"
// SuspendResult is the response from a Suspend operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type SuspendResult struct {
gophercloud.ErrResult
}
// UnsuspendResult is the response from an Unsuspend operation. Call
// its ExtractErr method to determine if the request succeeded or failed.
type UnsuspendResult struct {
gophercloud.ErrResult
}

View File

@ -203,3 +203,17 @@ func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) (r Act
_, r.Err = client.Post(actionURL(client, id), &b, nil, nil)
return
}
// AttachConfigurationGroup will attach configuration group to the instance
func AttachConfigurationGroup(client *gophercloud.ServiceClient, instanceID string, configID string) (r ConfigurationResult) {
b := map[string]interface{}{"instance": map[string]interface{}{"configuration": configID}}
_, r.Err = client.Put(resourceURL(client, instanceID), &b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}})
return
}
// DetachConfigurationGroup will dettach configuration group from the instance
func DetachConfigurationGroup(client *gophercloud.ServiceClient, instanceID string) (r ConfigurationResult) {
b := map[string]interface{}{"instance": map[string]interface{}{}}
_, r.Err = client.Put(resourceURL(client, instanceID), &b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}})
return
}

View File

@ -106,6 +106,11 @@ type DeleteResult struct {
gophercloud.ErrResult
}
// ConfigurationResult represents the result of a AttachConfigurationGroup/DetachConfigurationGroup operation.
type ConfigurationResult struct {
gophercloud.ErrResult
}
// Extract will extract an Instance from various result structs.
func (r commonResult) Extract() (*Instance, error) {
var s struct {

View File

@ -87,17 +87,20 @@ var createReq = `
`
var (
instanceID = "{instanceID}"
rootURL = "/instances"
resURL = rootURL + "/" + instanceID
uRootURL = resURL + "/root"
aURL = resURL + "/action"
instanceID = "{instanceID}"
configGroupID = "00000000-0000-0000-0000-000000000000"
rootURL = "/instances"
resURL = rootURL + "/" + instanceID
uRootURL = resURL + "/root"
aURL = resURL + "/action"
)
var (
restartReq = `{"restart": {}}`
resizeReq = `{"resize": {"flavorRef": "2"}}`
resizeVolReq = `{"resize": {"volume": {"size": 4}}}`
restartReq = `{"restart": {}}`
resizeReq = `{"resize": {"flavorRef": "2"}}`
resizeVolReq = `{"resize": {"volume": {"size": 4}}}`
attachConfigurationGroupReq = `{"instance": {"configuration": "00000000-0000-0000-0000-000000000000"}}`
detachConfigurationGroupReq = `{"instance": {}}`
)
var (
@ -167,3 +170,11 @@ func HandleResize(t *testing.T) {
func HandleResizeVol(t *testing.T) {
fixture.SetupHandler(t, aURL, "POST", resizeVolReq, "", 202)
}
func HandleAttachConfigurationGroup(t *testing.T) {
fixture.SetupHandler(t, resURL, "PUT", attachConfigurationGroupReq, "", 202)
}
func HandleDetachConfigurationGroup(t *testing.T) {
fixture.SetupHandler(t, resURL, "PUT", detachConfigurationGroupReq, "", 202)
}

View File

@ -132,3 +132,21 @@ func TestResizeVolume(t *testing.T) {
res := instances.ResizeVolume(fake.ServiceClient(), instanceID, 4)
th.AssertNoErr(t, res.Err)
}
func TestAttachConfigurationGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleAttachConfigurationGroup(t)
res := instances.AttachConfigurationGroup(fake.ServiceClient(), instanceID, configGroupID)
th.AssertNoErr(t, res.Err)
}
func TestDetachConfigurationGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDetachConfigurationGroup(t)
res := instances.DetachConfigurationGroup(fake.ServiceClient(), instanceID)
th.AssertNoErr(t, res.Err)
}

View File

@ -0,0 +1,59 @@
/*
Package domains manages and retrieves Domains in the OpenStack Identity Service.
Example to List Domains
var iTrue bool = true
listOpts := domains.ListOpts{
Enabled: &iTrue,
}
allPages, err := domains.List(identityClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allDomains, err := domains.ExtractDomains(allPages)
if err != nil {
panic(err)
}
for _, domain := range allDomains {
fmt.Printf("%+v\n", domain)
}
Example to Create a Domain
createOpts := domains.CreateOpts{
Name: "domain name",
Description: "Test domain",
}
domain, err := domains.Create(identityClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Update a Domain
domainID := "0fe36e73809d46aeae6705c39077b1b3"
var iFalse bool = false
updateOpts := domains.UpdateOpts{
Enabled: &iFalse,
}
domain, err := domains.Update(identityClient, domainID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Domain
domainID := "0fe36e73809d46aeae6705c39077b1b3"
err := domains.Delete(identityClient, domainID).ExtractErr()
if err != nil {
panic(err)
}
*/
package domains

View File

@ -0,0 +1,126 @@
package domains
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to
// the List request
type ListOptsBuilder interface {
ToDomainListQuery() (string, error)
}
// ListOpts provides options to filter the List results.
type ListOpts struct {
// Enabled filters the response by enabled domains.
Enabled *bool `q:"enabled"`
// Name filters the response by domain name.
Name string `q:"name"`
}
// ToDomainListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToDomainListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List enumerates the domains to which the current token has access.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToDomainListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return DomainPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get retrieves details on a single domain, by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// CreateOptsBuilder allows extensions to add additional parameters to
// the Create request.
type CreateOptsBuilder interface {
ToDomainCreateMap() (map[string]interface{}, error)
}
// CreateOpts provides options used to create a domain.
type CreateOpts struct {
// Name is the name of the new domain.
Name string `json:"name" required:"true"`
// Description is a description of the domain.
Description string `json:"description,omitempty"`
// Enabled sets the domain status to enabled or disabled.
Enabled *bool `json:"enabled,omitempty"`
}
// ToDomainCreateMap formats a CreateOpts into a create request.
func (opts CreateOpts) ToDomainCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "domain")
}
// Create creates a new Domain.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToDomainCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201},
})
return
}
// Delete deletes a domain.
func Delete(client *gophercloud.ServiceClient, domainID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, domainID), nil)
return
}
// UpdateOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateOptsBuilder interface {
ToDomainUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts represents parameters to update a domain.
type UpdateOpts struct {
// Name is the name of the domain.
Name string `json:"name,omitempty"`
// Description is the description of the domain.
Description string `json:"description,omitempty"`
// Enabled sets the domain status to enabled or disabled.
Enabled *bool `json:"enabled,omitempty"`
}
// ToUpdateCreateMap formats a UpdateOpts into an update request.
func (opts UpdateOpts) ToDomainUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "domain")
}
// Update modifies the attributes of a domain.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToDomainUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}

View File

@ -0,0 +1,97 @@
package domains
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// A Domain is a collection of projects, users, and roles.
type Domain struct {
// Description is the description of the Domain.
Description string `json:"description"`
// Enabled is whether or not the domain is enabled.
Enabled bool `json:"enabled"`
// ID is the unique ID of the domain.
ID string `json:"id"`
// Links contains referencing links to the domain.
Links map[string]interface{} `json:"links"`
// Name is the name of the domain.
Name string `json:"name"`
}
type domainResult struct {
gophercloud.Result
}
// GetResult is the response from a Get operation. Call its Extract method
// to interpret it as a Domain.
type GetResult struct {
domainResult
}
// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a Domain.
type CreateResult struct {
domainResult
}
// DeleteResult is the response from a Delete operation. Call its ExtractErr to
// determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// UpdateResult is the result of an Update request. Call its Extract method to
// interpret it as a Domain.
type UpdateResult struct {
domainResult
}
// DomainPage is a single page of Domain results.
type DomainPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines whether or not a page of Domains contains any results.
func (r DomainPage) IsEmpty() (bool, error) {
domains, err := ExtractDomains(r)
return len(domains) == 0, err
}
// NextPageURL extracts the "next" link from the links section of the result.
func (r DomainPage) NextPageURL() (string, error) {
var s struct {
Links struct {
Next string `json:"next"`
Previous string `json:"previous"`
} `json:"links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Links.Next, err
}
// ExtractDomains returns a slice of Domains contained in a single page of
// results.
func ExtractDomains(r pagination.Page) ([]Domain, error) {
var s struct {
Domains []Domain `json:"domains"`
}
err := (r.(DomainPage)).ExtractInto(&s)
return s.Domains, err
}
// Extract interprets any domainResults as a Domain.
func (r domainResult) Extract() (*Domain, error) {
var s struct {
Domain *Domain `json:"domain"`
}
err := r.ExtractInto(&s)
return s.Domain, err
}

View File

@ -0,0 +1,188 @@
package testing
import (
"fmt"
"net/http"
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/domains"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
// ListOutput provides a single page of Domain results.
const ListOutput = `
{
"links": {
"next": null,
"previous": null,
"self": "http://example.com/identity/v3/domains"
},
"domains": [
{
"enabled": true,
"id": "2844b2a08be147a08ef58317d6471f1f",
"links": {
"self": "http://example.com/identity/v3/domains/2844b2a08be147a08ef58317d6471f1f"
},
"name": "domain one",
"description": "some description"
},
{
"enabled": true,
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/domains/9fe1d3"
},
"name": "domain two"
}
]
}
`
// GetOutput provides a Get result.
const GetOutput = `
{
"domain": {
"enabled": true,
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/domains/9fe1d3"
},
"name": "domain two"
}
}
`
// CreateRequest provides the input to a Create request.
const CreateRequest = `
{
"domain": {
"name": "domain two"
}
}
`
// UpdateRequest provides the input to as Update request.
const UpdateRequest = `
{
"domain": {
"description": "Staging Domain"
}
}
`
// UpdateOutput provides an update result.
const UpdateOutput = `
{
"domain": {
"enabled": true,
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/domains/9fe1d3"
},
"name": "domain two",
"description": "Staging Domain"
}
}
`
// FirstDomain is the first domain in the List request.
var FirstDomain = domains.Domain{
Enabled: true,
ID: "2844b2a08be147a08ef58317d6471f1f",
Links: map[string]interface{}{
"self": "http://example.com/identity/v3/domains/2844b2a08be147a08ef58317d6471f1f",
},
Name: "domain one",
Description: "some description",
}
// SecondDomain is the second domain in the List request.
var SecondDomain = domains.Domain{
Enabled: true,
ID: "9fe1d3",
Links: map[string]interface{}{
"self": "https://example.com/identity/v3/domains/9fe1d3",
},
Name: "domain two",
}
// SecondDomainUpdated is how SecondDomain should look after an Update.
var SecondDomainUpdated = domains.Domain{
Enabled: true,
ID: "9fe1d3",
Links: map[string]interface{}{
"self": "https://example.com/identity/v3/domains/9fe1d3",
},
Name: "domain two",
Description: "Staging Domain",
}
// ExpectedDomainsSlice is the slice of domains expected to be returned from ListOutput.
var ExpectedDomainsSlice = []domains.Domain{FirstDomain, SecondDomain}
// HandleListDomainsSuccessfully creates an HTTP handler at `/domains` on the
// test handler mux that responds with a list of two domains.
func HandleListDomainsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/domains", 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)
})
}
// HandleGetDomainSuccessfully creates an HTTP handler at `/domains` on the
// test handler mux that responds with a single domain.
func HandleGetDomainSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/domains/9fe1d3", 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, GetOutput)
})
}
// HandleCreateDomainSuccessfully creates an HTTP handler at `/domains` on the
// test handler mux that tests domain creation.
func HandleCreateDomainSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, CreateRequest)
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, GetOutput)
})
}
// HandleDeleteDomainSuccessfully creates an HTTP handler at `/domains` on the
// test handler mux that tests domain deletion.
func HandleDeleteDomainSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}
// HandleUpdateDomainSuccessfully creates an HTTP handler at `/domains` on the
// test handler mux that tests domain update.
func HandleUpdateDomainSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PATCH")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, UpdateRequest)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, UpdateOutput)
})
}

View File

@ -0,0 +1,89 @@
package testing
import (
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/domains"
"github.com/gophercloud/gophercloud/pagination"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func TestListDomains(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListDomainsSuccessfully(t)
count := 0
err := domains.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := domains.ExtractDomains(page)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedDomainsSlice, actual)
return true, nil
})
th.AssertNoErr(t, err)
th.CheckEquals(t, count, 1)
}
func TestListDomainsAllPages(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListDomainsSuccessfully(t)
allPages, err := domains.List(client.ServiceClient(), nil).AllPages()
th.AssertNoErr(t, err)
actual, err := domains.ExtractDomains(allPages)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedDomainsSlice, actual)
}
func TestGetDomain(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetDomainSuccessfully(t)
actual, err := domains.Get(client.ServiceClient(), "9fe1d3").Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondDomain, *actual)
}
func TestCreateDomain(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleCreateDomainSuccessfully(t)
createOpts := domains.CreateOpts{
Name: "domain two",
}
actual, err := domains.Create(client.ServiceClient(), createOpts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondDomain, *actual)
}
func TestDeleteDomain(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDeleteDomainSuccessfully(t)
res := domains.Delete(client.ServiceClient(), "9fe1d3")
th.AssertNoErr(t, res.Err)
}
func TestUpdateDomain(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleUpdateDomainSuccessfully(t)
updateOpts := domains.UpdateOpts{
Description: "Staging Domain",
}
actual, err := domains.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondDomainUpdated, *actual)
}

View File

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

View File

@ -1,3 +1,60 @@
// Package groups retrieves and manages groups in the OpenStack Identity
// Service.
/*
Package groups manages and retrieves Groups in the OpenStack Identity Service.
Example to List Groups
listOpts := groups.ListOpts{
DomainID: "default",
}
allPages, err := groups.List(identityClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allGroups, err := groups.ExtractGroups(allPages)
if err != nil {
panic(err)
}
for _, group := range allGroups {
fmt.Printf("%+v\n", group)
}
Example to Create a Group
createOpts := groups.CreateOpts{
Name: "groupname",
DomainID: "default",
Extra: map[string]interface{}{
"email": "groupname@example.com",
}
}
group, err := groups.Create(identityClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Update a Group
groupID := "0fe36e73809d46aeae6705c39077b1b3"
updateOpts := groups.UpdateOpts{
Description: "Updated Description for group",
}
group, err := groups.Update(identityClient, groupID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Group
groupID := "0fe36e73809d46aeae6705c39077b1b3"
err := groups.Delete(identityClient, groupID).ExtractErr()
if err != nil {
panic(err)
}
*/
package groups

View File

@ -0,0 +1,158 @@
package groups
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to
// the List request
type ListOptsBuilder interface {
ToGroupListQuery() (string, error)
}
// ListOpts provides options to filter the List results.
type ListOpts struct {
// DomainID filters the response by a domain ID.
DomainID string `q:"domain_id"`
// Name filters the response by group name.
Name string `q:"name"`
}
// ToGroupListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToGroupListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List enumerates the Groups to which the current token has access.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToGroupListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return GroupPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get retrieves details on a single group, by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// CreateOptsBuilder allows extensions to add additional parameters to
// the Create request.
type CreateOptsBuilder interface {
ToGroupCreateMap() (map[string]interface{}, error)
}
// CreateOpts provides options used to create a group.
type CreateOpts struct {
// Name is the name of the new group.
Name string `json:"name" required:"true"`
// Description is a description of the group.
Description string `json:"description,omitempty"`
// DomainID is the ID of the domain the group belongs to.
DomainID string `json:"domain_id,omitempty"`
// Extra is free-form extra key/value pairs to describe the group.
Extra map[string]interface{} `json:"-"`
}
// ToGroupCreateMap formats a CreateOpts into a create request.
func (opts CreateOpts) ToGroupCreateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "group")
if err != nil {
return nil, err
}
if opts.Extra != nil {
if v, ok := b["group"].(map[string]interface{}); ok {
for key, value := range opts.Extra {
v[key] = value
}
}
}
return b, nil
}
// Create creates a new Group.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToGroupCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201},
})
return
}
// UpdateOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateOptsBuilder interface {
ToGroupUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts provides options for updating a group.
type UpdateOpts struct {
// Name is the name of the new group.
Name string `json:"name,omitempty"`
// Description is a description of the group.
Description string `json:"description,omitempty"`
// DomainID is the ID of the domain the group belongs to.
DomainID string `json:"domain_id,omitempty"`
// Extra is free-form extra key/value pairs to describe the group.
Extra map[string]interface{} `json:"-"`
}
// ToGroupUpdateMap formats a UpdateOpts into an update request.
func (opts UpdateOpts) ToGroupUpdateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "group")
if err != nil {
return nil, err
}
if opts.Extra != nil {
if v, ok := b["group"].(map[string]interface{}); ok {
for key, value := range opts.Extra {
v[key] = value
}
}
}
return b, nil
}
// Update updates an existing Group.
func Update(client *gophercloud.ServiceClient, groupID string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToGroupUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Patch(updateURL(client, groupID), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Delete deletes a group.
func Delete(client *gophercloud.ServiceClient, groupID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, groupID), nil)
return
}

View File

@ -63,6 +63,30 @@ type groupResult struct {
gophercloud.Result
}
// GetResult is the response from a Get operation. Call its Extract method
// to interpret it as a Group.
type GetResult struct {
groupResult
}
// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a Group.
type CreateResult struct {
groupResult
}
// UpdateResult is the response from an Update operation. Call its Extract
// method to interpret it as a Group.
type UpdateResult struct {
groupResult
}
// DeleteResult is the response from a Delete operation. Call its ExtractErr to
// determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// GroupPage is a single page of Group results.
type GroupPage struct {
pagination.LinkedPageBase

View File

@ -0,0 +1,216 @@
package testing
import (
"fmt"
"net/http"
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
// ListOutput provides a single page of Group results.
const ListOutput = `
{
"links": {
"next": null,
"previous": null,
"self": "http://example.com/identity/v3/groups"
},
"groups": [
{
"domain_id": "default",
"id": "2844b2a08be147a08ef58317d6471f1f",
"description": "group for internal support users",
"links": {
"self": "http://example.com/identity/v3/groups/2844b2a08be147a08ef58317d6471f1f"
},
"name": "internal support",
"extra": {
"email": "support@localhost"
}
},
{
"domain_id": "1789d1",
"id": "9fe1d3",
"description": "group for support users",
"links": {
"self": "https://example.com/identity/v3/groups/9fe1d3"
},
"name": "support",
"extra": {
"email": "support@example.com"
}
}
]
}
`
// GetOutput provides a Get result.
const GetOutput = `
{
"group": {
"domain_id": "1789d1",
"id": "9fe1d3",
"description": "group for support users",
"links": {
"self": "https://example.com/identity/v3/groups/9fe1d3"
},
"name": "support",
"extra": {
"email": "support@example.com"
}
}
}
`
// CreateRequest provides the input to a Create request.
const CreateRequest = `
{
"group": {
"domain_id": "1789d1",
"name": "support",
"description": "group for support users",
"email": "support@example.com"
}
}
`
// UpdateRequest provides the input to as Update request.
const UpdateRequest = `
{
"group": {
"description": "L2 Support Team",
"email": "supportteam@example.com"
}
}
`
// UpdateOutput provides an update result.
const UpdateOutput = `
{
"group": {
"domain_id": "1789d1",
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/groups/9fe1d3"
},
"name": "support",
"description": "L2 Support Team",
"extra": {
"email": "supportteam@example.com"
}
}
}
`
// FirstGroup is the first group in the List request.
var FirstGroup = groups.Group{
DomainID: "default",
ID: "2844b2a08be147a08ef58317d6471f1f",
Links: map[string]interface{}{
"self": "http://example.com/identity/v3/groups/2844b2a08be147a08ef58317d6471f1f",
},
Name: "internal support",
Description: "group for internal support users",
Extra: map[string]interface{}{
"email": "support@localhost",
},
}
// SecondGroup is the second group in the List request.
var SecondGroup = groups.Group{
DomainID: "1789d1",
ID: "9fe1d3",
Links: map[string]interface{}{
"self": "https://example.com/identity/v3/groups/9fe1d3",
},
Name: "support",
Description: "group for support users",
Extra: map[string]interface{}{
"email": "support@example.com",
},
}
// SecondGroupUpdated is how SecondGroup should look after an Update.
var SecondGroupUpdated = groups.Group{
DomainID: "1789d1",
ID: "9fe1d3",
Links: map[string]interface{}{
"self": "https://example.com/identity/v3/groups/9fe1d3",
},
Name: "support",
Description: "L2 Support Team",
Extra: map[string]interface{}{
"email": "supportteam@example.com",
},
}
// ExpectedGroupsSlice is the slice of groups expected to be returned from ListOutput.
var ExpectedGroupsSlice = []groups.Group{FirstGroup, SecondGroup}
// HandleListGroupsSuccessfully creates an HTTP handler at `/groups` on the
// test handler mux that responds with a list of two groups.
func HandleListGroupsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/groups", 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)
})
}
// HandleGetGroupSuccessfully creates an HTTP handler at `/groups` on the
// test handler mux that responds with a single group.
func HandleGetGroupSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/groups/9fe1d3", 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, GetOutput)
})
}
// HandleCreateGroupSuccessfully creates an HTTP handler at `/groups` on the
// test handler mux that tests group creation.
func HandleCreateGroupSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, CreateRequest)
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, GetOutput)
})
}
// HandleUpdateGroupSuccessfully creates an HTTP handler at `/groups` on the
// test handler mux that tests group update.
func HandleUpdateGroupSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PATCH")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, UpdateRequest)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, UpdateOutput)
})
}
// HandleDeleteGroupSuccessfully creates an HTTP handler at `/groups` on the
// test handler mux that tests group deletion.
func HandleDeleteGroupSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}

View File

@ -0,0 +1,101 @@
package testing
import (
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/pagination"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func TestListGroups(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListGroupsSuccessfully(t)
count := 0
err := groups.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := groups.ExtractGroups(page)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedGroupsSlice, actual)
return true, nil
})
th.AssertNoErr(t, err)
th.CheckEquals(t, count, 1)
}
func TestListGroupsAllPages(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListGroupsSuccessfully(t)
allPages, err := groups.List(client.ServiceClient(), nil).AllPages()
th.AssertNoErr(t, err)
actual, err := groups.ExtractGroups(allPages)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedGroupsSlice, actual)
th.AssertEquals(t, ExpectedGroupsSlice[0].Extra["email"], "support@localhost")
th.AssertEquals(t, ExpectedGroupsSlice[1].Extra["email"], "support@example.com")
}
func TestGetGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetGroupSuccessfully(t)
actual, err := groups.Get(client.ServiceClient(), "9fe1d3").Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondGroup, *actual)
th.AssertEquals(t, SecondGroup.Extra["email"], "support@example.com")
}
func TestCreateGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleCreateGroupSuccessfully(t)
createOpts := groups.CreateOpts{
Name: "support",
DomainID: "1789d1",
Description: "group for support users",
Extra: map[string]interface{}{
"email": "support@example.com",
},
}
actual, err := groups.Create(client.ServiceClient(), createOpts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondGroup, *actual)
}
func TestUpdateGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleUpdateGroupSuccessfully(t)
updateOpts := groups.UpdateOpts{
Description: "L2 Support Team",
Extra: map[string]interface{}{
"email": "supportteam@example.com",
},
}
actual, err := groups.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondGroupUpdated, *actual)
}
func TestDeleteGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDeleteGroupSuccessfully(t)
res := groups.Delete(client.ServiceClient(), "9fe1d3")
th.AssertNoErr(t, res.Err)
}

View File

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

View File

@ -2,6 +2,62 @@
Package roles provides information and interaction with the roles API
resource for the OpenStack Identity service.
Example to List Roles
listOpts := roles.ListOpts{
DomainID: "default",
}
allPages, err := roles.List(identityClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allRoles, err := roles.ExtractRoles(allPages)
if err != nil {
panic(err)
}
for _, role := range allRoles {
fmt.Printf("%+v\n", role)
}
Example to Create a Role
createOpts := roles.CreateOpts{
Name: "read-only-admin",
DomainID: "default",
Extra: map[string]interface{}{
"description": "this role grants read-only privilege cross tenant",
}
}
role, err := roles.Create(identityClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Update a Role
roleID := "0fe36e73809d46aeae6705c39077b1b3"
updateOpts := roles.UpdateOpts{
Name: "read only admin",
}
role, err := roles.Update(identityClient, roleID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Role
roleID := "0fe36e73809d46aeae6705c39077b1b3"
err := roles.Delete(identityClient, roleID).ExtractErr()
if err != nil {
panic(err)
}
Example to List Role Assignments
listOpts := roles.ListAssignmentsOpts{
@ -22,5 +78,35 @@ Example to List Role Assignments
for _, role := range allRoles {
fmt.Printf("%+v\n", role)
}
Example to Assign a Role to a User in a Project
projectID := "a99e9b4e620e4db09a2dfb6e42a01e66"
userID := "9df1a02f5eb2416a9781e8b0c022d3ae"
roleID := "9fe2ff9ee4384b1894a90878d3e92bab"
err := roles.Assign(identityClient, roleID, roles.AssignOpts{
UserID: userID,
ProjectID: projectID,
}).ExtractErr()
if err != nil {
panic(err)
}
Example to Unassign a Role From a User in a Project
projectID := "a99e9b4e620e4db09a2dfb6e42a01e66"
userID := "9df1a02f5eb2416a9781e8b0c022d3ae"
roleID := "9fe2ff9ee4384b1894a90878d3e92bab"
err := roles.Unassign(identityClient, roleID, roles.UnassignOpts{
UserID: userID,
ProjectID: projectID,
}).ExtractErr()
if err != nil {
panic(err)
}
*/
package roles

View File

@ -5,6 +5,150 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to
// the List request
type ListOptsBuilder interface {
ToRoleListQuery() (string, error)
}
// ListOpts provides options to filter the List results.
type ListOpts struct {
// DomainID filters the response by a domain ID.
DomainID string `q:"domain_id"`
// Name filters the response by role name.
Name string `q:"name"`
}
// ToRoleListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToRoleListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List enumerates the roles to which the current token has access.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToRoleListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return RolePage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get retrieves details on a single role, by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// CreateOptsBuilder allows extensions to add additional parameters to
// the Create request.
type CreateOptsBuilder interface {
ToRoleCreateMap() (map[string]interface{}, error)
}
// CreateOpts provides options used to create a role.
type CreateOpts struct {
// Name is the name of the new role.
Name string `json:"name" required:"true"`
// DomainID is the ID of the domain the role belongs to.
DomainID string `json:"domain_id,omitempty"`
// Extra is free-form extra key/value pairs to describe the role.
Extra map[string]interface{} `json:"-"`
}
// ToRoleCreateMap formats a CreateOpts into a create request.
func (opts CreateOpts) ToRoleCreateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "role")
if err != nil {
return nil, err
}
if opts.Extra != nil {
if v, ok := b["role"].(map[string]interface{}); ok {
for key, value := range opts.Extra {
v[key] = value
}
}
}
return b, nil
}
// Create creates a new Role.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToRoleCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201},
})
return
}
// UpdateOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateOptsBuilder interface {
ToRoleUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts provides options for updating a role.
type UpdateOpts struct {
// Name is the name of the new role.
Name string `json:"name,omitempty"`
// Extra is free-form extra key/value pairs to describe the role.
Extra map[string]interface{} `json:"-"`
}
// ToRoleUpdateMap formats a UpdateOpts into an update request.
func (opts UpdateOpts) ToRoleUpdateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "role")
if err != nil {
return nil, err
}
if opts.Extra != nil {
if v, ok := b["role"].(map[string]interface{}); ok {
for key, value := range opts.Extra {
v[key] = value
}
}
}
return b, nil
}
// Update updates an existing Role.
func Update(client *gophercloud.ServiceClient, roleID string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToRoleUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Patch(updateURL(client, roleID), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Delete deletes a role.
func Delete(client *gophercloud.ServiceClient, roleID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, roleID), nil)
return
}
// ListAssignmentsOptsBuilder allows extensions to add additional parameters to
// the ListAssignments request.
type ListAssignmentsOptsBuilder interface {
@ -56,3 +200,115 @@ func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOpts
return RoleAssignmentPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// AssignOpts provides options to assign a role
type AssignOpts struct {
// UserID is the ID of a user to assign a role
// Note: exactly one of UserID or GroupID must be provided
UserID string `xor:"GroupID"`
// GroupID is the ID of a group to assign a role
// Note: exactly one of UserID or GroupID must be provided
GroupID string `xor:"UserID"`
// ProjectID is the ID of a project to assign a role on
// Note: exactly one of ProjectID or DomainID must be provided
ProjectID string `xor:"DomainID"`
// DomainID is the ID of a domain to assign a role on
// Note: exactly one of ProjectID or DomainID must be provided
DomainID string `xor:"ProjectID"`
}
// UnassignOpts provides options to unassign a role
type UnassignOpts struct {
// UserID is the ID of a user to unassign a role
// Note: exactly one of UserID or GroupID must be provided
UserID string `xor:"GroupID"`
// GroupID is the ID of a group to unassign a role
// Note: exactly one of UserID or GroupID must be provided
GroupID string `xor:"UserID"`
// ProjectID is the ID of a project to unassign a role on
// Note: exactly one of ProjectID or DomainID must be provided
ProjectID string `xor:"DomainID"`
// DomainID is the ID of a domain to unassign a role on
// Note: exactly one of ProjectID or DomainID must be provided
DomainID string `xor:"ProjectID"`
}
// Assign is the operation responsible for assigning a role
// to a user/group on a project/domain.
func Assign(client *gophercloud.ServiceClient, roleID string, opts AssignOpts) (r AssignmentResult) {
// Check xor conditions
_, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
r.Err = err
return
}
// Get corresponding URL
var targetID string
var targetType string
if opts.ProjectID != "" {
targetID = opts.ProjectID
targetType = "projects"
} else {
targetID = opts.DomainID
targetType = "domains"
}
var actorID string
var actorType string
if opts.UserID != "" {
actorID = opts.UserID
actorType = "users"
} else {
actorID = opts.GroupID
actorType = "groups"
}
_, r.Err = client.Put(assignURL(client, targetType, targetID, actorType, actorID, roleID), nil, nil, &gophercloud.RequestOpts{
OkCodes: []int{204},
})
return
}
// Unassign is the operation responsible for unassigning a role
// from a user/group on a project/domain.
func Unassign(client *gophercloud.ServiceClient, roleID string, opts UnassignOpts) (r UnassignmentResult) {
// Check xor conditions
_, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
r.Err = err
return
}
// Get corresponding URL
var targetID string
var targetType string
if opts.ProjectID != "" {
targetID = opts.ProjectID
targetType = "projects"
} else {
targetID = opts.DomainID
targetType = "domains"
}
var actorID string
var actorType string
if opts.UserID != "" {
actorID = opts.UserID
actorType = "users"
} else {
actorID = opts.GroupID
actorType = "groups"
}
_, r.Err = client.Delete(assignURL(client, targetType, targetID, actorType, actorID, roleID), &gophercloud.RequestOpts{
OkCodes: []int{204},
})
return
}

View File

@ -1,17 +1,144 @@
package roles
import "github.com/gophercloud/gophercloud/pagination"
import (
"encoding/json"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/internal"
"github.com/gophercloud/gophercloud/pagination"
)
// Role grants permissions to a user.
type Role struct {
// DomainID is the domain ID the role belongs to.
DomainID string `json:"domain_id"`
// ID is the unique ID of the role.
ID string `json:"id"`
// Links contains referencing links to the role.
Links map[string]interface{} `json:"links"`
// Name is the role name
Name string `json:"name"`
// Extra is a collection of miscellaneous key/values.
Extra map[string]interface{} `json:"-"`
}
func (r *Role) UnmarshalJSON(b []byte) error {
type tmp Role
var s struct {
tmp
Extra map[string]interface{} `json:"extra"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Role(s.tmp)
// Collect other fields and bundle them into Extra
// but only if a field titled "extra" wasn't sent.
if s.Extra != nil {
r.Extra = s.Extra
} else {
var result interface{}
err := json.Unmarshal(b, &result)
if err != nil {
return err
}
if resultMap, ok := result.(map[string]interface{}); ok {
r.Extra = internal.RemainingKeys(Role{}, resultMap)
}
}
return err
}
type roleResult struct {
gophercloud.Result
}
// GetResult is the response from a Get operation. Call its Extract method
// to interpret it as a Role.
type GetResult struct {
roleResult
}
// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a Role
type CreateResult struct {
roleResult
}
// UpdateResult is the response from an Update operation. Call its Extract
// method to interpret it as a Role.
type UpdateResult struct {
roleResult
}
// DeleteResult is the response from a Delete operation. Call its ExtractErr to
// determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// RolePage is a single page of Role results.
type RolePage struct {
pagination.LinkedPageBase
}
// IsEmpty determines whether or not a page of Roles contains any results.
func (r RolePage) IsEmpty() (bool, error) {
roles, err := ExtractRoles(r)
return len(roles) == 0, err
}
// NextPageURL extracts the "next" link from the links section of the result.
func (r RolePage) NextPageURL() (string, error) {
var s struct {
Links struct {
Next string `json:"next"`
Previous string `json:"previous"`
} `json:"links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Links.Next, err
}
// ExtractProjects returns a slice of Roles contained in a single page of
// results.
func ExtractRoles(r pagination.Page) ([]Role, error) {
var s struct {
Roles []Role `json:"roles"`
}
err := (r.(RolePage)).ExtractInto(&s)
return s.Roles, err
}
// Extract interprets any roleResults as a Role.
func (r roleResult) Extract() (*Role, error) {
var s struct {
Role *Role `json:"role"`
}
err := r.ExtractInto(&s)
return s.Role, err
}
// RoleAssignment is the result of a role assignments query.
type RoleAssignment struct {
Role Role `json:"role,omitempty"`
Scope Scope `json:"scope,omitempty"`
User User `json:"user,omitempty"`
Group Group `json:"group,omitempty"`
Role AssignedRole `json:"role,omitempty"`
Scope Scope `json:"scope,omitempty"`
User User `json:"user,omitempty"`
Group Group `json:"group,omitempty"`
}
// Role represents a Role in an assignment.
type Role struct {
// AssignedRole represents a Role in an assignment.
type AssignedRole struct {
ID string `json:"id,omitempty"`
}
@ -73,3 +200,15 @@ func ExtractRoleAssignments(r pagination.Page) ([]RoleAssignment, error) {
err := (r.(RoleAssignmentPage)).ExtractInto(&s)
return s.RoleAssignments, err
}
// AssignmentResult represents the result of an assign operation.
// Call ExtractErr method to determine if the request succeeded or failed.
type AssignmentResult struct {
gophercloud.ErrResult
}
// UnassignmentResult represents the result of an unassign operation.
// Call ExtractErr method to determine if the request succeeded or failed.
type UnassignmentResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,333 @@
package testing
import (
"fmt"
"net/http"
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/roles"
th "github.com/gophercloud/gophercloud/testhelper"
fake "github.com/gophercloud/gophercloud/testhelper/client"
)
// ListOutput provides a single page of Role results.
const ListOutput = `
{
"links": {
"next": null,
"previous": null,
"self": "http://example.com/identity/v3/roles"
},
"roles": [
{
"domain_id": "default",
"id": "2844b2a08be147a08ef58317d6471f1f",
"links": {
"self": "http://example.com/identity/v3/roles/2844b2a08be147a08ef58317d6471f1f"
},
"name": "admin-read-only"
},
{
"domain_id": "1789d1",
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/roles/9fe1d3"
},
"name": "support",
"extra": {
"description": "read-only support role"
}
}
]
}
`
// GetOutput provides a Get result.
const GetOutput = `
{
"role": {
"domain_id": "1789d1",
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/roles/9fe1d3"
},
"name": "support",
"extra": {
"description": "read-only support role"
}
}
}
`
// CreateRequest provides the input to a Create request.
const CreateRequest = `
{
"role": {
"domain_id": "1789d1",
"name": "support",
"description": "read-only support role"
}
}
`
// UpdateRequest provides the input to as Update request.
const UpdateRequest = `
{
"role": {
"description": "admin read-only support role"
}
}
`
// UpdateOutput provides an update result.
const UpdateOutput = `
{
"role": {
"domain_id": "1789d1",
"id": "9fe1d3",
"links": {
"self": "https://example.com/identity/v3/roles/9fe1d3"
},
"name": "support",
"extra": {
"description": "admin read-only support role"
}
}
}
`
const ListAssignmentOutput = `
{
"role_assignments": [
{
"links": {
"assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456"
},
"role": {
"id": "123456"
},
"scope": {
"domain": {
"id": "161718"
}
},
"user": {
"id": "313233"
}
},
{
"links": {
"assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456",
"membership": "http://identity:35357/v3/groups/101112/users/313233"
},
"role": {
"id": "123456"
},
"scope": {
"project": {
"id": "456789"
}
},
"user": {
"id": "313233"
}
}
],
"links": {
"self": "http://identity:35357/v3/role_assignments?effective",
"previous": null,
"next": null
}
}
`
// FirstRole is the first role in the List request.
var FirstRole = roles.Role{
DomainID: "default",
ID: "2844b2a08be147a08ef58317d6471f1f",
Links: map[string]interface{}{
"self": "http://example.com/identity/v3/roles/2844b2a08be147a08ef58317d6471f1f",
},
Name: "admin-read-only",
Extra: map[string]interface{}{},
}
// SecondRole is the second role in the List request.
var SecondRole = roles.Role{
DomainID: "1789d1",
ID: "9fe1d3",
Links: map[string]interface{}{
"self": "https://example.com/identity/v3/roles/9fe1d3",
},
Name: "support",
Extra: map[string]interface{}{
"description": "read-only support role",
},
}
// SecondRoleUpdated is how SecondRole should look after an Update.
var SecondRoleUpdated = roles.Role{
DomainID: "1789d1",
ID: "9fe1d3",
Links: map[string]interface{}{
"self": "https://example.com/identity/v3/roles/9fe1d3",
},
Name: "support",
Extra: map[string]interface{}{
"description": "admin read-only support role",
},
}
// ExpectedRolesSlice is the slice of roles expected to be returned from ListOutput.
var ExpectedRolesSlice = []roles.Role{FirstRole, SecondRole}
// HandleListRolesSuccessfully creates an HTTP handler at `/roles` on the
// test handler mux that responds with a list of two roles.
func HandleListRolesSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/roles", 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", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, ListOutput)
})
}
// HandleGetRoleSuccessfully creates an HTTP handler at `/roles` on the
// test handler mux that responds with a single role.
func HandleGetRoleSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/roles/9fe1d3", 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", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, GetOutput)
})
}
// HandleCreateRoleSuccessfully creates an HTTP handler at `/roles` on the
// test handler mux that tests role creation.
func HandleCreateRoleSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, CreateRequest)
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, GetOutput)
})
}
// HandleUpdateRoleSuccessfully creates an HTTP handler at `/roles` on the
// test handler mux that tests role update.
func HandleUpdateRoleSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PATCH")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, UpdateRequest)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, UpdateOutput)
})
}
// HandleDeleteRoleSuccessfully creates an HTTP handler at `/roles` on the
// test handler mux that tests role deletion.
func HandleDeleteRoleSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}
func HandleAssignSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}
func HandleUnassignSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}
// FirstRoleAssignment is the first role assignment in the List request.
var FirstRoleAssignment = roles.RoleAssignment{
Role: roles.AssignedRole{ID: "123456"},
Scope: roles.Scope{Domain: roles.Domain{ID: "161718"}},
User: roles.User{ID: "313233"},
Group: roles.Group{},
}
// SecondRoleAssignemnt is the second role assignemnt in the List request.
var SecondRoleAssignment = roles.RoleAssignment{
Role: roles.AssignedRole{ID: "123456"},
Scope: roles.Scope{Project: roles.Project{ID: "456789"}},
User: roles.User{ID: "313233"},
Group: roles.Group{},
}
// ExpectedRoleAssignmentsSlice is the slice of role assignments expected to be
// returned from ListAssignmentOutput.
var ExpectedRoleAssignmentsSlice = []roles.RoleAssignment{FirstRoleAssignment, SecondRoleAssignment}
// HandleListRoleAssignmentsSuccessfully creates an HTTP handler at `/role_assignments` on the
// test handler mux that responds with a list of two role assignments.
func HandleListRoleAssignmentsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/role_assignments", 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", fake.TokenID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, ListAssignmentOutput)
})
}

View File

@ -1,105 +1,176 @@
package testing
import (
"fmt"
"net/http"
"reflect"
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/roles"
"github.com/gophercloud/gophercloud/pagination"
"github.com/gophercloud/gophercloud/testhelper"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
)
func TestListSinglePage(t *testing.T) {
testhelper.SetupHTTP()
defer testhelper.TeardownHTTP()
func TestListRoles(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListRolesSuccessfully(t)
testhelper.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "GET")
testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
count := 0
err := roles.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
count++
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, `
{
"role_assignments": [
{
"links": {
"assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456"
},
"role": {
"id": "123456"
},
"scope": {
"domain": {
"id": "161718"
}
},
"user": {
"id": "313233"
}
},
{
"links": {
"assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456",
"membership": "http://identity:35357/v3/groups/101112/users/313233"
},
"role": {
"id": "123456"
},
"scope": {
"project": {
"id": "456789"
}
},
"user": {
"id": "313233"
}
}
],
"links": {
"self": "http://identity:35357/v3/role_assignments?effective",
"previous": null,
"next": null
}
}
`)
actual, err := roles.ExtractRoles(page)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedRolesSlice, actual)
return true, nil
})
th.AssertNoErr(t, err)
th.CheckEquals(t, count, 1)
}
func TestListRolesAllPages(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListRolesSuccessfully(t)
allPages, err := roles.List(client.ServiceClient(), nil).AllPages()
th.AssertNoErr(t, err)
actual, err := roles.ExtractRoles(allPages)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedRolesSlice, actual)
th.AssertEquals(t, ExpectedRolesSlice[1].Extra["description"], "read-only support role")
}
func TestGetRole(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetRoleSuccessfully(t)
actual, err := roles.Get(client.ServiceClient(), "9fe1d3").Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondRole, *actual)
}
func TestCreateRole(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleCreateRoleSuccessfully(t)
createOpts := roles.CreateOpts{
Name: "support",
DomainID: "1789d1",
Extra: map[string]interface{}{
"description": "read-only support role",
},
}
actual, err := roles.Create(client.ServiceClient(), createOpts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondRole, *actual)
}
func TestUpdateRole(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleUpdateRoleSuccessfully(t)
updateOpts := roles.UpdateOpts{
Extra: map[string]interface{}{
"description": "admin read-only support role",
},
}
actual, err := roles.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, SecondRoleUpdated, *actual)
}
func TestDeleteRole(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDeleteRoleSuccessfully(t)
res := roles.Delete(client.ServiceClient(), "9fe1d3")
th.AssertNoErr(t, res.Err)
}
func TestListAssignmentsSinglePage(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListRoleAssignmentsSuccessfully(t)
count := 0
err := roles.ListAssignments(client.ServiceClient(), roles.ListAssignmentsOpts{}).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := roles.ExtractRoleAssignments(page)
if err != nil {
return false, err
}
th.AssertNoErr(t, err)
expected := []roles.RoleAssignment{
{
Role: roles.Role{ID: "123456"},
Scope: roles.Scope{Domain: roles.Domain{ID: "161718"}},
User: roles.User{ID: "313233"},
Group: roles.Group{},
},
{
Role: roles.Role{ID: "123456"},
Scope: roles.Scope{Project: roles.Project{ID: "456789"}},
User: roles.User{ID: "313233"},
Group: roles.Group{},
},
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected %#v, got %#v", expected, actual)
}
th.CheckDeepEquals(t, ExpectedRoleAssignmentsSlice, actual)
return true, nil
})
if err != nil {
t.Errorf("Unexpected error while paging: %v", err)
}
if count != 1 {
t.Errorf("Expected 1 page, got %d", count)
}
th.AssertNoErr(t, err)
th.CheckEquals(t, count, 1)
}
func TestAssign(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleAssignSuccessfully(t)
err := roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{
UserID: "{user_id}",
ProjectID: "{project_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
err = roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{
UserID: "{user_id}",
DomainID: "{domain_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
err = roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{
GroupID: "{group_id}",
ProjectID: "{project_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
err = roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{
GroupID: "{group_id}",
DomainID: "{domain_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
}
func TestUnassign(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleUnassignSuccessfully(t)
err := roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{
UserID: "{user_id}",
ProjectID: "{project_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
err = roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{
UserID: "{user_id}",
DomainID: "{domain_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
err = roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{
GroupID: "{group_id}",
ProjectID: "{project_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
err = roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{
GroupID: "{group_id}",
DomainID: "{domain_id}",
}).ExtractErr()
th.AssertNoErr(t, err)
}

View File

@ -2,6 +2,34 @@ package roles
import "github.com/gophercloud/gophercloud"
const (
rolePath = "roles"
)
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL(rolePath)
}
func getURL(client *gophercloud.ServiceClient, roleID string) string {
return client.ServiceURL(rolePath, roleID)
}
func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL(rolePath)
}
func updateURL(client *gophercloud.ServiceClient, roleID string) string {
return client.ServiceURL(rolePath, roleID)
}
func deleteURL(client *gophercloud.ServiceClient, roleID string) string {
return client.ServiceURL(rolePath, roleID)
}
func listAssignmentsURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("role_assignments")
}
func assignURL(client *gophercloud.ServiceClient, targetType, targetID, actorType, actorID, roleID string) string {
return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath, roleID)
}

View File

@ -71,7 +71,7 @@ Example to List Groups a User Belongs To
panic(err)
}
allGroups, err := users.ExtractGroups(allPages)
allGroups, err := groups.ExtractGroups(allPages)
if err != nil {
panic(err)
}
@ -79,5 +79,45 @@ Example to List Groups a User Belongs To
for _, group := range allGroups {
fmt.Printf("%+v\n", group)
}
Example to List Projects a User Belongs To
userID := "0fe36e73809d46aeae6705c39077b1b3"
allPages, err := users.ListProjects(identityClient, userID).AllPages()
if err != nil {
panic(err)
}
allProjects, err := projects.ExtractProjects(allPages)
if err != nil {
panic(err)
}
for _, project := range allProjects {
fmt.Printf("%+v\n", project)
}
Example to List Users in a Group
groupID := "bede500ee1124ae9b0006ff859758b3a"
listOpts := users.ListOpts{
DomainID: "default",
}
allPages, err := users.ListInGroup(identityClient, groupID, listOpts).AllPages()
if err != nil {
panic(err)
}
allUsers, err := users.ExtractUsers(allPages)
if err != nil {
panic(err)
}
for _, user := range allUsers {
fmt.Printf("%+v\n", user)
}
*/
package users

View File

@ -3,6 +3,7 @@ package users
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/pagination"
)
@ -213,6 +214,29 @@ func Delete(client *gophercloud.ServiceClient, userID string) (r DeleteResult) {
func ListGroups(client *gophercloud.ServiceClient, userID string) pagination.Pager {
url := listGroupsURL(client, userID)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return groups.GroupPage{pagination.LinkedPageBase{PageResult: r}}
return groups.GroupPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}}
})
}
// ListProjects enumerates groups user belongs to.
func ListProjects(client *gophercloud.ServiceClient, userID string) pagination.Pager {
url := listProjectsURL(client, userID)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return projects.ProjectPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}}
})
}
// ListInGroup enumerates users that belong to a group.
func ListInGroup(client *gophercloud.ServiceClient, groupID string, opts ListOptsBuilder) pagination.Pager {
url := listInGroupURL(client, groupID)
if opts != nil {
query, err := opts.ToUserListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return UserPage{pagination.LinkedPageBase{PageResult: r}}
})
}

View File

@ -98,8 +98,8 @@ type UpdateResult struct {
userResult
}
// DeleteResult is the response from a Delete operation. Call its ExtractErr
// method to interpret it as a User.
// DeleteResult is the response from a Delete operation. Call its ExtractErr to
// determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -8,6 +8,7 @@ import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/gophercloud/testhelper/client"
@ -195,6 +196,41 @@ const ListGroupsOutput = `
}
`
// ListProjectsOutput provides a ListProjects result.
const ListProjectsOutput = `
{
"links": {
"next": null,
"previous": null,
"self": "http://localhost:5000/identity/v3/users/foobar/projects"
},
"projects": [
{
"description": "my first project",
"domain_id": "11111",
"enabled": true,
"id": "abcde",
"links": {
"self": "http://localhost:5000/identity/v3/projects/abcde"
},
"name": "project 1",
"parent_id": "11111"
},
{
"description": "my second project",
"domain_id": "22222",
"enabled": true,
"id": "bcdef",
"links": {
"self": "http://localhost:5000/identity/v3/projects/bcdef"
},
"name": "project 2",
"parent_id": "22222"
}
]
}
`
// FirstUser is the first user in the List request.
var nilTime time.Time
var FirstUser = users.User{
@ -300,6 +336,26 @@ var SecondGroup = groups.Group{
var ExpectedGroupsSlice = []groups.Group{FirstGroup, SecondGroup}
var FirstProject = projects.Project{
Description: "my first project",
DomainID: "11111",
Enabled: true,
ID: "abcde",
Name: "project 1",
ParentID: "11111",
}
var SecondProject = projects.Project{
Description: "my second project",
DomainID: "22222",
Enabled: true,
ID: "bcdef",
Name: "project 2",
ParentID: "22222",
}
var ExpectedProjectsSlice = []projects.Project{FirstProject, SecondProject}
// HandleListUsersSuccessfully creates an HTTP handler at `/users` on the
// test handler mux that responds with a list of two users.
func HandleListUsersSuccessfully(t *testing.T) {
@ -379,7 +435,7 @@ func HandleDeleteUserSuccessfully(t *testing.T) {
}
// HandleListUserGroupsSuccessfully creates an HTTP handler at /users/{userID}/groups
// on the test handler mux that respons wit a list of two groups
// on the test handler mux that respons with a list of two groups
func HandleListUserGroupsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/users/9fe1d3/groups", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
@ -391,3 +447,31 @@ func HandleListUserGroupsSuccessfully(t *testing.T) {
fmt.Fprintf(w, ListGroupsOutput)
})
}
// HandleListUserProjectsSuccessfully creates an HTTP handler at /users/{userID}/projects
// on the test handler mux that respons wit a list of two projects
func HandleListUserProjectsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/users/9fe1d3/projects", 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, ListProjectsOutput)
})
}
// HandleListInGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users
// on the test handler mux that response with a list of two users
func HandleListInGroupSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/groups/ea167b/users", 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)
})
}

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
"github.com/gophercloud/gophercloud/pagination"
th "github.com/gophercloud/gophercloud/testhelper"
@ -146,3 +147,31 @@ func TestListUserGroups(t *testing.T) {
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedGroupsSlice, actual)
}
func TestListUserProjects(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListUserProjectsSuccessfully(t)
allPages, err := users.ListProjects(client.ServiceClient(), "9fe1d3").AllPages()
th.AssertNoErr(t, err)
actual, err := projects.ExtractProjects(allPages)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedProjectsSlice, actual)
}
func TestListInGroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListInGroupSuccessfully(t)
iTrue := true
listOpts := users.ListOpts{
Enabled: &iTrue,
}
allPages, err := users.ListInGroup(client.ServiceClient(), "ea167b", listOpts).AllPages()
th.AssertNoErr(t, err)
actual, err := users.ExtractUsers(allPages)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedUsersSlice, actual)
}

View File

@ -25,3 +25,11 @@ func deleteURL(client *gophercloud.ServiceClient, userID string) string {
func listGroupsURL(client *gophercloud.ServiceClient, userID string) string {
return client.ServiceURL("users", userID, "groups")
}
func listProjectsURL(client *gophercloud.ServiceClient, userID string) string {
return client.ServiceURL("users", userID, "projects")
}
func listInGroupURL(client *gophercloud.ServiceClient, groupID string) string {
return client.ServiceURL("groups", groupID, "users")
}

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