tests: upload & boot image in OpenStack. Closes #339
This commit is contained in:
parent
18b17c87fa
commit
f7c4dca5d5
96 changed files with 22287 additions and 1965 deletions
|
|
@ -21,8 +21,11 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack"
|
||||||
|
|
||||||
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/azuretest"
|
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/azuretest"
|
||||||
|
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/openstacktest"
|
||||||
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/constants"
|
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/constants"
|
||||||
"github.com/osbuild/osbuild-composer/internal/common"
|
"github.com/osbuild/osbuild-composer/internal/common"
|
||||||
)
|
)
|
||||||
|
|
@ -296,6 +299,49 @@ func testBootUsingAzure(t *testing.T, imagePath string) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBootUsingOpenStack(t *testing.T, imagePath string) {
|
||||||
|
creds, err := openstack.AuthOptionsFromEnv()
|
||||||
|
|
||||||
|
// if no credentials are given, fall back to qemu
|
||||||
|
if (creds == gophercloud.AuthOptions{}) {
|
||||||
|
log.Print("No OpenStack credentials given, falling back to booting using qemu")
|
||||||
|
testBootUsingQemu(t, imagePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// provider is the top-level client that all OpenStack services derive from
|
||||||
|
provider, err := openstack.AuthenticatedClient(creds)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create a random test id to name all the resources used in this test
|
||||||
|
imageName, err := generateRandomString("osbuild-image-tests-openstack-image-")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// the following line should be done by osbuild-composer at some point
|
||||||
|
image, err := openstacktest.UploadImageToOpenStack(provider, imagePath, imageName)
|
||||||
|
require.NoErrorf(t, err, "Upload to OpenStack failed, resources could have been leaked")
|
||||||
|
require.NotNil(t, image)
|
||||||
|
|
||||||
|
// delete the image after the test is over
|
||||||
|
defer func() {
|
||||||
|
err = openstacktest.DeleteImageFromOpenStack(provider, image.ID)
|
||||||
|
require.NoErrorf(t, err, "Cannot delete OpenStack image, resources could have been leaked")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// boot the uploaded image and try to connect to it
|
||||||
|
err = withSSHKeyPair(func(privateKey, publicKey string) error {
|
||||||
|
userData, err := createUserData(publicKey)
|
||||||
|
require.NoErrorf(t, err, "Creating user data failed: %v", err)
|
||||||
|
|
||||||
|
return openstacktest.WithBootedImageInOpenStack(provider, image.ID, userData, func(address string) error {
|
||||||
|
testSSH(t, address, privateKey, nil)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
// testBoot tests if the image is able to successfully boot
|
// testBoot tests if the image is able to successfully boot
|
||||||
// Before the test it boots the image respecting the specified bootType.
|
// Before the test it boots the image respecting the specified bootType.
|
||||||
// The test passes if the function is able to connect to the image via ssh
|
// The test passes if the function is able to connect to the image via ssh
|
||||||
|
|
@ -318,6 +364,9 @@ func testBoot(t *testing.T, imagePath string, bootType string) {
|
||||||
case "azure":
|
case "azure":
|
||||||
testBootUsingAzure(t, imagePath)
|
testBootUsingAzure(t, imagePath)
|
||||||
|
|
||||||
|
case "openstack":
|
||||||
|
testBootUsingOpenStack(t, imagePath)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("unknown boot type!")
|
panic("unknown boot type!")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
133
cmd/osbuild-image-tests/openstacktest/openstack.go
Normal file
133
cmd/osbuild-image-tests/openstacktest/openstack.go
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
// +build integration
|
||||||
|
|
||||||
|
package openstacktest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WaitTimeout = 600 // in seconds
|
||||||
|
|
||||||
|
func UploadImageToOpenStack(p *gophercloud.ProviderClient, imagePath string, imageName string) (*images.Image, error) {
|
||||||
|
client, err := openstack.NewImageServiceV2(p, gophercloud.EndpointOpts{
|
||||||
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error creating ImageService client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new image which gives us the ID
|
||||||
|
image, err := images.Create(client, images.CreateOpts{
|
||||||
|
Name: imageName,
|
||||||
|
DiskFormat: "qcow2",
|
||||||
|
ContainerFormat: "bare",
|
||||||
|
}).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return image, fmt.Errorf("Creating image failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// then upload the actual binary data
|
||||||
|
imageData, err := os.Open(imagePath)
|
||||||
|
if err != nil {
|
||||||
|
return image, fmt.Errorf("Error opening %s: %v", imagePath, err)
|
||||||
|
}
|
||||||
|
defer imageData.Close()
|
||||||
|
|
||||||
|
err = imagedata.Upload(client, image.ID, imageData).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return image, fmt.Errorf("Upload to OpenStack failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the status to change from Queued to Active
|
||||||
|
err = gophercloud.WaitFor(WaitTimeout, func() (bool, error) {
|
||||||
|
actual, err := images.Get(client, image.ID).Extract()
|
||||||
|
return actual.Status == images.ImageStatusActive, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return image, fmt.Errorf("Waiting for image to become Active failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteImageFromOpenStack(p *gophercloud.ProviderClient, imageUUID string) error {
|
||||||
|
client, err := openstack.NewImageServiceV2(p, gophercloud.EndpointOpts{
|
||||||
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating ImageService client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = images.Delete(client, imageUUID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete the image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBootedImageInOpenStack(p *gophercloud.ProviderClient, imageID, userData string, f func(address string) error) (retErr error) {
|
||||||
|
client, err := openstack.NewComputeV2(p, gophercloud.EndpointOpts{
|
||||||
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating Compute client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := servers.Create(client, servers.CreateOpts{
|
||||||
|
Name: "osbuild-composer-vm-for-" + imageID,
|
||||||
|
FlavorRef: "77b8cf27-be16-40d9-95b1-81db4522be1e", // ci.m1.medium.ephemeral
|
||||||
|
Networks: []servers.Network{ // provider_net_cci_2
|
||||||
|
servers.Network{UUID: "74e8faa7-87ba-41b2-a000-438013194814"},
|
||||||
|
},
|
||||||
|
ImageRef: imageID,
|
||||||
|
UserData: []byte(userData),
|
||||||
|
}).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot create instance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
defer func(){
|
||||||
|
err := servers.ForceDelete(client, server.ID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Force deleting instance %s failed: %v", server.ID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait for the status to become Active
|
||||||
|
err = servers.WaitForStatus(client, server.ID, "ACTIVE", WaitTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Waiting for instance %s to become Active failed: %v", server.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get server details again to refresh the IP addresses
|
||||||
|
server, err = servers.Get(client, server.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot get instance details: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.AccessIPv4 is empty so list all addresses and
|
||||||
|
// get the first fixed one. ssh should be equally happy with v4 or v6
|
||||||
|
var fixedIP string
|
||||||
|
for _, networkAddresses := range server.Addresses["provider_net_cci_2"].([]interface{}) {
|
||||||
|
address := networkAddresses.(map[string]interface{})
|
||||||
|
if address["OS-EXT-IPS:type"] == "fixed" {
|
||||||
|
fixedIP = address["addr"].(string)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fixedIP == "" {
|
||||||
|
return fmt.Errorf("Cannot find IP address for instance %s", server.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(fixedIP)
|
||||||
|
}
|
||||||
1
go.mod
1
go.mod
|
|
@ -16,6 +16,7 @@ require (
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/google/go-cmp v0.3.1
|
github.com/google/go-cmp v0.3.1
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
|
github.com/gophercloud/gophercloud v0.11.0
|
||||||
github.com/julienschmidt/httprouter v1.2.0
|
github.com/julienschmidt/httprouter v1.2.0
|
||||||
github.com/kolo/xmlrpc v0.0.0-20190909154602-56d5ec7c422e
|
github.com/kolo/xmlrpc v0.0.0-20190909154602-56d5ec7c422e
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
|
|
||||||
18
go.sum
18
go.sum
|
|
@ -54,6 +54,8 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gophercloud/gophercloud v0.11.0 h1:pYMP9UZBdQa3lsfIZ1tZor4EbtxiuB6BHhocenkiH/E=
|
||||||
|
github.com/gophercloud/gophercloud v0.11.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||||
|
|
@ -78,19 +80,35 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ BuildRequires: golang(github.com/google/uuid)
|
||||||
BuildRequires: golang(github.com/julienschmidt/httprouter)
|
BuildRequires: golang(github.com/julienschmidt/httprouter)
|
||||||
BuildRequires: golang(github.com/gobwas/glob)
|
BuildRequires: golang(github.com/gobwas/glob)
|
||||||
BuildRequires: golang(github.com/google/go-cmp/cmp)
|
BuildRequires: golang(github.com/google/go-cmp/cmp)
|
||||||
|
BuildRequires: golang(github.com/gophercloud/gophercloud)
|
||||||
BuildRequires: golang(github.com/stretchr/testify/assert)
|
BuildRequires: golang(github.com/stretchr/testify/assert)
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
|
|
||||||
3
vendor/github.com/gophercloud/gophercloud/.gitignore
generated
vendored
Normal file
3
vendor/github.com/gophercloud/gophercloud/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
**/*.swp
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
25
vendor/github.com/gophercloud/gophercloud/.travis.yml
generated
vendored
Normal file
25
vendor/github.com/gophercloud/gophercloud/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
install:
|
||||||
|
- GO111MODULE=off go get golang.org/x/crypto/ssh
|
||||||
|
- GO111MODULE=off go get -v -tags 'fixtures acceptance' ./...
|
||||||
|
- GO111MODULE=off go get github.com/wadey/gocovmerge
|
||||||
|
- GO111MODULE=off go get github.com/mattn/goveralls
|
||||||
|
- GO111MODULE=off go get golang.org/x/tools/cmd/goimports
|
||||||
|
go:
|
||||||
|
- "1.11"
|
||||||
|
- "1.12"
|
||||||
|
- "1.13"
|
||||||
|
- "tip"
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
|
||||||
|
- GO111MODULE=on
|
||||||
|
before_script:
|
||||||
|
- go vet ./...
|
||||||
|
script:
|
||||||
|
- ./script/coverage
|
||||||
|
- ./script/unittest
|
||||||
|
- ./script/format
|
||||||
|
after_success:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out
|
||||||
103
vendor/github.com/gophercloud/gophercloud/.zuul.yaml
generated
vendored
Normal file
103
vendor/github.com/gophercloud/gophercloud/.zuul.yaml
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
- job:
|
||||||
|
name: gophercloud-unittest
|
||||||
|
parent: golang-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud unit test
|
||||||
|
run: .zuul/playbooks/gophercloud-unittest/run.yaml
|
||||||
|
nodeset: ubuntu-xenial-ut
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test
|
||||||
|
parent: golang-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on master branch
|
||||||
|
run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
|
||||||
|
nodeset: ubuntu-bionic
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-ironic
|
||||||
|
parent: golang-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud ironic acceptance test on master branch
|
||||||
|
run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml
|
||||||
|
nodeset: ubuntu-bionic
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-stein
|
||||||
|
parent: gophercloud-acceptance-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on stein branch
|
||||||
|
vars:
|
||||||
|
global_env:
|
||||||
|
OS_BRANCH: stable/stein
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-rocky
|
||||||
|
parent: gophercloud-acceptance-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on rocky branch
|
||||||
|
vars:
|
||||||
|
global_env:
|
||||||
|
OS_BRANCH: stable/rocky
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-queens
|
||||||
|
parent: gophercloud-acceptance-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on queens branch
|
||||||
|
vars:
|
||||||
|
global_env:
|
||||||
|
OS_BRANCH: stable/queens
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-pike
|
||||||
|
parent: gophercloud-acceptance-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on pike branch
|
||||||
|
vars:
|
||||||
|
global_env:
|
||||||
|
OS_BRANCH: stable/pike
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-ocata
|
||||||
|
parent: gophercloud-acceptance-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on ocata branch
|
||||||
|
vars:
|
||||||
|
global_env:
|
||||||
|
OS_BRANCH: stable/ocata
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: gophercloud-acceptance-test-newton
|
||||||
|
parent: gophercloud-acceptance-test
|
||||||
|
description: |
|
||||||
|
Run gophercloud acceptance test on newton branch
|
||||||
|
vars:
|
||||||
|
global_env:
|
||||||
|
OS_BRANCH: stable/newton
|
||||||
|
|
||||||
|
- project:
|
||||||
|
name: gophercloud/gophercloud
|
||||||
|
check:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-unittest
|
||||||
|
- gophercloud-acceptance-test
|
||||||
|
- gophercloud-acceptance-test-ironic
|
||||||
|
recheck-newton:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-acceptance-test-newton
|
||||||
|
recheck-ocata:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-acceptance-test-ocata
|
||||||
|
recheck-pike:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-acceptance-test-pike
|
||||||
|
recheck-queens:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-acceptance-test-queens
|
||||||
|
recheck-rocky:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-acceptance-test-rocky
|
||||||
|
recheck-stein:
|
||||||
|
jobs:
|
||||||
|
- gophercloud-acceptance-test-stein
|
||||||
358
vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
generated
vendored
Normal file
358
vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,358 @@
|
||||||
|
## 0.12.0 (Unreleased)
|
||||||
|
|
||||||
|
## 0.11.0 (May 14, 2020)
|
||||||
|
|
||||||
|
UPGRADE NOTES
|
||||||
|
|
||||||
|
* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||||
|
* All responses now have access to the returned headers. Please report any issues this has caused [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942)
|
||||||
|
* Changes have been made to the internal HTTP client to ensure response bodies are handled in a way that enables connections to be re-used more efficiently [GH-1952](https://github.com/gophercloud/gophercloud/pull/1952)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `objectstorage/v1/containers.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||||
|
* Added `objectstorage/v1/objects.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||||
|
* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||||
|
* All responses now have access to the returned headers [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942)
|
||||||
|
* Added `compute/v2/extensions/injectnetworkinfo.InjectNetworkInfo` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941)
|
||||||
|
* Added `compute/v2/extensions/resetnetwork.ResetNetwork` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941)
|
||||||
|
* Added `identity/v3/extensions/trusts.ListRoles` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||||
|
* Added `identity/v3/extensions/trusts.GetRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||||
|
* Added `identity/v3/extensions/trusts.CheckRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||||
|
* Added `identity/v3/extensions/oauth1.Create` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.CreateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.DeleteConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.ListConsumers` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.GetConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.UpdateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.RequestToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.AuthorizeToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.CreateAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.GetAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.RevokeAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.ListAccessTokens` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.ListAccessTokenRoles` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `identity/v3/extensions/oauth1.GetAccessTokenRole` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||||
|
* Added `networking/v2/extensions/agents.Update` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||||
|
* Added `networking/v2/extensions/agents.Delete` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||||
|
* Added `networking/v2/extensions/agents.ScheduleDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||||
|
* Added `networking/v2/extensions/agents.RemoveDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||||
|
* Added `identity/v3/projects.CreateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||||
|
* Added `identity/v3/projects.CreateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||||
|
* Added `identity/v3/projects.UpdateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||||
|
* Added `identity/v3/projects.UpdateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||||
|
* Added `identity/v3/projects.Project.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||||
|
* Added `identity/v3/projects.Options.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||||
|
* Added `imageservice/v2/images.Image.OpenStackImageImportMethods` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962)
|
||||||
|
* Added `imageservice/v2/images.Image.OpenStackImageStoreIDs` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Changed`identity/v3/extensions/trusts.Trust.RemainingUses` from `bool` to `int` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||||
|
* Changed `identity/v3/applicationcredentials.CreateOpts.ExpiresAt` from `string` to `*time.Time` [GH-1937](https://github.com/gophercloud/gophercloud/pull/1937)
|
||||||
|
* Fixed issue with unmarshalling/decoding slices of composed structs [GH-1964](https://github.com/gophercloud/gophercloud/pull/1964)
|
||||||
|
|
||||||
|
## 0.10.0 (April 12, 2020)
|
||||||
|
|
||||||
|
UPGRADE NOTES
|
||||||
|
|
||||||
|
* The various `IDFromName` convenience functions have been moved to https://github.com/gophercloud/utils [GH-1897](https://github.com/gophercloud/gophercloud/pull/1897)
|
||||||
|
* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `blockstorage/extensions/volumeactions.SetBootable` [GH-1891](https://github.com/gophercloud/gophercloud/pull/1891)
|
||||||
|
* Added `blockstorage/extensions/backups.Export` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894)
|
||||||
|
* Added `blockstorage/extensions/backups.Import` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894)
|
||||||
|
* Added `placement/v1/resourceproviders.GetTraits` [GH-1899](https://github.com/gophercloud/gophercloud/pull/1899)
|
||||||
|
* Added the ability to authenticate with Amazon EC2 Credentials [GH-1900](https://github.com/gophercloud/gophercloud/pull/1900)
|
||||||
|
* Added ability to list Nova services by binary and host [GH-1904](https://github.com/gophercloud/gophercloud/pull/1904)
|
||||||
|
* Added `compute/v2/extensions/services.Update` [GH-1902](https://github.com/gophercloud/gophercloud/pull/1902)
|
||||||
|
* Added system scope to v3 authentication [GH-1908](https://github.com/gophercloud/gophercloud/pull/1908)
|
||||||
|
* Added `identity/v3/extensions/ec2tokens.ValidateS3Token` [GH-1906](https://github.com/gophercloud/gophercloud/pull/1906)
|
||||||
|
* Added `containerinfra/v1/clusters.Cluster.HealthStatus` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910)
|
||||||
|
* Added `containerinfra/v1/clusters.Cluster.HealthStatusReason` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910)
|
||||||
|
* Added `loadbalancer/v2/amphorae.Failover` [GH-1912](https://github.com/gophercloud/gophercloud/pull/1912)
|
||||||
|
* Added `identity/v3/extensions/ec2credentials.List` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||||
|
* Added `identity/v3/extensions/ec2credentials.Get` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||||
|
* Added `identity/v3/extensions/ec2credentials.Create` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||||
|
* Added `identity/v3/extensions/ec2credentials.Delete` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||||
|
* Added `ErrUnexpectedResponseCode.ResponseHeader` [GH-1919](https://github.com/gophercloud/gophercloud/pull/1919)
|
||||||
|
* Added support for TOTP authentication [GH-1922](https://github.com/gophercloud/gophercloud/pull/1922)
|
||||||
|
* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
|
||||||
|
* Added `sharedfilesystems/v2/shares.GetExportLocation` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
|
||||||
|
* Added `sharedfilesystems/v2/shares.Revert` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||||
|
* Added `sharedfilesystems/v2/shares.ResetStatus` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||||
|
* Added `sharedfilesystems/v2/shares.ForceDelete` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||||
|
* Added `sharedfilesystems/v2/shares.Unmanage` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||||
|
* Added `blockstorage/v3/attachments.Create` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||||
|
* Added `blockstorage/v3/attachments.List` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||||
|
* Added `blockstorage/v3/attachments.Get` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||||
|
* Added `blockstorage/v3/attachments.Update` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||||
|
* Added `blockstorage/v3/attachments.Delete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||||
|
* Added `blockstorage/v3/attachments.Complete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Fixed issue with Orchestration `get_file` only being able to read JSON and YAML files [GH-1915](https://github.com/gophercloud/gophercloud/pull/1915)
|
||||||
|
|
||||||
|
## 0.9.0 (March 10, 2020)
|
||||||
|
|
||||||
|
UPGRADE NOTES
|
||||||
|
|
||||||
|
* The way we implement new API result fields added by microversions has changed. Previously, we would declare a dedicated `ExtractFoo` function in a file called `microversions.go`. Now, we are declaring those fields inline of the original result struct as a pointer. [GH-1854](https://github.com/gophercloud/gophercloud/pull/1854)
|
||||||
|
|
||||||
|
* `compute/v2/servers.CreateOpts.Networks` has changed from `[]Network` to `interface{}` in order to support creating servers that have no networks. [GH-1884](https://github.com/gophercloud/gophercloud/pull/1884)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `compute/v2/extensions/instanceactions.List` [GH-1848](https://github.com/gophercloud/gophercloud/pull/1848)
|
||||||
|
* Added `compute/v2/extensions/instanceactions.Get` [GH-1848](https://github.com/gophercloud/gophercloud/pull/1848)
|
||||||
|
* Added `networking/v2/ports.List.FixedIPs` [GH-1849](https://github.com/gophercloud/gophercloud/pull/1849)
|
||||||
|
* Added `identity/v3/extensions/trusts.List` [GH-1855](https://github.com/gophercloud/gophercloud/pull/1855)
|
||||||
|
* Added `identity/v3/extensions/trusts.Get` [GH-1855](https://github.com/gophercloud/gophercloud/pull/1855)
|
||||||
|
* Added `identity/v3/extensions/trusts.Trust.ExpiresAt` [GH-1857](https://github.com/gophercloud/gophercloud/pull/1857)
|
||||||
|
* Added `identity/v3/extensions/trusts.Trust.DeletedAt` [GH-1857](https://github.com/gophercloud/gophercloud/pull/1857)
|
||||||
|
* Added `compute/v2/extensions/instanceactions.InstanceActionDetail` [GH-1851](https://github.com/gophercloud/gophercloud/pull/1851)
|
||||||
|
* Added `compute/v2/extensions/instanceactions.Event` [GH-1851](https://github.com/gophercloud/gophercloud/pull/1851)
|
||||||
|
* Added `compute/v2/extensions/instanceactions.ListOpts` [GH-1858](https://github.com/gophercloud/gophercloud/pull/1858)
|
||||||
|
* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey` [GH-1864](https://github.com/gophercloud/gophercloud/pull/1864)
|
||||||
|
* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey2` [GH-1864](https://github.com/gophercloud/gophercloud/pull/1864)
|
||||||
|
* Added `placement/v1/resourceproviders.GetUsages` [GH-1862](https://github.com/gophercloud/gophercloud/pull/1862)
|
||||||
|
* Added `placement/v1/resourceproviders.GetInventories` [GH-1862](https://github.com/gophercloud/gophercloud/pull/1862)
|
||||||
|
* Added `imageservice/v2/images.ReplaceImageMinRam` [GH-1867](https://github.com/gophercloud/gophercloud/pull/1867)
|
||||||
|
* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey` [GH-1865](https://github.com/gophercloud/gophercloud/pull/1865)
|
||||||
|
* Added `objectstorage/v1/containers.CreateOpts.TempURLKey2` [GH-1865](https://github.com/gophercloud/gophercloud/pull/1865)
|
||||||
|
* Added `blockstorage/extensions/volumetransfers.List` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||||
|
* Added `blockstorage/extensions/volumetransfers.Create` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||||
|
* Added `blockstorage/extensions/volumetransfers.Accept` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||||
|
* Added `blockstorage/extensions/volumetransfers.Get` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||||
|
* Added `blockstorage/extensions/volumetransfers.Delete` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||||
|
* Added `blockstorage/extensions/backups.RestoreFromBackup` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871)
|
||||||
|
* Added `blockstorage/v3/volumes.CreateOpts.BackupID` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871)
|
||||||
|
* Added `blockstorage/v3/volumes.Volume.BackupID` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871)
|
||||||
|
* Added `identity/v3/projects.ListOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Added `identity/v3/projects.ListOpts.TagsAny` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Added `identity/v3/projects.ListOpts.NotTags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Added `identity/v3/projects.ListOpts.NotTagsAny` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Added `identity/v3/projects.CreateOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Added `identity/v3/projects.UpdateOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Added `identity/v3/projects.Project.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||||
|
* Changed `compute/v2/servers.CreateOpts.Networks` from `[]Network` to `interface{}` to support creating servers with no networks. [GH-1884](https://github.com/gophercloud/gophercloud/pull/1884)
|
||||||
|
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Added support for `int64` headers, which were previously being silently dropped [GH-1860](https://github.com/gophercloud/gophercloud/pull/1860)
|
||||||
|
* Allow image properties with empty values [GH-1875](https://github.com/gophercloud/gophercloud/pull/1875)
|
||||||
|
* Fixed `compute/v2/extensions/extendedserverattributes.ServerAttributesExt.Userdata` JSON tag [GH-1881](https://github.com/gophercloud/gophercloud/pull/1881)
|
||||||
|
|
||||||
|
## 0.8.0 (February 8, 2020)
|
||||||
|
|
||||||
|
UPGRADE NOTES
|
||||||
|
|
||||||
|
* The behavior of `keymanager/v1/acls.SetOpts` has changed. Instead of a struct, it is now `[]SetOpt`. See [GH-1816](https://github.com/gophercloud/gophercloud/pull/1816) for implementation details.
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* The result of `containerinfra/v1/clusters.Resize` now returns only the UUID when calling `Extract`. This is a backwards-breaking change from the previous struct that was returned [GH-1649](https://github.com/gophercloud/gophercloud/pull/1649)
|
||||||
|
* Added `compute/v2/extensions/shelveunshelve.Shelve` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799)
|
||||||
|
* Added `compute/v2/extensions/shelveunshelve.ShelveOffload` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799)
|
||||||
|
* Added `compute/v2/extensions/shelveunshelve.Unshelve` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799)
|
||||||
|
* Added `containerinfra/v1/nodegroups.Get` [GH-1774](https://github.com/gophercloud/gophercloud/pull/1774)
|
||||||
|
* Added `containerinfra/v1/nodegroups.List` [GH-1774](https://github.com/gophercloud/gophercloud/pull/1774)
|
||||||
|
* Added `orchestration/v1/resourcetypes.List` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806)
|
||||||
|
* Added `orchestration/v1/resourcetypes.GetSchema` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806)
|
||||||
|
* Added `orchestration/v1/resourcetypes.GenerateTemplate` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806)
|
||||||
|
* Added `keymanager/v1/acls.SetOpt` and changed `keymanager/v1/acls.SetOpts` to `[]SetOpt` [GH-1816](https://github.com/gophercloud/gophercloud/pull/1816)
|
||||||
|
* Added `blockstorage/apiversions.List` [GH-458](https://github.com/gophercloud/gophercloud/pull/458)
|
||||||
|
* Added `blockstorage/apiversions.Get` [GH-458](https://github.com/gophercloud/gophercloud/pull/458)
|
||||||
|
* Added `StatusCodeError` interface and `GetStatusCode` convenience method [GH-1820](https://github.com/gophercloud/gophercloud/pull/1820)
|
||||||
|
* Added pagination support to `compute/v2/extensions/usage.SingleTenant` [GH-1819](https://github.com/gophercloud/gophercloud/pull/1819)
|
||||||
|
* Added pagination support to `compute/v2/extensions/usage.AllTenants` [GH-1819](https://github.com/gophercloud/gophercloud/pull/1819)
|
||||||
|
* Added `placement/v1/resourceproviders.List` [GH-1815](https://github.com/gophercloud/gophercloud/pull/1815)
|
||||||
|
* Allow `CreateMemberOptsBuilder` to be passed in `loadbalancer/v2/pools.Create` [GH-1822](https://github.com/gophercloud/gophercloud/pull/1822)
|
||||||
|
* Added `Backup` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824)
|
||||||
|
* Added `MonitorAddress` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824)
|
||||||
|
* Added `MonitorPort` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824)
|
||||||
|
* Changed `Impersonation` to a non-required field in `identity/v3/extensions/trusts.CreateOpts` [GH-1818](https://github.com/gophercloud/gophercloud/pull/1818)
|
||||||
|
* Added `InsertHeaders` to `loadbalancer/v2/listeners.UpdateOpts` [GH-1835]
|
||||||
|
* Added `NUMATopology` to `baremetalintrospection/v1/introspection.Data` [GH-1842](https://github.com/gophercloud/gophercloud/pull/1842)
|
||||||
|
* Added `placement/v1/resourceproviders.Create` [GH-1841](https://github.com/gophercloud/gophercloud/pull/1841)
|
||||||
|
* Added `blockstorage/extensions/volumeactions.UploadImageOpts.Visibility` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||||
|
* Added `blockstorage/extensions/volumeactions.UploadImageOpts.Protected` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||||
|
* Added `blockstorage/extensions/volumeactions.VolumeImage.Visibility` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||||
|
* Added `blockstorage/extensions/volumeactions.VolumeImage.Protected` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Changed `sort_key` to `sort_keys` in ` workflow/v2/crontriggers.ListOpts` [GH-1809](https://github.com/gophercloud/gophercloud/pull/1809)
|
||||||
|
* Allow `blockstorage/extensions/schedulerstats.Capabilities.MaxOverSubscriptionRatio` to accept both string and int/float responses [GH-1817](https://github.com/gophercloud/gophercloud/pull/1817)
|
||||||
|
* Fixed bug in `NewLoadBalancerV2` for situations when the LBaaS service was advertised without a `/v2.0` endpoint [GH-1829](https://github.com/gophercloud/gophercloud/pull/1829)
|
||||||
|
* Fixed JSON tags in `baremetal/v1/ports.UpdateOperation` [GH-1840](https://github.com/gophercloud/gophercloud/pull/1840)
|
||||||
|
* Fixed JSON tags in `networking/v2/extensions/lbaas/vips.commonResult.Extract()` [GH-1840](https://github.com/gophercloud/gophercloud/pull/1840)
|
||||||
|
|
||||||
|
## 0.7.0 (December 3, 2019)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Allow a token to be used directly for authentication instead of generating a new token based on a given token [GH-1752](https://github.com/gophercloud/gophercloud/pull/1752)
|
||||||
|
* Moved `tags.ServerTagsExt` to servers.TagsExt` [GH-1760](https://github.com/gophercloud/gophercloud/pull/1760)
|
||||||
|
* Added `tags`, `tags-any`, `not-tags`, and `not-tags-any` to `compute/v2/servers.ListOpts` [GH-1759](https://github.com/gophercloud/gophercloud/pull/1759)
|
||||||
|
* Added `AccessRule` to `identity/v3/applicationcredentials` [GH-1758](https://github.com/gophercloud/gophercloud/pull/1758)
|
||||||
|
* Gophercloud no longer returns an error when multiple endpoints are found. Instead, it will choose the first endpoint and discard the others [GH-1766](https://github.com/gophercloud/gophercloud/pull/1766)
|
||||||
|
* Added `networking/v2/extensions/fwaas_v2/rules.Create` [GH-1768](https://github.com/gophercloud/gophercloud/pull/1768)
|
||||||
|
* Added `networking/v2/extensions/fwaas_v2/rules.Delete` [GH-1771](https://github.com/gophercloud/gophercloud/pull/1771)
|
||||||
|
* Added `loadbalancer/v2/providers.List` [GH-1765](https://github.com/gophercloud/gophercloud/pull/1765)
|
||||||
|
* Added `networking/v2/extensions/fwaas_v2/rules.Get` [GH-1772](https://github.com/gophercloud/gophercloud/pull/1772)
|
||||||
|
* Added `networking/v2/extensions/fwaas_v2/rules.Update` [GH-1776](https://github.com/gophercloud/gophercloud/pull/1776)
|
||||||
|
* Added `networking/v2/extensions/fwaas_v2/rules.List` [GH-1783](https://github.com/gophercloud/gophercloud/pull/1783)
|
||||||
|
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.CreateOpts` [GH-1785](https://github.com/gophercloud/gophercloud/pull/1785)
|
||||||
|
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.UpdateOpts` [GH-1786](https://github.com/gophercloud/gophercloud/pull/1786)
|
||||||
|
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.Monitor` [GH-1787](https://github.com/gophercloud/gophercloud/pull/1787)
|
||||||
|
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.ListOpts` [GH-1788](https://github.com/gophercloud/gophercloud/pull/1788)
|
||||||
|
* Updated `go.mod` dependencies, specifically to account for CVE-2019-11840 with `golang.org/x/crypto` [GH-1793](https://github.com/gophercloud/gophercloud/pull/1788)
|
||||||
|
|
||||||
|
## 0.6.0 (October 17, 2019)
|
||||||
|
|
||||||
|
UPGRADE NOTES
|
||||||
|
|
||||||
|
* The way reauthentication works has been refactored. This should not cause a problem, but please report bugs if it does. See [GH-1746](https://github.com/gophercloud/gophercloud/pull/1746) for more information.
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `networking/v2/extensions/quotas.Get` [GH-1742](https://github.com/gophercloud/gophercloud/pull/1742)
|
||||||
|
* Added `networking/v2/extensions/quotas.Update` [GH-1747](https://github.com/gophercloud/gophercloud/pull/1747)
|
||||||
|
* Refactored the reauthentication implementation to use goroutines and added a check to prevent an infinite loop in certain situations. [GH-1746](https://github.com/gophercloud/gophercloud/pull/1746)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Changed `Flavor` to `FlavorID` in `loadbalancer/v2/loadbalancers` [GH-1744](https://github.com/gophercloud/gophercloud/pull/1744)
|
||||||
|
* Changed `Flavor` to `FlavorID` in `networking/v2/extensions/lbaas_v2/loadbalancers` [GH-1744](https://github.com/gophercloud/gophercloud/pull/1744)
|
||||||
|
* The `go-yaml` dependency was updated to `v2.2.4` to fix possible DDOS vulnerabilities [GH-1751](https://github.com/gophercloud/gophercloud/pull/1751)
|
||||||
|
|
||||||
|
## 0.5.0 (October 13, 2019)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `VolumeType` to `compute/v2/extensions/bootfromvolume.BlockDevice`[GH-1690](https://github.com/gophercloud/gophercloud/pull/1690)
|
||||||
|
* Added `networking/v2/extensions/layer3/portforwarding.List` [GH-1688](https://github.com/gophercloud/gophercloud/pull/1688)
|
||||||
|
* Added `networking/v2/extensions/layer3/portforwarding.Get` [GH-1698](https://github.com/gophercloud/gophercloud/pull/1696)
|
||||||
|
* Added `compute/v2/extensions/tags.ReplaceAll` [GH-1696](https://github.com/gophercloud/gophercloud/pull/1696)
|
||||||
|
* Added `compute/v2/extensions/tags.Add` [GH-1696](https://github.com/gophercloud/gophercloud/pull/1696)
|
||||||
|
* Added `networking/v2/extensions/layer3/portforwarding.Update` [GH-1703](https://github.com/gophercloud/gophercloud/pull/1703)
|
||||||
|
* Added `ExtractDomain` method to token results in `identity/v3/tokens` [GH-1712](https://github.com/gophercloud/gophercloud/pull/1712)
|
||||||
|
* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.CreateOpts` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710)
|
||||||
|
* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.UpdateOpts` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710)
|
||||||
|
* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.Listener` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710)
|
||||||
|
* Added `compute/v2/extensions/tags.Add` [GH-1695](https://github.com/gophercloud/gophercloud/pull/1695)
|
||||||
|
* Added `compute/v2/extensions/tags.ReplaceAll` [GH-1694](https://github.com/gophercloud/gophercloud/pull/1694)
|
||||||
|
* Added `compute/v2/extensions/tags.Delete` [GH-1699](https://github.com/gophercloud/gophercloud/pull/1699)
|
||||||
|
* Added `compute/v2/extensions/tags.DeleteAll` [GH-1700](https://github.com/gophercloud/gophercloud/pull/1700)
|
||||||
|
* Added `ImageStatusImporting` as an image status [GH-1725](https://github.com/gophercloud/gophercloud/pull/1725)
|
||||||
|
* Added `ByPath` to `baremetalintrospection/v1/introspection.RootDiskType` [GH-1730](https://github.com/gophercloud/gophercloud/pull/1730)
|
||||||
|
* Added `AttachedVolumes` to `compute/v2/servers.Server` [GH-1732](https://github.com/gophercloud/gophercloud/pull/1732)
|
||||||
|
* Enable unmarshaling server tags to a `compute/v2/servers.Server` struct [GH-1734]
|
||||||
|
* Allow setting an empty members list in `loadbalancer/v2/pools.BatchUpdateMembers` [GH-1736](https://github.com/gophercloud/gophercloud/pull/1736)
|
||||||
|
* Allow unsetting members' subnet ID and name in `loadbalancer/v2/pools.BatchUpdateMemberOpts` [GH-1738](https://github.com/gophercloud/gophercloud/pull/1738)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Changed struct type for options in `networking/v2/extensions/lbaas_v2/listeners` to `UpdateOptsBuilder` interface instead of specific UpdateOpts type [GH-1705](https://github.com/gophercloud/gophercloud/pull/1705)
|
||||||
|
* Changed struct type for options in `networking/v2/extensions/lbaas_v2/loadbalancers` to `UpdateOptsBuilder` interface instead of specific UpdateOpts type [GH-1706](https://github.com/gophercloud/gophercloud/pull/1706)
|
||||||
|
* Fixed issue with `blockstorage/v1/volumes.Create` where the response was expected to be 202 [GH-1720](https://github.com/gophercloud/gophercloud/pull/1720)
|
||||||
|
* Changed `DefaultTlsContainerRef` from `string` to `*string` in `loadbalancer/v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||||
|
* Changed `SniContainerRefs` from `[]string{}` to `*[]string{}` in `loadbalancer/v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||||
|
* Changed `DefaultTlsContainerRef` from `string` to `*string` in `networking/v2/extensions/lbaas_v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||||
|
* Changed `SniContainerRefs` from `[]string{}` to `*[]string{}` in `networking/v2/extensions/lbaas_v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0 (September 3, 2019)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `blockstorage/extensions/quotasets.results.QuotaSet.Groups` [GH-1668](https://github.com/gophercloud/gophercloud/pull/1668)
|
||||||
|
* Added `blockstorage/extensions/quotasets.results.QuotaUsageSet.Groups` [GH-1668](https://github.com/gophercloud/gophercloud/pull/1668)
|
||||||
|
* Added `containerinfra/v1/clusters.CreateOpts.FixedNetwork` [GH-1674](https://github.com/gophercloud/gophercloud/pull/1674)
|
||||||
|
* Added `containerinfra/v1/clusters.CreateOpts.FixedSubnet` [GH-1676](https://github.com/gophercloud/gophercloud/pull/1676)
|
||||||
|
* Added `containerinfra/v1/clusters.CreateOpts.FloatingIPEnabled` [GH-1677](https://github.com/gophercloud/gophercloud/pull/1677)
|
||||||
|
* Added `CreatedAt` and `UpdatedAt` to `loadbalancers/v2/loadbalancers.LoadBalancer` [GH-1681](https://github.com/gophercloud/gophercloud/pull/1681)
|
||||||
|
* Added `networking/v2/extensions/layer3/portforwarding.Create` [GH-1651](https://github.com/gophercloud/gophercloud/pull/1651)
|
||||||
|
* Added `networking/v2/extensions/agents.ListDHCPNetworks` [GH-1686](https://github.com/gophercloud/gophercloud/pull/1686)
|
||||||
|
* Added `networking/v2/extensions/layer3/portforwarding.Delete` [GH-1652](https://github.com/gophercloud/gophercloud/pull/1652)
|
||||||
|
* Added `compute/v2/extensions/tags.List` [GH-1679](https://github.com/gophercloud/gophercloud/pull/1679)
|
||||||
|
* Added `compute/v2/extensions/tags.Check` [GH-1679](https://github.com/gophercloud/gophercloud/pull/1679)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Changed `identity/v3/endpoints.ListOpts.RegionID` from `int` to `string` [GH-1664](https://github.com/gophercloud/gophercloud/pull/1664)
|
||||||
|
* Fixed issue where older time formats in some networking APIs/resources were unable to be parsed [GH-1671](https://github.com/gophercloud/gophercloud/pull/1664)
|
||||||
|
* Changed `SATA`, `SCSI`, and `SAS` types to `InterfaceType` in `baremetal/v1/nodes` [GH-1683]
|
||||||
|
|
||||||
|
## 0.3.0 (July 31, 2019)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `baremetal/apiversions.List` [GH-1577](https://github.com/gophercloud/gophercloud/pull/1577)
|
||||||
|
* Added `baremetal/apiversions.Get` [GH-1577](https://github.com/gophercloud/gophercloud/pull/1577)
|
||||||
|
* Added `compute/v2/extensions/servergroups.CreateOpts.Policy` [GH-1636](https://github.com/gophercloud/gophercloud/pull/1636)
|
||||||
|
* Added `identity/v3/extensions/trusts.Create` [GH-1644](https://github.com/gophercloud/gophercloud/pull/1644)
|
||||||
|
* Added `identity/v3/extensions/trusts.Delete` [GH-1644](https://github.com/gophercloud/gophercloud/pull/1644)
|
||||||
|
* Added `CreatedAt` and `UpdatedAt` to `networking/v2/extensions/layer3/floatingips.FloatingIP` [GH-1647](https://github.com/gophercloud/gophercloud/issues/1646)
|
||||||
|
* Added `CreatedAt` and `UpdatedAt` to `networking/v2/extensions/security/groups.SecGroup` [GH-1654](https://github.com/gophercloud/gophercloud/issues/1654)
|
||||||
|
* Added `CreatedAt` and `UpdatedAt` to `networking/v2/networks.Network` [GH-1657](https://github.com/gophercloud/gophercloud/issues/1657)
|
||||||
|
* Added `keymanager/v1/containers.CreateSecretRef` [GH-1659](https://github.com/gophercloud/gophercloud/issues/1659)
|
||||||
|
* Added `keymanager/v1/containers.DeleteSecretRef` [GH-1659](https://github.com/gophercloud/gophercloud/issues/1659)
|
||||||
|
* Added `sharedfilesystems/v2/shares.GetMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||||
|
* Added `sharedfilesystems/v2/shares.GetMetadatum` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||||
|
* Added `sharedfilesystems/v2/shares.SetMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||||
|
* Added `sharedfilesystems/v2/shares.UpdateMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||||
|
* Added `sharedfilesystems/v2/shares.DeleteMetadatum` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||||
|
* Added `sharedfilesystems/v2/sharetypes.IDFromName` [GH-1662](https://github.com/gophercloud/gophercloud/issues/1662)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Changed `baremetal/v1/nodes.CleanStep.Args` from `map[string]string` to `map[string]interface{}` [GH-1638](https://github.com/gophercloud/gophercloud/pull/1638)
|
||||||
|
* Removed `URLPath` and `ExpectedCodes` from `loadbalancer/v2/monitors.ToMonitorCreateMap` since Octavia now provides default values when these fields are not specified [GH-1640](https://github.com/gophercloud/gophercloud/pull/1540)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.2.0 (June 17, 2019)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Added `networking/v2/extensions/qos/rules.ListBandwidthLimitRules` [GH-1584](https://github.com/gophercloud/gophercloud/pull/1584)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.GetBandwidthLimitRule` [GH-1584](https://github.com/gophercloud/gophercloud/pull/1584)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.CreateBandwidthLimitRule` [GH-1584](https://github.com/gophercloud/gophercloud/pull/1584)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.UpdateBandwidthLimitRule` [GH-1589](https://github.com/gophercloud/gophercloud/pull/1589)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.DeleteBandwidthLimitRule` [GH-1590](https://github.com/gophercloud/gophercloud/pull/1590)
|
||||||
|
* Added `networking/v2/extensions/qos/policies.List` [GH-1591](https://github.com/gophercloud/gophercloud/pull/1591)
|
||||||
|
* Added `networking/v2/extensions/qos/policies.Get` [GH-1593](https://github.com/gophercloud/gophercloud/pull/1593)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.ListDSCPMarkingRules` [GH-1594](https://github.com/gophercloud/gophercloud/pull/1594)
|
||||||
|
* Added `networking/v2/extensions/qos/policies.Create` [GH-1595](https://github.com/gophercloud/gophercloud/pull/1595)
|
||||||
|
* Added `compute/v2/extensions/diagnostics.Get` [GH-1592](https://github.com/gophercloud/gophercloud/pull/1592)
|
||||||
|
* Added `networking/v2/extensions/qos/policies.Update` [GH-1603](https://github.com/gophercloud/gophercloud/pull/1603)
|
||||||
|
* Added `networking/v2/extensions/qos/policies.Delete` [GH-1603](https://github.com/gophercloud/gophercloud/pull/1603)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.CreateDSCPMarkingRule` [GH-1605](https://github.com/gophercloud/gophercloud/pull/1605)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.UpdateDSCPMarkingRule` [GH-1605](https://github.com/gophercloud/gophercloud/pull/1605)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.GetDSCPMarkingRule` [GH-1609](https://github.com/gophercloud/gophercloud/pull/1609)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.DeleteDSCPMarkingRule` [GH-1609](https://github.com/gophercloud/gophercloud/pull/1609)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.ListMinimumBandwidthRules` [GH-1615](https://github.com/gophercloud/gophercloud/pull/1615)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.GetMinimumBandwidthRule` [GH-1615](https://github.com/gophercloud/gophercloud/pull/1615)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.CreateMinimumBandwidthRule` [GH-1615](https://github.com/gophercloud/gophercloud/pull/1615)
|
||||||
|
* Added `Hostname` to `baremetalintrospection/v1/introspection.Data` [GH-1627](https://github.com/gophercloud/gophercloud/pull/1627)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.UpdateMinimumBandwidthRule` [GH-1624](https://github.com/gophercloud/gophercloud/pull/1624)
|
||||||
|
* Added `networking/v2/extensions/qos/rules.DeleteMinimumBandwidthRule` [GH-1624](https://github.com/gophercloud/gophercloud/pull/1624)
|
||||||
|
* Added `networking/v2/extensions/qos/ruletypes.GetRuleType` [GH-1625](https://github.com/gophercloud/gophercloud/pull/1625)
|
||||||
|
* Added `Extra` to `baremetalintrospection/v1/introspection.Data` [GH-1611](https://github.com/gophercloud/gophercloud/pull/1611)
|
||||||
|
* Added `blockstorage/extensions/volumeactions.SetImageMetadata` [GH-1621](https://github.com/gophercloud/gophercloud/pull/1621)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Updated `networking/v2/extensions/qos/rules.UpdateBandwidthLimitRule` to use return code 200 [GH-1606](https://github.com/gophercloud/gophercloud/pull/1606)
|
||||||
|
* Fixed bug in `compute/v2/extensions/schedulerhints.SchedulerHints.Query` where contents will now be marshalled to a string [GH-1620](https://github.com/gophercloud/gophercloud/pull/1620)
|
||||||
|
|
||||||
|
## 0.1.0 (May 27, 2019)
|
||||||
|
|
||||||
|
Initial tagged release.
|
||||||
191
vendor/github.com/gophercloud/gophercloud/LICENSE
generated
vendored
Normal file
191
vendor/github.com/gophercloud/gophercloud/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
Copyright 2012-2013 Rackspace, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
159
vendor/github.com/gophercloud/gophercloud/README.md
generated
vendored
Normal file
159
vendor/github.com/gophercloud/gophercloud/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
# Gophercloud: an OpenStack SDK for Go
|
||||||
|
[](https://travis-ci.org/gophercloud/gophercloud)
|
||||||
|
[](https://coveralls.io/github/gophercloud/gophercloud?branch=master)
|
||||||
|
|
||||||
|
Gophercloud is an OpenStack Go SDK.
|
||||||
|
|
||||||
|
## Useful links
|
||||||
|
|
||||||
|
* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud)
|
||||||
|
* [Effective Go](https://golang.org/doc/effective_go.html)
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
|
||||||
|
is pointing to an appropriate directory where you want to install Gophercloud:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir $HOME/go
|
||||||
|
export GOPATH=$HOME/go
|
||||||
|
```
|
||||||
|
|
||||||
|
To protect yourself against changes in your dependencies, we highly recommend choosing a
|
||||||
|
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
|
||||||
|
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
|
||||||
|
Gophercloud as a dependency like so:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/gophercloud/gophercloud
|
||||||
|
|
||||||
|
# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
godep save ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install all the source files you need into a `Godeps/_workspace` directory, which is
|
||||||
|
referenceable from your own source files when you use the `godep go` command.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
### Credentials
|
||||||
|
|
||||||
|
Because you'll be hitting an API, you will need to retrieve your OpenStack
|
||||||
|
credentials and either store them as environment variables or in your local Go
|
||||||
|
files. The first method is recommended because it decouples credential
|
||||||
|
information from source code, allowing you to push the latter to your version
|
||||||
|
control system without any security risk.
|
||||||
|
|
||||||
|
You will need to retrieve the following:
|
||||||
|
|
||||||
|
* username
|
||||||
|
* password
|
||||||
|
* a valid Keystone identity URL
|
||||||
|
|
||||||
|
For users that have the OpenStack dashboard installed, there's a shortcut. If
|
||||||
|
you visit the `project/access_and_security` path in Horizon and click on the
|
||||||
|
"Download OpenStack RC File" button at the top right hand corner, you will
|
||||||
|
download a bash file that exports all of your access details to environment
|
||||||
|
variables. To execute the file, run `source admin-openrc.sh` and you will be
|
||||||
|
prompted for your password.
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Once you have access to your credentials, you can begin plugging them into
|
||||||
|
Gophercloud. The next step is authentication, and this is handled by a base
|
||||||
|
"Provider" struct. To get one, you can either pass in your credentials
|
||||||
|
explicitly, or tell Gophercloud to use environment variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option 1: Pass in the values yourself
|
||||||
|
opts := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
|
||||||
|
Username: "{username}",
|
||||||
|
Password: "{password}",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option 2: Use a utility function to retrieve all your environment variables
|
||||||
|
opts, err := openstack.AuthOptionsFromEnv()
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have the `opts` variable, you can pass it in and get back a
|
||||||
|
`ProviderClient` struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ProviderClient` is the top-level client that all of your OpenStack services
|
||||||
|
derive from. The provider contains all of the authentication details that allow
|
||||||
|
your Go code to access the API - such as the base URL and token ID.
|
||||||
|
|
||||||
|
### Provision a server
|
||||||
|
|
||||||
|
Once we have a base Provider, we inject it as a dependency into each OpenStack
|
||||||
|
service. In order to work with the Compute API, we need a Compute service
|
||||||
|
client; which can be created like so:
|
||||||
|
|
||||||
|
```go
|
||||||
|
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||||
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
We then use this `client` for any Compute API operation we want. In our case,
|
||||||
|
we want to provision a new server - so we invoke the `Create` method and pass
|
||||||
|
in the flavor ID (hardware specification) and image ID (operating system) we're
|
||||||
|
interested in:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||||
|
|
||||||
|
server, err := servers.Create(client, servers.CreateOpts{
|
||||||
|
Name: "My new server!",
|
||||||
|
FlavorRef: "flavor_id",
|
||||||
|
ImageRef: "image_id",
|
||||||
|
}).Extract()
|
||||||
|
```
|
||||||
|
|
||||||
|
The above code sample creates a new server with the parameters, and embodies the
|
||||||
|
new resource in the `server` variable (a
|
||||||
|
[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct).
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works.
|
||||||
|
|
||||||
|
## Backwards-Compatibility Guarantees
|
||||||
|
|
||||||
|
None. Vendor it and write tests covering the parts you use.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See the [contributing guide](./.github/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## Help and feedback
|
||||||
|
|
||||||
|
If you're struggling with something or have spotted a potential bug, feel free
|
||||||
|
to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues).
|
||||||
|
|
||||||
|
## Thank You
|
||||||
|
|
||||||
|
We'd like to extend special thanks and appreciation to the following:
|
||||||
|
|
||||||
|
### OpenLab
|
||||||
|
|
||||||
|
<a href="http://openlabtesting.org/"><img src="./docs/assets/openlab.png" width="600px"></a>
|
||||||
|
|
||||||
|
OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases.
|
||||||
|
|
||||||
|
### VEXXHOST
|
||||||
|
|
||||||
|
<a href="https://vexxhost.com/"><img src="./docs/assets/vexxhost.png" width="600px"></a>
|
||||||
|
|
||||||
|
VEXXHOST is providing their services to assist with the development and testing of Gophercloud.
|
||||||
514
vendor/github.com/gophercloud/gophercloud/auth_options.go
generated
vendored
Normal file
514
vendor/github.com/gophercloud/gophercloud/auth_options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,514 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
/*
|
||||||
|
AuthOptions stores information needed to authenticate to an OpenStack Cloud.
|
||||||
|
You can populate one manually, or use a provider's AuthOptionsFromEnv() function
|
||||||
|
to read relevant information from the standard environment variables. Pass one
|
||||||
|
to a provider's AuthenticatedClient function to authenticate and obtain a
|
||||||
|
ProviderClient representing an active session on that provider.
|
||||||
|
|
||||||
|
Its fields are the union of those recognized by each identity implementation and
|
||||||
|
provider.
|
||||||
|
|
||||||
|
An example of manually providing authentication information:
|
||||||
|
|
||||||
|
opts := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
|
||||||
|
Username: "{username}",
|
||||||
|
Password: "{password}",
|
||||||
|
TenantID: "{tenant_id}",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
|
||||||
|
An example of using AuthOptionsFromEnv(), where the environment variables can
|
||||||
|
be read from a file, such as a standard openrc file:
|
||||||
|
|
||||||
|
opts, err := openstack.AuthOptionsFromEnv()
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
*/
|
||||||
|
type AuthOptions struct {
|
||||||
|
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
|
||||||
|
// the Identity API of the appropriate version. While it's ultimately needed by
|
||||||
|
// all of the identity services, it will often be populated by a provider-level
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// The IdentityEndpoint is typically referred to as the "auth_url" or
|
||||||
|
// "OS_AUTH_URL" in the information provided by the cloud operator.
|
||||||
|
IdentityEndpoint string `json:"-"`
|
||||||
|
|
||||||
|
// Username is required if using Identity V2 API. Consult with your provider's
|
||||||
|
// control panel to discover your account's username. In Identity V3, either
|
||||||
|
// UserID or a combination of Username and DomainID or DomainName are needed.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
UserID string `json:"-"`
|
||||||
|
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
|
||||||
|
// Passcode is used in TOTP authentication method
|
||||||
|
Passcode string `json:"passcode,omitempty"`
|
||||||
|
|
||||||
|
// At most one of DomainID and DomainName must be provided if using Username
|
||||||
|
// with Identity V3. Otherwise, either are optional.
|
||||||
|
DomainID string `json:"-"`
|
||||||
|
DomainName string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
||||||
|
// The same fields are known as project_id and project_name in the Identity
|
||||||
|
// V3 API, but are collected as TenantID and TenantName here in both cases.
|
||||||
|
// Some providers allow you to specify a TenantName instead of the TenantId.
|
||||||
|
// Some require both. Your provider's authentication policies will determine
|
||||||
|
// how these fields influence authentication.
|
||||||
|
// If DomainID or DomainName are provided, they will also apply to TenantName.
|
||||||
|
// It is not currently possible to authenticate with Username and a Domain
|
||||||
|
// and scope to a Project in a different Domain by using TenantName. To
|
||||||
|
// accomplish that, the ProjectID will need to be provided as the TenantID
|
||||||
|
// option.
|
||||||
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
TenantName string `json:"tenantName,omitempty"`
|
||||||
|
|
||||||
|
// AllowReauth should be set to true if you grant permission for Gophercloud to
|
||||||
|
// cache your credentials in memory, and to allow Gophercloud to attempt to
|
||||||
|
// re-authenticate automatically if/when your token expires. If you set it to
|
||||||
|
// false, it will not cache these settings, but re-authentication will not be
|
||||||
|
// possible. This setting defaults to false.
|
||||||
|
//
|
||||||
|
// NOTE: The reauth function will try to re-authenticate endlessly if left
|
||||||
|
// unchecked. The way to limit the number of attempts is to provide a custom
|
||||||
|
// HTTP client to the provider client and provide a transport that implements
|
||||||
|
// the RoundTripper interface and stores the number of failed retries. For an
|
||||||
|
// example of this, see here:
|
||||||
|
// https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
|
||||||
|
AllowReauth bool `json:"-"`
|
||||||
|
|
||||||
|
// TokenID allows users to authenticate (possibly as another user) with an
|
||||||
|
// authentication token ID.
|
||||||
|
TokenID string `json:"-"`
|
||||||
|
|
||||||
|
// Scope determines the scoping of the authentication request.
|
||||||
|
Scope *AuthScope `json:"-"`
|
||||||
|
|
||||||
|
// Authentication through Application Credentials requires supplying name, project and secret
|
||||||
|
// For project we can use TenantID
|
||||||
|
ApplicationCredentialID string `json:"-"`
|
||||||
|
ApplicationCredentialName string `json:"-"`
|
||||||
|
ApplicationCredentialSecret string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthScope allows a created token to be limited to a specific domain or project.
|
||||||
|
type AuthScope struct {
|
||||||
|
ProjectID string
|
||||||
|
ProjectName string
|
||||||
|
DomainID string
|
||||||
|
DomainName string
|
||||||
|
System bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||||
|
// interface in the v2 tokens package
|
||||||
|
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||||
|
// Populate the request map.
|
||||||
|
authMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
if opts.Username != "" {
|
||||||
|
if opts.Password != "" {
|
||||||
|
authMap["passwordCredentials"] = map[string]interface{}{
|
||||||
|
"username": opts.Username,
|
||||||
|
"password": opts.Password,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrMissingInput{Argument: "Password"}
|
||||||
|
}
|
||||||
|
} else if opts.TokenID != "" {
|
||||||
|
authMap["token"] = map[string]interface{}{
|
||||||
|
"id": opts.TokenID,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrMissingInput{Argument: "Username"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.TenantID != "" {
|
||||||
|
authMap["tenantId"] = opts.TenantID
|
||||||
|
}
|
||||||
|
if opts.TenantName != "" {
|
||||||
|
authMap["tenantName"] = opts.TenantName
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{"auth": authMap}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||||
|
// interface in the v3 tokens package
|
||||||
|
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
type domainReq struct {
|
||||||
|
ID *string `json:"id,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type projectReq struct {
|
||||||
|
Domain *domainReq `json:"domain,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
ID *string `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type userReq struct {
|
||||||
|
ID *string `json:"id,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Password *string `json:"password,omitempty"`
|
||||||
|
Passcode *string `json:"passcode,omitempty"`
|
||||||
|
Domain *domainReq `json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type passwordReq struct {
|
||||||
|
User userReq `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenReq struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type applicationCredentialReq struct {
|
||||||
|
ID *string `json:"id,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
User *userReq `json:"user,omitempty"`
|
||||||
|
Secret *string `json:"secret,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type totpReq struct {
|
||||||
|
User *userReq `json:"user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type identityReq struct {
|
||||||
|
Methods []string `json:"methods"`
|
||||||
|
Password *passwordReq `json:"password,omitempty"`
|
||||||
|
Token *tokenReq `json:"token,omitempty"`
|
||||||
|
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
|
||||||
|
TOTP *totpReq `json:"totp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type authReq struct {
|
||||||
|
Identity identityReq `json:"identity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Auth authReq `json:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the request structure based on the provided arguments. Create and return an error
|
||||||
|
// if insufficient or incompatible information is present.
|
||||||
|
var req request
|
||||||
|
|
||||||
|
if opts.Password == "" && opts.Passcode == "" {
|
||||||
|
if opts.TokenID != "" {
|
||||||
|
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
||||||
|
// parameters.
|
||||||
|
if opts.Username != "" {
|
||||||
|
return nil, ErrUsernameWithToken{}
|
||||||
|
}
|
||||||
|
if opts.UserID != "" {
|
||||||
|
return nil, ErrUserIDWithToken{}
|
||||||
|
}
|
||||||
|
if opts.DomainID != "" {
|
||||||
|
return nil, ErrDomainIDWithToken{}
|
||||||
|
}
|
||||||
|
if opts.DomainName != "" {
|
||||||
|
return nil, ErrDomainNameWithToken{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the request for Token authentication.
|
||||||
|
req.Auth.Identity.Methods = []string{"token"}
|
||||||
|
req.Auth.Identity.Token = &tokenReq{
|
||||||
|
ID: opts.TokenID,
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if opts.ApplicationCredentialID != "" {
|
||||||
|
// Configure the request for ApplicationCredentialID authentication.
|
||||||
|
// https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
|
||||||
|
// There are three kinds of possible application_credential requests
|
||||||
|
// 1. application_credential id + secret
|
||||||
|
// 2. application_credential name + secret + user_id
|
||||||
|
// 3. application_credential name + secret + username + domain_id / domain_name
|
||||||
|
if opts.ApplicationCredentialSecret == "" {
|
||||||
|
return nil, ErrAppCredMissingSecret{}
|
||||||
|
}
|
||||||
|
req.Auth.Identity.Methods = []string{"application_credential"}
|
||||||
|
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
||||||
|
ID: &opts.ApplicationCredentialID,
|
||||||
|
Secret: &opts.ApplicationCredentialSecret,
|
||||||
|
}
|
||||||
|
} else if opts.ApplicationCredentialName != "" {
|
||||||
|
if opts.ApplicationCredentialSecret == "" {
|
||||||
|
return nil, ErrAppCredMissingSecret{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userRequest *userReq
|
||||||
|
|
||||||
|
if opts.UserID != "" {
|
||||||
|
// UserID could be used without the domain information
|
||||||
|
userRequest = &userReq{
|
||||||
|
ID: &opts.UserID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if userRequest == nil && opts.Username == "" {
|
||||||
|
// Make sure that Username or UserID are provided
|
||||||
|
return nil, ErrUsernameOrUserID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if userRequest == nil && opts.DomainID != "" {
|
||||||
|
userRequest = &userReq{
|
||||||
|
Name: &opts.Username,
|
||||||
|
Domain: &domainReq{ID: &opts.DomainID},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if userRequest == nil && opts.DomainName != "" {
|
||||||
|
userRequest = &userReq{
|
||||||
|
Name: &opts.Username,
|
||||||
|
Domain: &domainReq{Name: &opts.DomainName},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that DomainID or DomainName are provided among Username
|
||||||
|
if userRequest == nil {
|
||||||
|
return nil, ErrDomainIDOrDomainName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Auth.Identity.Methods = []string{"application_credential"}
|
||||||
|
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
||||||
|
Name: &opts.ApplicationCredentialName,
|
||||||
|
User: userRequest,
|
||||||
|
Secret: &opts.ApplicationCredentialSecret,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no password or token ID or ApplicationCredential are available, authentication can't continue.
|
||||||
|
return nil, ErrMissingPassword{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Password authentication.
|
||||||
|
if opts.Password != "" {
|
||||||
|
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOTP authentication.
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp")
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one of Username and UserID must be specified.
|
||||||
|
if opts.Username == "" && opts.UserID == "" {
|
||||||
|
return nil, ErrUsernameOrUserID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Username != "" {
|
||||||
|
// If Username is provided, UserID may not be provided.
|
||||||
|
if opts.UserID != "" {
|
||||||
|
return nil, ErrUsernameOrUserID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either DomainID or DomainName must also be specified.
|
||||||
|
if opts.DomainID == "" && opts.DomainName == "" {
|
||||||
|
return nil, ErrDomainIDOrDomainName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.DomainID != "" {
|
||||||
|
if opts.DomainName != "" {
|
||||||
|
return nil, ErrDomainIDOrDomainName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the request for Username and Password authentication with a DomainID.
|
||||||
|
if opts.Password != "" {
|
||||||
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
|
User: userReq{
|
||||||
|
Name: &opts.Username,
|
||||||
|
Password: &opts.Password,
|
||||||
|
Domain: &domainReq{ID: &opts.DomainID},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
req.Auth.Identity.TOTP = &totpReq{
|
||||||
|
User: &userReq{
|
||||||
|
Name: &opts.Username,
|
||||||
|
Passcode: &opts.Passcode,
|
||||||
|
Domain: &domainReq{ID: &opts.DomainID},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.DomainName != "" {
|
||||||
|
// Configure the request for Username and Password authentication with a DomainName.
|
||||||
|
if opts.Password != "" {
|
||||||
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
|
User: userReq{
|
||||||
|
Name: &opts.Username,
|
||||||
|
Password: &opts.Password,
|
||||||
|
Domain: &domainReq{Name: &opts.DomainName},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
req.Auth.Identity.TOTP = &totpReq{
|
||||||
|
User: &userReq{
|
||||||
|
Name: &opts.Username,
|
||||||
|
Passcode: &opts.Passcode,
|
||||||
|
Domain: &domainReq{Name: &opts.DomainName},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.UserID != "" {
|
||||||
|
// If UserID is specified, neither DomainID nor DomainName may be.
|
||||||
|
if opts.DomainID != "" {
|
||||||
|
return nil, ErrDomainIDWithUserID{}
|
||||||
|
}
|
||||||
|
if opts.DomainName != "" {
|
||||||
|
return nil, ErrDomainNameWithUserID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the request for UserID and Password authentication.
|
||||||
|
if opts.Password != "" {
|
||||||
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
|
User: userReq{
|
||||||
|
ID: &opts.UserID,
|
||||||
|
Password: &opts.Password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
req.Auth.Identity.TOTP = &totpReq{
|
||||||
|
User: &userReq{
|
||||||
|
ID: &opts.UserID,
|
||||||
|
Passcode: &opts.Passcode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := BuildRequestBody(req, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(scope) != 0 {
|
||||||
|
b["auth"].(map[string]interface{})["scope"] = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in
|
||||||
|
// the v3 tokens package.
|
||||||
|
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
|
// For backwards compatibility.
|
||||||
|
// If AuthOptions.Scope was not set, try to determine it.
|
||||||
|
// This works well for common scenarios.
|
||||||
|
if opts.Scope == nil {
|
||||||
|
opts.Scope = new(AuthScope)
|
||||||
|
if opts.TenantID != "" {
|
||||||
|
opts.Scope.ProjectID = opts.TenantID
|
||||||
|
} else {
|
||||||
|
if opts.TenantName != "" {
|
||||||
|
opts.Scope.ProjectName = opts.TenantName
|
||||||
|
opts.Scope.DomainID = opts.DomainID
|
||||||
|
opts.Scope.DomainName = opts.DomainName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Scope.System {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"system": map[string]interface{}{
|
||||||
|
"all": true,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Scope.ProjectName != "" {
|
||||||
|
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||||
|
// ProjectID may not be supplied.
|
||||||
|
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
|
||||||
|
return nil, ErrScopeDomainIDOrDomainName{}
|
||||||
|
}
|
||||||
|
if opts.Scope.ProjectID != "" {
|
||||||
|
return nil, ErrScopeProjectIDOrProjectName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Scope.DomainID != "" {
|
||||||
|
// ProjectName + DomainID
|
||||||
|
return map[string]interface{}{
|
||||||
|
"project": map[string]interface{}{
|
||||||
|
"name": &opts.Scope.ProjectName,
|
||||||
|
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Scope.DomainName != "" {
|
||||||
|
// ProjectName + DomainName
|
||||||
|
return map[string]interface{}{
|
||||||
|
"project": map[string]interface{}{
|
||||||
|
"name": &opts.Scope.ProjectName,
|
||||||
|
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
} else if opts.Scope.ProjectID != "" {
|
||||||
|
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
|
||||||
|
if opts.Scope.DomainID != "" {
|
||||||
|
return nil, ErrScopeProjectIDAlone{}
|
||||||
|
}
|
||||||
|
if opts.Scope.DomainName != "" {
|
||||||
|
return nil, ErrScopeProjectIDAlone{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectID
|
||||||
|
return map[string]interface{}{
|
||||||
|
"project": map[string]interface{}{
|
||||||
|
"id": &opts.Scope.ProjectID,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else if opts.Scope.DomainID != "" {
|
||||||
|
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
|
||||||
|
if opts.Scope.DomainName != "" {
|
||||||
|
return nil, ErrScopeDomainIDOrDomainName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainID
|
||||||
|
return map[string]interface{}{
|
||||||
|
"domain": map[string]interface{}{
|
||||||
|
"id": &opts.Scope.DomainID,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else if opts.Scope.DomainName != "" {
|
||||||
|
// DomainName
|
||||||
|
return map[string]interface{}{
|
||||||
|
"domain": map[string]interface{}{
|
||||||
|
"name": &opts.Scope.DomainName,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts AuthOptions) CanReauth() bool {
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
// cannot reauth using TOTP passcode
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts.AllowReauth
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||||
|
// interface in the v3 tokens package.
|
||||||
|
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
52
vendor/github.com/gophercloud/gophercloud/auth_result.go
generated
vendored
Normal file
52
vendor/github.com/gophercloud/gophercloud/auth_result.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
/*
|
||||||
|
AuthResult is the result from the request that was used to obtain a provider
|
||||||
|
client's Keystone token. It is returned from ProviderClient.GetAuthResult().
|
||||||
|
|
||||||
|
The following types satisfy this interface:
|
||||||
|
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v2/tokens.CreateResult
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v3/tokens.CreateResult
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||||
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) {
|
||||||
|
r := providerClient.GetAuthResult()
|
||||||
|
if r == nil {
|
||||||
|
//ProviderClient did not use openstack.Authenticate(), e.g. because token
|
||||||
|
//was set manually with ProviderClient.SetToken()
|
||||||
|
return "", errors.New("no AuthResult available")
|
||||||
|
}
|
||||||
|
switch r := r.(type) {
|
||||||
|
case tokens2.CreateResult:
|
||||||
|
u, err := r.ExtractUser()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return u.ID, nil
|
||||||
|
case tokens3.CreateResult:
|
||||||
|
u, err := r.ExtractUser()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return u.ID, nil
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("got unexpected AuthResult type %t", r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Both implementing types share a lot of methods by name, like ExtractUser() in
|
||||||
|
this example. But those methods cannot be part of the AuthResult interface
|
||||||
|
because the return types are different (in this case, type tokens2.User vs.
|
||||||
|
type tokens3.User).
|
||||||
|
*/
|
||||||
|
type AuthResult interface {
|
||||||
|
ExtractTokenID() (string, error)
|
||||||
|
}
|
||||||
110
vendor/github.com/gophercloud/gophercloud/doc.go
generated
vendored
Normal file
110
vendor/github.com/gophercloud/gophercloud/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
Package gophercloud provides a multi-vendor interface to OpenStack-compatible
|
||||||
|
clouds. The library has a three-level hierarchy: providers, services, and
|
||||||
|
resources.
|
||||||
|
|
||||||
|
Authenticating with Providers
|
||||||
|
|
||||||
|
Provider structs represent the cloud providers that offer and manage a
|
||||||
|
collection of services. You will generally want to create one Provider
|
||||||
|
client per OpenStack cloud.
|
||||||
|
|
||||||
|
It is now recommended to use the `clientconfig` package found at
|
||||||
|
https://github.com/gophercloud/utils/tree/master/openstack/clientconfig
|
||||||
|
for all authentication purposes.
|
||||||
|
|
||||||
|
The below documentation is still relevant. clientconfig simply implements
|
||||||
|
the below and presents it in an easier and more flexible way.
|
||||||
|
|
||||||
|
Use your OpenStack credentials to create a Provider client. The
|
||||||
|
IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in
|
||||||
|
information provided by the cloud operator. Additionally, the cloud may refer to
|
||||||
|
TenantID or TenantName as project_id and project_name. Credentials are
|
||||||
|
specified like so:
|
||||||
|
|
||||||
|
opts := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
|
||||||
|
Username: "{username}",
|
||||||
|
Password: "{password}",
|
||||||
|
TenantID: "{tenant_id}",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
|
||||||
|
You can authenticate with a token by doing:
|
||||||
|
|
||||||
|
opts := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
|
||||||
|
TokenID: "{token_id}",
|
||||||
|
TenantID: "{tenant_id}",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
|
||||||
|
You may also use the openstack.AuthOptionsFromEnv() helper function. This
|
||||||
|
function reads in standard environment variables frequently found in an
|
||||||
|
OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant"
|
||||||
|
instead of "project".
|
||||||
|
|
||||||
|
opts, err := openstack.AuthOptionsFromEnv()
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
|
||||||
|
Service Clients
|
||||||
|
|
||||||
|
Service structs are specific to a provider and handle all of the logic and
|
||||||
|
operations for a particular OpenStack service. Examples of services include:
|
||||||
|
Compute, Object Storage, Block Storage. In order to define one, you need to
|
||||||
|
pass in the parent provider, like so:
|
||||||
|
|
||||||
|
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
|
||||||
|
|
||||||
|
client, err := openstack.NewComputeV2(provider, opts)
|
||||||
|
|
||||||
|
Resources
|
||||||
|
|
||||||
|
Resource structs are the domain models that services make use of in order
|
||||||
|
to work with and represent the state of API resources:
|
||||||
|
|
||||||
|
server, err := servers.Get(client, "{serverId}").Extract()
|
||||||
|
|
||||||
|
Intermediate Result structs are returned for API operations, which allow
|
||||||
|
generic access to the HTTP headers, response body, and any errors associated
|
||||||
|
with the network transaction. To turn a result into a usable resource struct,
|
||||||
|
you must call the Extract method which is chained to the response, or an
|
||||||
|
Extract function from an applicable extension:
|
||||||
|
|
||||||
|
result := servers.Get(client, "{serverId}")
|
||||||
|
|
||||||
|
// Attempt to extract the disk configuration from the OS-DCF disk config
|
||||||
|
// extension:
|
||||||
|
config, err := diskconfig.ExtractGet(result)
|
||||||
|
|
||||||
|
All requests that enumerate a collection return a Pager struct that is used to
|
||||||
|
iterate through the results one page at a time. Use the EachPage method on that
|
||||||
|
Pager to handle each successive Page in a closure, then use the appropriate
|
||||||
|
extraction method from that request's package to interpret that Page as a slice
|
||||||
|
of results:
|
||||||
|
|
||||||
|
err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
|
||||||
|
s, err := servers.ExtractServers(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the []servers.Server slice.
|
||||||
|
|
||||||
|
// Return "false" or an error to prematurely stop fetching new pages.
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
If you want to obtain the entire collection of pages without doing any
|
||||||
|
intermediary processing on each page, you can use the AllPages method:
|
||||||
|
|
||||||
|
allPages, err := servers.List(client, nil).AllPages()
|
||||||
|
allServers, err := servers.ExtractServers(allPages)
|
||||||
|
|
||||||
|
This top-level package contains utility functions and data types that are used
|
||||||
|
throughout the provider and service packages. Of particular note for end users
|
||||||
|
are the AuthOptions and EndpointOpts structs.
|
||||||
|
*/
|
||||||
|
package gophercloud
|
||||||
76
vendor/github.com/gophercloud/gophercloud/endpoint_search.go
generated
vendored
Normal file
76
vendor/github.com/gophercloud/gophercloud/endpoint_search.go
generated
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
// Availability indicates to whom a specific service endpoint is accessible:
|
||||||
|
// the internet at large, internal networks only, or only to administrators.
|
||||||
|
// Different identity services use different terminology for these. Identity v2
|
||||||
|
// lists them as different kinds of URLs within the service catalog ("adminURL",
|
||||||
|
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
|
||||||
|
// endpoint's response.
|
||||||
|
type Availability string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AvailabilityAdmin indicates that an endpoint is only available to
|
||||||
|
// administrators.
|
||||||
|
AvailabilityAdmin Availability = "admin"
|
||||||
|
|
||||||
|
// AvailabilityPublic indicates that an endpoint is available to everyone on
|
||||||
|
// the internet.
|
||||||
|
AvailabilityPublic Availability = "public"
|
||||||
|
|
||||||
|
// AvailabilityInternal indicates that an endpoint is only available within
|
||||||
|
// the cluster's internal network.
|
||||||
|
AvailabilityInternal Availability = "internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EndpointOpts specifies search criteria used by queries against an
|
||||||
|
// OpenStack service catalog. The options must contain enough information to
|
||||||
|
// unambiguously identify one, and only one, endpoint within the catalog.
|
||||||
|
//
|
||||||
|
// Usually, these are passed to service client factory functions in a provider
|
||||||
|
// package, like "openstack.NewComputeV2()".
|
||||||
|
type EndpointOpts struct {
|
||||||
|
// Type [required] is the service type for the client (e.g., "compute",
|
||||||
|
// "object-store"). Generally, this will be supplied by the service client
|
||||||
|
// function, but a user-given value will be honored if provided.
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// Name [optional] is the service name for the client (e.g., "nova") as it
|
||||||
|
// appears in the service catalog. Services can have the same Type but a
|
||||||
|
// different Name, which is why both Type and Name are sometimes needed.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Region [required] is the geographic region in which the endpoint resides,
|
||||||
|
// generally specifying which datacenter should house your resources.
|
||||||
|
// Required only for services that span multiple regions.
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// Availability [optional] is the visibility of the endpoint to be returned.
|
||||||
|
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
|
||||||
|
// or AvailabilityAdmin from this package.
|
||||||
|
//
|
||||||
|
// Availability is not required, and defaults to AvailabilityPublic. Not all
|
||||||
|
// providers or services offer all Availability options.
|
||||||
|
Availability Availability
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
EndpointLocator is an internal function to be used by provider implementations.
|
||||||
|
|
||||||
|
It provides an implementation that locates a single endpoint from a service
|
||||||
|
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
|
||||||
|
provider then uses it to discover related ServiceClients.
|
||||||
|
*/
|
||||||
|
type EndpointLocator func(EndpointOpts) (string, error)
|
||||||
|
|
||||||
|
// ApplyDefaults is an internal method to be used by provider implementations.
|
||||||
|
//
|
||||||
|
// It sets EndpointOpts fields if not already set, including a default type.
|
||||||
|
// Currently, EndpointOpts.Availability defaults to the public endpoint.
|
||||||
|
func (eo *EndpointOpts) ApplyDefaults(t string) {
|
||||||
|
if eo.Type == "" {
|
||||||
|
eo.Type = t
|
||||||
|
}
|
||||||
|
if eo.Availability == "" {
|
||||||
|
eo.Availability = AvailabilityPublic
|
||||||
|
}
|
||||||
|
}
|
||||||
490
vendor/github.com/gophercloud/gophercloud/errors.go
generated
vendored
Normal file
490
vendor/github.com/gophercloud/gophercloud/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,490 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BaseError is an error type that all other error types embed.
|
||||||
|
type BaseError struct {
|
||||||
|
DefaultErrString string
|
||||||
|
Info string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e BaseError) Error() string {
|
||||||
|
e.DefaultErrString = "An error occurred while executing a Gophercloud request."
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e BaseError) choseErrString() string {
|
||||||
|
if e.Info != "" {
|
||||||
|
return e.Info
|
||||||
|
}
|
||||||
|
return e.DefaultErrString
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingInput is the error when input is required in a particular
|
||||||
|
// situation but not provided by the user
|
||||||
|
type ErrMissingInput struct {
|
||||||
|
BaseError
|
||||||
|
Argument string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingInput) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
|
||||||
|
type ErrInvalidInput struct {
|
||||||
|
ErrMissingInput
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidInput) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingEnvironmentVariable is the error when environment variable is required
|
||||||
|
// in a particular situation but not provided by the user
|
||||||
|
type ErrMissingEnvironmentVariable struct {
|
||||||
|
BaseError
|
||||||
|
EnvironmentVariable string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingEnvironmentVariable) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables
|
||||||
|
// is required in a particular situation but not provided by the user
|
||||||
|
type ErrMissingAnyoneOfEnvironmentVariables struct {
|
||||||
|
BaseError
|
||||||
|
EnvironmentVariables []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf(
|
||||||
|
"Missing one of the following environment variables [%s]",
|
||||||
|
strings.Join(e.EnvironmentVariables, ", "),
|
||||||
|
)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
|
||||||
|
// those listed in OkCodes is encountered.
|
||||||
|
type ErrUnexpectedResponseCode struct {
|
||||||
|
BaseError
|
||||||
|
URL string
|
||||||
|
Method string
|
||||||
|
Expected []int
|
||||||
|
Actual int
|
||||||
|
Body []byte
|
||||||
|
ResponseHeader http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrUnexpectedResponseCode) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf(
|
||||||
|
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
|
||||||
|
e.Expected, e.Method, e.URL, e.Actual, e.Body,
|
||||||
|
)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatusCode returns the actual status code of the error.
|
||||||
|
func (e ErrUnexpectedResponseCode) GetStatusCode() int {
|
||||||
|
return e.Actual
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusCodeError is a convenience interface to easily allow access to the
|
||||||
|
// status code field of the various ErrDefault* types.
|
||||||
|
//
|
||||||
|
// By using this interface, you only have to make a single type cast of
|
||||||
|
// the returned error to err.(StatusCodeError) and then call GetStatusCode()
|
||||||
|
// instead of having a large switch statement checking for each of the
|
||||||
|
// ErrDefault* types.
|
||||||
|
type StatusCodeError interface {
|
||||||
|
Error() string
|
||||||
|
GetStatusCode() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
|
||||||
|
type ErrDefault400 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault401 is the default error type returned on a 401 HTTP response code.
|
||||||
|
type ErrDefault401 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault403 is the default error type returned on a 403 HTTP response code.
|
||||||
|
type ErrDefault403 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
|
||||||
|
type ErrDefault404 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault405 is the default error type returned on a 405 HTTP response code.
|
||||||
|
type ErrDefault405 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault408 is the default error type returned on a 408 HTTP response code.
|
||||||
|
type ErrDefault408 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault409 is the default error type returned on a 409 HTTP response code.
|
||||||
|
type ErrDefault409 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault429 is the default error type returned on a 429 HTTP response code.
|
||||||
|
type ErrDefault429 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault500 is the default error type returned on a 500 HTTP response code.
|
||||||
|
type ErrDefault500 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault503 is the default error type returned on a 503 HTTP response code.
|
||||||
|
type ErrDefault503 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrDefault400) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf(
|
||||||
|
"Bad request with: [%s %s], error message: %s",
|
||||||
|
e.Method, e.URL, e.Body,
|
||||||
|
)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
func (e ErrDefault401) Error() string {
|
||||||
|
return "Authentication failed"
|
||||||
|
}
|
||||||
|
func (e ErrDefault403) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf(
|
||||||
|
"Request forbidden: [%s %s], error message: %s",
|
||||||
|
e.Method, e.URL, e.Body,
|
||||||
|
)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
func (e ErrDefault404) Error() string {
|
||||||
|
return "Resource not found"
|
||||||
|
}
|
||||||
|
func (e ErrDefault405) Error() string {
|
||||||
|
return "Method not allowed"
|
||||||
|
}
|
||||||
|
func (e ErrDefault408) Error() string {
|
||||||
|
return "The server timed out waiting for the request"
|
||||||
|
}
|
||||||
|
func (e ErrDefault429) Error() string {
|
||||||
|
return "Too many requests have been sent in a given amount of time. Pause" +
|
||||||
|
" requests, wait up to one minute, and try again."
|
||||||
|
}
|
||||||
|
func (e ErrDefault500) Error() string {
|
||||||
|
return "Internal Server Error"
|
||||||
|
}
|
||||||
|
func (e ErrDefault503) Error() string {
|
||||||
|
return "The service is currently unable to handle the request due to a temporary" +
|
||||||
|
" overloading or maintenance. This is a temporary condition. Try again later."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err400er is the interface resource error types implement to override the error message
|
||||||
|
// from a 400 error.
|
||||||
|
type Err400er interface {
|
||||||
|
Error400(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err401er is the interface resource error types implement to override the error message
|
||||||
|
// from a 401 error.
|
||||||
|
type Err401er interface {
|
||||||
|
Error401(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err403er is the interface resource error types implement to override the error message
|
||||||
|
// from a 403 error.
|
||||||
|
type Err403er interface {
|
||||||
|
Error403(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err404er is the interface resource error types implement to override the error message
|
||||||
|
// from a 404 error.
|
||||||
|
type Err404er interface {
|
||||||
|
Error404(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err405er is the interface resource error types implement to override the error message
|
||||||
|
// from a 405 error.
|
||||||
|
type Err405er interface {
|
||||||
|
Error405(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err408er is the interface resource error types implement to override the error message
|
||||||
|
// from a 408 error.
|
||||||
|
type Err408er interface {
|
||||||
|
Error408(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err409er is the interface resource error types implement to override the error message
|
||||||
|
// from a 409 error.
|
||||||
|
type Err409er interface {
|
||||||
|
Error409(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err429er is the interface resource error types implement to override the error message
|
||||||
|
// from a 429 error.
|
||||||
|
type Err429er interface {
|
||||||
|
Error429(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err500er is the interface resource error types implement to override the error message
|
||||||
|
// from a 500 error.
|
||||||
|
type Err500er interface {
|
||||||
|
Error500(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err503er is the interface resource error types implement to override the error message
|
||||||
|
// from a 503 error.
|
||||||
|
type Err503er interface {
|
||||||
|
Error503(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTimeOut is the error type returned when an operations times out.
|
||||||
|
type ErrTimeOut struct {
|
||||||
|
BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrTimeOut) Error() string {
|
||||||
|
e.DefaultErrString = "A time out occurred"
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
|
||||||
|
type ErrUnableToReauthenticate struct {
|
||||||
|
BaseError
|
||||||
|
ErrOriginal error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrUnableToReauthenticate) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrErrorAfterReauthentication is the error type returned when reauthentication
|
||||||
|
// succeeds, but an error occurs afterword (usually an HTTP error).
|
||||||
|
type ErrErrorAfterReauthentication struct {
|
||||||
|
BaseError
|
||||||
|
ErrOriginal error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrErrorAfterReauthentication) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrServiceNotFound is returned when no service in a service catalog matches
|
||||||
|
// the provided EndpointOpts. This is generally returned by provider service
|
||||||
|
// factory methods like "NewComputeV2()" and can mean that a service is not
|
||||||
|
// enabled for your account.
|
||||||
|
type ErrServiceNotFound struct {
|
||||||
|
BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrServiceNotFound) Error() string {
|
||||||
|
e.DefaultErrString = "No suitable service could be found in the service catalog."
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEndpointNotFound is returned when no available endpoints match the
|
||||||
|
// provided EndpointOpts. This is also generally returned by provider service
|
||||||
|
// factory methods, and usually indicates that a region was specified
|
||||||
|
// incorrectly.
|
||||||
|
type ErrEndpointNotFound struct {
|
||||||
|
BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrEndpointNotFound) Error() string {
|
||||||
|
e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrResourceNotFound is the error when trying to retrieve a resource's
|
||||||
|
// ID by name and the resource doesn't exist.
|
||||||
|
type ErrResourceNotFound struct {
|
||||||
|
BaseError
|
||||||
|
Name string
|
||||||
|
ResourceType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrResourceNotFound) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
|
||||||
|
// ID by name and multiple resources have the user-provided name.
|
||||||
|
type ErrMultipleResourcesFound struct {
|
||||||
|
BaseError
|
||||||
|
Name string
|
||||||
|
Count int
|
||||||
|
ResourceType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMultipleResourcesFound) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnexpectedType is the error when an unexpected type is encountered
|
||||||
|
type ErrUnexpectedType struct {
|
||||||
|
BaseError
|
||||||
|
Expected string
|
||||||
|
Actual string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrUnexpectedType) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func unacceptedAttributeErr(attribute string) string {
|
||||||
|
return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redundantWithTokenErr(attribute string) string {
|
||||||
|
return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redundantWithUserID(attribute string) string {
|
||||||
|
return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
|
||||||
|
type ErrAPIKeyProvided struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrAPIKeyProvided) Error() string {
|
||||||
|
return unacceptedAttributeErr("APIKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
|
||||||
|
type ErrTenantIDProvided struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrTenantIDProvided) Error() string {
|
||||||
|
return unacceptedAttributeErr("TenantID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
|
||||||
|
type ErrTenantNameProvided struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrTenantNameProvided) Error() string {
|
||||||
|
return unacceptedAttributeErr("TenantName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
|
||||||
|
type ErrUsernameWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrUsernameWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("Username")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
|
||||||
|
type ErrUserIDWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrUserIDWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("UserID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
|
||||||
|
type ErrDomainIDWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainIDWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("DomainID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
|
||||||
|
type ErrDomainNameWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainNameWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("DomainName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
|
||||||
|
type ErrUsernameOrUserID struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrUsernameOrUserID) Error() string {
|
||||||
|
return "Exactly one of Username and UserID must be provided for password authentication"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
|
||||||
|
type ErrDomainIDWithUserID struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainIDWithUserID) Error() string {
|
||||||
|
return redundantWithUserID("DomainID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
|
||||||
|
type ErrDomainNameWithUserID struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainNameWithUserID) Error() string {
|
||||||
|
return redundantWithUserID("DomainName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
|
||||||
|
// It may also indicate that both a DomainID and a DomainName were provided at once.
|
||||||
|
type ErrDomainIDOrDomainName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainIDOrDomainName) Error() string {
|
||||||
|
return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingPassword indicates that no password was provided and no token is available.
|
||||||
|
type ErrMissingPassword struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrMissingPassword) Error() string {
|
||||||
|
return "You must provide a password to authenticate"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
|
||||||
|
type ErrScopeDomainIDOrDomainName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeDomainIDOrDomainName) Error() string {
|
||||||
|
return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
|
||||||
|
type ErrScopeProjectIDOrProjectName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeProjectIDOrProjectName) Error() string {
|
||||||
|
return "You must provide at most one of ProjectID or ProjectName in a Scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
|
||||||
|
type ErrScopeProjectIDAlone struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeProjectIDAlone) Error() string {
|
||||||
|
return "ProjectID must be supplied alone in a Scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
|
||||||
|
type ErrScopeEmpty struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeEmpty) Error() string {
|
||||||
|
return "You must provide either a Project or Domain in a Scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name
|
||||||
|
type ErrAppCredMissingSecret struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrAppCredMissingSecret) Error() string {
|
||||||
|
return "You must provide an Application Credential Secret"
|
||||||
|
}
|
||||||
13
vendor/github.com/gophercloud/gophercloud/go.mod
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/go.mod
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
module github.com/gophercloud/gophercloud
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e
|
||||||
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.7
|
||||||
|
)
|
||||||
26
vendor/github.com/gophercloud/gophercloud/go.sum
generated
vendored
Normal file
26
vendor/github.com/gophercloud/gophercloud/go.sum
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
|
||||||
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
1
vendor/github.com/gophercloud/gophercloud/internal/pkg.go
generated
vendored
Normal file
1
vendor/github.com/gophercloud/gophercloud/internal/pkg.go
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package internal
|
||||||
34
vendor/github.com/gophercloud/gophercloud/internal/util.go
generated
vendored
Normal file
34
vendor/github.com/gophercloud/gophercloud/internal/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemainingKeys will inspect a struct and compare it to a map. Any struct
|
||||||
|
// field that does not have a JSON tag that matches a key in the map or
|
||||||
|
// a matching lower-case field in the map will be returned as an extra.
|
||||||
|
//
|
||||||
|
// This is useful for determining the extra fields returned in response bodies
|
||||||
|
// for resources that can contain an arbitrary or dynamic number of fields.
|
||||||
|
func RemainingKeys(s interface{}, m map[string]interface{}) (extras map[string]interface{}) {
|
||||||
|
extras = make(map[string]interface{})
|
||||||
|
for k, v := range m {
|
||||||
|
extras[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
valueOf := reflect.ValueOf(s)
|
||||||
|
typeOf := reflect.TypeOf(s)
|
||||||
|
for i := 0; i < valueOf.NumField(); i++ {
|
||||||
|
field := typeOf.Field(i)
|
||||||
|
|
||||||
|
lowerField := strings.ToLower(field.Name)
|
||||||
|
delete(extras, lowerField)
|
||||||
|
|
||||||
|
if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" {
|
||||||
|
delete(extras, tagValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
128
vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
generated
vendored
Normal file
128
vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
generated
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nilOptions = gophercloud.AuthOptions{}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AuthOptionsFromEnv fills out an identity.AuthOptions structure with the
|
||||||
|
settings found on the various OpenStack OS_* environment variables.
|
||||||
|
|
||||||
|
The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
|
||||||
|
OS_PASSWORD and OS_PROJECT_ID.
|
||||||
|
|
||||||
|
Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings,
|
||||||
|
or an error will result. OS_PROJECT_ID, is optional.
|
||||||
|
|
||||||
|
OS_TENANT_ID and OS_TENANT_NAME are deprecated forms of OS_PROJECT_ID and
|
||||||
|
OS_PROJECT_NAME and the latter are expected against a v3 auth api.
|
||||||
|
|
||||||
|
If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will still be referred
|
||||||
|
as "tenant" in Gophercloud.
|
||||||
|
|
||||||
|
If OS_PROJECT_NAME is set, it requires OS_PROJECT_ID to be set as well to
|
||||||
|
handle projects not on the default domain.
|
||||||
|
|
||||||
|
To use this function, first set the OS_* environment variables (for example,
|
||||||
|
by sourcing an `openrc` file), then:
|
||||||
|
|
||||||
|
opts, err := openstack.AuthOptionsFromEnv()
|
||||||
|
provider, err := openstack.AuthenticatedClient(opts)
|
||||||
|
*/
|
||||||
|
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
||||||
|
authURL := os.Getenv("OS_AUTH_URL")
|
||||||
|
username := os.Getenv("OS_USERNAME")
|
||||||
|
userID := os.Getenv("OS_USERID")
|
||||||
|
password := os.Getenv("OS_PASSWORD")
|
||||||
|
passcode := os.Getenv("OS_PASSCODE")
|
||||||
|
tenantID := os.Getenv("OS_TENANT_ID")
|
||||||
|
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||||
|
domainID := os.Getenv("OS_DOMAIN_ID")
|
||||||
|
domainName := os.Getenv("OS_DOMAIN_NAME")
|
||||||
|
applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID")
|
||||||
|
applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME")
|
||||||
|
applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET")
|
||||||
|
|
||||||
|
// If OS_PROJECT_ID is set, overwrite tenantID with the value.
|
||||||
|
if v := os.Getenv("OS_PROJECT_ID"); v != "" {
|
||||||
|
tenantID = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// If OS_PROJECT_NAME is set, overwrite tenantName with the value.
|
||||||
|
if v := os.Getenv("OS_PROJECT_NAME"); v != "" {
|
||||||
|
tenantName = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if authURL == "" {
|
||||||
|
err := gophercloud.ErrMissingEnvironmentVariable{
|
||||||
|
EnvironmentVariable: "OS_AUTH_URL",
|
||||||
|
}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID == "" && username == "" {
|
||||||
|
// Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set
|
||||||
|
if applicationCredentialID == "" && applicationCredentialSecret == "" {
|
||||||
|
err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
|
||||||
|
EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
|
||||||
|
}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if password == "" && passcode == "" && applicationCredentialID == "" && applicationCredentialName == "" {
|
||||||
|
err := gophercloud.ErrMissingEnvironmentVariable{
|
||||||
|
// silently ignore TOTP passcode warning, since it is not a common auth method
|
||||||
|
EnvironmentVariable: "OS_PASSWORD",
|
||||||
|
}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" {
|
||||||
|
err := gophercloud.ErrMissingEnvironmentVariable{
|
||||||
|
EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET",
|
||||||
|
}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if domainID == "" && domainName == "" && tenantID == "" && tenantName != "" {
|
||||||
|
err := gophercloud.ErrMissingEnvironmentVariable{
|
||||||
|
EnvironmentVariable: "OS_PROJECT_ID",
|
||||||
|
}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" {
|
||||||
|
if userID == "" && username == "" {
|
||||||
|
return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
|
||||||
|
EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if username != "" && domainID == "" && domainName == "" {
|
||||||
|
return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
|
||||||
|
EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ao := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: authURL,
|
||||||
|
UserID: userID,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Passcode: passcode,
|
||||||
|
TenantID: tenantID,
|
||||||
|
TenantName: tenantName,
|
||||||
|
DomainID: domainID,
|
||||||
|
DomainName: domainName,
|
||||||
|
ApplicationCredentialID: applicationCredentialID,
|
||||||
|
ApplicationCredentialName: applicationCredentialName,
|
||||||
|
ApplicationCredentialSecret: applicationCredentialSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ao, nil
|
||||||
|
}
|
||||||
503
vendor/github.com/gophercloud/gophercloud/openstack/client.go
generated
vendored
Normal file
503
vendor/github.com/gophercloud/gophercloud/openstack/client.go
generated
vendored
Normal file
|
|
@ -0,0 +1,503 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1"
|
||||||
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewClient prepares an unauthenticated ProviderClient instance.
|
||||||
|
Most users will probably prefer using the AuthenticatedClient function
|
||||||
|
instead.
|
||||||
|
|
||||||
|
This is useful if you wish to explicitly control the version of the identity
|
||||||
|
service that's used for authentication explicitly, for example.
|
||||||
|
|
||||||
|
A basic example of using this would be:
|
||||||
|
|
||||||
|
ao, err := openstack.AuthOptionsFromEnv()
|
||||||
|
provider, err := openstack.NewClient(ao.IdentityEndpoint)
|
||||||
|
client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
|
||||||
|
*/
|
||||||
|
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
|
||||||
|
base, err := utils.BaseEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = gophercloud.NormalizeURL(endpoint)
|
||||||
|
base = gophercloud.NormalizeURL(base)
|
||||||
|
|
||||||
|
p := new(gophercloud.ProviderClient)
|
||||||
|
p.IdentityBase = base
|
||||||
|
p.IdentityEndpoint = endpoint
|
||||||
|
p.UseTokenLock()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
|
||||||
|
specified by the options, acquires a token, and returns a Provider Client
|
||||||
|
instance that's ready to operate.
|
||||||
|
|
||||||
|
If the full path to a versioned identity endpoint was specified (example:
|
||||||
|
http://example.com:5000/v3), that path will be used as the endpoint to query.
|
||||||
|
|
||||||
|
If a versionless endpoint was specified (example: http://example.com:5000/),
|
||||||
|
the endpoint will be queried to determine which versions of the identity service
|
||||||
|
are available, then chooses the most recent or most supported version.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
ao, err := openstack.AuthOptionsFromEnv()
|
||||||
|
provider, err := openstack.AuthenticatedClient(ao)
|
||||||
|
client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
|
||||||
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
|
||||||
|
client, err := NewClient(options.IdentityEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Authenticate(client, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate or re-authenticate against the most recent identity service
|
||||||
|
// supported at the provided endpoint.
|
||||||
|
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
||||||
|
versions := []*utils.Version{
|
||||||
|
{ID: v2, Priority: 20, Suffix: "/v2.0/"},
|
||||||
|
{ID: v3, Priority: 30, Suffix: "/v3/"},
|
||||||
|
}
|
||||||
|
|
||||||
|
chosen, endpoint, err := utils.ChooseVersion(client, versions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch chosen.ID {
|
||||||
|
case v2:
|
||||||
|
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
|
||||||
|
case v3:
|
||||||
|
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
|
||||||
|
default:
|
||||||
|
// The switch statement must be out of date from the versions list.
|
||||||
|
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
|
||||||
|
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||||
|
return v2auth(client, "", options, eo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||||
|
v2Client, err := NewIdentityV2(client, eo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint != "" {
|
||||||
|
v2Client.Endpoint = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
v2Opts := tokens2.AuthOptions{
|
||||||
|
IdentityEndpoint: options.IdentityEndpoint,
|
||||||
|
Username: options.Username,
|
||||||
|
Password: options.Password,
|
||||||
|
TenantID: options.TenantID,
|
||||||
|
TenantName: options.TenantName,
|
||||||
|
AllowReauth: options.AllowReauth,
|
||||||
|
TokenID: options.TokenID,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tokens2.Create(v2Client, v2Opts)
|
||||||
|
|
||||||
|
err = client.SetTokenAndAuthResult(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err := result.ExtractServiceCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AllowReauth {
|
||||||
|
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
|
||||||
|
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
|
||||||
|
// this should retry authentication only once
|
||||||
|
tac := *client
|
||||||
|
tac.SetThrowaway(true)
|
||||||
|
tac.ReauthFunc = nil
|
||||||
|
tac.SetTokenAndAuthResult(nil)
|
||||||
|
tao := options
|
||||||
|
tao.AllowReauth = false
|
||||||
|
client.ReauthFunc = func() error {
|
||||||
|
err := v2auth(&tac, endpoint, tao, eo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client.CopyTokenFrom(&tac)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
return V2EndpointURL(catalog, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticateV3 explicitly authenticates against the identity v3 service.
|
||||||
|
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||||
|
return v3auth(client, "", options, eo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||||
|
// Override the generated service endpoint with the one returned by the version endpoint.
|
||||||
|
v3Client, err := NewIdentityV3(client, eo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint != "" {
|
||||||
|
v3Client.Endpoint = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
var catalog *tokens3.ServiceCatalog
|
||||||
|
|
||||||
|
var tokenID string
|
||||||
|
// passthroughToken allows to passthrough the token without a scope
|
||||||
|
var passthroughToken bool
|
||||||
|
switch v := opts.(type) {
|
||||||
|
case *gophercloud.AuthOptions:
|
||||||
|
tokenID = v.TokenID
|
||||||
|
passthroughToken = (v.Scope == nil || *v.Scope == gophercloud.AuthScope{})
|
||||||
|
case *tokens3.AuthOptions:
|
||||||
|
tokenID = v.TokenID
|
||||||
|
passthroughToken = (v.Scope == tokens3.Scope{})
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenID != "" && passthroughToken {
|
||||||
|
// passing through the token ID without requesting a new scope
|
||||||
|
if opts.CanReauth() {
|
||||||
|
return fmt.Errorf("cannot use AllowReauth, when the token ID is defined and auth scope is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
v3Client.SetToken(tokenID)
|
||||||
|
result := tokens3.Get(v3Client, tokenID)
|
||||||
|
if result.Err != nil {
|
||||||
|
return result.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetTokenAndAuthResult(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err = result.ExtractServiceCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var result tokens3.CreateResult
|
||||||
|
switch opts.(type) {
|
||||||
|
case *ec2tokens.AuthOptions:
|
||||||
|
result = ec2tokens.Create(v3Client, opts)
|
||||||
|
case *oauth1.AuthOptions:
|
||||||
|
result = oauth1.Create(v3Client, opts)
|
||||||
|
default:
|
||||||
|
result = tokens3.Create(v3Client, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetTokenAndAuthResult(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err = result.ExtractServiceCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.CanReauth() {
|
||||||
|
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
|
||||||
|
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
|
||||||
|
// this should retry authentication only once
|
||||||
|
tac := *client
|
||||||
|
tac.SetThrowaway(true)
|
||||||
|
tac.ReauthFunc = nil
|
||||||
|
tac.SetTokenAndAuthResult(nil)
|
||||||
|
var tao tokens3.AuthOptionsBuilder
|
||||||
|
switch ot := opts.(type) {
|
||||||
|
case *gophercloud.AuthOptions:
|
||||||
|
o := *ot
|
||||||
|
o.AllowReauth = false
|
||||||
|
tao = &o
|
||||||
|
case *tokens3.AuthOptions:
|
||||||
|
o := *ot
|
||||||
|
o.AllowReauth = false
|
||||||
|
tao = &o
|
||||||
|
case *ec2tokens.AuthOptions:
|
||||||
|
o := *ot
|
||||||
|
o.AllowReauth = false
|
||||||
|
tao = &o
|
||||||
|
case *oauth1.AuthOptions:
|
||||||
|
o := *ot
|
||||||
|
o.AllowReauth = false
|
||||||
|
tao = &o
|
||||||
|
default:
|
||||||
|
tao = opts
|
||||||
|
}
|
||||||
|
client.ReauthFunc = func() error {
|
||||||
|
err := v3auth(&tac, endpoint, tao, eo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client.CopyTokenFrom(&tac)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
return V3EndpointURL(catalog, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentityV2 creates a ServiceClient that may be used to interact with the
|
||||||
|
// v2 identity service.
|
||||||
|
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
endpoint := client.IdentityBase + "v2.0/"
|
||||||
|
clientType := "identity"
|
||||||
|
var err error
|
||||||
|
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||||
|
eo.ApplyDefaults(clientType)
|
||||||
|
endpoint, err = client.EndpointLocator(eo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gophercloud.ServiceClient{
|
||||||
|
ProviderClient: client,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
Type: clientType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentityV3 creates a ServiceClient that may be used to access the v3
|
||||||
|
// identity service.
|
||||||
|
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
endpoint := client.IdentityBase + "v3/"
|
||||||
|
clientType := "identity"
|
||||||
|
var err error
|
||||||
|
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||||
|
eo.ApplyDefaults(clientType)
|
||||||
|
endpoint, err = client.EndpointLocator(eo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure endpoint still has a suffix of v3.
|
||||||
|
// This is because EndpointLocator might have found a versionless
|
||||||
|
// endpoint or the published endpoint is still /v2.0. In both
|
||||||
|
// cases, we need to fix the endpoint to point to /v3.
|
||||||
|
base, err := utils.BaseEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
base = gophercloud.NormalizeURL(base)
|
||||||
|
|
||||||
|
endpoint = base + "v3/"
|
||||||
|
|
||||||
|
return &gophercloud.ServiceClient{
|
||||||
|
ProviderClient: client,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
Type: clientType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc := new(gophercloud.ServiceClient)
|
||||||
|
eo.ApplyDefaults(clientType)
|
||||||
|
url, err := client.EndpointLocator(eo)
|
||||||
|
if err != nil {
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
sc.ProviderClient = client
|
||||||
|
sc.Endpoint = url
|
||||||
|
sc.Type = clientType
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBareMetalV1 creates a ServiceClient that may be used with the v1
|
||||||
|
// bare metal package.
|
||||||
|
func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "baremetal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBareMetalIntrospectionV1 creates a ServiceClient that may be used with the v1
|
||||||
|
// bare metal introspection package.
|
||||||
|
func NewBareMetalIntrospectionV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "baremetal-inspector")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1
|
||||||
|
// object storage package.
|
||||||
|
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "object-store")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute
|
||||||
|
// package.
|
||||||
|
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "compute")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network
|
||||||
|
// package.
|
||||||
|
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "network")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v2.0/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1
|
||||||
|
// block storage service.
|
||||||
|
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "volume")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2
|
||||||
|
// block storage service.
|
||||||
|
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "volumev2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
|
||||||
|
func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "volumev3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
|
||||||
|
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "sharev2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
|
||||||
|
// CDN service.
|
||||||
|
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "cdn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1
|
||||||
|
// orchestration service.
|
||||||
|
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "orchestration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
|
||||||
|
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "database")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS
|
||||||
|
// service.
|
||||||
|
func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "dns")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v2/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2
|
||||||
|
// image service.
|
||||||
|
func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "image")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v2/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
|
||||||
|
// load balancer service.
|
||||||
|
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "load-balancer")
|
||||||
|
|
||||||
|
// Fixes edge case having an OpenStack lb endpoint with trailing version number.
|
||||||
|
endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1)
|
||||||
|
|
||||||
|
sc.ResourceBase = endpoint + "v2.0/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering
|
||||||
|
// package.
|
||||||
|
func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "clustering")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
|
||||||
|
// service.
|
||||||
|
func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "messaging")
|
||||||
|
sc.MoreHeaders = map[string]string{"Client-ID": clientID}
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerV1 creates a ServiceClient that may be used with v1 container package
|
||||||
|
func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "container")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
|
||||||
|
// manager service.
|
||||||
|
func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "key-manager")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v1/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
|
||||||
|
// package.
|
||||||
|
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "container-infra")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
|
||||||
|
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "workflowv2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPlacementV1 creates a ServiceClient that may be used with the placement package.
|
||||||
|
func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "placement")
|
||||||
|
}
|
||||||
115
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go
generated
vendored
Normal file
115
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
Package servers provides information and interaction with the server API
|
||||||
|
resource in the OpenStack Compute service.
|
||||||
|
|
||||||
|
A server is a virtual machine instance in the compute system. In order for
|
||||||
|
one to be provisioned, a valid flavor and image are required.
|
||||||
|
|
||||||
|
Example to List Servers
|
||||||
|
|
||||||
|
listOpts := servers.ListOpts{
|
||||||
|
AllTenants: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
allPages, err := servers.List(computeClient, listOpts).AllPages()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allServers, err := servers.ExtractServers(allPages)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range allServers {
|
||||||
|
fmt.Printf("%+v\n", server)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Server
|
||||||
|
|
||||||
|
createOpts := servers.CreateOpts{
|
||||||
|
Name: "server_name",
|
||||||
|
ImageRef: "image-uuid",
|
||||||
|
FlavorRef: "flavor-uuid",
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := servers.Create(computeClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Delete a Server
|
||||||
|
|
||||||
|
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
|
||||||
|
err := servers.Delete(computeClient, serverID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Force Delete a Server
|
||||||
|
|
||||||
|
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
|
||||||
|
err := servers.ForceDelete(computeClient, serverID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Reboot a Server
|
||||||
|
|
||||||
|
rebootOpts := servers.RebootOpts{
|
||||||
|
Type: servers.SoftReboot,
|
||||||
|
}
|
||||||
|
|
||||||
|
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
|
||||||
|
|
||||||
|
err := servers.Reboot(computeClient, serverID, rebootOpts).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Rebuild a Server
|
||||||
|
|
||||||
|
rebuildOpts := servers.RebuildOpts{
|
||||||
|
Name: "new_name",
|
||||||
|
ImageID: "image-uuid",
|
||||||
|
}
|
||||||
|
|
||||||
|
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
|
||||||
|
|
||||||
|
server, err := servers.Rebuilt(computeClient, serverID, rebuildOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Resize a Server
|
||||||
|
|
||||||
|
resizeOpts := servers.ResizeOpts{
|
||||||
|
FlavorRef: "flavor-uuid",
|
||||||
|
}
|
||||||
|
|
||||||
|
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
|
||||||
|
|
||||||
|
err := servers.Resize(computeClient, serverID, resizeOpts).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = servers.ConfirmResize(computeClient, serverID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Snapshot a Server
|
||||||
|
|
||||||
|
snapshotOpts := servers.CreateImageOpts{
|
||||||
|
Name: "snapshot_name",
|
||||||
|
}
|
||||||
|
|
||||||
|
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
|
||||||
|
|
||||||
|
image, err := servers.CreateImage(computeClient, serverID, snapshotOpts).ExtractImageID()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package servers
|
||||||
71
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go
generated
vendored
Normal file
71
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package servers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNeitherImageIDNorImageNameProvided is the error when neither the image
|
||||||
|
// ID nor the image name is provided for a server operation
|
||||||
|
type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput }
|
||||||
|
|
||||||
|
func (e ErrNeitherImageIDNorImageNameProvided) Error() string {
|
||||||
|
return "One and only one of the image ID and the image name must be provided."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor
|
||||||
|
// ID nor the flavor name is provided for a server operation
|
||||||
|
type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput }
|
||||||
|
|
||||||
|
func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string {
|
||||||
|
return "One and only one of the flavor ID and the flavor name must be provided."
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput }
|
||||||
|
|
||||||
|
func (e ErrNoClientProvidedForIDByName) Error() string {
|
||||||
|
return "A service client must be provided to find a resource ID by name."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidHowParameterProvided is the error when an unknown value is given
|
||||||
|
// for the `how` argument
|
||||||
|
type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
// ErrNoAdminPassProvided is the error when an administrative password isn't
|
||||||
|
// provided for a server operation
|
||||||
|
type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput }
|
||||||
|
|
||||||
|
// ErrNoImageIDProvided is the error when an image ID isn't provided for a server
|
||||||
|
// operation
|
||||||
|
type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput }
|
||||||
|
|
||||||
|
// ErrNoIDProvided is the error when a server ID isn't provided for a server
|
||||||
|
// operation
|
||||||
|
type ErrNoIDProvided struct{ gophercloud.ErrMissingInput }
|
||||||
|
|
||||||
|
// ErrServer is a generic error type for servers HTTP operations.
|
||||||
|
type ErrServer struct {
|
||||||
|
gophercloud.ErrUnexpectedResponseCode
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se ErrServer) Error() string {
|
||||||
|
return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error404 overrides the generic 404 error message.
|
||||||
|
func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error {
|
||||||
|
se.ErrUnexpectedResponseCode = e
|
||||||
|
return &ErrServerNotFound{se}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrServerNotFound is the error when a 404 is received during server HTTP
|
||||||
|
// operations.
|
||||||
|
type ErrServerNotFound struct {
|
||||||
|
ErrServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrServerNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("I couldn't find server [%s]", e.ID)
|
||||||
|
}
|
||||||
752
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
Normal file
752
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,752 @@
|
||||||
|
package servers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// List request.
|
||||||
|
type ListOptsBuilder interface {
|
||||||
|
ToServerListQuery() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOpts allows the filtering and sorting of paginated collections through
|
||||||
|
// the API. Filtering is achieved by passing in struct field values that map to
|
||||||
|
// the server attributes you want to see returned. Marker and Limit are used
|
||||||
|
// for pagination.
|
||||||
|
type ListOpts struct {
|
||||||
|
// ChangesSince is a time/date stamp for when the server last changed status.
|
||||||
|
ChangesSince string `q:"changes-since"`
|
||||||
|
|
||||||
|
// Image is the name of the image in URL format.
|
||||||
|
Image string `q:"image"`
|
||||||
|
|
||||||
|
// Flavor is the name of the flavor in URL format.
|
||||||
|
Flavor string `q:"flavor"`
|
||||||
|
|
||||||
|
// Name of the server as a string; can be queried with regular expressions.
|
||||||
|
// Realize that ?name=bob returns both bob and bobb. If you need to match bob
|
||||||
|
// only, you can use a regular expression matching the syntax of the
|
||||||
|
// underlying database server implemented for Compute.
|
||||||
|
Name string `q:"name"`
|
||||||
|
|
||||||
|
// Status is the value of the status of the server so that you can filter on
|
||||||
|
// "ACTIVE" for example.
|
||||||
|
Status string `q:"status"`
|
||||||
|
|
||||||
|
// Host is the name of the host as a string.
|
||||||
|
Host string `q:"host"`
|
||||||
|
|
||||||
|
// Marker is a UUID of the server at which you want to set a marker.
|
||||||
|
Marker string `q:"marker"`
|
||||||
|
|
||||||
|
// Limit is an integer value for the limit of values to return.
|
||||||
|
Limit int `q:"limit"`
|
||||||
|
|
||||||
|
// AllTenants is a bool to show all tenants.
|
||||||
|
AllTenants bool `q:"all_tenants"`
|
||||||
|
|
||||||
|
// TenantID lists servers for a particular tenant.
|
||||||
|
// Setting "AllTenants = true" is required.
|
||||||
|
TenantID string `q:"tenant_id"`
|
||||||
|
|
||||||
|
// This requires the client to be set to microversion 2.26 or later.
|
||||||
|
// Tags filters on specific server tags. All tags must be present for the server.
|
||||||
|
Tags string `q:"tags"`
|
||||||
|
|
||||||
|
// This requires the client to be set to microversion 2.26 or later.
|
||||||
|
// TagsAny filters on specific server tags. At least one of the tags must be present for the server.
|
||||||
|
TagsAny string `q:"tags-any"`
|
||||||
|
|
||||||
|
// This requires the client to be set to microversion 2.26 or later.
|
||||||
|
// NotTags filters on specific server tags. All tags must be absent for the server.
|
||||||
|
NotTags string `q:"not-tags"`
|
||||||
|
|
||||||
|
// This requires the client to be set to microversion 2.26 or later.
|
||||||
|
// NotTagsAny filters on specific server tags. At least one of the tags must be absent for the server.
|
||||||
|
NotTagsAny string `q:"not-tags-any"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerListQuery formats a ListOpts into a query string.
|
||||||
|
func (opts ListOpts) ToServerListQuery() (string, error) {
|
||||||
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
|
return q.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List makes a request against the API to list servers accessible to you.
|
||||||
|
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||||
|
url := listDetailURL(client)
|
||||||
|
if opts != nil {
|
||||||
|
query, err := opts.ToServerListQuery()
|
||||||
|
if err != nil {
|
||||||
|
return pagination.Pager{Err: err}
|
||||||
|
}
|
||||||
|
url += query
|
||||||
|
}
|
||||||
|
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||||
|
return ServerPage{pagination.LinkedPageBase{PageResult: r}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// Create request.
|
||||||
|
type CreateOptsBuilder interface {
|
||||||
|
ToServerCreateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network is used within CreateOpts to control a new server's network
|
||||||
|
// attachments.
|
||||||
|
type Network struct {
|
||||||
|
// UUID of a network to attach to the newly provisioned server.
|
||||||
|
// Required unless Port is provided.
|
||||||
|
UUID string
|
||||||
|
|
||||||
|
// Port of a neutron network to attach to the newly provisioned server.
|
||||||
|
// Required unless UUID is provided.
|
||||||
|
Port string
|
||||||
|
|
||||||
|
// FixedIP specifies a fixed IPv4 address to be used on this network.
|
||||||
|
FixedIP string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Personality is an array of files that are injected into the server at launch.
|
||||||
|
type Personality []*File
|
||||||
|
|
||||||
|
// File is used within CreateOpts and RebuildOpts to inject a file into the
|
||||||
|
// server at launch.
|
||||||
|
// File implements the json.Marshaler interface, so when a Create or Rebuild
|
||||||
|
// operation is requested, json.Marshal will call File's MarshalJSON method.
|
||||||
|
type File struct {
|
||||||
|
// Path of the file.
|
||||||
|
Path string
|
||||||
|
|
||||||
|
// Contents of the file. Maximum content size is 255 bytes.
|
||||||
|
Contents []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the escaped file, base64 encoding the contents.
|
||||||
|
func (f *File) MarshalJSON() ([]byte, error) {
|
||||||
|
file := struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Contents string `json:"contents"`
|
||||||
|
}{
|
||||||
|
Path: f.Path,
|
||||||
|
Contents: base64.StdEncoding.EncodeToString(f.Contents),
|
||||||
|
}
|
||||||
|
return json.Marshal(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOpts specifies server creation parameters.
|
||||||
|
type CreateOpts struct {
|
||||||
|
// Name is the name to assign to the newly launched server.
|
||||||
|
Name string `json:"name" required:"true"`
|
||||||
|
|
||||||
|
// ImageRef is the ID or full URL to the image that contains the
|
||||||
|
// server's OS and initial state.
|
||||||
|
// Also optional if using the boot-from-volume extension.
|
||||||
|
ImageRef string `json:"imageRef"`
|
||||||
|
|
||||||
|
// FlavorRef is the ID or full URL to the flavor that describes the server's specs.
|
||||||
|
FlavorRef string `json:"flavorRef"`
|
||||||
|
|
||||||
|
// SecurityGroups lists the names of the security groups to which this server
|
||||||
|
// should belong.
|
||||||
|
SecurityGroups []string `json:"-"`
|
||||||
|
|
||||||
|
// UserData contains configuration information or scripts to use upon launch.
|
||||||
|
// Create will base64-encode it for you, if it isn't already.
|
||||||
|
UserData []byte `json:"-"`
|
||||||
|
|
||||||
|
// AvailabilityZone in which to launch the server.
|
||||||
|
AvailabilityZone string `json:"availability_zone,omitempty"`
|
||||||
|
|
||||||
|
// Networks dictates how this server will be attached to available networks.
|
||||||
|
// By default, the server will be attached to all isolated networks for the
|
||||||
|
// tenant.
|
||||||
|
// Starting with microversion 2.37 networks can also be an "auto" or "none"
|
||||||
|
// string.
|
||||||
|
Networks interface{} `json:"-"`
|
||||||
|
|
||||||
|
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the
|
||||||
|
// server.
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Personality includes files to inject into the server at launch.
|
||||||
|
// Create will base64-encode file contents for you.
|
||||||
|
Personality Personality `json:"personality,omitempty"`
|
||||||
|
|
||||||
|
// ConfigDrive enables metadata injection through a configuration drive.
|
||||||
|
ConfigDrive *bool `json:"config_drive,omitempty"`
|
||||||
|
|
||||||
|
// AdminPass sets the root user password. If not set, a randomly-generated
|
||||||
|
// password will be created and returned in the response.
|
||||||
|
AdminPass string `json:"adminPass,omitempty"`
|
||||||
|
|
||||||
|
// AccessIPv4 specifies an IPv4 address for the instance.
|
||||||
|
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||||
|
|
||||||
|
// AccessIPv6 specifies an IPv6 address for the instance.
|
||||||
|
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||||
|
|
||||||
|
// Min specifies Minimum number of servers to launch.
|
||||||
|
Min int `json:"min_count,omitempty"`
|
||||||
|
|
||||||
|
// Max specifies Maximum number of servers to launch.
|
||||||
|
Max int `json:"max_count,omitempty"`
|
||||||
|
|
||||||
|
// ServiceClient will allow calls to be made to retrieve an image or
|
||||||
|
// flavor ID by name.
|
||||||
|
ServiceClient *gophercloud.ServiceClient `json:"-"`
|
||||||
|
|
||||||
|
// Tags allows a server to be tagged with single-word metadata.
|
||||||
|
// Requires microversion 2.52 or later.
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerCreateMap assembles a request body based on the contents of a
|
||||||
|
// CreateOpts.
|
||||||
|
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||||
|
opts.ServiceClient = nil
|
||||||
|
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.UserData != nil {
|
||||||
|
var userData string
|
||||||
|
if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil {
|
||||||
|
userData = base64.StdEncoding.EncodeToString(opts.UserData)
|
||||||
|
} else {
|
||||||
|
userData = string(opts.UserData)
|
||||||
|
}
|
||||||
|
b["user_data"] = &userData
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.SecurityGroups) > 0 {
|
||||||
|
securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
|
||||||
|
for i, groupName := range opts.SecurityGroups {
|
||||||
|
securityGroups[i] = map[string]interface{}{"name": groupName}
|
||||||
|
}
|
||||||
|
b["security_groups"] = securityGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := opts.Networks.(type) {
|
||||||
|
case []Network:
|
||||||
|
if len(v) > 0 {
|
||||||
|
networks := make([]map[string]interface{}, len(v))
|
||||||
|
for i, net := range v {
|
||||||
|
networks[i] = make(map[string]interface{})
|
||||||
|
if net.UUID != "" {
|
||||||
|
networks[i]["uuid"] = net.UUID
|
||||||
|
}
|
||||||
|
if net.Port != "" {
|
||||||
|
networks[i]["port"] = net.Port
|
||||||
|
}
|
||||||
|
if net.FixedIP != "" {
|
||||||
|
networks[i]["fixed_ip"] = net.FixedIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b["networks"] = networks
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
if v == "auto" || v == "none" {
|
||||||
|
b["networks"] = v
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf(`networks must be a slice of Network struct or a string with "auto" or "none" values, current value is %q`, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Min != 0 {
|
||||||
|
b["min_count"] = opts.Min
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Max != 0 {
|
||||||
|
b["max_count"] = opts.Max
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{"server": b}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create requests a server to be provisioned to the user in the current tenant.
|
||||||
|
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||||
|
reqBody, err := opts.ToServerCreateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(listURL(client), reqBody, &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete requests that a server previously provisioned be removed from your
|
||||||
|
// account.
|
||||||
|
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||||
|
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceDelete forces the deletion of a server.
|
||||||
|
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||||
|
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get requests details on a single server, by ID.
|
||||||
|
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||||
|
resp, err := client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200, 203},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOptsBuilder allows extensions to add additional attributes to the
|
||||||
|
// Update request.
|
||||||
|
type UpdateOptsBuilder interface {
|
||||||
|
ToServerUpdateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOpts specifies the base attributes that may be updated on an existing
|
||||||
|
// server.
|
||||||
|
type UpdateOpts struct {
|
||||||
|
// Name changes the displayed name of the server.
|
||||||
|
// The server host name will *not* change.
|
||||||
|
// Server names are not constrained to be unique, even within the same tenant.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// AccessIPv4 provides a new IPv4 address for the instance.
|
||||||
|
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||||
|
|
||||||
|
// AccessIPv6 provides a new IPv6 address for the instance.
|
||||||
|
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerUpdateMap formats an UpdateOpts structure into a request body.
|
||||||
|
func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "server")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update requests that various attributes of the indicated server be changed.
|
||||||
|
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||||
|
b, err := opts.ToServerUpdateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeAdminPassword alters the administrator or root password for a specified
|
||||||
|
// server.
|
||||||
|
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) {
|
||||||
|
b := map[string]interface{}{
|
||||||
|
"changePassword": map[string]string{
|
||||||
|
"adminPass": newPassword,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := client.Post(actionURL(client, id), b, nil, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebootMethod describes the mechanisms by which a server reboot can be requested.
|
||||||
|
type RebootMethod string
|
||||||
|
|
||||||
|
// These constants determine how a server should be rebooted.
|
||||||
|
// See the Reboot() function for further details.
|
||||||
|
const (
|
||||||
|
SoftReboot RebootMethod = "SOFT"
|
||||||
|
HardReboot RebootMethod = "HARD"
|
||||||
|
OSReboot = SoftReboot
|
||||||
|
PowerCycle = HardReboot
|
||||||
|
)
|
||||||
|
|
||||||
|
// RebootOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// reboot request.
|
||||||
|
type RebootOptsBuilder interface {
|
||||||
|
ToServerRebootMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebootOpts provides options to the reboot request.
|
||||||
|
type RebootOpts struct {
|
||||||
|
// Type is the type of reboot to perform on the server.
|
||||||
|
Type RebootMethod `json:"type" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerRebootMap builds a body for the reboot request.
|
||||||
|
func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "reboot")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reboot requests that a given server reboot.
|
||||||
|
|
||||||
|
Two methods exist for rebooting a server:
|
||||||
|
|
||||||
|
HardReboot (aka PowerCycle) starts the server instance by physically cutting
|
||||||
|
power to the machine, or if a VM, terminating it at the hypervisor level.
|
||||||
|
It's done. Caput. Full stop.
|
||||||
|
Then, after a brief while, power is restored or the VM instance restarted.
|
||||||
|
|
||||||
|
SoftReboot (aka OSReboot) simply tells the OS to restart under its own
|
||||||
|
procedure.
|
||||||
|
E.g., in Linux, asking it to enter runlevel 6, or executing
|
||||||
|
"sudo shutdown -r now", or by asking Windows to rtart the machine.
|
||||||
|
*/
|
||||||
|
func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
|
||||||
|
b, err := opts.ToServerRebootMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(actionURL(client, id), b, nil, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebuildOptsBuilder allows extensions to provide additional parameters to the
|
||||||
|
// rebuild request.
|
||||||
|
type RebuildOptsBuilder interface {
|
||||||
|
ToServerRebuildMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebuildOpts represents the configuration options used in a server rebuild
|
||||||
|
// operation.
|
||||||
|
type RebuildOpts struct {
|
||||||
|
// AdminPass is the server's admin password
|
||||||
|
AdminPass string `json:"adminPass,omitempty"`
|
||||||
|
|
||||||
|
// ImageRef is the ID of the image you want your server to be provisioned on.
|
||||||
|
ImageRef string `json:"imageRef"`
|
||||||
|
|
||||||
|
// Name to set the server to
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// AccessIPv4 [optional] provides a new IPv4 address for the instance.
|
||||||
|
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||||
|
|
||||||
|
// AccessIPv6 [optional] provides a new IPv6 address for the instance.
|
||||||
|
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||||
|
|
||||||
|
// Metadata [optional] contains key-value pairs (up to 255 bytes each)
|
||||||
|
// to attach to the server.
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Personality [optional] includes files to inject into the server at launch.
|
||||||
|
// Rebuild will base64-encode file contents for you.
|
||||||
|
Personality Personality `json:"personality,omitempty"`
|
||||||
|
|
||||||
|
// ServiceClient will allow calls to be made to retrieve an image or
|
||||||
|
// flavor ID by name.
|
||||||
|
ServiceClient *gophercloud.ServiceClient `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
|
||||||
|
func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
|
||||||
|
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{"rebuild": b}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild will reprovision the server according to the configuration options
|
||||||
|
// provided in the RebuildOpts struct.
|
||||||
|
func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) {
|
||||||
|
b, err := opts.ToServerRebuildMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(actionURL(client, id), b, &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// resize request.
|
||||||
|
type ResizeOptsBuilder interface {
|
||||||
|
ToServerResizeMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeOpts represents the configuration options used to control a Resize
|
||||||
|
// operation.
|
||||||
|
type ResizeOpts struct {
|
||||||
|
// FlavorRef is the ID of the flavor you wish your server to become.
|
||||||
|
FlavorRef string `json:"flavorRef" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON
|
||||||
|
// request body for the Resize request.
|
||||||
|
func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "resize")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize instructs the provider to change the flavor of the server.
|
||||||
|
//
|
||||||
|
// Note that this implies rebuilding it.
|
||||||
|
//
|
||||||
|
// Unfortunately, one cannot pass rebuild parameters to the resize function.
|
||||||
|
// When the resize completes, the server will be in VERIFY_RESIZE state.
|
||||||
|
// While in this state, you can explore the use of the new server's
|
||||||
|
// configuration. If you like it, call ConfirmResize() to commit the resize
|
||||||
|
// permanently. Otherwise, call RevertResize() to restore the old configuration.
|
||||||
|
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
|
||||||
|
b, err := opts.ToServerResizeMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(actionURL(client, id), b, nil, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmResize confirms a previous resize operation on a server.
|
||||||
|
// See Resize() for more details.
|
||||||
|
func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||||
|
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{201, 202, 204},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevertResize cancels a previous resize operation on a server.
|
||||||
|
// See Resize() for more details.
|
||||||
|
func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||||
|
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetMetadataOptsBuilder allows extensions to add additional parameters to
|
||||||
|
// the Reset request.
|
||||||
|
type ResetMetadataOptsBuilder interface {
|
||||||
|
ToMetadataResetMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataOpts is a map that contains key-value pairs.
|
||||||
|
type MetadataOpts map[string]string
|
||||||
|
|
||||||
|
// ToMetadataResetMap assembles a body for a Reset request based on the contents
|
||||||
|
// of a MetadataOpts.
|
||||||
|
func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
|
||||||
|
return map[string]interface{}{"metadata": opts}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMetadataUpdateMap assembles a body for an Update request based on the
|
||||||
|
// contents of a MetadataOpts.
|
||||||
|
func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
|
||||||
|
return map[string]interface{}{"metadata": opts}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetMetadata will create multiple new key-value pairs for the given server
|
||||||
|
// ID.
|
||||||
|
// Note: Using this operation will erase any already-existing metadata and
|
||||||
|
// create the new metadata provided. To keep any already-existing metadata,
|
||||||
|
// use the UpdateMetadatas or UpdateMetadata function.
|
||||||
|
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) {
|
||||||
|
b, err := opts.ToMetadataResetMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata requests all the metadata for the given server ID.
|
||||||
|
func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
|
||||||
|
resp, err := client.Get(metadataURL(client, id), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
|
||||||
|
// the Create request.
|
||||||
|
type UpdateMetadataOptsBuilder interface {
|
||||||
|
ToMetadataUpdateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMetadata updates (or creates) all the metadata specified by opts for
|
||||||
|
// the given server ID. This operation does not affect already-existing metadata
|
||||||
|
// that is not specified by opts.
|
||||||
|
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
|
||||||
|
b, err := opts.ToMetadataUpdateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadatumOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// Create request.
|
||||||
|
type MetadatumOptsBuilder interface {
|
||||||
|
ToMetadatumCreateMap() (map[string]interface{}, string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadatumOpts is a map of length one that contains a key-value pair.
|
||||||
|
type MetadatumOpts map[string]string
|
||||||
|
|
||||||
|
// ToMetadatumCreateMap assembles a body for a Create request based on the
|
||||||
|
// contents of a MetadataumOpts.
|
||||||
|
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
|
||||||
|
if len(opts) != 1 {
|
||||||
|
err := gophercloud.ErrInvalidInput{}
|
||||||
|
err.Argument = "servers.MetadatumOpts"
|
||||||
|
err.Info = "Must have 1 and only 1 key-value pair"
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
metadatum := map[string]interface{}{"meta": opts}
|
||||||
|
var key string
|
||||||
|
for k := range metadatum["meta"].(MetadatumOpts) {
|
||||||
|
key = k
|
||||||
|
}
|
||||||
|
return metadatum, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMetadatum will create or update the key-value pair with the given key
|
||||||
|
// for the given server ID.
|
||||||
|
func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) {
|
||||||
|
b, key, err := opts.ToMetadatumCreateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadatum requests the key-value pair with the given key for the given
|
||||||
|
// server ID.
|
||||||
|
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
|
||||||
|
resp, err := client.Get(metadatumURL(client, id, key), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMetadatum will delete the key-value pair with the given key for the
|
||||||
|
// given server ID.
|
||||||
|
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
|
||||||
|
resp, err := client.Delete(metadatumURL(client, id, key), nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAddresses makes a request against the API to list the servers IP
|
||||||
|
// addresses.
|
||||||
|
func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
|
||||||
|
return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
|
||||||
|
return AddressPage{pagination.SinglePageBase(r)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAddressesByNetwork makes a request against the API to list the servers IP
|
||||||
|
// addresses for the given network.
|
||||||
|
func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
|
||||||
|
return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
|
||||||
|
return NetworkAddressPage{pagination.SinglePageBase(r)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateImageOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// CreateImage request.
|
||||||
|
type CreateImageOptsBuilder interface {
|
||||||
|
ToServerCreateImageMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateImageOpts provides options to pass to the CreateImage request.
|
||||||
|
type CreateImageOpts struct {
|
||||||
|
// Name of the image/snapshot.
|
||||||
|
Name string `json:"name" required:"true"`
|
||||||
|
|
||||||
|
// Metadata contains key-value pairs (up to 255 bytes each) to attach to
|
||||||
|
// the created image.
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerCreateImageMap formats a CreateImageOpts structure into a request
|
||||||
|
// body.
|
||||||
|
func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "createImage")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateImage makes a request against the nova API to schedule an image to be
|
||||||
|
// created of the server
|
||||||
|
func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) {
|
||||||
|
b, err := opts.ToServerCreateImageMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{202},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPassword makes a request against the nova API to get the encrypted
|
||||||
|
// administrative password.
|
||||||
|
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
|
||||||
|
resp, err := client.Get(passwordURL(client, serverId), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowConsoleOutputOptsBuilder is the interface types must satisfy in order to be
|
||||||
|
// used as ShowConsoleOutput options
|
||||||
|
type ShowConsoleOutputOptsBuilder interface {
|
||||||
|
ToServerShowConsoleOutputMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder
|
||||||
|
type ShowConsoleOutputOpts struct {
|
||||||
|
// The number of lines to fetch from the end of console log.
|
||||||
|
// All lines will be returned if this is not specified.
|
||||||
|
Length int `json:"length,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToServerShowConsoleOutputMap formats a ShowConsoleOutputOpts structure into a request body.
|
||||||
|
func (opts ShowConsoleOutputOpts) ToServerShowConsoleOutputMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "os-getConsoleOutput")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowConsoleOutput makes a request against the nova API to get console log from the server
|
||||||
|
func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowConsoleOutputOptsBuilder) (r ShowConsoleOutputResult) {
|
||||||
|
b, err := opts.ToServerShowConsoleOutputMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
425
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
Normal file
425
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,425 @@
|
||||||
|
package servers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any serverResult as a Server, if possible.
|
||||||
|
func (r serverResult) Extract() (*Server, error) {
|
||||||
|
var s Server
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return &s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r serverResult) ExtractInto(v interface{}) error {
|
||||||
|
return r.Result.ExtractIntoStructPtr(v, "server")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractServersInto(r pagination.Page, v interface{}) error {
|
||||||
|
return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResult is the response from a Create operation. Call its Extract
|
||||||
|
// method to interpret it as a Server.
|
||||||
|
type CreateResult struct {
|
||||||
|
serverResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult is the response from a Get operation. Call its Extract
|
||||||
|
// method to interpret it as a Server.
|
||||||
|
type GetResult struct {
|
||||||
|
serverResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResult is the response from an Update operation. Call its Extract
|
||||||
|
// method to interpret it as a Server.
|
||||||
|
type UpdateResult struct {
|
||||||
|
serverResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebuildResult is the response from a Rebuild operation. Call its Extract
|
||||||
|
// method to interpret it as a Server.
|
||||||
|
type RebuildResult struct {
|
||||||
|
serverResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionResult represents the result of server action operations, like reboot.
|
||||||
|
// Call its ExtractErr method to determine if the action succeeded or failed.
|
||||||
|
type ActionResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateImageResult is the response from a CreateImage operation. Call its
|
||||||
|
// ExtractImageID method to retrieve the ID of the newly created image.
|
||||||
|
type CreateImageResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowConsoleOutputResult represents the result of console output from a server
|
||||||
|
type ShowConsoleOutputResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return the console output from a ShowConsoleOutput request.
|
||||||
|
func (r ShowConsoleOutputResult) Extract() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Output string `json:"output"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Output, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswordResult represent the result of a get os-server-password operation.
|
||||||
|
// Call its ExtractPassword method to retrieve the password.
|
||||||
|
type GetPasswordResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractPassword gets the encrypted password.
|
||||||
|
// If privateKey != nil the password is decrypted with the private key.
|
||||||
|
// If privateKey == nil the encrypted password is returned and can be decrypted
|
||||||
|
// with:
|
||||||
|
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
|
||||||
|
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err == nil && privateKey != nil && s.Password != "" {
|
||||||
|
return decryptPassword(s.Password, privateKey)
|
||||||
|
}
|
||||||
|
return s.Password, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
|
||||||
|
b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
|
||||||
|
|
||||||
|
n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
|
||||||
|
}
|
||||||
|
password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Failed to decrypt password: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(password), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractImageID gets the ID of the newly created server image from the header.
|
||||||
|
func (r CreateImageResult) ExtractImageID() (string, error) {
|
||||||
|
if r.Err != nil {
|
||||||
|
return "", r.Err
|
||||||
|
}
|
||||||
|
// Get the image id from the header
|
||||||
|
u, err := url.ParseRequestURI(r.Header.Get("Location"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
imageID := path.Base(u.Path)
|
||||||
|
if imageID == "." || imageID == "/" {
|
||||||
|
return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u)
|
||||||
|
}
|
||||||
|
return imageID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server represents a server/instance in the OpenStack cloud.
|
||||||
|
type Server struct {
|
||||||
|
// ID uniquely identifies this server amongst all other servers,
|
||||||
|
// including those not accessible to the current tenant.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// TenantID identifies the tenant owning this server resource.
|
||||||
|
TenantID string `json:"tenant_id"`
|
||||||
|
|
||||||
|
// UserID uniquely identifies the user account owning the tenant.
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
|
||||||
|
// Name contains the human-readable name for the server.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Updated and Created contain ISO-8601 timestamps of when the state of the
|
||||||
|
// server last changed, and when it was created.
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
|
||||||
|
// HostID is the host where the server is located in the cloud.
|
||||||
|
HostID string `json:"hostid"`
|
||||||
|
|
||||||
|
// Status contains the current operational status of the server,
|
||||||
|
// such as IN_PROGRESS or ACTIVE.
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// Progress ranges from 0..100.
|
||||||
|
// A request made against the server completes only once Progress reaches 100.
|
||||||
|
Progress int `json:"progress"`
|
||||||
|
|
||||||
|
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server,
|
||||||
|
// suitable for remote access for administration.
|
||||||
|
AccessIPv4 string `json:"accessIPv4"`
|
||||||
|
AccessIPv6 string `json:"accessIPv6"`
|
||||||
|
|
||||||
|
// Image refers to a JSON object, which itself indicates the OS image used to
|
||||||
|
// deploy the server.
|
||||||
|
Image map[string]interface{} `json:"-"`
|
||||||
|
|
||||||
|
// Flavor refers to a JSON object, which itself indicates the hardware
|
||||||
|
// configuration of the deployed server.
|
||||||
|
Flavor map[string]interface{} `json:"flavor"`
|
||||||
|
|
||||||
|
// Addresses includes a list of all IP addresses assigned to the server,
|
||||||
|
// keyed by pool.
|
||||||
|
Addresses map[string]interface{} `json:"addresses"`
|
||||||
|
|
||||||
|
// Metadata includes a list of all user-specified key-value pairs attached
|
||||||
|
// to the server.
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
|
||||||
|
// Links includes HTTP references to the itself, useful for passing along to
|
||||||
|
// other APIs that might want a server reference.
|
||||||
|
Links []interface{} `json:"links"`
|
||||||
|
|
||||||
|
// KeyName indicates which public key was injected into the server on launch.
|
||||||
|
KeyName string `json:"key_name"`
|
||||||
|
|
||||||
|
// AdminPass will generally be empty (""). However, it will contain the
|
||||||
|
// administrative password chosen when provisioning a new server without a
|
||||||
|
// set AdminPass setting in the first place.
|
||||||
|
// Note that this is the ONLY time this field will be valid.
|
||||||
|
AdminPass string `json:"adminPass"`
|
||||||
|
|
||||||
|
// SecurityGroups includes the security groups that this instance has applied
|
||||||
|
// to it.
|
||||||
|
SecurityGroups []map[string]interface{} `json:"security_groups"`
|
||||||
|
|
||||||
|
// AttachedVolumes includes the volume attachments of this instance
|
||||||
|
AttachedVolumes []AttachedVolume `json:"os-extended-volumes:volumes_attached"`
|
||||||
|
|
||||||
|
// Fault contains failure information about a server.
|
||||||
|
Fault Fault `json:"fault"`
|
||||||
|
|
||||||
|
// Tags is a slice/list of string tags in a server.
|
||||||
|
// The requires microversion 2.26 or later.
|
||||||
|
Tags *[]string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttachedVolume struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fault struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Details string `json:"details"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Server) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp Server
|
||||||
|
var s struct {
|
||||||
|
tmp
|
||||||
|
Image interface{} `json:"image"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*r = Server(s.tmp)
|
||||||
|
|
||||||
|
switch t := s.Image.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
r.Image = t
|
||||||
|
case string:
|
||||||
|
switch t {
|
||||||
|
case "":
|
||||||
|
r.Image = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerPage abstracts the raw results of making a List() request against
|
||||||
|
// the API. As OpenStack extensions may freely alter the response bodies of
|
||||||
|
// structures returned to the client, you may only safely access the data
|
||||||
|
// provided through the ExtractServers call.
|
||||||
|
type ServerPage struct {
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if a page contains no Server results.
|
||||||
|
func (r ServerPage) IsEmpty() (bool, error) {
|
||||||
|
s, err := ExtractServers(r)
|
||||||
|
return len(s) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL uses the response's embedded link reference to navigate to the
|
||||||
|
// next page of results.
|
||||||
|
func (r ServerPage) NextPageURL() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Links []gophercloud.Link `json:"servers_links"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return gophercloud.ExtractNextURL(s.Links)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractServers interprets the results of a single page from a List() call,
|
||||||
|
// producing a slice of Server entities.
|
||||||
|
func ExtractServers(r pagination.Page) ([]Server, error) {
|
||||||
|
var s []Server
|
||||||
|
err := ExtractServersInto(r, &s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataResult contains the result of a call for (potentially) multiple
|
||||||
|
// key-value pairs. Call its Extract method to interpret it as a
|
||||||
|
// map[string]interface.
|
||||||
|
type MetadataResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetadataResult contains the result of a Get operation. Call its Extract
|
||||||
|
// method to interpret it as a map[string]interface.
|
||||||
|
type GetMetadataResult struct {
|
||||||
|
MetadataResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetMetadataResult contains the result of a Reset operation. Call its
|
||||||
|
// Extract method to interpret it as a map[string]interface.
|
||||||
|
type ResetMetadataResult struct {
|
||||||
|
MetadataResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMetadataResult contains the result of an Update operation. Call its
|
||||||
|
// Extract method to interpret it as a map[string]interface.
|
||||||
|
type UpdateMetadataResult struct {
|
||||||
|
MetadataResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadatumResult contains the result of a call for individual a single
|
||||||
|
// key-value pair.
|
||||||
|
type MetadatumResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetadatumResult contains the result of a Get operation. Call its Extract
|
||||||
|
// method to interpret it as a map[string]interface.
|
||||||
|
type GetMetadatumResult struct {
|
||||||
|
MetadatumResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMetadatumResult contains the result of a Create operation. Call its
|
||||||
|
// Extract method to interpret it as a map[string]interface.
|
||||||
|
type CreateMetadatumResult struct {
|
||||||
|
MetadatumResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMetadatumResult contains the result of a Delete operation. Call its
|
||||||
|
// ExtractErr method to determine if the call succeeded or failed.
|
||||||
|
type DeleteMetadatumResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any MetadataResult as a Metadata, if possible.
|
||||||
|
func (r MetadataResult) Extract() (map[string]string, error) {
|
||||||
|
var s struct {
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any MetadatumResult as a Metadatum, if possible.
|
||||||
|
func (r MetadatumResult) Extract() (map[string]string, error) {
|
||||||
|
var s struct {
|
||||||
|
Metadatum map[string]string `json:"meta"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Metadatum, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address represents an IP address.
|
||||||
|
type Address struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
Address string `json:"addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressPage abstracts the raw results of making a ListAddresses() request
|
||||||
|
// against the API. As OpenStack extensions may freely alter the response bodies
|
||||||
|
// of structures returned to the client, you may only safely access the data
|
||||||
|
// provided through the ExtractAddresses call.
|
||||||
|
type AddressPage struct {
|
||||||
|
pagination.SinglePageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if an AddressPage contains no networks.
|
||||||
|
func (r AddressPage) IsEmpty() (bool, error) {
|
||||||
|
addresses, err := ExtractAddresses(r)
|
||||||
|
return len(addresses) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAddresses interprets the results of a single page from a
|
||||||
|
// ListAddresses() call, producing a map of addresses.
|
||||||
|
func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
|
||||||
|
var s struct {
|
||||||
|
Addresses map[string][]Address `json:"addresses"`
|
||||||
|
}
|
||||||
|
err := (r.(AddressPage)).ExtractInto(&s)
|
||||||
|
return s.Addresses, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkAddressPage abstracts the raw results of making a
|
||||||
|
// ListAddressesByNetwork() request against the API.
|
||||||
|
// As OpenStack extensions may freely alter the response bodies of structures
|
||||||
|
// returned to the client, you may only safely access the data provided through
|
||||||
|
// the ExtractAddresses call.
|
||||||
|
type NetworkAddressPage struct {
|
||||||
|
pagination.SinglePageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if a NetworkAddressPage contains no addresses.
|
||||||
|
func (r NetworkAddressPage) IsEmpty() (bool, error) {
|
||||||
|
addresses, err := ExtractNetworkAddresses(r)
|
||||||
|
return len(addresses) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractNetworkAddresses interprets the results of a single page from a
|
||||||
|
// ListAddressesByNetwork() call, producing a slice of addresses.
|
||||||
|
func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
|
||||||
|
var s map[string][]Address
|
||||||
|
err := (r.(NetworkAddressPage)).ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var key string
|
||||||
|
for k := range s {
|
||||||
|
key = k
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[key], err
|
||||||
|
}
|
||||||
51
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go
generated
vendored
Normal file
51
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package servers
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func createURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("servers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func listURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return createURL(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listDetailURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("servers", "detail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return client.ServiceURL("servers", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return deleteURL(client, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return deleteURL(client, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return client.ServiceURL("servers", id, "action")
|
||||||
|
}
|
||||||
|
|
||||||
|
func metadatumURL(client *gophercloud.ServiceClient, id, key string) string {
|
||||||
|
return client.ServiceURL("servers", id, "metadata", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func metadataURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return client.ServiceURL("servers", id, "metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
func listAddressesURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return client.ServiceURL("servers", id, "ips")
|
||||||
|
}
|
||||||
|
|
||||||
|
func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string {
|
||||||
|
return client.ServiceURL("servers", id, "ips", network)
|
||||||
|
}
|
||||||
|
|
||||||
|
func passwordURL(client *gophercloud.ServiceClient, id string) string {
|
||||||
|
return client.ServiceURL("servers", id, "os-server-password")
|
||||||
|
}
|
||||||
21
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go
generated
vendored
Normal file
21
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package servers
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// WaitForStatus will continually poll a server until it successfully
|
||||||
|
// transitions to a specified status. It will do this for at most the number
|
||||||
|
// of seconds specified.
|
||||||
|
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
|
||||||
|
return gophercloud.WaitFor(secs, func() (bool, error) {
|
||||||
|
current, err := Get(c, id).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.Status == status {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
14
vendor/github.com/gophercloud/gophercloud/openstack/doc.go
generated
vendored
Normal file
14
vendor/github.com/gophercloud/gophercloud/openstack/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
Package openstack contains resources for the individual OpenStack projects
|
||||||
|
supported in Gophercloud. It also includes functions to authenticate to an
|
||||||
|
OpenStack cloud and for provisioning various service-level clients.
|
||||||
|
|
||||||
|
Example of Creating a Service Client
|
||||||
|
|
||||||
|
ao, err := openstack.AuthOptionsFromEnv()
|
||||||
|
provider, err := openstack.AuthenticatedClient(ao)
|
||||||
|
client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
|
||||||
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
package openstack
|
||||||
111
vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
generated
vendored
Normal file
111
vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||||
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
V2EndpointURL discovers the endpoint URL for a specific service from a
|
||||||
|
ServiceCatalog acquired during the v2 identity service.
|
||||||
|
|
||||||
|
The specified EndpointOpts are used to identify a unique, unambiguous endpoint
|
||||||
|
to return. It's an error both when multiple endpoints match the provided
|
||||||
|
criteria and when none do. The minimum that can be specified is a Type, but you
|
||||||
|
will also often need to specify a Name and/or a Region depending on what's
|
||||||
|
available on your OpenStack deployment.
|
||||||
|
*/
|
||||||
|
func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
|
||||||
|
var endpoints = make([]tokens2.Endpoint, 0, 1)
|
||||||
|
for _, entry := range catalog.Entries {
|
||||||
|
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
||||||
|
for _, endpoint := range entry.Endpoints {
|
||||||
|
if opts.Region == "" || endpoint.Region == opts.Region {
|
||||||
|
endpoints = append(endpoints, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If multiple endpoints were found, use the first result
|
||||||
|
// and disregard the other endpoints.
|
||||||
|
//
|
||||||
|
// This behavior matches the Python library. See GH-1764.
|
||||||
|
if len(endpoints) > 1 {
|
||||||
|
endpoints = endpoints[0:1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the appropriate URL from the matching Endpoint.
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
switch opts.Availability {
|
||||||
|
case gophercloud.AvailabilityPublic:
|
||||||
|
return gophercloud.NormalizeURL(endpoint.PublicURL), nil
|
||||||
|
case gophercloud.AvailabilityInternal:
|
||||||
|
return gophercloud.NormalizeURL(endpoint.InternalURL), nil
|
||||||
|
case gophercloud.AvailabilityAdmin:
|
||||||
|
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
|
||||||
|
default:
|
||||||
|
err := &ErrInvalidAvailabilityProvided{}
|
||||||
|
err.Argument = "Availability"
|
||||||
|
err.Value = opts.Availability
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if there were no matching endpoints.
|
||||||
|
err := &gophercloud.ErrEndpointNotFound{}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
V3EndpointURL discovers the endpoint URL for a specific service from a Catalog
|
||||||
|
acquired during the v3 identity service.
|
||||||
|
|
||||||
|
The specified EndpointOpts are used to identify a unique, unambiguous endpoint
|
||||||
|
to return. It's an error both when multiple endpoints match the provided
|
||||||
|
criteria and when none do. The minimum that can be specified is a Type, but you
|
||||||
|
will also often need to specify a Name and/or a Region depending on what's
|
||||||
|
available on your OpenStack deployment.
|
||||||
|
*/
|
||||||
|
func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
|
||||||
|
// Name if provided, and Region if provided.
|
||||||
|
var endpoints = make([]tokens3.Endpoint, 0, 1)
|
||||||
|
for _, entry := range catalog.Entries {
|
||||||
|
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
||||||
|
for _, endpoint := range entry.Endpoints {
|
||||||
|
if opts.Availability != gophercloud.AvailabilityAdmin &&
|
||||||
|
opts.Availability != gophercloud.AvailabilityPublic &&
|
||||||
|
opts.Availability != gophercloud.AvailabilityInternal {
|
||||||
|
err := &ErrInvalidAvailabilityProvided{}
|
||||||
|
err.Argument = "Availability"
|
||||||
|
err.Value = opts.Availability
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
|
||||||
|
(opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) {
|
||||||
|
endpoints = append(endpoints, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If multiple endpoints were found, use the first result
|
||||||
|
// and disregard the other endpoints.
|
||||||
|
//
|
||||||
|
// This behavior matches the Python library. See GH-1764.
|
||||||
|
if len(endpoints) > 1 {
|
||||||
|
endpoints = endpoints[0:1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the URL from the matching Endpoint.
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
return gophercloud.NormalizeURL(endpoint.URL), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if there were no matching endpoints.
|
||||||
|
err := &gophercloud.ErrEndpointNotFound{}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
47
vendor/github.com/gophercloud/gophercloud/openstack/errors.go
generated
vendored
Normal file
47
vendor/github.com/gophercloud/gophercloud/openstack/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrEndpointNotFound is the error when no suitable endpoint can be found
|
||||||
|
// in the user's catalog
|
||||||
|
type ErrEndpointNotFound struct{ gophercloud.BaseError }
|
||||||
|
|
||||||
|
func (e ErrEndpointNotFound) Error() string {
|
||||||
|
return "No suitable endpoint could be found in the service catalog."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
|
||||||
|
// availability is provided
|
||||||
|
type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrInvalidAvailabilityProvided) Error() string {
|
||||||
|
return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
|
||||||
|
// found
|
||||||
|
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrNoAuthURL) Error() string {
|
||||||
|
return "Environment variable OS_AUTH_URL needs to be set."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoUsername is the error when the OS_USERNAME environment variable is not
|
||||||
|
// found
|
||||||
|
type ErrNoUsername struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrNoUsername) Error() string {
|
||||||
|
return "Environment variable OS_USERNAME needs to be set."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
|
||||||
|
// found
|
||||||
|
type ErrNoPassword struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrNoPassword) Error() string {
|
||||||
|
return "Environment variable OS_PASSWORD needs to be set."
|
||||||
|
}
|
||||||
65
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
generated
vendored
Normal file
65
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Package tenants provides information and interaction with the
|
||||||
|
tenants API resource for the OpenStack Identity service.
|
||||||
|
|
||||||
|
See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
|
||||||
|
and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
Example to List Tenants
|
||||||
|
|
||||||
|
listOpts := tenants.ListOpts{
|
||||||
|
Limit: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
allPages, err := tenants.List(identityClient, listOpts).AllPages()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allTenants, err := tenants.ExtractTenants(allPages)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tenant := range allTenants {
|
||||||
|
fmt.Printf("%+v\n", tenant)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Tenant
|
||||||
|
|
||||||
|
createOpts := tenants.CreateOpts{
|
||||||
|
Name: "tenant_name",
|
||||||
|
Description: "this is a tenant",
|
||||||
|
Enabled: gophercloud.Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
tenant, err := tenants.Create(identityClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Update a Tenant
|
||||||
|
|
||||||
|
tenantID := "e6db6ed6277c461a853458589063b295"
|
||||||
|
|
||||||
|
updateOpts := tenants.UpdateOpts{
|
||||||
|
Description: "this is a new description",
|
||||||
|
Enabled: gophercloud.Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
tenant, err := tenants.Update(identityClient, tenantID, updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Delete a Tenant
|
||||||
|
|
||||||
|
tenantID := "e6db6ed6277c461a853458589063b295"
|
||||||
|
|
||||||
|
err := tenants.Delete(identitYClient, tenantID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package tenants
|
||||||
120
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
Normal file
120
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
package tenants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListOpts filters the Tenants that are returned by the List call.
|
||||||
|
type ListOpts struct {
|
||||||
|
// Marker is the ID of the last Tenant on the previous page.
|
||||||
|
Marker string `q:"marker"`
|
||||||
|
|
||||||
|
// Limit specifies the page size.
|
||||||
|
Limit int `q:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// List enumerates the Tenants to which the current token has access.
|
||||||
|
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
|
||||||
|
url := listURL(client)
|
||||||
|
if opts != nil {
|
||||||
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
|
if err != nil {
|
||||||
|
return pagination.Pager{Err: err}
|
||||||
|
}
|
||||||
|
url += q.String()
|
||||||
|
}
|
||||||
|
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||||
|
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOpts represents the options needed when creating new tenant.
|
||||||
|
type CreateOpts struct {
|
||||||
|
// Name is the name of the tenant.
|
||||||
|
Name string `json:"name" required:"true"`
|
||||||
|
|
||||||
|
// Description is the description of the tenant.
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
// Enabled sets the tenant status to enabled or disabled.
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOptsBuilder enables extensions to add additional parameters to the
|
||||||
|
// Create request.
|
||||||
|
type CreateOptsBuilder interface {
|
||||||
|
ToTenantCreateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTenantCreateMap assembles a request body based on the contents of
|
||||||
|
// a CreateOpts.
|
||||||
|
func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "tenant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create is the operation responsible for creating new tenant.
|
||||||
|
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||||
|
b, err := opts.ToTenantCreateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200, 201},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get requests details on a single tenant by ID.
|
||||||
|
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||||
|
resp, err := client.Get(getURL(client, id), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// Update request.
|
||||||
|
type UpdateOptsBuilder interface {
|
||||||
|
ToTenantUpdateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOpts specifies the base attributes that may be updated on an existing
|
||||||
|
// tenant.
|
||||||
|
type UpdateOpts struct {
|
||||||
|
// Name is the name of the tenant.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// Description is the description of the tenant.
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
// Enabled sets the tenant status to enabled or disabled.
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTenantUpdateMap formats an UpdateOpts structure into a request body.
|
||||||
|
func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "tenant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is the operation responsible for updating exist tenants by their TenantID.
|
||||||
|
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||||
|
b, err := opts.ToTenantUpdateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is the operation responsible for permanently deleting a tenant.
|
||||||
|
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||||
|
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
91
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
91
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package tenants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tenant is a grouping of users in the identity service.
|
||||||
|
type Tenant struct {
|
||||||
|
// ID is a unique identifier for this tenant.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Name is a friendlier user-facing name for this tenant.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Description is a human-readable explanation of this Tenant's purpose.
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Enabled indicates whether or not a tenant is active.
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TenantPage is a single page of Tenant results.
|
||||||
|
type TenantPage struct {
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty determines whether or not a page of Tenants contains any results.
|
||||||
|
func (r TenantPage) IsEmpty() (bool, error) {
|
||||||
|
tenants, err := ExtractTenants(r)
|
||||||
|
return len(tenants) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the "next" link from the tenants_links section of the result.
|
||||||
|
func (r TenantPage) NextPageURL() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Links []gophercloud.Link `json:"tenants_links"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return gophercloud.ExtractNextURL(s.Links)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractTenants returns a slice of Tenants contained in a single page of
|
||||||
|
// results.
|
||||||
|
func ExtractTenants(r pagination.Page) ([]Tenant, error) {
|
||||||
|
var s struct {
|
||||||
|
Tenants []Tenant `json:"tenants"`
|
||||||
|
}
|
||||||
|
err := (r.(TenantPage)).ExtractInto(&s)
|
||||||
|
return s.Tenants, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type tenantResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any tenantResults as a Tenant.
|
||||||
|
func (r tenantResult) Extract() (*Tenant, error) {
|
||||||
|
var s struct {
|
||||||
|
Tenant *Tenant `json:"tenant"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Tenant, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult is the response from a Get request. Call its Extract method to
|
||||||
|
// interpret it as a Tenant.
|
||||||
|
type GetResult struct {
|
||||||
|
tenantResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResult is the response from a Create request. Call its Extract method
|
||||||
|
// to interpret it as a Tenant.
|
||||||
|
type CreateResult struct {
|
||||||
|
tenantResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResult is the response from a Get request. Call its ExtractErr method
|
||||||
|
// to determine if the call succeeded or failed.
|
||||||
|
type DeleteResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResult is the response from a Update request. Call its Extract method
|
||||||
|
// to interpret it as a Tenant.
|
||||||
|
type UpdateResult struct {
|
||||||
|
tenantResult
|
||||||
|
}
|
||||||
23
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
generated
vendored
Normal file
23
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package tenants
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func listURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("tenants")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getURL(client *gophercloud.ServiceClient, tenantID string) string {
|
||||||
|
return client.ServiceURL("tenants", tenantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("tenants")
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteURL(client *gophercloud.ServiceClient, tenantID string) string {
|
||||||
|
return client.ServiceURL("tenants", tenantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateURL(client *gophercloud.ServiceClient, tenantID string) string {
|
||||||
|
return client.ServiceURL("tenants", tenantID)
|
||||||
|
}
|
||||||
46
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
generated
vendored
Normal file
46
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Package tokens provides information and interaction with the token API
|
||||||
|
resource for the OpenStack Identity service.
|
||||||
|
|
||||||
|
For more information, see:
|
||||||
|
http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
|
||||||
|
|
||||||
|
Example to Create an Unscoped Token from a Password
|
||||||
|
|
||||||
|
authOpts := gophercloud.AuthOptions{
|
||||||
|
Username: "user",
|
||||||
|
Password: "pass"
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokens.Create(identityClient, authOpts).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token from a Tenant ID and Password
|
||||||
|
|
||||||
|
authOpts := gophercloud.AuthOptions{
|
||||||
|
Username: "user",
|
||||||
|
Password: "password",
|
||||||
|
TenantID: "fc394f2ab2df4114bde39905f800dc57"
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokens.Create(identityClient, authOpts).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token from a Tenant Name and Password
|
||||||
|
|
||||||
|
authOpts := gophercloud.AuthOptions{
|
||||||
|
Username: "user",
|
||||||
|
Password: "password",
|
||||||
|
TenantName: "tenantname"
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokens.Create(identityClient, authOpts).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package tokens
|
||||||
105
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
105
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// PasswordCredentialsV2 represents the required options to authenticate
|
||||||
|
// with a username and password.
|
||||||
|
type PasswordCredentialsV2 struct {
|
||||||
|
Username string `json:"username" required:"true"`
|
||||||
|
Password string `json:"password" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenCredentialsV2 represents the required options to authenticate
|
||||||
|
// with a token.
|
||||||
|
type TokenCredentialsV2 struct {
|
||||||
|
ID string `json:"id,omitempty" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the
|
||||||
|
// AuthOptionsBuilder interface.
|
||||||
|
type AuthOptionsV2 struct {
|
||||||
|
PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
|
||||||
|
|
||||||
|
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
||||||
|
// Some providers allow you to specify a TenantName instead of the TenantId.
|
||||||
|
// Some require both. Your provider's authentication policies will determine
|
||||||
|
// how these fields influence authentication.
|
||||||
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
TenantName string `json:"tenantName,omitempty"`
|
||||||
|
|
||||||
|
// TokenCredentials allows users to authenticate (possibly as another user)
|
||||||
|
// with an authentication token ID.
|
||||||
|
TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptionsBuilder allows extensions to add additional parameters to the
|
||||||
|
// token create request.
|
||||||
|
type AuthOptionsBuilder interface {
|
||||||
|
// ToTokenCreateMap assembles the Create request body, returning an error
|
||||||
|
// if parameters are missing or inconsistent.
|
||||||
|
ToTokenV2CreateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptions are the valid options for Openstack Identity v2 authentication.
|
||||||
|
// For field descriptions, see gophercloud.AuthOptions.
|
||||||
|
type AuthOptions struct {
|
||||||
|
IdentityEndpoint string `json:"-"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
TenantName string `json:"tenantName,omitempty"`
|
||||||
|
AllowReauth bool `json:"-"`
|
||||||
|
TokenID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV2CreateMap builds a token request body from the given AuthOptions.
|
||||||
|
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||||
|
v2Opts := AuthOptionsV2{
|
||||||
|
TenantID: opts.TenantID,
|
||||||
|
TenantName: opts.TenantName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Password != "" {
|
||||||
|
v2Opts.PasswordCredentials = &PasswordCredentialsV2{
|
||||||
|
Username: opts.Username,
|
||||||
|
Password: opts.Password,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v2Opts.TokenCredentials = &TokenCredentialsV2{
|
||||||
|
ID: opts.TokenID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create authenticates to the identity service and attempts to acquire a Token.
|
||||||
|
// Generally, rather than interact with this call directly, end users should
|
||||||
|
// call openstack.AuthenticatedClient(), which abstracts all of the gory details
|
||||||
|
// about navigating service catalogs and such.
|
||||||
|
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
|
||||||
|
b, err := auth.ToTokenV2CreateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200, 203},
|
||||||
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get validates and retrieves information for user's token.
|
||||||
|
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||||
|
resp, err := client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200, 203},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
174
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
Normal file
174
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token provides only the most basic information related to an authentication
|
||||||
|
// token.
|
||||||
|
type Token struct {
|
||||||
|
// ID provides the primary means of identifying a user to the OpenStack API.
|
||||||
|
// OpenStack defines this field as an opaque value, so do not depend on its
|
||||||
|
// content. It is safe, however, to compare for equality.
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the
|
||||||
|
// authentication token becomes invalid. After this point in time, future
|
||||||
|
// API requests made using this authentication token will respond with
|
||||||
|
// errors. Either the caller will need to reauthenticate manually, or more
|
||||||
|
// preferably, the caller should exploit automatic re-authentication.
|
||||||
|
// See the AuthOptions structure for more details.
|
||||||
|
ExpiresAt time.Time
|
||||||
|
|
||||||
|
// Tenant provides information about the tenant to which this token grants
|
||||||
|
// access.
|
||||||
|
Tenant tenants.Tenant
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role is a role for a user.
|
||||||
|
type Role struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is an OpenStack user.
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UserName string `json:"username"`
|
||||||
|
Roles []Role `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint represents a single API endpoint offered by a service.
|
||||||
|
// It provides the public and internal URLs, if supported, along with a region
|
||||||
|
// specifier, again if provided.
|
||||||
|
//
|
||||||
|
// The significance of the Region field will depend upon your provider.
|
||||||
|
//
|
||||||
|
// In addition, the interface offered by the service will have version
|
||||||
|
// information associated with it through the VersionId, VersionInfo, and
|
||||||
|
// VersionList fields, if provided or supported.
|
||||||
|
//
|
||||||
|
// In all cases, fields which aren't supported by the provider and service
|
||||||
|
// combined will assume a zero-value ("").
|
||||||
|
type Endpoint struct {
|
||||||
|
TenantID string `json:"tenantId"`
|
||||||
|
PublicURL string `json:"publicURL"`
|
||||||
|
InternalURL string `json:"internalURL"`
|
||||||
|
AdminURL string `json:"adminURL"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
VersionID string `json:"versionId"`
|
||||||
|
VersionInfo string `json:"versionInfo"`
|
||||||
|
VersionList string `json:"versionList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CatalogEntry provides a type-safe interface to an Identity API V2 service
|
||||||
|
// catalog listing.
|
||||||
|
//
|
||||||
|
// Each class of service, such as cloud DNS or block storage services, will have
|
||||||
|
// a single CatalogEntry representing it.
|
||||||
|
//
|
||||||
|
// Note: when looking for the desired service, try, whenever possible, to key
|
||||||
|
// off the type field. Otherwise, you'll tie the representation of the service
|
||||||
|
// to a specific provider.
|
||||||
|
type CatalogEntry struct {
|
||||||
|
// Name will contain the provider-specified name for the service.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Type will contain a type string if OpenStack defines a type for the
|
||||||
|
// service. Otherwise, for provider-specific services, the provider may assign
|
||||||
|
// their own type strings.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Endpoints will let the caller iterate over all the different endpoints that
|
||||||
|
// may exist for the service.
|
||||||
|
Endpoints []Endpoint `json:"endpoints"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceCatalog provides a view into the service catalog from a previous,
|
||||||
|
// successful authentication.
|
||||||
|
type ServiceCatalog struct {
|
||||||
|
Entries []CatalogEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResult is the response from a Create request. Use ExtractToken() to
|
||||||
|
// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a
|
||||||
|
// service catalog.
|
||||||
|
type CreateResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult is the deferred response from a Get call, which is the same with a
|
||||||
|
// Created token. Use ExtractUser() to interpret it as a User.
|
||||||
|
type GetResult struct {
|
||||||
|
CreateResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractToken returns the just-created Token from a CreateResult.
|
||||||
|
func (r CreateResult) ExtractToken() (*Token, error) {
|
||||||
|
var s struct {
|
||||||
|
Access struct {
|
||||||
|
Token struct {
|
||||||
|
Expires string `json:"expires"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Tenant tenants.Tenant `json:"tenant"`
|
||||||
|
} `json:"token"`
|
||||||
|
} `json:"access"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
ID: s.Access.Token.ID,
|
||||||
|
ExpiresAt: expiresTs,
|
||||||
|
Tenant: s.Access.Token.Tenant,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
|
||||||
|
// string is the same as the ID field of the Token struct returned from
|
||||||
|
// ExtractToken().
|
||||||
|
func (r CreateResult) ExtractTokenID() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Access struct {
|
||||||
|
Token struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"token"`
|
||||||
|
} `json:"access"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Access.Token.ID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
|
||||||
|
// with the user's Token.
|
||||||
|
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||||
|
var s struct {
|
||||||
|
Access struct {
|
||||||
|
Entries []CatalogEntry `json:"serviceCatalog"`
|
||||||
|
} `json:"access"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return &ServiceCatalog{Entries: s.Access.Entries}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractUser returns the User from a GetResult.
|
||||||
|
func (r GetResult) ExtractUser() (*User, error) {
|
||||||
|
var s struct {
|
||||||
|
Access struct {
|
||||||
|
User User `json:"user"`
|
||||||
|
} `json:"access"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return &s.Access.User, err
|
||||||
|
}
|
||||||
13
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// CreateURL generates the URL used to create new Tokens.
|
||||||
|
func CreateURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURL generates the URL used to Validate Tokens.
|
||||||
|
func GetURL(client *gophercloud.ServiceClient, token string) string {
|
||||||
|
return client.ServiceURL("tokens", token)
|
||||||
|
}
|
||||||
41
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
generated
vendored
Normal file
41
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
Package tokens provides information and interaction with the EC2 token API
|
||||||
|
resource for the OpenStack Identity service.
|
||||||
|
|
||||||
|
For more information, see:
|
||||||
|
https://docs.openstack.org/api-ref/identity/v2-ext/
|
||||||
|
|
||||||
|
Example to Create a Token From an EC2 access and secret keys
|
||||||
|
|
||||||
|
var authOptions tokens.AuthOptionsBuilder
|
||||||
|
authOptions = &ec2tokens.AuthOptions{
|
||||||
|
Access: "a7f1e798b7c2417cba4a02de97dc3cdc",
|
||||||
|
Secret: "18f4f6761ada4e3795fa5273c30349b9",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := ec2tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to auth a client using EC2 access and secret keys
|
||||||
|
|
||||||
|
client, err := openstack.NewClient("http://localhost:5000/v3")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var authOptions tokens.AuthOptionsBuilder
|
||||||
|
authOptions = &ec2tokens.AuthOptions{
|
||||||
|
Access: "a7f1e798b7c2417cba4a02de97dc3cdc",
|
||||||
|
Secret: "18f4f6761ada4e3795fa5273c30349b9",
|
||||||
|
AllowReauth: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = openstack.AuthenticateV3(client, authOptions, gophercloud.EndpointOpts{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package ec2tokens
|
||||||
377
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go
generated
vendored
Normal file
377
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
package ec2tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EC2CredentialsAwsRequestV4 is a constant, used to generate AWS
|
||||||
|
// Credential V4.
|
||||||
|
EC2CredentialsAwsRequestV4 = "aws4_request"
|
||||||
|
// EC2CredentialsHmacSha1V2 is a HMAC SHA1 signature method. Used to
|
||||||
|
// generate AWS Credential V2.
|
||||||
|
EC2CredentialsHmacSha1V2 = "HmacSHA1"
|
||||||
|
// EC2CredentialsHmacSha256V2 is a HMAC SHA256 signature method. Used
|
||||||
|
// to generate AWS Credential V2.
|
||||||
|
EC2CredentialsHmacSha256V2 = "HmacSHA256"
|
||||||
|
// EC2CredentialsAwsHmacV4 is an AWS signature V4 signing method.
|
||||||
|
// More details:
|
||||||
|
// https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||||
|
EC2CredentialsAwsHmacV4 = "AWS4-HMAC-SHA256"
|
||||||
|
// EC2CredentialsTimestampFormatV4 is an AWS signature V4 timestamp
|
||||||
|
// format.
|
||||||
|
EC2CredentialsTimestampFormatV4 = "20060102T150405Z"
|
||||||
|
// EC2CredentialsDateFormatV4 is an AWS signature V4 date format.
|
||||||
|
EC2CredentialsDateFormatV4 = "20060102"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthOptions represents options for authenticating a user using EC2 credentials.
|
||||||
|
type AuthOptions struct {
|
||||||
|
// Access is the EC2 Credential Access ID.
|
||||||
|
Access string `json:"access" required:"true"`
|
||||||
|
// Secret is the EC2 Credential Secret, used to calculate signature.
|
||||||
|
// Not used, when a Signature is is.
|
||||||
|
Secret string `json:"-"`
|
||||||
|
// Host is a HTTP request Host header. Used to calculate an AWS
|
||||||
|
// signature V2. For signature V4 set the Host inside Headers map.
|
||||||
|
// Optional.
|
||||||
|
Host string `json:"host"`
|
||||||
|
// Path is a HTTP request path. Optional.
|
||||||
|
Path string `json:"path"`
|
||||||
|
// Verb is a HTTP request method. Optional.
|
||||||
|
Verb string `json:"verb"`
|
||||||
|
// Headers is a map of HTTP request headers. Optional.
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
// Region is a region name to calculate an AWS signature V4. Optional.
|
||||||
|
Region string `json:"-"`
|
||||||
|
// Service is a service name to calculate an AWS signature V4. Optional.
|
||||||
|
Service string `json:"-"`
|
||||||
|
// Params is a map of GET method parameters. Optional.
|
||||||
|
Params map[string]string `json:"params"`
|
||||||
|
// AllowReauth allows Gophercloud to re-authenticate automatically
|
||||||
|
// if/when your token expires.
|
||||||
|
AllowReauth bool `json:"-"`
|
||||||
|
// Signature can be either a []byte (encoded to base64 automatically) or
|
||||||
|
// a string. You can set the singature explicitly, when you already know
|
||||||
|
// it. In this case default Params won't be automatically set. Optional.
|
||||||
|
Signature interface{} `json:"signature"`
|
||||||
|
// BodyHash is a HTTP request body sha256 hash. When nil and Signature
|
||||||
|
// is not set, a random hash is generated. Optional.
|
||||||
|
BodyHash *string `json:"body_hash"`
|
||||||
|
// Timestamp is a timestamp to calculate a V4 signature. Optional.
|
||||||
|
Timestamp *time.Time `json:"-"`
|
||||||
|
// Token is a []byte string (encoded to base64 automatically) which was
|
||||||
|
// signed by an EC2 secret key. Used by S3 tokens for validation only.
|
||||||
|
// Token must be set with a Signature. If a Signature is not provided,
|
||||||
|
// a Token will be generated automatically along with a Signature.
|
||||||
|
Token []byte `json:"token,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string
|
||||||
|
// for an AWS signature V2.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L133
|
||||||
|
func EC2CredentialsBuildCanonicalQueryStringV2(params map[string]string) string {
|
||||||
|
var keys []string
|
||||||
|
for k := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var pairs []string
|
||||||
|
for _, k := range keys {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%s", k, url.QueryEscape(params[k])))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(pairs, "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildStringToSignV2 builds a string to sign an AWS signature
|
||||||
|
// V2.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L148
|
||||||
|
func EC2CredentialsBuildStringToSignV2(opts AuthOptions) []byte {
|
||||||
|
stringToSign := strings.Join([]string{
|
||||||
|
opts.Verb,
|
||||||
|
opts.Host,
|
||||||
|
opts.Path,
|
||||||
|
}, "\n")
|
||||||
|
|
||||||
|
return []byte(strings.Join([]string{
|
||||||
|
stringToSign,
|
||||||
|
EC2CredentialsBuildCanonicalQueryStringV2(opts.Params),
|
||||||
|
}, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string
|
||||||
|
// for an AWS signature V4.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L244
|
||||||
|
func EC2CredentialsBuildCanonicalQueryStringV4(verb string, params map[string]string) string {
|
||||||
|
if verb == "POST" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return EC2CredentialsBuildCanonicalQueryStringV2(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildCanonicalHeadersV4 builds a canonical string based on
|
||||||
|
// "headers" map and "signedHeaders" string parameters.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L216
|
||||||
|
func EC2CredentialsBuildCanonicalHeadersV4(headers map[string]string, signedHeaders string) string {
|
||||||
|
headersLower := make(map[string]string, len(headers))
|
||||||
|
for k, v := range headers {
|
||||||
|
headersLower[strings.ToLower(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
var headersList []string
|
||||||
|
for _, h := range strings.Split(signedHeaders, ";") {
|
||||||
|
if v, ok := headersLower[h]; ok {
|
||||||
|
headersList = append(headersList, h+":"+v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(headersList, "\n") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildSignatureKeyV4 builds a HMAC 256 signature key based on
|
||||||
|
// input parameters.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L169
|
||||||
|
func EC2CredentialsBuildSignatureKeyV4(secret, region, service string, date time.Time) []byte {
|
||||||
|
kDate := sumHMAC256([]byte("AWS4"+secret), []byte(date.Format(EC2CredentialsDateFormatV4)))
|
||||||
|
kRegion := sumHMAC256(kDate, []byte(region))
|
||||||
|
kService := sumHMAC256(kRegion, []byte(service))
|
||||||
|
return sumHMAC256(kService, []byte(EC2CredentialsAwsRequestV4))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildStringToSignV4 builds an AWS v4 signature string to sign
|
||||||
|
// based on input parameters.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L251
|
||||||
|
func EC2CredentialsBuildStringToSignV4(opts AuthOptions, signedHeaders string, bodyHash string, date time.Time) []byte {
|
||||||
|
scope := strings.Join([]string{
|
||||||
|
date.Format(EC2CredentialsDateFormatV4),
|
||||||
|
opts.Region,
|
||||||
|
opts.Service,
|
||||||
|
EC2CredentialsAwsRequestV4,
|
||||||
|
}, "/")
|
||||||
|
|
||||||
|
canonicalRequest := strings.Join([]string{
|
||||||
|
opts.Verb,
|
||||||
|
opts.Path,
|
||||||
|
EC2CredentialsBuildCanonicalQueryStringV4(opts.Verb, opts.Params),
|
||||||
|
EC2CredentialsBuildCanonicalHeadersV4(opts.Headers, signedHeaders),
|
||||||
|
signedHeaders,
|
||||||
|
bodyHash,
|
||||||
|
}, "\n")
|
||||||
|
hash := sha256.Sum256([]byte(canonicalRequest))
|
||||||
|
|
||||||
|
return []byte(strings.Join([]string{
|
||||||
|
EC2CredentialsAwsHmacV4,
|
||||||
|
date.Format(EC2CredentialsTimestampFormatV4),
|
||||||
|
scope,
|
||||||
|
hex.EncodeToString(hash[:]),
|
||||||
|
}, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildSignatureV4 builds an AWS v4 signature based on input
|
||||||
|
// parameters.
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L285..L286
|
||||||
|
func EC2CredentialsBuildSignatureV4(key []byte, stringToSign []byte) string {
|
||||||
|
return hex.EncodeToString(sumHMAC256(key, stringToSign))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC2CredentialsBuildAuthorizationHeaderV4 builds an AWS v4 Authorization
|
||||||
|
// header based on auth parameters, date and signature
|
||||||
|
func EC2CredentialsBuildAuthorizationHeaderV4(opts AuthOptions, signedHeaders string, signature string, date time.Time) string {
|
||||||
|
return fmt.Sprintf("%s Credential=%s/%s/%s/%s/%s, SignedHeaders=%s, Signature=%s",
|
||||||
|
EC2CredentialsAwsHmacV4,
|
||||||
|
opts.Access,
|
||||||
|
date.Format(EC2CredentialsDateFormatV4),
|
||||||
|
opts.Region,
|
||||||
|
opts.Service,
|
||||||
|
EC2CredentialsAwsRequestV4,
|
||||||
|
signedHeaders,
|
||||||
|
signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3ScopeMap is a dummy method to satisfy tokens.AuthOptionsBuilder
|
||||||
|
// interface.
|
||||||
|
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||||
|
// interface in the v3 tokens package.
|
||||||
|
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanReauth is a method method to satisfy tokens.AuthOptionsBuilder interface
|
||||||
|
func (opts *AuthOptions) CanReauth() bool {
|
||||||
|
return opts.AllowReauth
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3CreateMap formats an AuthOptions into a create request.
|
||||||
|
func (opts *AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
b, err := gophercloud.BuildRequestBody(opts, "credentials")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Signature != nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate signature, when it is not set
|
||||||
|
c, _ := b["credentials"].(map[string]interface{})
|
||||||
|
h := interfaceToMap(c, "headers")
|
||||||
|
p := interfaceToMap(c, "params")
|
||||||
|
|
||||||
|
// detect and process a signature v2
|
||||||
|
if v, ok := p["SignatureVersion"]; ok && v == "2" {
|
||||||
|
if _, ok := c["body_hash"]; ok {
|
||||||
|
delete(c, "body_hash")
|
||||||
|
}
|
||||||
|
if _, ok := c["headers"]; ok {
|
||||||
|
delete(c, "headers")
|
||||||
|
}
|
||||||
|
if v, ok := p["SignatureMethod"]; ok {
|
||||||
|
// params is a map of strings
|
||||||
|
strToSign := EC2CredentialsBuildStringToSignV2(*opts)
|
||||||
|
switch v {
|
||||||
|
case EC2CredentialsHmacSha1V2:
|
||||||
|
// keystone uses this method only when HmacSHA256 is not available on the server side
|
||||||
|
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L151..L156
|
||||||
|
c["signature"] = sumHMAC1([]byte(opts.Secret), strToSign)
|
||||||
|
return b, nil
|
||||||
|
case EC2CredentialsHmacSha256V2:
|
||||||
|
c["signature"] = sumHMAC256([]byte(opts.Secret), strToSign)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unsupported signature method: %s", v)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("signature method must be provided")
|
||||||
|
} else if ok {
|
||||||
|
return nil, fmt.Errorf("unsupported signature version: %s", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// it is not a signature v2, but a signature v4
|
||||||
|
date := time.Now().UTC()
|
||||||
|
if opts.Timestamp != nil {
|
||||||
|
date = *opts.Timestamp
|
||||||
|
}
|
||||||
|
if v, _ := c["body_hash"]; v == nil {
|
||||||
|
// when body_hash is not set, generate a random one
|
||||||
|
c["body_hash"] = randomBodyHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
signedHeaders, _ := h["X-Amz-SignedHeaders"]
|
||||||
|
|
||||||
|
stringToSign := EC2CredentialsBuildStringToSignV4(*opts, signedHeaders, c["body_hash"].(string), date)
|
||||||
|
key := EC2CredentialsBuildSignatureKeyV4(opts.Secret, opts.Region, opts.Service, date)
|
||||||
|
c["signature"] = EC2CredentialsBuildSignatureV4(key, stringToSign)
|
||||||
|
h["X-Amz-Date"] = date.Format(EC2CredentialsTimestampFormatV4)
|
||||||
|
h["Authorization"] = EC2CredentialsBuildAuthorizationHeaderV4(*opts, signedHeaders, c["signature"].(string), date)
|
||||||
|
|
||||||
|
// token is only used for S3 tokens validation and will be removed when using EC2 validation
|
||||||
|
c["token"] = stringToSign
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create authenticates and either generates a new token from EC2 credentials
|
||||||
|
func Create(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
||||||
|
b, err := opts.ToTokenV3CreateMap(nil)
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete "token" element, since it is used in s3tokens
|
||||||
|
deleteBodyElements(b, "token")
|
||||||
|
|
||||||
|
resp, err := c.Post(ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateS3Token authenticates an S3 request using EC2 credentials. Doesn't
|
||||||
|
// generate a new token ID, but returns a tokens.CreateResult.
|
||||||
|
func ValidateS3Token(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
||||||
|
b, err := opts.ToTokenV3CreateMap(nil)
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete unused element, since it is used in ec2tokens only
|
||||||
|
deleteBodyElements(b, "body_hash", "headers", "host", "params", "path", "verb")
|
||||||
|
|
||||||
|
resp, err := c.Post(s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following are small helper functions used to help build the signature.
|
||||||
|
|
||||||
|
// sumHMAC1 is a func to implement the HMAC SHA1 signature method.
|
||||||
|
func sumHMAC1(key []byte, data []byte) []byte {
|
||||||
|
hash := hmac.New(sha1.New, key)
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sumHMAC256 is a func to implement the HMAC SHA256 signature method.
|
||||||
|
func sumHMAC256(key []byte, data []byte) []byte {
|
||||||
|
hash := hmac.New(sha256.New, key)
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomBodyHash is a func to generate a random sha256 hexdigest.
|
||||||
|
func randomBodyHash() string {
|
||||||
|
h := make([]byte, 64)
|
||||||
|
rand.Read(h)
|
||||||
|
return hex.EncodeToString(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// interfaceToMap is a func used to represent a "credentials" map element as a
|
||||||
|
// "map[string]string"
|
||||||
|
func interfaceToMap(c map[string]interface{}, key string) map[string]string {
|
||||||
|
// convert map[string]interface{} to map[string]string
|
||||||
|
m := make(map[string]string)
|
||||||
|
if v, _ := c[key].(map[string]interface{}); v != nil {
|
||||||
|
for k, v := range v {
|
||||||
|
m[k] = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c[key] = m
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteBodyElements deletes map body elements
|
||||||
|
func deleteBodyElements(b map[string]interface{}, elements ...string) {
|
||||||
|
if c, ok := b["credentials"].(map[string]interface{}); ok {
|
||||||
|
for _, k := range elements {
|
||||||
|
if _, ok := c[k]; ok {
|
||||||
|
delete(c, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go
generated
vendored
Normal file
11
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
package ec2tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func ec2tokensURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("ec2tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
func s3tokensURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("s3tokens")
|
||||||
|
}
|
||||||
123
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
generated
vendored
Normal file
123
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
Package oauth1 enables management of OpenStack OAuth1 tokens and Authentication.
|
||||||
|
|
||||||
|
Example to Create an OAuth1 Consumer
|
||||||
|
|
||||||
|
createConsumerOpts := oauth1.CreateConsumerOpts{
|
||||||
|
Description: "My consumer",
|
||||||
|
}
|
||||||
|
consumer, err := oauth1.CreateConsumer(identityClient, createConsumerOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Consumer secret is available only on create response
|
||||||
|
fmt.Printf("Consumer: %+v\n", consumer)
|
||||||
|
|
||||||
|
Example to Request an unauthorized OAuth1 token
|
||||||
|
|
||||||
|
requestTokenOpts := oauth1.RequestTokenOpts{
|
||||||
|
OAuthConsumerKey: consumer.ID,
|
||||||
|
OAuthConsumerSecret: consumer.Secret,
|
||||||
|
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||||
|
RequestedProjectID: projectID,
|
||||||
|
}
|
||||||
|
requestToken, err := oauth1.RequestToken(identityClient, requestTokenOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Request token secret is available only on request response
|
||||||
|
fmt.Printf("Request token: %+v\n", requestToken)
|
||||||
|
|
||||||
|
Example to Authorize an unauthorized OAuth1 token
|
||||||
|
|
||||||
|
authorizeTokenOpts := oauth1.AuthorizeTokenOpts{
|
||||||
|
Roles: []oauth1.Role{
|
||||||
|
{Name: "member"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
authToken, err := oauth1.AuthorizeToken(identityClient, requestToken.OAuthToken, authorizeTokenOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Verifier ID of the unauthorized Token: %+v\n", authToken.OAuthVerifier)
|
||||||
|
|
||||||
|
Example to Create an OAuth1 Access Token
|
||||||
|
|
||||||
|
accessTokenOpts := oauth1.CreateAccessTokenOpts{
|
||||||
|
OAuthConsumerKey: consumer.ID,
|
||||||
|
OAuthConsumerSecret: consumer.Secret,
|
||||||
|
OAuthToken: requestToken.OAuthToken,
|
||||||
|
OAuthTokenSecret: requestToken.OAuthTokenSecret,
|
||||||
|
OAuthVerifier: authToken.OAuthVerifier,
|
||||||
|
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||||
|
}
|
||||||
|
accessToken, err := oauth1.CreateAccessToken(identityClient, accessTokenOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Access token secret is available only on create response
|
||||||
|
fmt.Printf("OAuth1 Access Token: %+v\n", accessToken)
|
||||||
|
|
||||||
|
Example to List User's OAuth1 Access Tokens
|
||||||
|
|
||||||
|
allPages, err := oauth1.ListAccessTokens(identityClient, userID).AllPages()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
accessTokens, err := oauth1.ExtractAccessTokens(allPages)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, accessToken := range accessTokens {
|
||||||
|
fmt.Printf("Access Token: %+v\n", accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Authenticate a client using OAuth1 method
|
||||||
|
|
||||||
|
client, err := openstack.NewClient("http://localhost:5000/v3")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authOptions := &oauth1.AuthOptions{
|
||||||
|
// consumer token, created earlier
|
||||||
|
OAuthConsumerKey: consumer.ID,
|
||||||
|
OAuthConsumerSecret: consumer.Secret,
|
||||||
|
// access token, created earlier
|
||||||
|
OAuthToken: accessToken.OAuthToken,
|
||||||
|
OAuthTokenSecret: accessToken.OAuthTokenSecret,
|
||||||
|
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||||
|
}
|
||||||
|
err = openstack.AuthenticateV3(client, authOptions, gophercloud.EndpointOpts{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token using OAuth1 method
|
||||||
|
|
||||||
|
var oauth1Token struct {
|
||||||
|
tokens.Token
|
||||||
|
oauth1.TokenExt
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := &oauth1.AuthOptions{
|
||||||
|
// consumer token, created earlier
|
||||||
|
OAuthConsumerKey: consumer.ID,
|
||||||
|
OAuthConsumerSecret: consumer.Secret,
|
||||||
|
// access token, created earlier
|
||||||
|
OAuthToken: accessToken.OAuthToken,
|
||||||
|
OAuthTokenSecret: accessToken.OAuthTokenSecret,
|
||||||
|
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||||
|
}
|
||||||
|
err := tokens.Create(identityClient, createOpts).ExtractInto(&oauth1Token)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package oauth1
|
||||||
587
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go
generated
vendored
Normal file
587
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,587 @@
|
||||||
|
package oauth1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type SignatureMethod is a OAuth1 SignatureMethod type.
|
||||||
|
type SignatureMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HMACSHA1 is a recommended OAuth1 signature method.
|
||||||
|
HMACSHA1 SignatureMethod = "HMAC-SHA1"
|
||||||
|
|
||||||
|
// PLAINTEXT signature method is not recommended to be used in
|
||||||
|
// production environment.
|
||||||
|
PLAINTEXT SignatureMethod = "PLAINTEXT"
|
||||||
|
|
||||||
|
// OAuth1TokenContentType is a supported content type for an OAuth1
|
||||||
|
// token.
|
||||||
|
OAuth1TokenContentType = "application/x-www-form-urlencoded"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthOptions represents options for authenticating a user using OAuth1 tokens.
|
||||||
|
type AuthOptions struct {
|
||||||
|
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
||||||
|
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
||||||
|
|
||||||
|
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
||||||
|
// an OAuth1 request signature.
|
||||||
|
OAuthConsumerSecret string `required:"true"`
|
||||||
|
|
||||||
|
// OAuthToken is the OAuth1 Request Token.
|
||||||
|
OAuthToken string `q:"oauth_token" required:"true"`
|
||||||
|
|
||||||
|
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
|
||||||
|
// an OAuth1 request signature.
|
||||||
|
OAuthTokenSecret string `required:"true"`
|
||||||
|
|
||||||
|
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
||||||
|
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
||||||
|
// "PLAINTEXT" is not recommended for production usage.
|
||||||
|
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
||||||
|
|
||||||
|
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
||||||
|
// timestamp will be used.
|
||||||
|
OAuthTimestamp *time.Time
|
||||||
|
|
||||||
|
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
||||||
|
// uniquely generated for each request. Will be generated automatically
|
||||||
|
// when it is not set.
|
||||||
|
OAuthNonce string `q:"oauth_nonce"`
|
||||||
|
|
||||||
|
// AllowReauth allows Gophercloud to re-authenticate automatically
|
||||||
|
// if/when your token expires.
|
||||||
|
AllowReauth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3HeadersMap builds the headers required for an OAuth1-based create
|
||||||
|
// request.
|
||||||
|
func (opts AuthOptions) ToTokenV3HeadersMap(headerOpts map[string]interface{}) (map[string]string, error) {
|
||||||
|
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
|
||||||
|
|
||||||
|
method := headerOpts["method"].(string)
|
||||||
|
u := headerOpts["url"].(string)
|
||||||
|
stringToSign := buildStringToSign(method, u, q.Query())
|
||||||
|
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
||||||
|
|
||||||
|
authHeader := buildAuthHeader(q.Query(), signature)
|
||||||
|
|
||||||
|
headers := map[string]string{
|
||||||
|
"Authorization": authHeader,
|
||||||
|
"X-Auth-Token": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3ScopeMap allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
|
||||||
|
// interface.
|
||||||
|
func (opts AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanReauth allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
|
||||||
|
// interface.
|
||||||
|
func (opts AuthOptions) CanReauth() bool {
|
||||||
|
return opts.AllowReauth
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3CreateMap builds a create request body.
|
||||||
|
func (opts AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// identityReq defines the "identity" portion of an OAuth1-based authentication
|
||||||
|
// create request body.
|
||||||
|
type identityReq struct {
|
||||||
|
Methods []string `json:"methods"`
|
||||||
|
OAuth1 struct{} `json:"oauth1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// authReq defines the "auth" portion of an OAuth1-based authentication
|
||||||
|
// create request body.
|
||||||
|
type authReq struct {
|
||||||
|
Identity identityReq `json:"identity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// oauth1Request defines how an OAuth1-based authentication create
|
||||||
|
// request body looks.
|
||||||
|
type oauth1Request struct {
|
||||||
|
Auth authReq `json:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req oauth1Request
|
||||||
|
|
||||||
|
req.Auth.Identity.Methods = []string{"oauth1"}
|
||||||
|
return gophercloud.BuildRequestBody(req, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create authenticates and either generates a new OpenStack token from an
|
||||||
|
// OAuth1 token.
|
||||||
|
func Create(client *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
||||||
|
b, err := opts.ToTokenV3CreateMap(nil)
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
headerOpts := map[string]interface{}{
|
||||||
|
"method": "POST",
|
||||||
|
"url": authURL(client),
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := opts.ToTokenV3HeadersMap(headerOpts)
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Post(authURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: h,
|
||||||
|
OkCodes: []int{201},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConsumerOptsBuilder allows extensions to add additional parameters to
|
||||||
|
// the CreateConsumer request.
|
||||||
|
type CreateConsumerOptsBuilder interface {
|
||||||
|
ToOAuth1CreateConsumerMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConsumerOpts provides options used to create a new Consumer.
|
||||||
|
type CreateConsumerOpts struct {
|
||||||
|
// Description is the consumer description.
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOAuth1CreateConsumerMap formats a CreateConsumerOpts into a create request.
|
||||||
|
func (opts CreateConsumerOpts) ToOAuth1CreateConsumerMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "consumer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new Consumer.
|
||||||
|
func CreateConsumer(client *gophercloud.ServiceClient, opts CreateConsumerOptsBuilder) (r CreateConsumerResult) {
|
||||||
|
b, err := opts.ToOAuth1CreateConsumerMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Post(consumersURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{201},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a Consumer.
|
||||||
|
func DeleteConsumer(client *gophercloud.ServiceClient, id string) (r DeleteConsumerResult) {
|
||||||
|
resp, err := client.Delete(consumerURL(client, id), nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List enumerates Consumers.
|
||||||
|
func ListConsumers(client *gophercloud.ServiceClient) pagination.Pager {
|
||||||
|
return pagination.NewPager(client, consumersURL(client), func(r pagination.PageResult) pagination.Page {
|
||||||
|
return ConsumersPage{pagination.LinkedPageBase{PageResult: r}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsumer retrieves details on a single Consumer by ID.
|
||||||
|
func GetConsumer(client *gophercloud.ServiceClient, id string) (r GetConsumerResult) {
|
||||||
|
resp, err := client.Get(consumerURL(client, id), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConsumerOpts provides options used to update a consumer.
|
||||||
|
type UpdateConsumerOpts struct {
|
||||||
|
// Description is the consumer description.
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOAuth1UpdateConsumerMap formats an UpdateConsumerOpts into a consumer update
|
||||||
|
// request.
|
||||||
|
func (opts UpdateConsumerOpts) ToOAuth1UpdateConsumerMap() (map[string]interface{}, error) {
|
||||||
|
return gophercloud.BuildRequestBody(opts, "consumer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConsumer updates an existing Consumer.
|
||||||
|
func UpdateConsumer(client *gophercloud.ServiceClient, id string, opts UpdateConsumerOpts) (r UpdateConsumerResult) {
|
||||||
|
b, err := opts.ToOAuth1UpdateConsumerMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Patch(consumerURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestTokenOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// RequestToken request.
|
||||||
|
type RequestTokenOptsBuilder interface {
|
||||||
|
ToOAuth1RequestTokenHeaders(string, string) (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestTokenOpts provides options used to get a consumer unauthorized
|
||||||
|
// request token.
|
||||||
|
type RequestTokenOpts struct {
|
||||||
|
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
||||||
|
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
||||||
|
|
||||||
|
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
||||||
|
// an OAuth1 request signature.
|
||||||
|
OAuthConsumerSecret string `required:"true"`
|
||||||
|
|
||||||
|
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
||||||
|
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
||||||
|
// "PLAINTEXT" is not recommended for production usage.
|
||||||
|
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
||||||
|
|
||||||
|
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
||||||
|
// timestamp will be used.
|
||||||
|
OAuthTimestamp *time.Time
|
||||||
|
|
||||||
|
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
||||||
|
// uniquely generated for each request. Will be generated automatically
|
||||||
|
// when it is not set.
|
||||||
|
OAuthNonce string `q:"oauth_nonce"`
|
||||||
|
|
||||||
|
// RequestedProjectID is a Project ID a consumer user requested an
|
||||||
|
// access to.
|
||||||
|
RequestedProjectID string `h:"Requested-Project-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOAuth1RequestTokenHeaders formats a RequestTokenOpts into a map of request
|
||||||
|
// headers.
|
||||||
|
func (opts RequestTokenOpts) ToOAuth1RequestTokenHeaders(method, u string) (map[string]string, error) {
|
||||||
|
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "oob")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := gophercloud.BuildHeaders(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureKeys := []string{opts.OAuthConsumerSecret}
|
||||||
|
stringToSign := buildStringToSign(method, u, q.Query())
|
||||||
|
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
||||||
|
authHeader := buildAuthHeader(q.Query(), signature)
|
||||||
|
|
||||||
|
h["Authorization"] = authHeader
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestToken requests an unauthorized OAuth1 Token.
|
||||||
|
func RequestToken(client *gophercloud.ServiceClient, opts RequestTokenOptsBuilder) (r TokenResult) {
|
||||||
|
h, err := opts.ToOAuth1RequestTokenHeaders("POST", requestTokenURL(client))
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Post(requestTokenURL(client), nil, nil, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: h,
|
||||||
|
OkCodes: []int{201},
|
||||||
|
KeepResponseBody: true,
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
if r.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
|
||||||
|
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Body, r.Err = ioutil.ReadAll(resp.Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeTokenOptsBuilder allows extensions to add additional parameters to
|
||||||
|
// the AuthorizeToken request.
|
||||||
|
type AuthorizeTokenOptsBuilder interface {
|
||||||
|
ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeTokenOpts provides options used to authorize a request token.
|
||||||
|
type AuthorizeTokenOpts struct {
|
||||||
|
Roles []Role `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role is a struct representing a role object in a AuthorizeTokenOpts struct.
|
||||||
|
type Role struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOAuth1AuthorizeTokenMap formats an AuthorizeTokenOpts into an authorize token
|
||||||
|
// request.
|
||||||
|
func (opts AuthorizeTokenOpts) ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error) {
|
||||||
|
for _, r := range opts.Roles {
|
||||||
|
if r == (Role{}) {
|
||||||
|
return nil, fmt.Errorf("role must not be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gophercloud.BuildRequestBody(opts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeToken authorizes an unauthorized consumer token.
|
||||||
|
func AuthorizeToken(client *gophercloud.ServiceClient, id string, opts AuthorizeTokenOptsBuilder) (r AuthorizeTokenResult) {
|
||||||
|
b, err := opts.ToOAuth1AuthorizeTokenMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Put(authorizeTokenURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessTokenOptsBuilder allows extensions to add additional parameters
|
||||||
|
// to the CreateAccessToken request.
|
||||||
|
type CreateAccessTokenOptsBuilder interface {
|
||||||
|
ToOAuth1CreateAccessTokenHeaders(string, string) (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessTokenOpts provides options used to create an OAuth1 token.
|
||||||
|
type CreateAccessTokenOpts struct {
|
||||||
|
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
||||||
|
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
||||||
|
|
||||||
|
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
||||||
|
// an OAuth1 request signature.
|
||||||
|
OAuthConsumerSecret string `required:"true"`
|
||||||
|
|
||||||
|
// OAuthToken is the OAuth1 Request Token.
|
||||||
|
OAuthToken string `q:"oauth_token" required:"true"`
|
||||||
|
|
||||||
|
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
|
||||||
|
// an OAuth1 request signature.
|
||||||
|
OAuthTokenSecret string `required:"true"`
|
||||||
|
|
||||||
|
// OAuthVerifier is the OAuth1 verification code.
|
||||||
|
OAuthVerifier string `q:"oauth_verifier" required:"true"`
|
||||||
|
|
||||||
|
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
||||||
|
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
||||||
|
// "PLAINTEXT" is not recommended for production usage.
|
||||||
|
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
||||||
|
|
||||||
|
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
||||||
|
// timestamp will be used.
|
||||||
|
OAuthTimestamp *time.Time
|
||||||
|
|
||||||
|
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
||||||
|
// uniquely generated for each request. Will be generated automatically
|
||||||
|
// when it is not set.
|
||||||
|
OAuthNonce string `q:"oauth_nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOAuth1CreateAccessTokenHeaders formats a CreateAccessTokenOpts into a map of
|
||||||
|
// request headers.
|
||||||
|
func (opts CreateAccessTokenOpts) ToOAuth1CreateAccessTokenHeaders(method, u string) (map[string]string, error) {
|
||||||
|
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
|
||||||
|
stringToSign := buildStringToSign(method, u, q.Query())
|
||||||
|
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
||||||
|
authHeader := buildAuthHeader(q.Query(), signature)
|
||||||
|
|
||||||
|
headers := map[string]string{
|
||||||
|
"Authorization": authHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessToken creates a new OAuth1 Access Token
|
||||||
|
func CreateAccessToken(client *gophercloud.ServiceClient, opts CreateAccessTokenOptsBuilder) (r TokenResult) {
|
||||||
|
h, err := opts.ToOAuth1CreateAccessTokenHeaders("POST", createAccessTokenURL(client))
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Post(createAccessTokenURL(client), nil, nil, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: h,
|
||||||
|
OkCodes: []int{201},
|
||||||
|
KeepResponseBody: true,
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
if r.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
|
||||||
|
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Body, r.Err = ioutil.ReadAll(resp.Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessToken retrieves details on a single OAuth1 access token by an ID.
|
||||||
|
func GetAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r GetAccessTokenResult) {
|
||||||
|
resp, err := client.Get(userAccessTokenURL(client, userID, id), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeAccessToken revokes an OAuth1 access token.
|
||||||
|
func RevokeAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r RevokeAccessTokenResult) {
|
||||||
|
resp, err := client.Delete(userAccessTokenURL(client, userID, id), nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccessTokens enumerates authorized access tokens.
|
||||||
|
func ListAccessTokens(client *gophercloud.ServiceClient, userID string) pagination.Pager {
|
||||||
|
url := userAccessTokensURL(client, userID)
|
||||||
|
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||||
|
return AccessTokensPage{pagination.LinkedPageBase{PageResult: r}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccessTokenRoles enumerates authorized access token roles.
|
||||||
|
func ListAccessTokenRoles(client *gophercloud.ServiceClient, userID string, id string) pagination.Pager {
|
||||||
|
url := userAccessTokenRolesURL(client, userID, id)
|
||||||
|
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||||
|
return AccessTokenRolesPage{pagination.LinkedPageBase{PageResult: r}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessTokenRole retrieves details on a single OAuth1 access token role by
|
||||||
|
// an ID.
|
||||||
|
func GetAccessTokenRole(client *gophercloud.ServiceClient, userID string, id string, roleID string) (r GetAccessTokenRoleResult) {
|
||||||
|
resp, err := client.Get(userAccessTokenRoleURL(client, userID, id, roleID), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following are small helper functions used to help build the signature.
|
||||||
|
|
||||||
|
// buildOAuth1QueryString builds a URLEncoded parameters string specific for
|
||||||
|
// OAuth1-based requests.
|
||||||
|
func buildOAuth1QueryString(opts interface{}, timestamp *time.Time, callback string) (*url.URL, error) {
|
||||||
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := q.Query()
|
||||||
|
|
||||||
|
if timestamp != nil {
|
||||||
|
// use provided timestamp
|
||||||
|
query.Set("oauth_timestamp", strconv.FormatInt(timestamp.Unix(), 10))
|
||||||
|
} else {
|
||||||
|
// use current timestamp
|
||||||
|
query.Set("oauth_timestamp", strconv.FormatInt(time.Now().UTC().Unix(), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Get("oauth_nonce") == "" {
|
||||||
|
// when nonce is not set, generate a random one
|
||||||
|
query.Set("oauth_nonce", strconv.FormatInt(rand.Int63(), 10)+query.Get("oauth_timestamp"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback != "" {
|
||||||
|
query.Set("oauth_callback", callback)
|
||||||
|
}
|
||||||
|
query.Set("oauth_version", "1.0")
|
||||||
|
|
||||||
|
return &url.URL{RawQuery: query.Encode()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildStringToSign builds a string to be signed.
|
||||||
|
func buildStringToSign(method string, u string, query url.Values) []byte {
|
||||||
|
parsedURL, _ := url.Parse(u)
|
||||||
|
p := parsedURL.Port()
|
||||||
|
s := parsedURL.Scheme
|
||||||
|
|
||||||
|
// Default scheme port must be stripped
|
||||||
|
if s == "http" && p == "80" || s == "https" && p == "443" {
|
||||||
|
parsedURL.Host = strings.TrimSuffix(parsedURL.Host, ":"+p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that URL doesn't contain queries
|
||||||
|
parsedURL.RawQuery = ""
|
||||||
|
|
||||||
|
v := strings.Join(
|
||||||
|
[]string{method, url.QueryEscape(parsedURL.String()), url.QueryEscape(query.Encode())}, "&")
|
||||||
|
|
||||||
|
return []byte(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signString signs a string using an OAuth1 signature method.
|
||||||
|
func signString(signatureMethod SignatureMethod, strToSign []byte, signatureKeys []string) string {
|
||||||
|
var key []byte
|
||||||
|
for i, k := range signatureKeys {
|
||||||
|
key = append(key, []byte(url.QueryEscape(k))...)
|
||||||
|
if i == 0 {
|
||||||
|
key = append(key, '&')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var signedString string
|
||||||
|
switch signatureMethod {
|
||||||
|
case PLAINTEXT:
|
||||||
|
signedString = string(key)
|
||||||
|
default:
|
||||||
|
h := hmac.New(sha1.New, key)
|
||||||
|
h.Write(strToSign)
|
||||||
|
signedString = base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedString
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildAuthHeader generates an OAuth1 Authorization header with a signature
|
||||||
|
// calculated using an OAuth1 signature method.
|
||||||
|
func buildAuthHeader(query url.Values, signature string) string {
|
||||||
|
var authHeader []string
|
||||||
|
var keys []string
|
||||||
|
for k := range query {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
for _, v := range query[k] {
|
||||||
|
authHeader = append(authHeader, fmt.Sprintf("%s=%q", k, url.QueryEscape(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader = append(authHeader, fmt.Sprintf("oauth_signature=%q", signature))
|
||||||
|
|
||||||
|
return "OAuth " + strings.Join(authHeader, ", ")
|
||||||
|
}
|
||||||
305
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go
generated
vendored
Normal file
305
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
package oauth1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Consumer represents a delegated authorization request between two
|
||||||
|
// identities.
|
||||||
|
type Consumer struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type consumerResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConsumerResult is the response from a Create operation. Call its
|
||||||
|
// Extract method to interpret it as a Consumer.
|
||||||
|
type CreateConsumerResult struct {
|
||||||
|
consumerResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConsumerResult is the response from a Create operation. Call its
|
||||||
|
// Extract method to interpret it as a Consumer.
|
||||||
|
type UpdateConsumerResult struct {
|
||||||
|
consumerResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConsumerResult is the response from a Delete operation. Call its
|
||||||
|
// ExtractErr to determine if the request succeeded or failed.
|
||||||
|
type DeleteConsumerResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumersPage is a single page of Region results.
|
||||||
|
type ConsumersPage struct {
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsumerResult is the response from a Get operation. Call its Extract
|
||||||
|
// method to interpret it as a Consumer.
|
||||||
|
type GetConsumerResult struct {
|
||||||
|
consumerResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty determines whether or not a page of Consumers contains any results.
|
||||||
|
func (c ConsumersPage) IsEmpty() (bool, error) {
|
||||||
|
consumers, err := ExtractConsumers(c)
|
||||||
|
return len(consumers) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the "next" link from the links section of the result.
|
||||||
|
func (c ConsumersPage) NextPageURL() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Links struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
} `json:"links"`
|
||||||
|
}
|
||||||
|
err := c.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return s.Links.Next, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractConsumers returns a slice of Consumers contained in a single page of
|
||||||
|
// results.
|
||||||
|
func ExtractConsumers(r pagination.Page) ([]Consumer, error) {
|
||||||
|
var s struct {
|
||||||
|
Consumers []Consumer `json:"consumers"`
|
||||||
|
}
|
||||||
|
err := (r.(ConsumersPage)).ExtractInto(&s)
|
||||||
|
return s.Consumers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any consumer result as a Consumer.
|
||||||
|
func (c consumerResult) Extract() (*Consumer, error) {
|
||||||
|
var s struct {
|
||||||
|
Consumer *Consumer `json:"consumer"`
|
||||||
|
}
|
||||||
|
err := c.ExtractInto(&s)
|
||||||
|
return s.Consumer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token contains an OAuth1 token.
|
||||||
|
type Token struct {
|
||||||
|
// OAuthToken is the key value for the oauth token that the Identity API returns.
|
||||||
|
OAuthToken string `q:"oauth_token"`
|
||||||
|
// OAuthTokenSecret is the secret value associated with the OAuth Token.
|
||||||
|
OAuthTokenSecret string `q:"oauth_token_secret"`
|
||||||
|
// OAUthExpiresAt is the date and time when an OAuth token expires.
|
||||||
|
OAUthExpiresAt *time.Time `q:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenResult is a struct to handle
|
||||||
|
// "Content-Type: application/x-www-form-urlencoded" response.
|
||||||
|
type TokenResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any OAuth1 token result as a Token.
|
||||||
|
func (r TokenResult) Extract() (*Token, error) {
|
||||||
|
if r.Err != nil {
|
||||||
|
return nil, r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
values, err := url.ParseQuery(string(r.Body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &Token{
|
||||||
|
OAuthToken: values.Get("oauth_token"),
|
||||||
|
OAuthTokenSecret: values.Get("oauth_token_secret"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := values.Get("oauth_expires_at"); v != "" {
|
||||||
|
if t, err := time.Parse(gophercloud.RFC3339Milli, v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
token.OAUthExpiresAt = &t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizedToken contains an OAuth1 authorized token info.
|
||||||
|
type AuthorizedToken struct {
|
||||||
|
// OAuthVerifier is the ID of the token verifier.
|
||||||
|
OAuthVerifier string `json:"oauth_verifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizeTokenResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets AuthorizeTokenResult result as a AuthorizedToken.
|
||||||
|
func (r AuthorizeTokenResult) Extract() (*AuthorizedToken, error) {
|
||||||
|
var s struct {
|
||||||
|
AuthorizedToken *AuthorizedToken `json:"token"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.AuthorizedToken, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessToken represents an AccessToken response as a struct.
|
||||||
|
type AccessToken struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ConsumerID string `json:"consumer_id"`
|
||||||
|
ProjectID string `json:"project_id"`
|
||||||
|
AuthorizingUserID string `json:"authorizing_user_id"`
|
||||||
|
ExpiresAt *time.Time `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessToken) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp AccessToken
|
||||||
|
var s struct {
|
||||||
|
tmp
|
||||||
|
ExpiresAt *gophercloud.JSONRFC3339Milli `json:"expires_at"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*r = AccessToken(s.tmp)
|
||||||
|
|
||||||
|
if s.ExpiresAt != nil {
|
||||||
|
t := time.Time(*s.ExpiresAt)
|
||||||
|
r.ExpiresAt = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAccessTokenResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any GetAccessTokenResult result as an AccessToken.
|
||||||
|
func (r GetAccessTokenResult) Extract() (*AccessToken, error) {
|
||||||
|
var s struct {
|
||||||
|
AccessToken *AccessToken `json:"access_token"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.AccessToken, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeAccessTokenResult is the response from a Delete operation. Call its
|
||||||
|
// ExtractErr to determine if the request succeeded or failed.
|
||||||
|
type RevokeAccessTokenResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessTokensPage is a single page of Access Tokens results.
|
||||||
|
type AccessTokensPage struct {
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty determines whether or not a an AccessTokensPage contains any results.
|
||||||
|
func (r AccessTokensPage) IsEmpty() (bool, error) {
|
||||||
|
accessTokens, err := ExtractAccessTokens(r)
|
||||||
|
return len(accessTokens) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the "next" link from the links section of the result.
|
||||||
|
func (r AccessTokensPage) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAccessTokens returns a slice of AccessTokens contained in a single
|
||||||
|
// page of results.
|
||||||
|
func ExtractAccessTokens(r pagination.Page) ([]AccessToken, error) {
|
||||||
|
var s struct {
|
||||||
|
AccessTokens []AccessToken `json:"access_tokens"`
|
||||||
|
}
|
||||||
|
err := (r.(AccessTokensPage)).ExtractInto(&s)
|
||||||
|
return s.AccessTokens, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessTokenRole represents an Access Token Role struct.
|
||||||
|
type AccessTokenRole struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DomainID string `json:"domain_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessTokenRolesPage is a single page of Access Token roles results.
|
||||||
|
type AccessTokenRolesPage struct {
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty determines whether or not a an AccessTokensPage contains any results.
|
||||||
|
func (r AccessTokenRolesPage) IsEmpty() (bool, error) {
|
||||||
|
accessTokenRoles, err := ExtractAccessTokenRoles(r)
|
||||||
|
return len(accessTokenRoles) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the "next" link from the links section of the result.
|
||||||
|
func (r AccessTokenRolesPage) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAccessTokenRoles returns a slice of AccessTokenRole contained in a
|
||||||
|
// single page of results.
|
||||||
|
func ExtractAccessTokenRoles(r pagination.Page) ([]AccessTokenRole, error) {
|
||||||
|
var s struct {
|
||||||
|
AccessTokenRoles []AccessTokenRole `json:"roles"`
|
||||||
|
}
|
||||||
|
err := (r.(AccessTokenRolesPage)).ExtractInto(&s)
|
||||||
|
return s.AccessTokenRoles, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAccessTokenRoleResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any GetAccessTokenRoleResult result as an AccessTokenRole.
|
||||||
|
func (r GetAccessTokenRoleResult) Extract() (*AccessTokenRole, error) {
|
||||||
|
var s struct {
|
||||||
|
AccessTokenRole *AccessTokenRole `json:"role"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.AccessTokenRole, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuth1 is an OAuth1 object, returned in OAuth1 token result.
|
||||||
|
type OAuth1 struct {
|
||||||
|
AccessTokenID string `json:"access_token_id"`
|
||||||
|
ConsumerID string `json:"consumer_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenExt represents an extension of the base token result.
|
||||||
|
type TokenExt struct {
|
||||||
|
OAuth1 OAuth1 `json:"OS-OAUTH1"`
|
||||||
|
}
|
||||||
43
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go
generated
vendored
Normal file
43
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package oauth1
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func consumersURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("OS-OAUTH1", "consumers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumerURL(c *gophercloud.ServiceClient, id string) string {
|
||||||
|
return c.ServiceURL("OS-OAUTH1", "consumers", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestTokenURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("OS-OAUTH1", "request_token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizeTokenURL(c *gophercloud.ServiceClient, id string) string {
|
||||||
|
return c.ServiceURL("OS-OAUTH1", "authorize", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAccessTokenURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("OS-OAUTH1", "access_token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func userAccessTokensURL(c *gophercloud.ServiceClient, userID string) string {
|
||||||
|
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
func userAccessTokenURL(c *gophercloud.ServiceClient, userID string, id string) string {
|
||||||
|
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func userAccessTokenRolesURL(c *gophercloud.ServiceClient, userID string, id string) string {
|
||||||
|
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles")
|
||||||
|
}
|
||||||
|
|
||||||
|
func userAccessTokenRoleURL(c *gophercloud.ServiceClient, userID string, id string, roleID string) string {
|
||||||
|
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles", roleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("auth", "tokens")
|
||||||
|
}
|
||||||
108
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
generated
vendored
Normal file
108
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
Package tokens provides information and interaction with the token API
|
||||||
|
resource for the OpenStack Identity service.
|
||||||
|
|
||||||
|
For more information, see:
|
||||||
|
http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
|
||||||
|
|
||||||
|
Example to Create a Token From a Username and Password
|
||||||
|
|
||||||
|
authOptions := tokens.AuthOptions{
|
||||||
|
UserID: "username",
|
||||||
|
Password: "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token From a Username, Password, and Domain
|
||||||
|
|
||||||
|
authOptions := tokens.AuthOptions{
|
||||||
|
UserID: "username",
|
||||||
|
Password: "password",
|
||||||
|
DomainID: "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authOptions = tokens.AuthOptions{
|
||||||
|
UserID: "username",
|
||||||
|
Password: "password",
|
||||||
|
DomainName: "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token From a Token
|
||||||
|
|
||||||
|
authOptions := tokens.AuthOptions{
|
||||||
|
TokenID: "token_id",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token from a Username and Password with Project ID Scope
|
||||||
|
|
||||||
|
scope := tokens.Scope{
|
||||||
|
ProjectID: "0fe36e73809d46aeae6705c39077b1b3",
|
||||||
|
}
|
||||||
|
|
||||||
|
authOptions := tokens.AuthOptions{
|
||||||
|
Scope: &scope,
|
||||||
|
UserID: "username",
|
||||||
|
Password: "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token from a Username and Password with Domain ID Scope
|
||||||
|
|
||||||
|
scope := tokens.Scope{
|
||||||
|
DomainID: "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
authOptions := tokens.AuthOptions{
|
||||||
|
Scope: &scope,
|
||||||
|
UserID: "username",
|
||||||
|
Password: "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create a Token from a Username and Password with Project Name Scope
|
||||||
|
|
||||||
|
scope := tokens.Scope{
|
||||||
|
ProjectName: "project_name",
|
||||||
|
DomainID: "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
authOptions := tokens.AuthOptions{
|
||||||
|
Scope: &scope,
|
||||||
|
UserID: "username",
|
||||||
|
Password: "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package tokens
|
||||||
174
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
174
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// Scope allows a created token to be limited to a specific domain or project.
|
||||||
|
type Scope struct {
|
||||||
|
ProjectID string
|
||||||
|
ProjectName string
|
||||||
|
DomainID string
|
||||||
|
DomainName string
|
||||||
|
System bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptionsBuilder provides the ability for extensions to add additional
|
||||||
|
// parameters to AuthOptions. Extensions must satisfy all required methods.
|
||||||
|
type AuthOptionsBuilder interface {
|
||||||
|
// ToTokenV3CreateMap assembles the Create request body, returning an error
|
||||||
|
// if parameters are missing or inconsistent.
|
||||||
|
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
|
||||||
|
ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error)
|
||||||
|
ToTokenV3ScopeMap() (map[string]interface{}, error)
|
||||||
|
CanReauth() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptions represents options for authenticating a user.
|
||||||
|
type AuthOptions struct {
|
||||||
|
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
|
||||||
|
// the Identity API of the appropriate version. While it's ultimately needed
|
||||||
|
// by all of the identity services, it will often be populated by a
|
||||||
|
// provider-level function.
|
||||||
|
IdentityEndpoint string `json:"-"`
|
||||||
|
|
||||||
|
// Username is required if using Identity V2 API. Consult with your provider's
|
||||||
|
// control panel to discover your account's username. In Identity V3, either
|
||||||
|
// UserID or a combination of Username and DomainID or DomainName are needed.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
UserID string `json:"id,omitempty"`
|
||||||
|
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
|
||||||
|
// Passcode is used in TOTP authentication method
|
||||||
|
Passcode string `json:"passcode,omitempty"`
|
||||||
|
|
||||||
|
// At most one of DomainID and DomainName must be provided if using Username
|
||||||
|
// with Identity V3. Otherwise, either are optional.
|
||||||
|
DomainID string `json:"-"`
|
||||||
|
DomainName string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// AllowReauth should be set to true if you grant permission for Gophercloud
|
||||||
|
// to cache your credentials in memory, and to allow Gophercloud to attempt
|
||||||
|
// to re-authenticate automatically if/when your token expires. If you set
|
||||||
|
// it to false, it will not cache these settings, but re-authentication will
|
||||||
|
// not be possible. This setting defaults to false.
|
||||||
|
AllowReauth bool `json:"-"`
|
||||||
|
|
||||||
|
// TokenID allows users to authenticate (possibly as another user) with an
|
||||||
|
// authentication token ID.
|
||||||
|
TokenID string `json:"-"`
|
||||||
|
|
||||||
|
// Authentication through Application Credentials requires supplying name, project and secret
|
||||||
|
// For project we can use TenantID
|
||||||
|
ApplicationCredentialID string `json:"-"`
|
||||||
|
ApplicationCredentialName string `json:"-"`
|
||||||
|
ApplicationCredentialSecret string `json:"-"`
|
||||||
|
|
||||||
|
Scope Scope `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3CreateMap builds a request body from AuthOptions.
|
||||||
|
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
gophercloudAuthOpts := gophercloud.AuthOptions{
|
||||||
|
Username: opts.Username,
|
||||||
|
UserID: opts.UserID,
|
||||||
|
Password: opts.Password,
|
||||||
|
Passcode: opts.Passcode,
|
||||||
|
DomainID: opts.DomainID,
|
||||||
|
DomainName: opts.DomainName,
|
||||||
|
AllowReauth: opts.AllowReauth,
|
||||||
|
TokenID: opts.TokenID,
|
||||||
|
ApplicationCredentialID: opts.ApplicationCredentialID,
|
||||||
|
ApplicationCredentialName: opts.ApplicationCredentialName,
|
||||||
|
ApplicationCredentialSecret: opts.ApplicationCredentialSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3ScopeMap builds a scope request body from AuthOptions.
|
||||||
|
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
|
scope := gophercloud.AuthScope(opts.Scope)
|
||||||
|
|
||||||
|
gophercloudAuthOpts := gophercloud.AuthOptions{
|
||||||
|
Scope: &scope,
|
||||||
|
DomainID: opts.DomainID,
|
||||||
|
DomainName: opts.DomainName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return gophercloudAuthOpts.ToTokenV3ScopeMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *AuthOptions) CanReauth() bool {
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
// cannot reauth using TOTP passcode
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts.AllowReauth
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||||
|
// interface in the v3 tokens package.
|
||||||
|
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func subjectTokenHeaders(subjectToken string) map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"X-Subject-Token": subjectToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create authenticates and either generates a new token, or changes the Scope
|
||||||
|
// of an existing token.
|
||||||
|
func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) {
|
||||||
|
scope, err := opts.ToTokenV3ScopeMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := opts.ToTokenV3CreateMap(scope)
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get validates and retrieves information about another token.
|
||||||
|
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||||
|
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: subjectTokenHeaders(token),
|
||||||
|
OkCodes: []int{200, 203},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate determines if a specified token is valid or not.
|
||||||
|
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
||||||
|
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: subjectTokenHeaders(token),
|
||||||
|
OkCodes: []int{200, 204, 404},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.StatusCode == 200 || resp.StatusCode == 204, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke immediately makes specified token invalid.
|
||||||
|
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
|
||||||
|
resp, err := c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: subjectTokenHeaders(token),
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
194
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
Normal file
194
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint represents a single API endpoint offered by a service.
|
||||||
|
// It matches either a public, internal or admin URL.
|
||||||
|
// If supported, it contains a region specifier, again if provided.
|
||||||
|
// The significance of the Region field will depend upon your provider.
|
||||||
|
type Endpoint struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
RegionID string `json:"region_id"`
|
||||||
|
Interface string `json:"interface"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CatalogEntry provides a type-safe interface to an Identity API V3 service
|
||||||
|
// catalog listing. Each class of service, such as cloud DNS or block storage
|
||||||
|
// services, could have multiple CatalogEntry representing it (one by interface
|
||||||
|
// type, e.g public, admin or internal).
|
||||||
|
//
|
||||||
|
// Note: when looking for the desired service, try, whenever possible, to key
|
||||||
|
// off the type field. Otherwise, you'll tie the representation of the service
|
||||||
|
// to a specific provider.
|
||||||
|
type CatalogEntry struct {
|
||||||
|
// Service ID
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Name will contain the provider-specified name for the service.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Type will contain a type string if OpenStack defines a type for the
|
||||||
|
// service. Otherwise, for provider-specific services, the provider may
|
||||||
|
// assign their own type strings.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Endpoints will let the caller iterate over all the different endpoints that
|
||||||
|
// may exist for the service.
|
||||||
|
Endpoints []Endpoint `json:"endpoints"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceCatalog provides a view into the service catalog from a previous,
|
||||||
|
// successful authentication.
|
||||||
|
type ServiceCatalog struct {
|
||||||
|
Entries []CatalogEntry `json:"catalog"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain provides information about the domain to which this token grants
|
||||||
|
// access.
|
||||||
|
type Domain struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a user resource that exists in the Identity Service.
|
||||||
|
type User struct {
|
||||||
|
Domain Domain `json:"domain"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role provides information about roles to which User is authorized.
|
||||||
|
type Role struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project provides information about project to which User is authorized.
|
||||||
|
type Project struct {
|
||||||
|
Domain Domain `json:"domain"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonResult is the response from a request. A commonResult has various
|
||||||
|
// methods which can be used to extract different details about the result.
|
||||||
|
type commonResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract is a shortcut for ExtractToken.
|
||||||
|
// This function is deprecated and still present for backward compatibility.
|
||||||
|
func (r commonResult) Extract() (*Token, error) {
|
||||||
|
return r.ExtractToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractToken interprets a commonResult as a Token.
|
||||||
|
func (r commonResult) ExtractToken() (*Token, error) {
|
||||||
|
var s Token
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the token itself from the stored headers.
|
||||||
|
s.ID = r.Header.Get("X-Subject-Token")
|
||||||
|
|
||||||
|
return &s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
|
||||||
|
// string is the same as the ID field of the Token struct returned from
|
||||||
|
// ExtractToken().
|
||||||
|
func (r CreateResult) ExtractTokenID() (string, error) {
|
||||||
|
return r.Header.Get("X-Subject-Token"), r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
|
||||||
|
// string is the same as the ID field of the Token struct returned from
|
||||||
|
// ExtractToken().
|
||||||
|
func (r GetResult) ExtractTokenID() (string, error) {
|
||||||
|
return r.Header.Get("X-Subject-Token"), r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
|
||||||
|
// with the user's Token.
|
||||||
|
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||||
|
var s ServiceCatalog
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return &s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractUser returns the User that is the owner of the Token.
|
||||||
|
func (r commonResult) ExtractUser() (*User, error) {
|
||||||
|
var s struct {
|
||||||
|
User *User `json:"user"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.User, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractRoles returns Roles to which User is authorized.
|
||||||
|
func (r commonResult) ExtractRoles() ([]Role, error) {
|
||||||
|
var s struct {
|
||||||
|
Roles []Role `json:"roles"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Roles, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractProject returns Project to which User is authorized.
|
||||||
|
func (r commonResult) ExtractProject() (*Project, error) {
|
||||||
|
var s struct {
|
||||||
|
Project *Project `json:"project"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Project, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractDomain returns Domain to which User is authorized.
|
||||||
|
func (r commonResult) ExtractDomain() (*Domain, error) {
|
||||||
|
var s struct {
|
||||||
|
Domain *Domain `json:"domain"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s.Domain, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResult is the response from a Create request. Use ExtractToken()
|
||||||
|
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
|
||||||
|
// as a service catalog.
|
||||||
|
type CreateResult struct {
|
||||||
|
commonResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult is the response from a Get request. Use ExtractToken()
|
||||||
|
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
|
||||||
|
// as a service catalog.
|
||||||
|
type GetResult struct {
|
||||||
|
commonResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeResult is response from a Revoke request.
|
||||||
|
type RevokeResult struct {
|
||||||
|
commonResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token is a string that grants a user access to a controlled set of services
|
||||||
|
// in an OpenStack provider. Each Token is valid for a set length of time.
|
||||||
|
type Token struct {
|
||||||
|
// ID is the issued token.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// ExpiresAt is the timestamp at which this token will no longer be accepted.
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r commonResult) ExtractInto(v interface{}) error {
|
||||||
|
return r.ExtractIntoStructPtr(v, "token")
|
||||||
|
}
|
||||||
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func tokenURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("auth", "tokens")
|
||||||
|
}
|
||||||
51
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go
generated
vendored
Normal file
51
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Package imagedata enables management of image data.
|
||||||
|
|
||||||
|
Example to Upload Image Data
|
||||||
|
|
||||||
|
imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea"
|
||||||
|
|
||||||
|
imageData, err := os.Open("/path/to/image/file")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer imageData.Close()
|
||||||
|
|
||||||
|
err = imagedata.Upload(imageClient, imageID, imageData).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Stage Image Data
|
||||||
|
|
||||||
|
imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea"
|
||||||
|
|
||||||
|
imageData, err := os.Open("/path/to/image/file")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer imageData.Close()
|
||||||
|
|
||||||
|
err = imagedata.Stage(imageClient, imageID, imageData).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Download Image Data
|
||||||
|
|
||||||
|
imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea"
|
||||||
|
|
||||||
|
image, err := imagedata.Download(imageClient, imageID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the reader, when reading has finished
|
||||||
|
defer image.Close()
|
||||||
|
|
||||||
|
imageData, err := ioutil.ReadAll(image)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package imagedata
|
||||||
38
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go
generated
vendored
Normal file
38
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package imagedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Upload uploads an image file.
|
||||||
|
func Upload(client *gophercloud.ServiceClient, id string, data io.Reader) (r UploadResult) {
|
||||||
|
resp, err := client.Put(uploadURL(client, id), data, nil, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"},
|
||||||
|
OkCodes: []int{204},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage performs PUT call on the existing image object in the Imageservice with
|
||||||
|
// the provided file.
|
||||||
|
// Existing image object must be in the "queued" status.
|
||||||
|
func Stage(client *gophercloud.ServiceClient, id string, data io.Reader) (r StageResult) {
|
||||||
|
resp, err := client.Put(stageURL(client, id), data, nil, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"},
|
||||||
|
OkCodes: []int{204},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download retrieves an image.
|
||||||
|
func Download(client *gophercloud.ServiceClient, id string) (r DownloadResult) {
|
||||||
|
resp, err := client.Get(downloadURL(client, id), nil, &gophercloud.RequestOpts{
|
||||||
|
KeepResponseBody: true,
|
||||||
|
})
|
||||||
|
r.Body, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
34
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go
generated
vendored
Normal file
34
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package imagedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadResult is the result of an upload image operation. Call its ExtractErr
|
||||||
|
// method to determine if the request succeeded or failed.
|
||||||
|
type UploadResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// StageResult is the result of a stage image operation. Call its ExtractErr
|
||||||
|
// method to determine if the request succeeded or failed.
|
||||||
|
type StageResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadResult is the result of a download image operation. Call its Extract
|
||||||
|
// method to gain access to the image data.
|
||||||
|
type DownloadResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
Body io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract builds images model from io.Reader
|
||||||
|
func (r DownloadResult) Extract() (io.ReadCloser, error) {
|
||||||
|
if r.Err != nil {
|
||||||
|
return nil, r.Err
|
||||||
|
}
|
||||||
|
return r.Body, nil
|
||||||
|
}
|
||||||
23
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go
generated
vendored
Normal file
23
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package imagedata
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
const (
|
||||||
|
rootPath = "images"
|
||||||
|
uploadPath = "file"
|
||||||
|
stagePath = "stage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// `imageDataURL(c,i)` is the URL for the binary image data for the
|
||||||
|
// image identified by ID `i` in the service `c`.
|
||||||
|
func uploadURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return c.ServiceURL(rootPath, imageID, uploadPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stageURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return c.ServiceURL(rootPath, imageID, stagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return uploadURL(c, imageID)
|
||||||
|
}
|
||||||
60
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/doc.go
generated
vendored
Normal file
60
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
Package images enables management and retrieval of images from the OpenStack
|
||||||
|
Image Service.
|
||||||
|
|
||||||
|
Example to List Images
|
||||||
|
|
||||||
|
images.ListOpts{
|
||||||
|
Owner: "a7509e1ae65945fda83f3e52c6296017",
|
||||||
|
}
|
||||||
|
|
||||||
|
allPages, err := images.List(imagesClient, listOpts).AllPages()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allImages, err := images.ExtractImages(allPages)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, image := range allImages {
|
||||||
|
fmt.Printf("%+v\n", image)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Create an Image
|
||||||
|
|
||||||
|
createOpts := images.CreateOpts{
|
||||||
|
Name: "image_name",
|
||||||
|
Visibility: images.ImageVisibilityPrivate,
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err := images.Create(imageClient, createOpts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Update an Image
|
||||||
|
|
||||||
|
imageID := "1bea47ed-f6a9-463b-b423-14b9cca9ad27"
|
||||||
|
|
||||||
|
updateOpts := images.UpdateOpts{
|
||||||
|
images.ReplaceImageName{
|
||||||
|
NewName: "new_name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err := images.Update(imageClient, imageID, updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Example to Delete an Image
|
||||||
|
|
||||||
|
imageID := "1bea47ed-f6a9-463b-b423-14b9cca9ad27"
|
||||||
|
err := images.Delete(imageClient, imageID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package images
|
||||||
384
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go
generated
vendored
Normal file
384
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,384 @@
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// List request.
|
||||||
|
type ListOptsBuilder interface {
|
||||||
|
ToImageListQuery() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOpts allows the filtering and sorting of paginated collections through
|
||||||
|
// the API. Filtering is achieved by passing in struct field values that map to
|
||||||
|
// the server attributes you want to see returned. Marker and Limit are used
|
||||||
|
// for pagination.
|
||||||
|
//
|
||||||
|
// http://developer.openstack.org/api-ref-image-v2.html
|
||||||
|
type ListOpts struct {
|
||||||
|
// ID is the ID of the image.
|
||||||
|
// Multiple IDs can be specified by constructing a string
|
||||||
|
// such as "in:uuid1,uuid2,uuid3".
|
||||||
|
ID string `q:"id"`
|
||||||
|
|
||||||
|
// Integer value for the limit of values to return.
|
||||||
|
Limit int `q:"limit"`
|
||||||
|
|
||||||
|
// UUID of the server at which you want to set a marker.
|
||||||
|
Marker string `q:"marker"`
|
||||||
|
|
||||||
|
// Name filters on the name of the image.
|
||||||
|
// Multiple names can be specified by constructing a string
|
||||||
|
// such as "in:name1,name2,name3".
|
||||||
|
Name string `q:"name"`
|
||||||
|
|
||||||
|
// Visibility filters on the visibility of the image.
|
||||||
|
Visibility ImageVisibility `q:"visibility"`
|
||||||
|
|
||||||
|
// MemberStatus filters on the member status of the image.
|
||||||
|
MemberStatus ImageMemberStatus `q:"member_status"`
|
||||||
|
|
||||||
|
// Owner filters on the project ID of the image.
|
||||||
|
Owner string `q:"owner"`
|
||||||
|
|
||||||
|
// Status filters on the status of the image.
|
||||||
|
// Multiple statuses can be specified by constructing a string
|
||||||
|
// such as "in:saving,queued".
|
||||||
|
Status ImageStatus `q:"status"`
|
||||||
|
|
||||||
|
// SizeMin filters on the size_min image property.
|
||||||
|
SizeMin int64 `q:"size_min"`
|
||||||
|
|
||||||
|
// SizeMax filters on the size_max image property.
|
||||||
|
SizeMax int64 `q:"size_max"`
|
||||||
|
|
||||||
|
// Sort sorts the results using the new style of sorting. See the OpenStack
|
||||||
|
// Image API reference for the exact syntax.
|
||||||
|
//
|
||||||
|
// Sort cannot be used with the classic sort options (sort_key and sort_dir).
|
||||||
|
Sort string `q:"sort"`
|
||||||
|
|
||||||
|
// SortKey will sort the results based on a specified image property.
|
||||||
|
SortKey string `q:"sort_key"`
|
||||||
|
|
||||||
|
// SortDir will sort the list results either ascending or decending.
|
||||||
|
SortDir string `q:"sort_dir"`
|
||||||
|
|
||||||
|
// Tags filters on specific image tags.
|
||||||
|
Tags []string `q:"tag"`
|
||||||
|
|
||||||
|
// CreatedAtQuery filters images based on their creation date.
|
||||||
|
CreatedAtQuery *ImageDateQuery
|
||||||
|
|
||||||
|
// UpdatedAtQuery filters images based on their updated date.
|
||||||
|
UpdatedAtQuery *ImageDateQuery
|
||||||
|
|
||||||
|
// ContainerFormat filters images based on the container_format.
|
||||||
|
// Multiple container formats can be specified by constructing a
|
||||||
|
// string such as "in:bare,ami".
|
||||||
|
ContainerFormat string `q:"container_format"`
|
||||||
|
|
||||||
|
// DiskFormat filters images based on the disk_format.
|
||||||
|
// Multiple disk formats can be specified by constructing a string
|
||||||
|
// such as "in:qcow2,iso".
|
||||||
|
DiskFormat string `q:"disk_format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImageListQuery formats a ListOpts into a query string.
|
||||||
|
func (opts ListOpts) ToImageListQuery() (string, error) {
|
||||||
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
|
params := q.Query()
|
||||||
|
|
||||||
|
if opts.CreatedAtQuery != nil {
|
||||||
|
createdAt := opts.CreatedAtQuery.Date.Format(time.RFC3339)
|
||||||
|
if v := opts.CreatedAtQuery.Filter; v != "" {
|
||||||
|
createdAt = fmt.Sprintf("%s:%s", v, createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Add("created_at", createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.UpdatedAtQuery != nil {
|
||||||
|
updatedAt := opts.UpdatedAtQuery.Date.Format(time.RFC3339)
|
||||||
|
if v := opts.UpdatedAtQuery.Filter; v != "" {
|
||||||
|
updatedAt = fmt.Sprintf("%s:%s", v, updatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Add("updated_at", updatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
q = &url.URL{RawQuery: params.Encode()}
|
||||||
|
|
||||||
|
return q.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List implements image list request.
|
||||||
|
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||||
|
url := listURL(c)
|
||||||
|
if opts != nil {
|
||||||
|
query, err := opts.ToImageListQuery()
|
||||||
|
if err != nil {
|
||||||
|
return pagination.Pager{Err: err}
|
||||||
|
}
|
||||||
|
url += query
|
||||||
|
}
|
||||||
|
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
|
||||||
|
imagePage := ImagePage{
|
||||||
|
serviceURL: c.ServiceURL(),
|
||||||
|
LinkedPageBase: pagination.LinkedPageBase{PageResult: r},
|
||||||
|
}
|
||||||
|
|
||||||
|
return imagePage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOptsBuilder allows extensions to add parameters to the Create request.
|
||||||
|
type CreateOptsBuilder interface {
|
||||||
|
// Returns value that can be passed to json.Marshal
|
||||||
|
ToImageCreateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOpts represents options used to create an image.
|
||||||
|
type CreateOpts struct {
|
||||||
|
// Name is the name of the new image.
|
||||||
|
Name string `json:"name" required:"true"`
|
||||||
|
|
||||||
|
// Id is the the image ID.
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// Visibility defines who can see/use the image.
|
||||||
|
Visibility *ImageVisibility `json:"visibility,omitempty"`
|
||||||
|
|
||||||
|
// Tags is a set of image tags.
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
|
||||||
|
// ContainerFormat is the format of the
|
||||||
|
// container. Valid values are ami, ari, aki, bare, and ovf.
|
||||||
|
ContainerFormat string `json:"container_format,omitempty"`
|
||||||
|
|
||||||
|
// DiskFormat is the format of the disk. If set,
|
||||||
|
// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
|
||||||
|
// and iso.
|
||||||
|
DiskFormat string `json:"disk_format,omitempty"`
|
||||||
|
|
||||||
|
// MinDisk is the amount of disk space in
|
||||||
|
// GB that is required to boot the image.
|
||||||
|
MinDisk int `json:"min_disk,omitempty"`
|
||||||
|
|
||||||
|
// MinRAM is the amount of RAM in MB that
|
||||||
|
// is required to boot the image.
|
||||||
|
MinRAM int `json:"min_ram,omitempty"`
|
||||||
|
|
||||||
|
// protected is whether the image is not deletable.
|
||||||
|
Protected *bool `json:"protected,omitempty"`
|
||||||
|
|
||||||
|
// properties is a set of properties, if any, that
|
||||||
|
// are associated with the image.
|
||||||
|
Properties map[string]string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImageCreateMap assembles a request body based on the contents of
|
||||||
|
// a CreateOpts.
|
||||||
|
func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) {
|
||||||
|
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Properties != nil {
|
||||||
|
for k, v := range opts.Properties {
|
||||||
|
b[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create implements create image request.
|
||||||
|
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||||
|
b, err := opts.ToImageCreateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements image delete request.
|
||||||
|
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||||
|
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements image get request.
|
||||||
|
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||||
|
resp, err := client.Get(getURL(client, id), &r.Body, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements image updated request.
|
||||||
|
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||||
|
b, err := opts.ToImageUpdateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
resp, err := client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200},
|
||||||
|
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
|
||||||
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
// Update request.
|
||||||
|
type UpdateOptsBuilder interface {
|
||||||
|
// returns value implementing json.Marshaler which when marshaled matches
|
||||||
|
// the patch schema:
|
||||||
|
// http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html
|
||||||
|
ToImageUpdateMap() ([]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOpts implements UpdateOpts
|
||||||
|
type UpdateOpts []Patch
|
||||||
|
|
||||||
|
// ToImageUpdateMap assembles a request body based on the contents of
|
||||||
|
// UpdateOpts.
|
||||||
|
func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) {
|
||||||
|
m := make([]interface{}, len(opts))
|
||||||
|
for i, patch := range opts {
|
||||||
|
patchJSON := patch.ToImagePatchMap()
|
||||||
|
m[i] = patchJSON
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch represents a single update to an existing image. Multiple updates
|
||||||
|
// to an image can be submitted at the same time.
|
||||||
|
type Patch interface {
|
||||||
|
ToImagePatchMap() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVisibility represents an updated visibility property request.
|
||||||
|
type UpdateVisibility struct {
|
||||||
|
Visibility ImageVisibility
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImagePatchMap assembles a request body based on UpdateVisibility.
|
||||||
|
func (r UpdateVisibility) ToImagePatchMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/visibility",
|
||||||
|
"value": r.Visibility,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceImageName represents an updated image_name property request.
|
||||||
|
type ReplaceImageName struct {
|
||||||
|
NewName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImagePatchMap assembles a request body based on ReplaceImageName.
|
||||||
|
func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/name",
|
||||||
|
"value": r.NewName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceImageChecksum represents an updated checksum property request.
|
||||||
|
type ReplaceImageChecksum struct {
|
||||||
|
Checksum string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceImageChecksum assembles a request body based on ReplaceImageChecksum.
|
||||||
|
func (r ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/checksum",
|
||||||
|
"value": r.Checksum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceImageTags represents an updated tags property request.
|
||||||
|
type ReplaceImageTags struct {
|
||||||
|
NewTags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImagePatchMap assembles a request body based on ReplaceImageTags.
|
||||||
|
func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/tags",
|
||||||
|
"value": r.NewTags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceImageMinDisk represents an updated min_disk property request.
|
||||||
|
type ReplaceImageMinDisk struct {
|
||||||
|
NewMinDisk int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImagePatchMap assembles a request body based on ReplaceImageTags.
|
||||||
|
func (r ReplaceImageMinDisk) ToImagePatchMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/min_disk",
|
||||||
|
"value": r.NewMinDisk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceImageMinRam represents an updated min_ram property request.
|
||||||
|
type ReplaceImageMinRam struct {
|
||||||
|
NewMinRam int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImagePatchMap assembles a request body based on ReplaceImageTags.
|
||||||
|
func (r ReplaceImageMinRam) ToImagePatchMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/min_ram",
|
||||||
|
"value": r.NewMinRam,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOp represents a valid update operation.
|
||||||
|
type UpdateOp string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddOp UpdateOp = "add"
|
||||||
|
ReplaceOp UpdateOp = "replace"
|
||||||
|
RemoveOp UpdateOp = "remove"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateImageProperty represents an update property request.
|
||||||
|
type UpdateImageProperty struct {
|
||||||
|
Op UpdateOp
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToImagePatchMap assembles a request body based on UpdateImageProperty.
|
||||||
|
func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} {
|
||||||
|
updateMap := map[string]interface{}{
|
||||||
|
"op": r.Op,
|
||||||
|
"path": fmt.Sprintf("/%s", r.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Op != RemoveOp {
|
||||||
|
updateMap["value"] = r.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateMap
|
||||||
|
}
|
||||||
240
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
Normal file
240
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/internal"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image represents an image found in the OpenStack Image service.
|
||||||
|
type Image struct {
|
||||||
|
// ID is the image UUID.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Name is the human-readable display name for the image.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Status is the image status. It can be "queued" or "active"
|
||||||
|
// See imageservice/v2/images/type.go
|
||||||
|
Status ImageStatus `json:"status"`
|
||||||
|
|
||||||
|
// Tags is a list of image tags. Tags are arbitrarily defined strings
|
||||||
|
// attached to an image.
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
|
||||||
|
// ContainerFormat is the format of the container.
|
||||||
|
// Valid values are ami, ari, aki, bare, and ovf.
|
||||||
|
ContainerFormat string `json:"container_format"`
|
||||||
|
|
||||||
|
// DiskFormat is the format of the disk.
|
||||||
|
// If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
|
||||||
|
// and iso.
|
||||||
|
DiskFormat string `json:"disk_format"`
|
||||||
|
|
||||||
|
// MinDiskGigabytes is the amount of disk space in GB that is required to
|
||||||
|
// boot the image.
|
||||||
|
MinDiskGigabytes int `json:"min_disk"`
|
||||||
|
|
||||||
|
// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to
|
||||||
|
// boot the image.
|
||||||
|
MinRAMMegabytes int `json:"min_ram"`
|
||||||
|
|
||||||
|
// Owner is the tenant ID the image belongs to.
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
|
||||||
|
// Protected is whether the image is deletable or not.
|
||||||
|
Protected bool `json:"protected"`
|
||||||
|
|
||||||
|
// Visibility defines who can see/use the image.
|
||||||
|
Visibility ImageVisibility `json:"visibility"`
|
||||||
|
|
||||||
|
// Checksum is the checksum of the data that's associated with the image.
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
|
|
||||||
|
// SizeBytes is the size of the data that's associated with the image.
|
||||||
|
SizeBytes int64 `json:"-"`
|
||||||
|
|
||||||
|
// Metadata is a set of metadata associated with the image.
|
||||||
|
// Image metadata allow for meaningfully define the image properties
|
||||||
|
// and tags.
|
||||||
|
// See http://docs.openstack.org/developer/glance/metadefs-concepts.html.
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
|
||||||
|
// Properties is a set of key-value pairs, if any, that are associated with
|
||||||
|
// the image.
|
||||||
|
Properties map[string]interface{}
|
||||||
|
|
||||||
|
// CreatedAt is the date when the image has been created.
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
// UpdatedAt is the date when the last change has been made to the image or
|
||||||
|
// it's properties.
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// File is the trailing path after the glance endpoint that represent the
|
||||||
|
// location of the image or the path to retrieve it.
|
||||||
|
File string `json:"file"`
|
||||||
|
|
||||||
|
// Schema is the path to the JSON-schema that represent the image or image
|
||||||
|
// entity.
|
||||||
|
Schema string `json:"schema"`
|
||||||
|
|
||||||
|
// VirtualSize is the virtual size of the image
|
||||||
|
VirtualSize int64 `json:"virtual_size"`
|
||||||
|
|
||||||
|
// OpenStackImageImportMethods is a slice listing the types of import
|
||||||
|
// methods available in the cloud.
|
||||||
|
OpenStackImageImportMethods []string `json:"-"`
|
||||||
|
// OpenStackImageStoreIDs is a slice listing the store IDs available in
|
||||||
|
// the cloud.
|
||||||
|
OpenStackImageStoreIDs []string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Image) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp Image
|
||||||
|
var s struct {
|
||||||
|
tmp
|
||||||
|
SizeBytes interface{} `json:"size"`
|
||||||
|
OpenStackImageImportMethods string `json:"openstack-image-import-methods"`
|
||||||
|
OpenStackImageStoreIDs string `json:"openstack-image-store-ids"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*r = Image(s.tmp)
|
||||||
|
|
||||||
|
switch t := s.SizeBytes.(type) {
|
||||||
|
case nil:
|
||||||
|
r.SizeBytes = 0
|
||||||
|
case float32:
|
||||||
|
r.SizeBytes = int64(t)
|
||||||
|
case float64:
|
||||||
|
r.SizeBytes = int64(t)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundle all other fields into Properties
|
||||||
|
var result interface{}
|
||||||
|
err = json.Unmarshal(b, &result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resultMap, ok := result.(map[string]interface{}); ok {
|
||||||
|
delete(resultMap, "self")
|
||||||
|
delete(resultMap, "size")
|
||||||
|
delete(resultMap, "openstack-image-import-methods")
|
||||||
|
delete(resultMap, "openstack-image-store-ids")
|
||||||
|
r.Properties = internal.RemainingKeys(Image{}, resultMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageImportMethods), splitFunc); len(v) > 0 {
|
||||||
|
r.OpenStackImageImportMethods = v
|
||||||
|
}
|
||||||
|
if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageStoreIDs), splitFunc); len(v) > 0 {
|
||||||
|
r.OpenStackImageStoreIDs = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type commonResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract interprets any commonResult as an Image.
|
||||||
|
func (r commonResult) Extract() (*Image, error) {
|
||||||
|
var s *Image
|
||||||
|
if v, ok := r.Body.(map[string]interface{}); ok {
|
||||||
|
for k, h := range r.Header {
|
||||||
|
if strings.ToLower(k) == "openstack-image-import-methods" {
|
||||||
|
for _, s := range h {
|
||||||
|
v["openstack-image-import-methods"] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.ToLower(k) == "openstack-image-store-ids" {
|
||||||
|
for _, s := range h {
|
||||||
|
v["openstack-image-store-ids"] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResult represents the result of a Create operation. Call its Extract
|
||||||
|
// method to interpret it as an Image.
|
||||||
|
type CreateResult struct {
|
||||||
|
commonResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResult represents the result of an Update operation. Call its Extract
|
||||||
|
// method to interpret it as an Image.
|
||||||
|
type UpdateResult struct {
|
||||||
|
commonResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult represents the result of a Get operation. Call its Extract
|
||||||
|
// method to interpret it as an Image.
|
||||||
|
type GetResult struct {
|
||||||
|
commonResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResult represents the result of a Delete operation. Call its
|
||||||
|
// ExtractErr method to interpret it as an Image.
|
||||||
|
type DeleteResult struct {
|
||||||
|
gophercloud.ErrResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImagePage represents the results of a List request.
|
||||||
|
type ImagePage struct {
|
||||||
|
serviceURL string
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if an ImagePage contains no Images results.
|
||||||
|
func (r ImagePage) IsEmpty() (bool, error) {
|
||||||
|
images, err := ExtractImages(r)
|
||||||
|
return len(images) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL uses the response's embedded link reference to navigate to
|
||||||
|
// the next page of results.
|
||||||
|
func (r ImagePage) NextPageURL() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Next == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextPageURL(r.serviceURL, s.Next)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractImages interprets the results of a single page from a List() call,
|
||||||
|
// producing a slice of Image entities.
|
||||||
|
func ExtractImages(r pagination.Page) ([]Image, error) {
|
||||||
|
var s struct {
|
||||||
|
Images []Image `json:"images"`
|
||||||
|
}
|
||||||
|
err := (r.(ImagePage)).ExtractInto(&s)
|
||||||
|
return s.Images, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitFunc is a helper function used to avoid a slice of empty strings.
|
||||||
|
func splitFunc(c rune) bool {
|
||||||
|
return c == ','
|
||||||
|
}
|
||||||
108
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go
generated
vendored
Normal file
108
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageStatus image statuses
|
||||||
|
// http://docs.openstack.org/developer/glance/statuses.html
|
||||||
|
type ImageStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ImageStatusQueued is a status for an image which identifier has
|
||||||
|
// been reserved for an image in the image registry.
|
||||||
|
ImageStatusQueued ImageStatus = "queued"
|
||||||
|
|
||||||
|
// ImageStatusSaving denotes that an image’s raw data is currently being
|
||||||
|
// uploaded to Glance
|
||||||
|
ImageStatusSaving ImageStatus = "saving"
|
||||||
|
|
||||||
|
// ImageStatusActive denotes an image that is fully available in Glance.
|
||||||
|
ImageStatusActive ImageStatus = "active"
|
||||||
|
|
||||||
|
// ImageStatusKilled denotes that an error occurred during the uploading
|
||||||
|
// of an image’s data, and that the image is not readable.
|
||||||
|
ImageStatusKilled ImageStatus = "killed"
|
||||||
|
|
||||||
|
// ImageStatusDeleted is used for an image that is no longer available to use.
|
||||||
|
// The image information is retained in the image registry.
|
||||||
|
ImageStatusDeleted ImageStatus = "deleted"
|
||||||
|
|
||||||
|
// ImageStatusPendingDelete is similar to Delete, but the image is not yet
|
||||||
|
// deleted.
|
||||||
|
ImageStatusPendingDelete ImageStatus = "pending_delete"
|
||||||
|
|
||||||
|
// ImageStatusDeactivated denotes that access to image data is not allowed to
|
||||||
|
// any non-admin user.
|
||||||
|
ImageStatusDeactivated ImageStatus = "deactivated"
|
||||||
|
|
||||||
|
// ImageStatusImporting denotes that an import call has been made but that
|
||||||
|
// the image is not yet ready for use.
|
||||||
|
ImageStatusImporting ImageStatus = "importing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageVisibility denotes an image that is fully available in Glance.
|
||||||
|
// This occurs when the image data is uploaded, or the image size is explicitly
|
||||||
|
// set to zero on creation.
|
||||||
|
// According to design
|
||||||
|
// https://wiki.openstack.org/wiki/Glance-v2-community-image-visibility-design
|
||||||
|
type ImageVisibility string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ImageVisibilityPublic all users
|
||||||
|
ImageVisibilityPublic ImageVisibility = "public"
|
||||||
|
|
||||||
|
// ImageVisibilityPrivate users with tenantId == tenantId(owner)
|
||||||
|
ImageVisibilityPrivate ImageVisibility = "private"
|
||||||
|
|
||||||
|
// ImageVisibilityShared images are visible to:
|
||||||
|
// - users with tenantId == tenantId(owner)
|
||||||
|
// - users with tenantId in the member-list of the image
|
||||||
|
// - users with tenantId in the member-list with member_status == 'accepted'
|
||||||
|
ImageVisibilityShared ImageVisibility = "shared"
|
||||||
|
|
||||||
|
// ImageVisibilityCommunity images:
|
||||||
|
// - all users can see and boot it
|
||||||
|
// - users with tenantId in the member-list of the image with
|
||||||
|
// member_status == 'accepted' have this image in their default image-list.
|
||||||
|
ImageVisibilityCommunity ImageVisibility = "community"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemberStatus is a status for adding a new member (tenant) to an image
|
||||||
|
// member list.
|
||||||
|
type ImageMemberStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ImageMemberStatusAccepted is the status for an accepted image member.
|
||||||
|
ImageMemberStatusAccepted ImageMemberStatus = "accepted"
|
||||||
|
|
||||||
|
// ImageMemberStatusPending shows that the member addition is pending
|
||||||
|
ImageMemberStatusPending ImageMemberStatus = "pending"
|
||||||
|
|
||||||
|
// ImageMemberStatusAccepted is the status for a rejected image member
|
||||||
|
ImageMemberStatusRejected ImageMemberStatus = "rejected"
|
||||||
|
|
||||||
|
// ImageMemberStatusAll
|
||||||
|
ImageMemberStatusAll ImageMemberStatus = "all"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageDateFilter represents a valid filter to use for filtering
|
||||||
|
// images by their date during a List.
|
||||||
|
type ImageDateFilter string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FilterGT ImageDateFilter = "gt"
|
||||||
|
FilterGTE ImageDateFilter = "gte"
|
||||||
|
FilterLT ImageDateFilter = "lt"
|
||||||
|
FilterLTE ImageDateFilter = "lte"
|
||||||
|
FilterNEQ ImageDateFilter = "neq"
|
||||||
|
FilterEQ ImageDateFilter = "eq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageDateQuery represents a date field to be used for listing images.
|
||||||
|
// If no filter is specified, the query will act as though FilterEQ was
|
||||||
|
// set.
|
||||||
|
type ImageDateQuery struct {
|
||||||
|
Date time.Time
|
||||||
|
Filter ImageDateFilter
|
||||||
|
}
|
||||||
65
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go
generated
vendored
Normal file
65
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// `listURL` is a pure function. `listURL(c)` is a URL for which a GET
|
||||||
|
// request will respond with a list of images in the service `c`.
|
||||||
|
func listURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("images")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("images")
|
||||||
|
}
|
||||||
|
|
||||||
|
// `imageURL(c,i)` is the URL for the image identified by ID `i` in
|
||||||
|
// the service `c`.
|
||||||
|
func imageURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return c.ServiceURL("images", imageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `getURL(c,i)` is a URL for which a GET request will respond with
|
||||||
|
// information about the image identified by ID `i` in the service
|
||||||
|
// `c`.
|
||||||
|
func getURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return imageURL(c, imageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return imageURL(c, imageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||||
|
return imageURL(c, imageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// builds next page full url based on current url
|
||||||
|
func nextPageURL(serviceURL, requestedNext string) (string, error) {
|
||||||
|
base, err := utils.BaseEndpoint(serviceURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedNextURL, err := url.Parse(requestedNext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
base = gophercloud.NormalizeURL(base)
|
||||||
|
nextPath := base + strings.TrimPrefix(requestedNextURL.Path, "/")
|
||||||
|
|
||||||
|
nextURL, err := url.Parse(nextPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
nextURL.RawQuery = requestedNextURL.RawQuery
|
||||||
|
|
||||||
|
return nextURL.String(), nil
|
||||||
|
}
|
||||||
28
vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go
generated
vendored
Normal file
28
vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BaseEndpoint will return a URL without the /vX.Y
|
||||||
|
// portion of the URL.
|
||||||
|
func BaseEndpoint(endpoint string) (string, error) {
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery, u.Fragment = "", ""
|
||||||
|
|
||||||
|
path := u.Path
|
||||||
|
versionRe := regexp.MustCompile("v[0-9.]+/?")
|
||||||
|
|
||||||
|
if version := versionRe.FindString(path); version != "" {
|
||||||
|
versionIndex := strings.Index(path, version)
|
||||||
|
u.Path = path[:versionIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
111
vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
generated
vendored
Normal file
111
vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version is a supported API version, corresponding to a vN package within the appropriate service.
|
||||||
|
type Version struct {
|
||||||
|
ID string
|
||||||
|
Suffix string
|
||||||
|
Priority int
|
||||||
|
}
|
||||||
|
|
||||||
|
var goodStatus = map[string]bool{
|
||||||
|
"current": true,
|
||||||
|
"supported": true,
|
||||||
|
"stable": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's
|
||||||
|
// published versions.
|
||||||
|
// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
|
||||||
|
func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) {
|
||||||
|
type linkResp struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type valueResp struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Links []linkResp `json:"links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionsResp struct {
|
||||||
|
Values []valueResp `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Versions versionsResp `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize := func(endpoint string) string {
|
||||||
|
if !strings.HasSuffix(endpoint, "/") {
|
||||||
|
return endpoint + "/"
|
||||||
|
}
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
identityEndpoint := normalize(client.IdentityEndpoint)
|
||||||
|
|
||||||
|
// If a full endpoint is specified, check version suffixes for a match first.
|
||||||
|
for _, v := range recognized {
|
||||||
|
if strings.HasSuffix(identityEndpoint, v.Suffix) {
|
||||||
|
return v, identityEndpoint, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp response
|
||||||
|
_, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{
|
||||||
|
JSONResponse: &resp,
|
||||||
|
OkCodes: []int{200, 300},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var highest *Version
|
||||||
|
var endpoint string
|
||||||
|
|
||||||
|
for _, value := range resp.Versions.Values {
|
||||||
|
href := ""
|
||||||
|
for _, link := range value.Links {
|
||||||
|
if link.Rel == "self" {
|
||||||
|
href = normalize(link.Href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, version := range recognized {
|
||||||
|
if strings.Contains(value.ID, version.ID) {
|
||||||
|
// Prefer a version that exactly matches the provided endpoint.
|
||||||
|
if href == identityEndpoint {
|
||||||
|
if href == "" {
|
||||||
|
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
|
||||||
|
}
|
||||||
|
return version, href, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, find the highest-priority version with a whitelisted status.
|
||||||
|
if goodStatus[strings.ToLower(value.Status)] {
|
||||||
|
if highest == nil || version.Priority > highest.Priority {
|
||||||
|
highest = version
|
||||||
|
endpoint = href
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if highest == nil {
|
||||||
|
return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase)
|
||||||
|
}
|
||||||
|
if endpoint == "" {
|
||||||
|
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
return highest, endpoint, nil
|
||||||
|
}
|
||||||
61
vendor/github.com/gophercloud/gophercloud/pagination/http.go
generated
vendored
Normal file
61
vendor/github.com/gophercloud/gophercloud/pagination/http.go
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageResult stores the HTTP response that returned the current page of results.
|
||||||
|
type PageResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the
|
||||||
|
// results, interpreting it as JSON if the content type indicates.
|
||||||
|
func PageResultFrom(resp *http.Response) (PageResult, error) {
|
||||||
|
var parsedBody interface{}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return PageResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||||
|
err = json.Unmarshal(rawBody, &parsedBody)
|
||||||
|
if err != nil {
|
||||||
|
return PageResult{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedBody = rawBody
|
||||||
|
}
|
||||||
|
|
||||||
|
return PageResultFromParsed(resp, parsedBody), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its
|
||||||
|
// body parsed as JSON (and closed).
|
||||||
|
func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
|
||||||
|
return PageResult{
|
||||||
|
Result: gophercloud.Result{
|
||||||
|
Body: body,
|
||||||
|
Header: resp.Header,
|
||||||
|
},
|
||||||
|
URL: *resp.Request.URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request performs an HTTP request and extracts the http.Response from the result.
|
||||||
|
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
|
||||||
|
return client.Get(url, nil, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: headers,
|
||||||
|
OkCodes: []int{200, 204, 300},
|
||||||
|
KeepResponseBody: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
92
vendor/github.com/gophercloud/gophercloud/pagination/linked.go
generated
vendored
Normal file
92
vendor/github.com/gophercloud/gophercloud/pagination/linked.go
generated
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result.
|
||||||
|
type LinkedPageBase struct {
|
||||||
|
PageResult
|
||||||
|
|
||||||
|
// LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer.
|
||||||
|
// If any link along the path is missing, an empty URL will be returned.
|
||||||
|
// If any link results in an unexpected value type, an error will be returned.
|
||||||
|
// When left as "nil", []string{"links", "next"} will be used as a default.
|
||||||
|
LinkPath []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present.
|
||||||
|
// It assumes that the links are available in a "links" element of the top-level response object.
|
||||||
|
// If this is not the case, override NextPageURL on your result type.
|
||||||
|
func (current LinkedPageBase) NextPageURL() (string, error) {
|
||||||
|
var path []string
|
||||||
|
var key string
|
||||||
|
|
||||||
|
if current.LinkPath == nil {
|
||||||
|
path = []string{"links", "next"}
|
||||||
|
} else {
|
||||||
|
path = current.LinkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
submap, ok := current.Body.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "map[string]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
key, path = path[0], path[1:len(path)]
|
||||||
|
|
||||||
|
value, ok := submap[key]
|
||||||
|
if !ok {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(path) > 0 {
|
||||||
|
submap, ok = value.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "map[string]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if value == nil {
|
||||||
|
// Actual null element.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "string"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||||
|
func (current LinkedPageBase) IsEmpty() (bool, error) {
|
||||||
|
if b, ok := current.Body.([]interface{}); ok {
|
||||||
|
return len(b) == 0, nil
|
||||||
|
}
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||||
|
// Page interface.
|
||||||
|
func (current LinkedPageBase) GetBody() interface{} {
|
||||||
|
return current.Body
|
||||||
|
}
|
||||||
58
vendor/github.com/gophercloud/gophercloud/pagination/marker.go
generated
vendored
Normal file
58
vendor/github.com/gophercloud/gophercloud/pagination/marker.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager.
|
||||||
|
// For convenience, embed the MarkedPageBase struct.
|
||||||
|
type MarkerPage interface {
|
||||||
|
Page
|
||||||
|
|
||||||
|
// LastMarker returns the last "marker" value on this page.
|
||||||
|
LastMarker() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters.
|
||||||
|
type MarkerPageBase struct {
|
||||||
|
PageResult
|
||||||
|
|
||||||
|
// Owner is a reference to the embedding struct.
|
||||||
|
Owner MarkerPage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL generates the URL for the page of results after this one.
|
||||||
|
func (current MarkerPageBase) NextPageURL() (string, error) {
|
||||||
|
currentURL := current.URL
|
||||||
|
|
||||||
|
mark, err := current.Owner.LastMarker()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
q := currentURL.Query()
|
||||||
|
q.Set("marker", mark)
|
||||||
|
currentURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
return currentURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||||
|
func (current MarkerPageBase) IsEmpty() (bool, error) {
|
||||||
|
if b, ok := current.Body.([]interface{}); ok {
|
||||||
|
return len(b) == 0, nil
|
||||||
|
}
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||||
|
// Page interface.
|
||||||
|
func (current MarkerPageBase) GetBody() interface{} {
|
||||||
|
return current.Body
|
||||||
|
}
|
||||||
251
vendor/github.com/gophercloud/gophercloud/pagination/pager.go
generated
vendored
Normal file
251
vendor/github.com/gophercloud/gophercloud/pagination/pager.go
generated
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
|
||||||
|
ErrPageNotAvailable = errors.New("The requested page does not exist.")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Page must be satisfied by the result type of any resource collection.
|
||||||
|
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
|
||||||
|
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
|
||||||
|
// instead.
|
||||||
|
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
|
||||||
|
// will need to implement.
|
||||||
|
type Page interface {
|
||||||
|
// NextPageURL generates the URL for the page of data that follows this collection.
|
||||||
|
// Return "" if no such page exists.
|
||||||
|
NextPageURL() (string, error)
|
||||||
|
|
||||||
|
// IsEmpty returns true if this Page has no items in it.
|
||||||
|
IsEmpty() (bool, error)
|
||||||
|
|
||||||
|
// GetBody returns the Page Body. This is used in the `AllPages` method.
|
||||||
|
GetBody() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pager knows how to advance through a specific resource collection, one page at a time.
|
||||||
|
type Pager struct {
|
||||||
|
client *gophercloud.ServiceClient
|
||||||
|
|
||||||
|
initialURL string
|
||||||
|
|
||||||
|
createPage func(r PageResult) Page
|
||||||
|
|
||||||
|
firstPage Page
|
||||||
|
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Headers supplies additional HTTP headers to populate on each paged request.
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPager constructs a manually-configured pager.
|
||||||
|
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
|
||||||
|
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
|
||||||
|
return Pager{
|
||||||
|
client: client,
|
||||||
|
initialURL: initialURL,
|
||||||
|
createPage: createPage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
|
||||||
|
// useful for overriding List functions in delegation.
|
||||||
|
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
|
||||||
|
return Pager{
|
||||||
|
client: p.client,
|
||||||
|
initialURL: p.initialURL,
|
||||||
|
createPage: createPage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pager) fetchNextPage(url string) (Page, error) {
|
||||||
|
resp, err := Request(p.client, p.Headers, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remembered, err := PageResultFrom(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.createPage(remembered), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
|
||||||
|
// Return "false" from the handler to prematurely stop iterating.
|
||||||
|
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
|
||||||
|
if p.Err != nil {
|
||||||
|
return p.Err
|
||||||
|
}
|
||||||
|
currentURL := p.initialURL
|
||||||
|
for {
|
||||||
|
var currentPage Page
|
||||||
|
|
||||||
|
// if first page has already been fetched, no need to fetch it again
|
||||||
|
if p.firstPage != nil {
|
||||||
|
currentPage = p.firstPage
|
||||||
|
p.firstPage = nil
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
currentPage, err = p.fetchNextPage(currentURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty, err := currentPage.IsEmpty()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if empty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := handler(currentPage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentURL, err = currentPage.NextPageURL()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if currentURL == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllPages returns all the pages from a `List` operation in a single page,
|
||||||
|
// allowing the user to retrieve all the pages at once.
|
||||||
|
func (p Pager) AllPages() (Page, error) {
|
||||||
|
// pagesSlice holds all the pages until they get converted into as Page Body.
|
||||||
|
var pagesSlice []interface{}
|
||||||
|
// body will contain the final concatenated Page body.
|
||||||
|
var body reflect.Value
|
||||||
|
|
||||||
|
// Grab a first page to ascertain the page body type.
|
||||||
|
firstPage, err := p.fetchNextPage(p.initialURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Store the page type so we can use reflection to create a new mega-page of
|
||||||
|
// that type.
|
||||||
|
pageType := reflect.TypeOf(firstPage)
|
||||||
|
|
||||||
|
// if it's a single page, just return the firstPage (first page)
|
||||||
|
if _, found := pageType.FieldByName("SinglePageBase"); found {
|
||||||
|
return firstPage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the first page to avoid getting it twice
|
||||||
|
p.firstPage = firstPage
|
||||||
|
|
||||||
|
// Switch on the page body type. Recognized types are `map[string]interface{}`,
|
||||||
|
// `[]byte`, and `[]interface{}`.
|
||||||
|
switch pb := firstPage.GetBody().(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
// key is the map key for the page body if the body type is `map[string]interface{}`.
|
||||||
|
var key string
|
||||||
|
// Iterate over the pages to concatenate the bodies.
|
||||||
|
err = p.EachPage(func(page Page) (bool, error) {
|
||||||
|
b := page.GetBody().(map[string]interface{})
|
||||||
|
for k, v := range b {
|
||||||
|
// If it's a linked page, we don't want the `links`, we want the other one.
|
||||||
|
if !strings.HasSuffix(k, "links") {
|
||||||
|
// check the field's type. we only want []interface{} (which is really []map[string]interface{})
|
||||||
|
switch vt := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
key = k
|
||||||
|
pagesSlice = append(pagesSlice, vt...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set body to value of type `map[string]interface{}`
|
||||||
|
body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
|
||||||
|
body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
|
||||||
|
case []byte:
|
||||||
|
// Iterate over the pages to concatenate the bodies.
|
||||||
|
err = p.EachPage(func(page Page) (bool, error) {
|
||||||
|
b := page.GetBody().([]byte)
|
||||||
|
pagesSlice = append(pagesSlice, b)
|
||||||
|
// seperate pages with a comma
|
||||||
|
pagesSlice = append(pagesSlice, []byte{10})
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pagesSlice) > 0 {
|
||||||
|
// Remove the trailing comma.
|
||||||
|
pagesSlice = pagesSlice[:len(pagesSlice)-1]
|
||||||
|
}
|
||||||
|
var b []byte
|
||||||
|
// Combine the slice of slices in to a single slice.
|
||||||
|
for _, slice := range pagesSlice {
|
||||||
|
b = append(b, slice.([]byte)...)
|
||||||
|
}
|
||||||
|
// Set body to value of type `bytes`.
|
||||||
|
body = reflect.New(reflect.TypeOf(b)).Elem()
|
||||||
|
body.SetBytes(b)
|
||||||
|
case []interface{}:
|
||||||
|
// Iterate over the pages to concatenate the bodies.
|
||||||
|
err = p.EachPage(func(page Page) (bool, error) {
|
||||||
|
b := page.GetBody().([]interface{})
|
||||||
|
pagesSlice = append(pagesSlice, b...)
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set body to value of type `[]interface{}`
|
||||||
|
body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
|
||||||
|
for i, s := range pagesSlice {
|
||||||
|
body.Index(i).Set(reflect.ValueOf(s))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "map[string]interface{}/[]byte/[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%T", pb)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each `Extract*` function is expecting a specific type of page coming back,
|
||||||
|
// otherwise the type assertion in those functions will fail. pageType is needed
|
||||||
|
// to create a type in this method that has the same type that the `Extract*`
|
||||||
|
// function is expecting and set the Body of that object to the concatenated
|
||||||
|
// pages.
|
||||||
|
page := reflect.New(pageType)
|
||||||
|
// Set the page body to be the concatenated pages.
|
||||||
|
page.Elem().FieldByName("Body").Set(body)
|
||||||
|
// Set any additional headers that were pass along. The `objectstorage` pacakge,
|
||||||
|
// for example, passes a Content-Type header.
|
||||||
|
h := make(http.Header)
|
||||||
|
for k, v := range p.Headers {
|
||||||
|
h.Add(k, v)
|
||||||
|
}
|
||||||
|
page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
|
||||||
|
// Type assert the page to a Page interface so that the type assertion in the
|
||||||
|
// `Extract*` methods will work.
|
||||||
|
return page.Elem().Interface().(Page), err
|
||||||
|
}
|
||||||
4
vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
generated
vendored
Normal file
4
vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
/*
|
||||||
|
Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs.
|
||||||
|
*/
|
||||||
|
package pagination
|
||||||
33
vendor/github.com/gophercloud/gophercloud/pagination/single.go
generated
vendored
Normal file
33
vendor/github.com/gophercloud/gophercloud/pagination/single.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once.
|
||||||
|
type SinglePageBase PageResult
|
||||||
|
|
||||||
|
// NextPageURL always returns "" to indicate that there are no more pages to return.
|
||||||
|
func (current SinglePageBase) NextPageURL() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||||
|
func (current SinglePageBase) IsEmpty() (bool, error) {
|
||||||
|
if b, ok := current.Body.([]interface{}); ok {
|
||||||
|
return len(b) == 0, nil
|
||||||
|
}
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBody returns the single page's body. This method is needed to satisfy the
|
||||||
|
// Page interface.
|
||||||
|
func (current SinglePageBase) GetBody() interface{} {
|
||||||
|
return current.Body
|
||||||
|
}
|
||||||
493
vendor/github.com/gophercloud/gophercloud/params.go
generated
vendored
Normal file
493
vendor/github.com/gophercloud/gophercloud/params.go
generated
vendored
Normal file
|
|
@ -0,0 +1,493 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
BuildRequestBody builds a map[string]interface from the given `struct`. If
|
||||||
|
parent is not an empty string, the final map[string]interface returned will
|
||||||
|
encapsulate the built one. For example:
|
||||||
|
|
||||||
|
disk := 1
|
||||||
|
createOpts := flavors.CreateOpts{
|
||||||
|
ID: "1",
|
||||||
|
Name: "m1.tiny",
|
||||||
|
Disk: &disk,
|
||||||
|
RAM: 512,
|
||||||
|
VCPUs: 1,
|
||||||
|
RxTxFactor: 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := gophercloud.BuildRequestBody(createOpts, "flavor")
|
||||||
|
|
||||||
|
The above example can be run as-is, however it is recommended to look at how
|
||||||
|
BuildRequestBody is used within Gophercloud to more fully understand how it
|
||||||
|
fits within the request process as a whole rather than use it directly as shown
|
||||||
|
above.
|
||||||
|
*/
|
||||||
|
func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
|
||||||
|
optsValue := reflect.ValueOf(opts)
|
||||||
|
if optsValue.Kind() == reflect.Ptr {
|
||||||
|
optsValue = optsValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsType := reflect.TypeOf(opts)
|
||||||
|
if optsType.Kind() == reflect.Ptr {
|
||||||
|
optsType = optsType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsMap := make(map[string]interface{})
|
||||||
|
if optsValue.Kind() == reflect.Struct {
|
||||||
|
//fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
|
||||||
|
for i := 0; i < optsValue.NumField(); i++ {
|
||||||
|
v := optsValue.Field(i)
|
||||||
|
f := optsType.Field(i)
|
||||||
|
|
||||||
|
if f.Name != strings.Title(f.Name) {
|
||||||
|
//fmt.Printf("Skipping field: %s...\n", f.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("Starting on field: %s...\n", f.Name)
|
||||||
|
|
||||||
|
zero := isZero(v)
|
||||||
|
//fmt.Printf("v is zero?: %v\n", zero)
|
||||||
|
|
||||||
|
// if the field has a required tag that's set to "true"
|
||||||
|
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
|
||||||
|
//fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
|
||||||
|
// if the field's value is zero, return a missing-argument error
|
||||||
|
if zero {
|
||||||
|
// if the field has a 'required' tag, it can't have a zero-value
|
||||||
|
err := ErrMissingInput{}
|
||||||
|
err.Argument = f.Name
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if xorTag := f.Tag.Get("xor"); xorTag != "" {
|
||||||
|
//fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
|
||||||
|
xorField := optsValue.FieldByName(xorTag)
|
||||||
|
var xorFieldIsZero bool
|
||||||
|
if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
|
||||||
|
xorFieldIsZero = true
|
||||||
|
} else {
|
||||||
|
if xorField.Kind() == reflect.Ptr {
|
||||||
|
xorField = xorField.Elem()
|
||||||
|
}
|
||||||
|
xorFieldIsZero = isZero(xorField)
|
||||||
|
}
|
||||||
|
if !(zero != xorFieldIsZero) {
|
||||||
|
err := ErrMissingInput{}
|
||||||
|
err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
|
||||||
|
err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if orTag := f.Tag.Get("or"); orTag != "" {
|
||||||
|
//fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
|
||||||
|
//fmt.Printf("field is zero?: %v\n", zero)
|
||||||
|
if zero {
|
||||||
|
orField := optsValue.FieldByName(orTag)
|
||||||
|
var orFieldIsZero bool
|
||||||
|
if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
|
||||||
|
orFieldIsZero = true
|
||||||
|
} else {
|
||||||
|
if orField.Kind() == reflect.Ptr {
|
||||||
|
orField = orField.Elem()
|
||||||
|
}
|
||||||
|
orFieldIsZero = isZero(orField)
|
||||||
|
}
|
||||||
|
if orFieldIsZero {
|
||||||
|
err := ErrMissingInput{}
|
||||||
|
err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
|
||||||
|
err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonTag := f.Tag.Get("json")
|
||||||
|
if jsonTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
|
||||||
|
sliceValue := v
|
||||||
|
if sliceValue.Kind() == reflect.Ptr {
|
||||||
|
sliceValue = sliceValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < sliceValue.Len(); i++ {
|
||||||
|
element := sliceValue.Index(i)
|
||||||
|
if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) {
|
||||||
|
_, err := BuildRequestBody(element.Interface(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
|
||||||
|
if zero {
|
||||||
|
//fmt.Printf("value before change: %+v\n", optsValue.Field(i))
|
||||||
|
if jsonTag != "" {
|
||||||
|
jsonTagPieces := strings.Split(jsonTag, ",")
|
||||||
|
if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
|
||||||
|
if v.CanSet() {
|
||||||
|
if !v.IsNil() {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v.Set(reflect.Zero(v.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fmt.Printf("value after change: %+v\n", optsValue.Field(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
|
||||||
|
_, err := BuildRequestBody(v.Interface(), f.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("opts: %+v \n", opts)
|
||||||
|
|
||||||
|
b, err := json.Marshal(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("string(b): %s\n", string(b))
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, &optsMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("optsMap: %+v\n", optsMap)
|
||||||
|
|
||||||
|
if parent != "" {
|
||||||
|
optsMap = map[string]interface{}{parent: optsMap}
|
||||||
|
}
|
||||||
|
//fmt.Printf("optsMap after parent added: %+v\n", optsMap)
|
||||||
|
return optsMap, nil
|
||||||
|
}
|
||||||
|
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||||
|
return nil, fmt.Errorf("Options type is not a struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnabledState is a convenience type, mostly used in Create and Update
|
||||||
|
// operations. Because the zero value of a bool is FALSE, we need to use a
|
||||||
|
// pointer instead to indicate zero-ness.
|
||||||
|
type EnabledState *bool
|
||||||
|
|
||||||
|
// Convenience vars for EnabledState values.
|
||||||
|
var (
|
||||||
|
iTrue = true
|
||||||
|
iFalse = false
|
||||||
|
|
||||||
|
Enabled EnabledState = &iTrue
|
||||||
|
Disabled EnabledState = &iFalse
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPVersion is a type for the possible IP address versions. Valid instances
|
||||||
|
// are IPv4 and IPv6
|
||||||
|
type IPVersion int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IPv4 is used for IP version 4 addresses
|
||||||
|
IPv4 IPVersion = 4
|
||||||
|
// IPv6 is used for IP version 6 addresses
|
||||||
|
IPv6 IPVersion = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntToPointer is a function for converting integers into integer pointers.
|
||||||
|
// This is useful when passing in options to operations.
|
||||||
|
func IntToPointer(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
MaybeString is an internal function to be used by request methods in individual
|
||||||
|
resource packages.
|
||||||
|
|
||||||
|
It takes a string that might be a zero value and returns either a pointer to its
|
||||||
|
address or nil. This is useful for allowing users to conveniently omit values
|
||||||
|
from an options struct by leaving them zeroed, but still pass nil to the JSON
|
||||||
|
serializer so they'll be omitted from the request body.
|
||||||
|
*/
|
||||||
|
func MaybeString(original string) *string {
|
||||||
|
if original != "" {
|
||||||
|
return &original
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
MaybeInt is an internal function to be used by request methods in individual
|
||||||
|
resource packages.
|
||||||
|
|
||||||
|
Like MaybeString, it accepts an int that may or may not be a zero value, and
|
||||||
|
returns either a pointer to its address or nil. It's intended to hint that the
|
||||||
|
JSON serializer should omit its field.
|
||||||
|
*/
|
||||||
|
func MaybeInt(original int) *int {
|
||||||
|
if original != 0 {
|
||||||
|
return &original
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func isUnderlyingStructZero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return isUnderlyingStructZero(v.Elem())
|
||||||
|
default:
|
||||||
|
return isZero(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
//fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Func, reflect.Map, reflect.Slice:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Array:
|
||||||
|
z := true
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
z = z && isZero(v.Index(i))
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
case reflect.Struct:
|
||||||
|
if v.Type() == reflect.TypeOf(t) {
|
||||||
|
if v.Interface().(time.Time).IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
z := true
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
z = z && isZero(v.Field(i))
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
// Compare other types directly:
|
||||||
|
z := reflect.Zero(v.Type())
|
||||||
|
//fmt.Printf("zero type for value: %+v\n\n\n", z)
|
||||||
|
return v.Interface() == z.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BuildQueryString is an internal function to be used by request methods in
|
||||||
|
individual resource packages.
|
||||||
|
|
||||||
|
It accepts a tagged structure and expands it into a URL struct. Field names are
|
||||||
|
converted into query parameters based on a "q" tag. For example:
|
||||||
|
|
||||||
|
type struct Something {
|
||||||
|
Bar string `q:"x_bar"`
|
||||||
|
Baz int `q:"lorem_ipsum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := Something{
|
||||||
|
Bar: "AAA",
|
||||||
|
Baz: "BBB",
|
||||||
|
}
|
||||||
|
|
||||||
|
will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
|
||||||
|
|
||||||
|
The struct's fields may be strings, integers, or boolean values. Fields left at
|
||||||
|
their type's zero value will be omitted from the query.
|
||||||
|
*/
|
||||||
|
func BuildQueryString(opts interface{}) (*url.URL, error) {
|
||||||
|
optsValue := reflect.ValueOf(opts)
|
||||||
|
if optsValue.Kind() == reflect.Ptr {
|
||||||
|
optsValue = optsValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsType := reflect.TypeOf(opts)
|
||||||
|
if optsType.Kind() == reflect.Ptr {
|
||||||
|
optsType = optsType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
|
||||||
|
if optsValue.Kind() == reflect.Struct {
|
||||||
|
for i := 0; i < optsValue.NumField(); i++ {
|
||||||
|
v := optsValue.Field(i)
|
||||||
|
f := optsType.Field(i)
|
||||||
|
qTag := f.Tag.Get("q")
|
||||||
|
|
||||||
|
// if the field has a 'q' tag, it goes in the query string
|
||||||
|
if qTag != "" {
|
||||||
|
tags := strings.Split(qTag, ",")
|
||||||
|
|
||||||
|
// if the field is set, add it to the slice of query pieces
|
||||||
|
if !isZero(v) {
|
||||||
|
loop:
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
v = v.Elem()
|
||||||
|
goto loop
|
||||||
|
case reflect.String:
|
||||||
|
params.Add(tags[0], v.String())
|
||||||
|
case reflect.Int:
|
||||||
|
params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
|
||||||
|
case reflect.Bool:
|
||||||
|
params.Add(tags[0], strconv.FormatBool(v.Bool()))
|
||||||
|
case reflect.Slice:
|
||||||
|
switch v.Type().Elem() {
|
||||||
|
case reflect.TypeOf(0):
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
params.Add(tags[0], v.Index(i).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String {
|
||||||
|
var s []string
|
||||||
|
for _, k := range v.MapKeys() {
|
||||||
|
value := v.MapIndex(k).String()
|
||||||
|
s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value))
|
||||||
|
}
|
||||||
|
params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the field has a 'required' tag, it can't have a zero-value
|
||||||
|
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
|
||||||
|
return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &url.URL{RawQuery: params.Encode()}, nil
|
||||||
|
}
|
||||||
|
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||||
|
return nil, fmt.Errorf("Options type is not a struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BuildHeaders is an internal function to be used by request methods in
|
||||||
|
individual resource packages.
|
||||||
|
|
||||||
|
It accepts an arbitrary tagged structure and produces a string map that's
|
||||||
|
suitable for use as the HTTP headers of an outgoing request. Field names are
|
||||||
|
mapped to header names based in "h" tags.
|
||||||
|
|
||||||
|
type struct Something {
|
||||||
|
Bar string `h:"x_bar"`
|
||||||
|
Baz int `h:"lorem_ipsum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := Something{
|
||||||
|
Bar: "AAA",
|
||||||
|
Baz: "BBB",
|
||||||
|
}
|
||||||
|
|
||||||
|
will be converted into:
|
||||||
|
|
||||||
|
map[string]string{
|
||||||
|
"x_bar": "AAA",
|
||||||
|
"lorem_ipsum": "BBB",
|
||||||
|
}
|
||||||
|
|
||||||
|
Untagged fields and fields left at their zero values are skipped. Integers,
|
||||||
|
booleans and string values are supported.
|
||||||
|
*/
|
||||||
|
func BuildHeaders(opts interface{}) (map[string]string, error) {
|
||||||
|
optsValue := reflect.ValueOf(opts)
|
||||||
|
if optsValue.Kind() == reflect.Ptr {
|
||||||
|
optsValue = optsValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsType := reflect.TypeOf(opts)
|
||||||
|
if optsType.Kind() == reflect.Ptr {
|
||||||
|
optsType = optsType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsMap := make(map[string]string)
|
||||||
|
if optsValue.Kind() == reflect.Struct {
|
||||||
|
for i := 0; i < optsValue.NumField(); i++ {
|
||||||
|
v := optsValue.Field(i)
|
||||||
|
f := optsType.Field(i)
|
||||||
|
hTag := f.Tag.Get("h")
|
||||||
|
|
||||||
|
// if the field has a 'h' tag, it goes in the header
|
||||||
|
if hTag != "" {
|
||||||
|
tags := strings.Split(hTag, ",")
|
||||||
|
|
||||||
|
// if the field is set, add it to the slice of query pieces
|
||||||
|
if !isZero(v) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
optsMap[tags[0]] = v.String()
|
||||||
|
case reflect.Int:
|
||||||
|
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Int64:
|
||||||
|
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Bool:
|
||||||
|
optsMap[tags[0]] = strconv.FormatBool(v.Bool())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the field has a 'required' tag, it can't have a zero-value
|
||||||
|
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
|
||||||
|
return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return optsMap, nil
|
||||||
|
}
|
||||||
|
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||||
|
return optsMap, fmt.Errorf("Options type is not a struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDSliceToQueryString takes a slice of elements and converts them into a query
|
||||||
|
// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
|
||||||
|
// result would be `?name=20&name=40&name=60'
|
||||||
|
func IDSliceToQueryString(name string, ids []int) string {
|
||||||
|
str := ""
|
||||||
|
for k, v := range ids {
|
||||||
|
if k == 0 {
|
||||||
|
str += "?"
|
||||||
|
} else {
|
||||||
|
str += "&"
|
||||||
|
}
|
||||||
|
str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntWithinRange returns TRUE if an integer falls within a defined range, and
|
||||||
|
// FALSE if not.
|
||||||
|
func IntWithinRange(val, min, max int) bool {
|
||||||
|
return val > min && val < max
|
||||||
|
}
|
||||||
560
vendor/github.com/gophercloud/gophercloud/provider_client.go
generated
vendored
Normal file
560
vendor/github.com/gophercloud/gophercloud/provider_client.go
generated
vendored
Normal file
|
|
@ -0,0 +1,560 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultUserAgent is the default User-Agent string set in the request header.
|
||||||
|
const DefaultUserAgent = "gophercloud/2.0.0"
|
||||||
|
|
||||||
|
// UserAgent represents a User-Agent header.
|
||||||
|
type UserAgent struct {
|
||||||
|
// prepend is the slice of User-Agent strings to prepend to DefaultUserAgent.
|
||||||
|
// All the strings to prepend are accumulated and prepended in the Join method.
|
||||||
|
prepend []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend prepends a user-defined string to the default User-Agent string. Users
|
||||||
|
// may pass in one or more strings to prepend.
|
||||||
|
func (ua *UserAgent) Prepend(s ...string) {
|
||||||
|
ua.prepend = append(s, ua.prepend...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join concatenates all the user-defined User-Agend strings with the default
|
||||||
|
// Gophercloud User-Agent string.
|
||||||
|
func (ua *UserAgent) Join() string {
|
||||||
|
uaSlice := append(ua.prepend, DefaultUserAgent)
|
||||||
|
return strings.Join(uaSlice, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderClient stores details that are required to interact with any
|
||||||
|
// services within a specific provider's API.
|
||||||
|
//
|
||||||
|
// Generally, you acquire a ProviderClient by calling the NewClient method in
|
||||||
|
// the appropriate provider's child package, providing whatever authentication
|
||||||
|
// credentials are required.
|
||||||
|
type ProviderClient struct {
|
||||||
|
// IdentityBase is the base URL used for a particular provider's identity
|
||||||
|
// service - it will be used when issuing authenticatation requests. It
|
||||||
|
// should point to the root resource of the identity service, not a specific
|
||||||
|
// identity version.
|
||||||
|
IdentityBase string
|
||||||
|
|
||||||
|
// IdentityEndpoint is the identity endpoint. This may be a specific version
|
||||||
|
// of the identity service. If this is the case, this endpoint is used rather
|
||||||
|
// than querying versions first.
|
||||||
|
IdentityEndpoint string
|
||||||
|
|
||||||
|
// TokenID is the ID of the most recently issued valid token.
|
||||||
|
// NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application.
|
||||||
|
// To safely read or write this value, call `Token` or `SetToken`, respectively
|
||||||
|
TokenID string
|
||||||
|
|
||||||
|
// EndpointLocator describes how this provider discovers the endpoints for
|
||||||
|
// its constituent services.
|
||||||
|
EndpointLocator EndpointLocator
|
||||||
|
|
||||||
|
// HTTPClient allows users to interject arbitrary http, https, or other transit behaviors.
|
||||||
|
HTTPClient http.Client
|
||||||
|
|
||||||
|
// UserAgent represents the User-Agent header in the HTTP request.
|
||||||
|
UserAgent UserAgent
|
||||||
|
|
||||||
|
// ReauthFunc is the function used to re-authenticate the user if the request
|
||||||
|
// fails with a 401 HTTP response code. This a needed because there may be multiple
|
||||||
|
// authentication functions for different Identity service versions.
|
||||||
|
ReauthFunc func() error
|
||||||
|
|
||||||
|
// Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client
|
||||||
|
// with the token and reauth func zeroed. Such client can be used to perform reauthorization.
|
||||||
|
Throwaway bool
|
||||||
|
|
||||||
|
// Context is the context passed to the HTTP request.
|
||||||
|
Context context.Context
|
||||||
|
|
||||||
|
// mut is a mutex for the client. It protects read and write access to client attributes such as getting
|
||||||
|
// and setting the TokenID.
|
||||||
|
mut *sync.RWMutex
|
||||||
|
|
||||||
|
// reauthmut is a mutex for reauthentication it attempts to ensure that only one reauthentication
|
||||||
|
// attempt happens at one time.
|
||||||
|
reauthmut *reauthlock
|
||||||
|
|
||||||
|
authResult AuthResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// reauthlock represents a set of attributes used to help in the reauthentication process.
|
||||||
|
type reauthlock struct {
|
||||||
|
sync.RWMutex
|
||||||
|
// This channel is non-nil during reauthentication. It can be used to ask the
|
||||||
|
// goroutine doing Reauthenticate() for its result. Look at the implementation
|
||||||
|
// of Reauthenticate() for details.
|
||||||
|
ongoing chan<- (chan<- error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
|
||||||
|
// authenticated service requests. Blocks if Reauthenticate is in progress.
|
||||||
|
func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
|
||||||
|
if client.IsThrowaway() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if client.reauthmut != nil {
|
||||||
|
// If a Reauthenticate is in progress, wait for it to complete.
|
||||||
|
client.reauthmut.Lock()
|
||||||
|
ongoing := client.reauthmut.ongoing
|
||||||
|
client.reauthmut.Unlock()
|
||||||
|
if ongoing != nil {
|
||||||
|
responseChannel := make(chan error)
|
||||||
|
ongoing <- responseChannel
|
||||||
|
_ = <-responseChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t := client.Token()
|
||||||
|
if t == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return map[string]string{"X-Auth-Token": t}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token.
|
||||||
|
// If the application's ProviderClient is not used concurrently, this doesn't need to be called.
|
||||||
|
func (client *ProviderClient) UseTokenLock() {
|
||||||
|
client.mut = new(sync.RWMutex)
|
||||||
|
client.reauthmut = new(reauthlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthResult returns the result from the request that was used to obtain a
|
||||||
|
// provider client's Keystone token.
|
||||||
|
//
|
||||||
|
// The result is nil when authentication has not yet taken place, when the token
|
||||||
|
// was set manually with SetToken(), or when a ReauthFunc was used that does not
|
||||||
|
// record the AuthResult.
|
||||||
|
func (client *ProviderClient) GetAuthResult() AuthResult {
|
||||||
|
if client.mut != nil {
|
||||||
|
client.mut.RLock()
|
||||||
|
defer client.mut.RUnlock()
|
||||||
|
}
|
||||||
|
return client.authResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token safely reads the value of the auth token from the ProviderClient. Applications should
|
||||||
|
// call this method to access the token instead of the TokenID field
|
||||||
|
func (client *ProviderClient) Token() string {
|
||||||
|
if client.mut != nil {
|
||||||
|
client.mut.RLock()
|
||||||
|
defer client.mut.RUnlock()
|
||||||
|
}
|
||||||
|
return client.TokenID
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToken safely sets the value of the auth token in the ProviderClient. Applications may
|
||||||
|
// use this method in a custom ReauthFunc.
|
||||||
|
//
|
||||||
|
// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead.
|
||||||
|
func (client *ProviderClient) SetToken(t string) {
|
||||||
|
if client.mut != nil {
|
||||||
|
client.mut.Lock()
|
||||||
|
defer client.mut.Unlock()
|
||||||
|
}
|
||||||
|
client.TokenID = t
|
||||||
|
client.authResult = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTokenAndAuthResult safely sets the value of the auth token in the
|
||||||
|
// ProviderClient and also records the AuthResult that was returned from the
|
||||||
|
// token creation request. Applications may call this in a custom ReauthFunc.
|
||||||
|
func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error {
|
||||||
|
tokenID := ""
|
||||||
|
var err error
|
||||||
|
if r != nil {
|
||||||
|
tokenID, err = r.ExtractTokenID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.mut != nil {
|
||||||
|
client.mut.Lock()
|
||||||
|
defer client.mut.Unlock()
|
||||||
|
}
|
||||||
|
client.TokenID = tokenID
|
||||||
|
client.authResult = r
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyTokenFrom safely copies the token from another ProviderClient into the
|
||||||
|
// this one.
|
||||||
|
func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) {
|
||||||
|
if client.mut != nil {
|
||||||
|
client.mut.Lock()
|
||||||
|
defer client.mut.Unlock()
|
||||||
|
}
|
||||||
|
if other.mut != nil && other.mut != client.mut {
|
||||||
|
other.mut.RLock()
|
||||||
|
defer other.mut.RUnlock()
|
||||||
|
}
|
||||||
|
client.TokenID = other.TokenID
|
||||||
|
client.authResult = other.authResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsThrowaway safely reads the value of the client Throwaway field.
|
||||||
|
func (client *ProviderClient) IsThrowaway() bool {
|
||||||
|
if client.reauthmut != nil {
|
||||||
|
client.reauthmut.RLock()
|
||||||
|
defer client.reauthmut.RUnlock()
|
||||||
|
}
|
||||||
|
return client.Throwaway
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetThrowaway safely sets the value of the client Throwaway field.
|
||||||
|
func (client *ProviderClient) SetThrowaway(v bool) {
|
||||||
|
if client.reauthmut != nil {
|
||||||
|
client.reauthmut.Lock()
|
||||||
|
defer client.reauthmut.Unlock()
|
||||||
|
}
|
||||||
|
client.Throwaway = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
|
||||||
|
// called because of a 401 response, the caller may pass the previous token. In
|
||||||
|
// this case, the reauthentication can be skipped if another thread has already
|
||||||
|
// reauthenticated in the meantime. If no previous token is known, an empty
|
||||||
|
// string should be passed instead to force unconditional reauthentication.
|
||||||
|
func (client *ProviderClient) Reauthenticate(previousToken string) error {
|
||||||
|
if client.ReauthFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.reauthmut == nil {
|
||||||
|
return client.ReauthFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := make(chan (chan<- error))
|
||||||
|
|
||||||
|
// Check if a Reauthenticate is in progress, or start one if not.
|
||||||
|
client.reauthmut.Lock()
|
||||||
|
ongoing := client.reauthmut.ongoing
|
||||||
|
if ongoing == nil {
|
||||||
|
client.reauthmut.ongoing = messages
|
||||||
|
}
|
||||||
|
client.reauthmut.Unlock()
|
||||||
|
|
||||||
|
// If Reauthenticate is running elsewhere, wait for its result.
|
||||||
|
if ongoing != nil {
|
||||||
|
responseChannel := make(chan error)
|
||||||
|
ongoing <- responseChannel
|
||||||
|
return <-responseChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the actual reauthentication.
|
||||||
|
var err error
|
||||||
|
if previousToken == "" || client.TokenID == previousToken {
|
||||||
|
err = client.ReauthFunc()
|
||||||
|
} else {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark Reauthenticate as finished.
|
||||||
|
client.reauthmut.Lock()
|
||||||
|
client.reauthmut.ongoing = nil
|
||||||
|
client.reauthmut.Unlock()
|
||||||
|
|
||||||
|
// Report result to all other interested goroutines.
|
||||||
|
//
|
||||||
|
// This happens in a separate goroutine because another goroutine might have
|
||||||
|
// acquired a copy of `client.reauthmut.ongoing` before we cleared it, but not
|
||||||
|
// have come around to sending its request. By answering in a goroutine, we
|
||||||
|
// can have that goroutine linger until all responseChannels have been sent.
|
||||||
|
// When GC has collected all sendings ends of the channel, our receiving end
|
||||||
|
// will be closed and the goroutine will end.
|
||||||
|
go func() {
|
||||||
|
for responseChannel := range messages {
|
||||||
|
responseChannel <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestOpts customizes the behavior of the provider.Request() method.
|
||||||
|
type RequestOpts struct {
|
||||||
|
// JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The
|
||||||
|
// content type of the request will default to "application/json" unless overridden by MoreHeaders.
|
||||||
|
// It's an error to specify both a JSONBody and a RawBody.
|
||||||
|
JSONBody interface{}
|
||||||
|
// RawBody contains an io.Reader that will be consumed by the request directly. No content-type
|
||||||
|
// will be set unless one is provided explicitly by MoreHeaders.
|
||||||
|
RawBody io.Reader
|
||||||
|
// JSONResponse, if provided, will be populated with the contents of the response body parsed as
|
||||||
|
// JSON.
|
||||||
|
JSONResponse interface{}
|
||||||
|
// OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
|
||||||
|
// the response has a different code, an error will be returned.
|
||||||
|
OkCodes []int
|
||||||
|
// MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
|
||||||
|
// provided with a blank value (""), that header will be *omitted* instead: use this to suppress
|
||||||
|
// the default Accept header or an inferred Content-Type, for example.
|
||||||
|
MoreHeaders map[string]string
|
||||||
|
// ErrorContext specifies the resource error type to return if an error is encountered.
|
||||||
|
// This lets resources override default error messages based on the response status code.
|
||||||
|
ErrorContext error
|
||||||
|
// KeepResponseBody specifies whether to keep the HTTP response body. Usually used, when the HTTP
|
||||||
|
// response body is considered for further use. Valid when JSONResponse is nil.
|
||||||
|
KeepResponseBody bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestState contains temporary state for a single ProviderClient.Request() call.
|
||||||
|
type requestState struct {
|
||||||
|
// This flag indicates if we have reauthenticated during this request because of a 401 response.
|
||||||
|
// It ensures that we don't reauthenticate multiple times for a single request. If we
|
||||||
|
// reauthenticate, but keep getting 401 responses with the fresh token, reauthenticating some more
|
||||||
|
// will just get us into an infinite loop.
|
||||||
|
hasReauthenticated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var applicationJSON = "application/json"
|
||||||
|
|
||||||
|
// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
|
||||||
|
// header will automatically be provided.
|
||||||
|
func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
|
||||||
|
return client.doRequest(method, url, options, &requestState{
|
||||||
|
hasReauthenticated: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *ProviderClient) doRequest(method, url string, options *RequestOpts, state *requestState) (*http.Response, error) {
|
||||||
|
var body io.Reader
|
||||||
|
var contentType *string
|
||||||
|
|
||||||
|
// Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
|
||||||
|
// io.ReadSeeker as-is. Default the content-type to application/json.
|
||||||
|
if options.JSONBody != nil {
|
||||||
|
if options.RawBody != nil {
|
||||||
|
return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()")
|
||||||
|
}
|
||||||
|
|
||||||
|
rendered, err := json.Marshal(options.JSONBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body = bytes.NewReader(rendered)
|
||||||
|
contentType = &applicationJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error, when "KeepResponseBody" is true and "JSONResponse" is not nil
|
||||||
|
if options.KeepResponseBody && options.JSONResponse != nil {
|
||||||
|
return nil, errors.New("cannot use KeepResponseBody when JSONResponse is not nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.RawBody != nil {
|
||||||
|
body = options.RawBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the http.Request.
|
||||||
|
req, err := http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if client.Context != nil {
|
||||||
|
req = req.WithContext(client.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to
|
||||||
|
// modify or omit any header.
|
||||||
|
if contentType != nil {
|
||||||
|
req.Header.Set("Content-Type", *contentType)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", applicationJSON)
|
||||||
|
|
||||||
|
// Set the User-Agent header
|
||||||
|
req.Header.Set("User-Agent", client.UserAgent.Join())
|
||||||
|
|
||||||
|
if options.MoreHeaders != nil {
|
||||||
|
for k, v := range options.MoreHeaders {
|
||||||
|
if v != "" {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
} else {
|
||||||
|
req.Header.Del(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get latest token from client
|
||||||
|
for k, v := range client.AuthenticatedHeaders() {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
prereqtok := req.Header.Get("X-Auth-Token")
|
||||||
|
|
||||||
|
// Issue the request.
|
||||||
|
resp, err := client.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow default OkCodes if none explicitly set
|
||||||
|
okc := options.OkCodes
|
||||||
|
if okc == nil {
|
||||||
|
okc = defaultOkCodes(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the HTTP response status.
|
||||||
|
var ok bool
|
||||||
|
for _, code := range okc {
|
||||||
|
if resp.StatusCode == code {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
respErr := ErrUnexpectedResponseCode{
|
||||||
|
URL: url,
|
||||||
|
Method: method,
|
||||||
|
Expected: options.OkCodes,
|
||||||
|
Actual: resp.StatusCode,
|
||||||
|
Body: body,
|
||||||
|
ResponseHeader: resp.Header,
|
||||||
|
}
|
||||||
|
|
||||||
|
errType := options.ErrorContext
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
err = ErrDefault400{respErr}
|
||||||
|
if error400er, ok := errType.(Err400er); ok {
|
||||||
|
err = error400er.Error400(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
if client.ReauthFunc != nil && !state.hasReauthenticated {
|
||||||
|
err = client.Reauthenticate(prereqtok)
|
||||||
|
if err != nil {
|
||||||
|
e := &ErrUnableToReauthenticate{}
|
||||||
|
e.ErrOriginal = respErr
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
if options.RawBody != nil {
|
||||||
|
if seeker, ok := options.RawBody.(io.Seeker); ok {
|
||||||
|
seeker.Seek(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.hasReauthenticated = true
|
||||||
|
resp, err = client.doRequest(method, url, options, state)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *ErrUnexpectedResponseCode:
|
||||||
|
e := &ErrErrorAfterReauthentication{}
|
||||||
|
e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
|
||||||
|
return nil, e
|
||||||
|
default:
|
||||||
|
e := &ErrErrorAfterReauthentication{}
|
||||||
|
e.ErrOriginal = err
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
err = ErrDefault401{respErr}
|
||||||
|
if error401er, ok := errType.(Err401er); ok {
|
||||||
|
err = error401er.Error401(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusForbidden:
|
||||||
|
err = ErrDefault403{respErr}
|
||||||
|
if error403er, ok := errType.(Err403er); ok {
|
||||||
|
err = error403er.Error403(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusNotFound:
|
||||||
|
err = ErrDefault404{respErr}
|
||||||
|
if error404er, ok := errType.(Err404er); ok {
|
||||||
|
err = error404er.Error404(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusMethodNotAllowed:
|
||||||
|
err = ErrDefault405{respErr}
|
||||||
|
if error405er, ok := errType.(Err405er); ok {
|
||||||
|
err = error405er.Error405(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusRequestTimeout:
|
||||||
|
err = ErrDefault408{respErr}
|
||||||
|
if error408er, ok := errType.(Err408er); ok {
|
||||||
|
err = error408er.Error408(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusConflict:
|
||||||
|
err = ErrDefault409{respErr}
|
||||||
|
if error409er, ok := errType.(Err409er); ok {
|
||||||
|
err = error409er.Error409(respErr)
|
||||||
|
}
|
||||||
|
case 429:
|
||||||
|
err = ErrDefault429{respErr}
|
||||||
|
if error429er, ok := errType.(Err429er); ok {
|
||||||
|
err = error429er.Error429(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
err = ErrDefault500{respErr}
|
||||||
|
if error500er, ok := errType.(Err500er); ok {
|
||||||
|
err = error500er.Error500(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusServiceUnavailable:
|
||||||
|
err = ErrDefault503{respErr}
|
||||||
|
if error503er, ok := errType.(Err503er); ok {
|
||||||
|
err = error503er.Error503(respErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the response body as JSON, if requested to do so.
|
||||||
|
if options.JSONResponse != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
// Don't decode JSON when there is no content
|
||||||
|
if resp.StatusCode == http.StatusNoContent {
|
||||||
|
// read till EOF, otherwise the connection will be closed and cannot be reused
|
||||||
|
_, err = io.Copy(ioutil.Discard, resp.Body)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close unused body to allow the HTTP connection to be reused
|
||||||
|
if !options.KeepResponseBody && options.JSONResponse == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
// read till EOF, otherwise the connection will be closed and cannot be reused
|
||||||
|
if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOkCodes(method string) []int {
|
||||||
|
switch method {
|
||||||
|
case "GET", "HEAD":
|
||||||
|
return []int{200}
|
||||||
|
case "POST":
|
||||||
|
return []int{201, 202}
|
||||||
|
case "PUT":
|
||||||
|
return []int{201, 202}
|
||||||
|
case "PATCH":
|
||||||
|
return []int{200, 202, 204}
|
||||||
|
case "DELETE":
|
||||||
|
return []int{202, 204}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []int{}
|
||||||
|
}
|
||||||
460
vendor/github.com/gophercloud/gophercloud/results.go
generated
vendored
Normal file
460
vendor/github.com/gophercloud/gophercloud/results.go
generated
vendored
Normal file
|
|
@ -0,0 +1,460 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Result is an internal type to be used by individual resource packages, but its
|
||||||
|
methods will be available on a wide variety of user-facing embedding types.
|
||||||
|
|
||||||
|
It acts as a base struct that other Result types, returned from request
|
||||||
|
functions, can embed for convenience. All Results capture basic information
|
||||||
|
from the HTTP transaction that was performed, including the response body,
|
||||||
|
HTTP headers, and any errors that happened.
|
||||||
|
|
||||||
|
Generally, each Result type will have an Extract method that can be used to
|
||||||
|
further interpret the result's payload in a specific context. Extensions or
|
||||||
|
providers can then provide additional extraction functions to pull out
|
||||||
|
provider- or extension-specific information as well.
|
||||||
|
*/
|
||||||
|
type Result struct {
|
||||||
|
// Body is the payload of the HTTP response from the server. In most cases,
|
||||||
|
// this will be the deserialized JSON structure.
|
||||||
|
Body interface{}
|
||||||
|
|
||||||
|
// Header contains the HTTP header structure from the original response.
|
||||||
|
Header http.Header
|
||||||
|
|
||||||
|
// Err is an error that occurred during the operation. It's deferred until
|
||||||
|
// extraction to make it easier to chain the Extract call.
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractInto allows users to provide an object into which `Extract` will extract
|
||||||
|
// the `Result.Body`. This would be useful for OpenStack providers that have
|
||||||
|
// different fields in the response object than OpenStack proper.
|
||||||
|
func (r Result) ExtractInto(to interface{}) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader, ok := r.Body.(io.Reader); ok {
|
||||||
|
if readCloser, ok := reader.(io.Closer); ok {
|
||||||
|
defer readCloser.Close()
|
||||||
|
}
|
||||||
|
return json.NewDecoder(reader).Decode(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, to)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Result) extractIntoPtr(to interface{}, label string) error {
|
||||||
|
if label == "" {
|
||||||
|
return r.ExtractInto(&to)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m map[string]interface{}
|
||||||
|
err := r.ExtractInto(&m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(m[label])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
toValue := reflect.ValueOf(to)
|
||||||
|
if toValue.Kind() == reflect.Ptr {
|
||||||
|
toValue = toValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch toValue.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
typeOfV := toValue.Type().Elem()
|
||||||
|
if typeOfV.Kind() == reflect.Struct {
|
||||||
|
if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
|
||||||
|
newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
|
||||||
|
|
||||||
|
if mSlice, ok := m[label].([]interface{}); ok {
|
||||||
|
for _, v := range mSlice {
|
||||||
|
// For each iteration of the slice, we create a new struct.
|
||||||
|
// This is to work around a bug where elements of a slice
|
||||||
|
// are reused and not overwritten when the same copy of the
|
||||||
|
// struct is used:
|
||||||
|
//
|
||||||
|
// https://github.com/golang/go/issues/21092
|
||||||
|
// https://github.com/golang/go/issues/24155
|
||||||
|
// https://play.golang.org/p/NHo3ywlPZli
|
||||||
|
newType := reflect.New(typeOfV).Elem()
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is needed for structs with an UnmarshalJSON method.
|
||||||
|
// Technically this is just unmarshalling the response into
|
||||||
|
// a struct that is never used, but it's good enough to
|
||||||
|
// trigger the UnmarshalJSON method.
|
||||||
|
for i := 0; i < newType.NumField(); i++ {
|
||||||
|
s := newType.Field(i).Addr().Interface()
|
||||||
|
|
||||||
|
// Unmarshal is used rather than NewDecoder to also work
|
||||||
|
// around the above-mentioned bug.
|
||||||
|
err = json.Unmarshal(b, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newSlice = reflect.Append(newSlice, newType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "to" should now be properly modeled to receive the
|
||||||
|
// JSON response body and unmarshal into all the correct
|
||||||
|
// fields of the struct or composed extension struct
|
||||||
|
// at the end of this method.
|
||||||
|
toValue.Set(newSlice)
|
||||||
|
|
||||||
|
// jtopjian: This was put into place to resolve the issue
|
||||||
|
// described at
|
||||||
|
// https://github.com/gophercloud/gophercloud/issues/1963
|
||||||
|
//
|
||||||
|
// This probably isn't the best fix, but it appears to
|
||||||
|
// be resolving the issue, so I'm going to implement it
|
||||||
|
// for now.
|
||||||
|
//
|
||||||
|
// For future readers, this entire case statement could
|
||||||
|
// use a review.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
typeOfV := toValue.Type()
|
||||||
|
if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
|
||||||
|
for i := 0; i < toValue.NumField(); i++ {
|
||||||
|
toField := toValue.Field(i)
|
||||||
|
if toField.Kind() == reflect.Struct {
|
||||||
|
s := toField.Addr().Interface()
|
||||||
|
err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, &to)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
|
||||||
|
// interface{} (to).
|
||||||
|
//
|
||||||
|
// NOTE: For internal use only
|
||||||
|
//
|
||||||
|
// `to` must be a pointer to an underlying struct type
|
||||||
|
//
|
||||||
|
// If provided, `label` will be filtered out of the response
|
||||||
|
// body prior to `r` being unmarshalled into `to`.
|
||||||
|
func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(to)
|
||||||
|
if k := t.Kind(); k != reflect.Ptr {
|
||||||
|
return fmt.Errorf("Expected pointer, got %v", k)
|
||||||
|
}
|
||||||
|
switch t.Elem().Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return r.extractIntoPtr(to, label)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Expected pointer to struct, got: %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
|
||||||
|
// interface{} (to).
|
||||||
|
//
|
||||||
|
// NOTE: For internal use only
|
||||||
|
//
|
||||||
|
// `to` must be a pointer to an underlying slice type
|
||||||
|
//
|
||||||
|
// If provided, `label` will be filtered out of the response
|
||||||
|
// body prior to `r` being unmarshalled into `to`.
|
||||||
|
func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(to)
|
||||||
|
if k := t.Kind(); k != reflect.Ptr {
|
||||||
|
return fmt.Errorf("Expected pointer, got %v", k)
|
||||||
|
}
|
||||||
|
switch t.Elem().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return r.extractIntoPtr(to, label)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Expected pointer to slice, got: %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrettyPrintJSON creates a string containing the full response body as
|
||||||
|
// pretty-printed JSON. It's useful for capturing test fixtures and for
|
||||||
|
// debugging extraction bugs. If you include its output in an issue related to
|
||||||
|
// a buggy extraction function, we will all love you forever.
|
||||||
|
func (r Result) PrettyPrintJSON() string {
|
||||||
|
pretty, err := json.MarshalIndent(r.Body, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return string(pretty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrResult is an internal type to be used by individual resource packages, but
|
||||||
|
// its methods will be available on a wide variety of user-facing embedding
|
||||||
|
// types.
|
||||||
|
//
|
||||||
|
// It represents results that only contain a potential error and
|
||||||
|
// nothing else. Usually, if the operation executed successfully, the Err field
|
||||||
|
// will be nil; otherwise it will be stocked with a relevant error. Use the
|
||||||
|
// ExtractErr method
|
||||||
|
// to cleanly pull it out.
|
||||||
|
type ErrResult struct {
|
||||||
|
Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractErr is a function that extracts error information, or nil, from a result.
|
||||||
|
func (r ErrResult) ExtractErr() error {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HeaderResult is an internal type to be used by individual resource packages, but
|
||||||
|
its methods will be available on a wide variety of user-facing embedding types.
|
||||||
|
|
||||||
|
It represents a result that only contains an error (possibly nil) and an
|
||||||
|
http.Header. This is used, for example, by the objectstorage packages in
|
||||||
|
openstack, because most of the operations don't return response bodies, but do
|
||||||
|
have relevant information in headers.
|
||||||
|
*/
|
||||||
|
type HeaderResult struct {
|
||||||
|
Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractInto allows users to provide an object into which `Extract` will
|
||||||
|
// extract the http.Header headers of the result.
|
||||||
|
func (r HeaderResult) ExtractInto(to interface{}) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpHeaderMap := map[string]string{}
|
||||||
|
for k, v := range r.Header {
|
||||||
|
if len(v) > 0 {
|
||||||
|
tmpHeaderMap[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(tmpHeaderMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, to)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC3339Milli describes a common time format used by some API responses.
|
||||||
|
const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
|
||||||
|
|
||||||
|
type JSONRFC3339Milli time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
|
||||||
|
b := bytes.NewBuffer(data)
|
||||||
|
dec := json.NewDecoder(b)
|
||||||
|
var s string
|
||||||
|
if err := dec.Decode(&s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339Milli, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339Milli(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
|
||||||
|
|
||||||
|
type JSONRFC3339MilliNoZ time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339MilliNoZ, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339MilliNoZ(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONRFC1123 time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(time.RFC1123, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC1123(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONUnix time.Time
|
||||||
|
|
||||||
|
func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
unix, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t = time.Unix(unix, 0)
|
||||||
|
*jt = JSONUnix(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC3339NoZ is the time format used in Heat (Orchestration).
|
||||||
|
const RFC3339NoZ = "2006-01-02T15:04:05"
|
||||||
|
|
||||||
|
type JSONRFC3339NoZ time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339NoZ, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339NoZ(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC3339ZNoT is the time format used in Zun (Containers Service).
|
||||||
|
const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
|
||||||
|
|
||||||
|
type JSONRFC3339ZNoT time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339ZNoT, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339ZNoT(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service).
|
||||||
|
const RFC3339ZNoTNoZ = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
|
type JSONRFC3339ZNoTNoZ time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339ZNoTNoZ, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339ZNoTNoZ(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Link is an internal type to be used in packages of collection resources that are
|
||||||
|
paginated in a certain way.
|
||||||
|
|
||||||
|
It's a response substructure common to many paginated collection results that is
|
||||||
|
used to point to related pages. Usually, the one we care about is the one with
|
||||||
|
Rel field set to "next".
|
||||||
|
*/
|
||||||
|
type Link struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ExtractNextURL is an internal function useful for packages of collection
|
||||||
|
resources that are paginated in a certain way.
|
||||||
|
|
||||||
|
It attempts to extract the "next" URL from slice of Link structs, or
|
||||||
|
"" if no such URL is present.
|
||||||
|
*/
|
||||||
|
func ExtractNextURL(links []Link) (string, error) {
|
||||||
|
var url string
|
||||||
|
|
||||||
|
for _, l := range links {
|
||||||
|
if l.Rel == "next" {
|
||||||
|
url = l.Href
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if url == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
162
vendor/github.com/gophercloud/gophercloud/service_client.go
generated
vendored
Normal file
162
vendor/github.com/gophercloud/gophercloud/service_client.go
generated
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceClient stores details required to interact with a specific service API implemented by a provider.
|
||||||
|
// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient.
|
||||||
|
type ServiceClient struct {
|
||||||
|
// ProviderClient is a reference to the provider that implements this service.
|
||||||
|
*ProviderClient
|
||||||
|
|
||||||
|
// Endpoint is the base URL of the service's API, acquired from a service catalog.
|
||||||
|
// It MUST end with a /.
|
||||||
|
Endpoint string
|
||||||
|
|
||||||
|
// ResourceBase is the base URL shared by the resources within a service's API. It should include
|
||||||
|
// the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used
|
||||||
|
// as-is, instead.
|
||||||
|
ResourceBase string
|
||||||
|
|
||||||
|
// This is the service client type (e.g. compute, sharev2).
|
||||||
|
// NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS.
|
||||||
|
// It is only exported because it gets set in a different package.
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// The microversion of the service to use. Set this to use a particular microversion.
|
||||||
|
Microversion string
|
||||||
|
|
||||||
|
// MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way,
|
||||||
|
// values set in this field will be set on all the HTTP requests the service client sends.
|
||||||
|
MoreHeaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
|
||||||
|
func (client *ServiceClient) ResourceBaseURL() string {
|
||||||
|
if client.ResourceBase != "" {
|
||||||
|
return client.ResourceBase
|
||||||
|
}
|
||||||
|
return client.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceURL constructs a URL for a resource belonging to this provider.
|
||||||
|
func (client *ServiceClient) ServiceURL(parts ...string) string {
|
||||||
|
return client.ResourceBaseURL() + strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) {
|
||||||
|
if v, ok := (JSONBody).(io.Reader); ok {
|
||||||
|
opts.RawBody = v
|
||||||
|
} else if JSONBody != nil {
|
||||||
|
opts.JSONBody = JSONBody
|
||||||
|
}
|
||||||
|
|
||||||
|
if JSONResponse != nil {
|
||||||
|
opts.JSONResponse = JSONResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MoreHeaders == nil {
|
||||||
|
opts.MoreHeaders = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Microversion != "" {
|
||||||
|
client.setMicroversionHeader(opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get calls `Request` with the "GET" HTTP verb.
|
||||||
|
func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, nil, JSONResponse, opts)
|
||||||
|
return client.Request("GET", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post calls `Request` with the "POST" HTTP verb.
|
||||||
|
func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, JSONBody, JSONResponse, opts)
|
||||||
|
return client.Request("POST", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put calls `Request` with the "PUT" HTTP verb.
|
||||||
|
func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, JSONBody, JSONResponse, opts)
|
||||||
|
return client.Request("PUT", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch calls `Request` with the "PATCH" HTTP verb.
|
||||||
|
func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, JSONBody, JSONResponse, opts)
|
||||||
|
return client.Request("PATCH", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete calls `Request` with the "DELETE" HTTP verb.
|
||||||
|
func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, nil, nil, opts)
|
||||||
|
return client.Request("DELETE", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head calls `Request` with the "HEAD" HTTP verb.
|
||||||
|
func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, nil, nil, opts)
|
||||||
|
return client.Request("HEAD", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
|
||||||
|
switch client.Type {
|
||||||
|
case "compute":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||||
|
case "sharev2":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
|
||||||
|
case "volume":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion
|
||||||
|
case "baremetal":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion
|
||||||
|
case "baremetal-introspection":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Ironic-Inspector-API-Version"] = client.Microversion
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Type != "" {
|
||||||
|
opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request carries out the HTTP operation for the service client
|
||||||
|
func (client *ServiceClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
|
||||||
|
if len(client.MoreHeaders) > 0 {
|
||||||
|
if options == nil {
|
||||||
|
options = new(RequestOpts)
|
||||||
|
}
|
||||||
|
for k, v := range client.MoreHeaders {
|
||||||
|
options.MoreHeaders[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client.ProviderClient.Request(method, url, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseResponse is a helper function to parse http.Response to constituents.
|
||||||
|
func ParseResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, error) {
|
||||||
|
if resp != nil {
|
||||||
|
return resp.Body, resp.Header, err
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
102
vendor/github.com/gophercloud/gophercloud/util.go
generated
vendored
Normal file
102
vendor/github.com/gophercloud/gophercloud/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WaitFor polls a predicate function, once per second, up to a timeout limit.
|
||||||
|
// This is useful to wait for a resource to transition to a certain state.
|
||||||
|
// To handle situations when the predicate might hang indefinitely, the
|
||||||
|
// predicate will be prematurely cancelled after the timeout.
|
||||||
|
// Resource packages will wrap this in a more convenient function that's
|
||||||
|
// specific to a certain resource, but it can also be useful on its own.
|
||||||
|
func WaitFor(timeout int, predicate func() (bool, error)) error {
|
||||||
|
type WaitForResult struct {
|
||||||
|
Success bool
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now().Unix()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// If a timeout is set, and that's been exceeded, shut it down.
|
||||||
|
if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) {
|
||||||
|
return fmt.Errorf("A timeout occurred")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
var result WaitForResult
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
satisfied, err := predicate()
|
||||||
|
result.Success = satisfied
|
||||||
|
result.Error = err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.Success {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// If the predicate has not finished by the timeout, cancel it.
|
||||||
|
case <-time.After(time.Duration(timeout) * time.Second):
|
||||||
|
return fmt.Errorf("A timeout occurred")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeURL is an internal function to be used by provider clients.
|
||||||
|
//
|
||||||
|
// It ensures that each endpoint URL has a closing `/`, as expected by
|
||||||
|
// ServiceClient's methods.
|
||||||
|
func NormalizeURL(url string) string {
|
||||||
|
if !strings.HasSuffix(url, "/") {
|
||||||
|
return url + "/"
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as
|
||||||
|
// a reference in the filesystem, if necessary. basePath is assumed to contain
|
||||||
|
// either '.' when first used, or the file:// type fqdn of the parent resource.
|
||||||
|
// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml
|
||||||
|
func NormalizePathURL(basePath, rawPath string) (string, error) {
|
||||||
|
u, err := url.Parse(rawPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// if a scheme is defined, it must be a fqdn already
|
||||||
|
if u.Scheme != "" {
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
// if basePath is a url, then child resources are assumed to be relative to it
|
||||||
|
bu, err := url.Parse(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var basePathSys, absPathSys string
|
||||||
|
if bu.Scheme != "" {
|
||||||
|
basePathSys = filepath.FromSlash(bu.Path)
|
||||||
|
absPathSys = filepath.Join(basePathSys, rawPath)
|
||||||
|
bu.Path = filepath.ToSlash(absPathSys)
|
||||||
|
return bu.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
absPathSys = filepath.Join(basePath, rawPath)
|
||||||
|
u.Path = filepath.ToSlash(absPathSys)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
u.Scheme = "file"
|
||||||
|
return u.String(), nil
|
||||||
|
|
||||||
|
}
|
||||||
6
vendor/golang.org/x/text/transform/transform.go
generated
vendored
6
vendor/golang.org/x/text/transform/transform.go
generated
vendored
|
|
@ -78,8 +78,8 @@ type SpanningTransformer interface {
|
||||||
// considering the error err.
|
// considering the error err.
|
||||||
//
|
//
|
||||||
// A nil error means that all input bytes are known to be identical to the
|
// A nil error means that all input bytes are known to be identical to the
|
||||||
// output produced by the Transformer. A nil error can be be returned
|
// output produced by the Transformer. A nil error can be returned
|
||||||
// regardless of whether atEOF is true. If err is nil, then then n must
|
// regardless of whether atEOF is true. If err is nil, then n must
|
||||||
// equal len(src); the converse is not necessarily true.
|
// equal len(src); the converse is not necessarily true.
|
||||||
//
|
//
|
||||||
// ErrEndOfSpan means that the Transformer output may differ from the
|
// ErrEndOfSpan means that the Transformer output may differ from the
|
||||||
|
|
@ -493,7 +493,7 @@ func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err erro
|
||||||
return dstL.n, srcL.p, err
|
return dstL.n, srcL.p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: use runes.Remove instead.
|
// Deprecated: Use runes.Remove instead.
|
||||||
func RemoveFunc(f func(r rune) bool) Transformer {
|
func RemoveFunc(f func(r rune) bool) Transformer {
|
||||||
return removeF(f)
|
return removeF(f)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
vendor/golang.org/x/text/unicode/bidi/bidi.go
generated
vendored
2
vendor/golang.org/x/text/unicode/bidi/bidi.go
generated
vendored
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
// Package bidi contains functionality for bidirectional text support.
|
// Package bidi contains functionality for bidirectional text support.
|
||||||
//
|
//
|
||||||
// See http://www.unicode.org/reports/tr9.
|
// See https://www.unicode.org/reports/tr9.
|
||||||
//
|
//
|
||||||
// NOTE: UNDER CONSTRUCTION. This API may change in backwards incompatible ways
|
// NOTE: UNDER CONSTRUCTION. This API may change in backwards incompatible ways
|
||||||
// and without notice.
|
// and without notice.
|
||||||
|
|
|
||||||
4
vendor/golang.org/x/text/unicode/bidi/bracket.go
generated
vendored
4
vendor/golang.org/x/text/unicode/bidi/bracket.go
generated
vendored
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
// This file contains a port of the reference implementation of the
|
// This file contains a port of the reference implementation of the
|
||||||
// Bidi Parentheses Algorithm:
|
// Bidi Parentheses Algorithm:
|
||||||
// http://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/BidiPBAReference.java
|
// https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/BidiPBAReference.java
|
||||||
//
|
//
|
||||||
// The implementation in this file covers definitions BD14-BD16 and rule N0
|
// The implementation in this file covers definitions BD14-BD16 and rule N0
|
||||||
// of UAX#9.
|
// of UAX#9.
|
||||||
|
|
@ -246,7 +246,7 @@ func (p *bracketPairer) getStrongTypeN0(index int) Class {
|
||||||
// assuming the given embedding direction.
|
// assuming the given embedding direction.
|
||||||
//
|
//
|
||||||
// It returns ON if no strong type is found. If a single strong type is found,
|
// It returns ON if no strong type is found. If a single strong type is found,
|
||||||
// it returns this this type. Otherwise it returns the embedding direction.
|
// it returns this type. Otherwise it returns the embedding direction.
|
||||||
//
|
//
|
||||||
// TODO: use separate type for "strong" directionality.
|
// TODO: use separate type for "strong" directionality.
|
||||||
func (p *bracketPairer) classifyPairContent(loc bracketPair, dirEmbed Class) Class {
|
func (p *bracketPairer) classifyPairContent(loc bracketPair, dirEmbed Class) Class {
|
||||||
|
|
|
||||||
2
vendor/golang.org/x/text/unicode/bidi/core.go
generated
vendored
2
vendor/golang.org/x/text/unicode/bidi/core.go
generated
vendored
|
|
@ -7,7 +7,7 @@ package bidi
|
||||||
import "log"
|
import "log"
|
||||||
|
|
||||||
// This implementation is a port based on the reference implementation found at:
|
// This implementation is a port based on the reference implementation found at:
|
||||||
// http://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/
|
// https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/
|
||||||
//
|
//
|
||||||
// described in Unicode Bidirectional Algorithm (UAX #9).
|
// described in Unicode Bidirectional Algorithm (UAX #9).
|
||||||
//
|
//
|
||||||
|
|
|
||||||
2
vendor/golang.org/x/text/unicode/bidi/gen.go
generated
vendored
2
vendor/golang.org/x/text/unicode/bidi/gen.go
generated
vendored
|
|
@ -26,7 +26,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// bidiClass names and codes taken from class "bc" in
|
// bidiClass names and codes taken from class "bc" in
|
||||||
// http://www.unicode.org/Public/8.0.0/ucd/PropertyValueAliases.txt
|
// https://www.unicode.org/Public/8.0.0/ucd/PropertyValueAliases.txt
|
||||||
var bidiClass = map[string]Class{
|
var bidiClass = map[string]Class{
|
||||||
"AL": AL, // ArabicLetter
|
"AL": AL, // ArabicLetter
|
||||||
"AN": AN, // ArabicNumber
|
"AN": AN, // ArabicNumber
|
||||||
|
|
|
||||||
2
vendor/golang.org/x/text/unicode/bidi/gen_ranges.go
generated
vendored
2
vendor/golang.org/x/text/unicode/bidi/gen_ranges.go
generated
vendored
|
|
@ -15,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// These tables are hand-extracted from:
|
// These tables are hand-extracted from:
|
||||||
// http://www.unicode.org/Public/8.0.0/ucd/extracted/DerivedBidiClass.txt
|
// https://www.unicode.org/Public/8.0.0/ucd/extracted/DerivedBidiClass.txt
|
||||||
func visitDefaults(fn func(r rune, c Class)) {
|
func visitDefaults(fn func(r rune, c Class)) {
|
||||||
// first write default values for ranges listed above.
|
// first write default values for ranges listed above.
|
||||||
visitRunes(fn, AL, []rune{
|
visitRunes(fn, AL, []rune{
|
||||||
|
|
|
||||||
2
vendor/golang.org/x/text/unicode/bidi/tables10.0.0.go
generated
vendored
2
vendor/golang.org/x/text/unicode/bidi/tables10.0.0.go
generated
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||||
|
|
||||||
// +build go1.10
|
// +build go1.10,!go1.13
|
||||||
|
|
||||||
package bidi
|
package bidi
|
||||||
|
|
||||||
|
|
|
||||||
1887
vendor/golang.org/x/text/unicode/bidi/tables11.0.0.go
generated
vendored
Normal file
1887
vendor/golang.org/x/text/unicode/bidi/tables11.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
8
vendor/golang.org/x/text/unicode/norm/composition.go
generated
vendored
8
vendor/golang.org/x/text/unicode/norm/composition.go
generated
vendored
|
|
@ -407,7 +407,7 @@ func decomposeHangul(buf []byte, r rune) int {
|
||||||
|
|
||||||
// decomposeHangul algorithmically decomposes a Hangul rune into
|
// decomposeHangul algorithmically decomposes a Hangul rune into
|
||||||
// its Jamo components.
|
// its Jamo components.
|
||||||
// See http://unicode.org/reports/tr15/#Hangul for details on decomposing Hangul.
|
// See https://unicode.org/reports/tr15/#Hangul for details on decomposing Hangul.
|
||||||
func (rb *reorderBuffer) decomposeHangul(r rune) {
|
func (rb *reorderBuffer) decomposeHangul(r rune) {
|
||||||
r -= hangulBase
|
r -= hangulBase
|
||||||
x := r % jamoTCount
|
x := r % jamoTCount
|
||||||
|
|
@ -420,7 +420,7 @@ func (rb *reorderBuffer) decomposeHangul(r rune) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// combineHangul algorithmically combines Jamo character components into Hangul.
|
// combineHangul algorithmically combines Jamo character components into Hangul.
|
||||||
// See http://unicode.org/reports/tr15/#Hangul for details on combining Hangul.
|
// See https://unicode.org/reports/tr15/#Hangul for details on combining Hangul.
|
||||||
func (rb *reorderBuffer) combineHangul(s, i, k int) {
|
func (rb *reorderBuffer) combineHangul(s, i, k int) {
|
||||||
b := rb.rune[:]
|
b := rb.rune[:]
|
||||||
bn := rb.nrune
|
bn := rb.nrune
|
||||||
|
|
@ -461,6 +461,10 @@ func (rb *reorderBuffer) combineHangul(s, i, k int) {
|
||||||
// It should only be used to recompose a single segment, as it will not
|
// It should only be used to recompose a single segment, as it will not
|
||||||
// handle alternations between Hangul and non-Hangul characters correctly.
|
// handle alternations between Hangul and non-Hangul characters correctly.
|
||||||
func (rb *reorderBuffer) compose() {
|
func (rb *reorderBuffer) compose() {
|
||||||
|
// Lazily load the map used by the combine func below, but do
|
||||||
|
// it outside of the loop.
|
||||||
|
recompMapOnce.Do(buildRecompMap)
|
||||||
|
|
||||||
// UAX #15, section X5 , including Corrigendum #5
|
// UAX #15, section X5 , including Corrigendum #5
|
||||||
// "In any character sequence beginning with starter S, a character C is
|
// "In any character sequence beginning with starter S, a character C is
|
||||||
// blocked from S if and only if there is some character B between S
|
// blocked from S if and only if there is some character B between S
|
||||||
|
|
|
||||||
19
vendor/golang.org/x/text/unicode/norm/forminfo.go
generated
vendored
19
vendor/golang.org/x/text/unicode/norm/forminfo.go
generated
vendored
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package norm
|
package norm
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
// This file contains Form-specific logic and wrappers for data in tables.go.
|
// This file contains Form-specific logic and wrappers for data in tables.go.
|
||||||
|
|
||||||
// Rune info is stored in a separate trie per composing form. A composing form
|
// Rune info is stored in a separate trie per composing form. A composing form
|
||||||
|
|
@ -178,6 +180,17 @@ func (p Properties) TrailCCC() uint8 {
|
||||||
return ccc[p.tccc]
|
return ccc[p.tccc]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildRecompMap() {
|
||||||
|
recompMap = make(map[uint32]rune, len(recompMapPacked)/8)
|
||||||
|
var buf [8]byte
|
||||||
|
for i := 0; i < len(recompMapPacked); i += 8 {
|
||||||
|
copy(buf[:], recompMapPacked[i:i+8])
|
||||||
|
key := binary.BigEndian.Uint32(buf[:4])
|
||||||
|
val := binary.BigEndian.Uint32(buf[4:])
|
||||||
|
recompMap[key] = rune(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Recomposition
|
// Recomposition
|
||||||
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
|
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
|
||||||
// This clips off the bits of three entries, but we know this will not
|
// This clips off the bits of three entries, but we know this will not
|
||||||
|
|
@ -186,8 +199,14 @@ func (p Properties) TrailCCC() uint8 {
|
||||||
// Note that the recomposition map for NFC and NFKC are identical.
|
// Note that the recomposition map for NFC and NFKC are identical.
|
||||||
|
|
||||||
// combine returns the combined rune or 0 if it doesn't exist.
|
// combine returns the combined rune or 0 if it doesn't exist.
|
||||||
|
//
|
||||||
|
// The caller is responsible for calling
|
||||||
|
// recompMapOnce.Do(buildRecompMap) sometime before this is called.
|
||||||
func combine(a, b rune) rune {
|
func combine(a, b rune) rune {
|
||||||
key := uint32(uint16(a))<<16 + uint32(uint16(b))
|
key := uint32(uint16(a))<<16 + uint32(uint16(b))
|
||||||
|
if recompMap == nil {
|
||||||
|
panic("caller error") // see func comment
|
||||||
|
}
|
||||||
return recompMap[key]
|
return recompMap[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3
vendor/golang.org/x/text/unicode/norm/iter.go
generated
vendored
3
vendor/golang.org/x/text/unicode/norm/iter.go
generated
vendored
|
|
@ -128,8 +128,9 @@ func (i *Iter) Next() []byte {
|
||||||
func nextASCIIBytes(i *Iter) []byte {
|
func nextASCIIBytes(i *Iter) []byte {
|
||||||
p := i.p + 1
|
p := i.p + 1
|
||||||
if p >= i.rb.nsrc {
|
if p >= i.rb.nsrc {
|
||||||
|
p0 := i.p
|
||||||
i.setDone()
|
i.setDone()
|
||||||
return i.rb.src.bytes[i.p:p]
|
return i.rb.src.bytes[p0:p]
|
||||||
}
|
}
|
||||||
if i.rb.src.bytes[p] < utf8.RuneSelf {
|
if i.rb.src.bytes[p] < utf8.RuneSelf {
|
||||||
p0 := i.p
|
p0 := i.p
|
||||||
|
|
|
||||||
20
vendor/golang.org/x/text/unicode/norm/maketables.go
generated
vendored
20
vendor/golang.org/x/text/unicode/norm/maketables.go
generated
vendored
|
|
@ -12,6 +12,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -261,7 +262,7 @@ func compactCCC() {
|
||||||
|
|
||||||
// CompositionExclusions.txt has form:
|
// CompositionExclusions.txt has form:
|
||||||
// 0958 # ...
|
// 0958 # ...
|
||||||
// See http://unicode.org/reports/tr44/ for full explanation
|
// See https://unicode.org/reports/tr44/ for full explanation
|
||||||
func loadCompositionExclusions() {
|
func loadCompositionExclusions() {
|
||||||
f := gen.OpenUCDFile("CompositionExclusions.txt")
|
f := gen.OpenUCDFile("CompositionExclusions.txt")
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
@ -735,6 +736,8 @@ func makeTables() {
|
||||||
max = n
|
max = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fmt.Fprintln(w, `import "sync"`)
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
|
||||||
fmt.Fprintln(w, "const (")
|
fmt.Fprintln(w, "const (")
|
||||||
fmt.Fprintln(w, "\t// Version is the Unicode edition from which the tables are derived.")
|
fmt.Fprintln(w, "\t// Version is the Unicode edition from which the tables are derived.")
|
||||||
|
|
@ -782,16 +785,23 @@ func makeTables() {
|
||||||
sz := nrentries * 8
|
sz := nrentries * 8
|
||||||
size += sz
|
size += sz
|
||||||
fmt.Fprintf(w, "// recompMap: %d bytes (entries only)\n", sz)
|
fmt.Fprintf(w, "// recompMap: %d bytes (entries only)\n", sz)
|
||||||
fmt.Fprintln(w, "var recompMap = map[uint32]rune{")
|
fmt.Fprintln(w, "var recompMap map[uint32]rune")
|
||||||
|
fmt.Fprintln(w, "var recompMapOnce sync.Once\n")
|
||||||
|
fmt.Fprintln(w, `const recompMapPacked = "" +`)
|
||||||
|
var buf [8]byte
|
||||||
for i, c := range chars {
|
for i, c := range chars {
|
||||||
f := c.forms[FCanonical]
|
f := c.forms[FCanonical]
|
||||||
d := f.decomp
|
d := f.decomp
|
||||||
if !f.isOneWay && len(d) > 0 {
|
if !f.isOneWay && len(d) > 0 {
|
||||||
key := uint32(uint16(d[0]))<<16 + uint32(uint16(d[1]))
|
key := uint32(uint16(d[0]))<<16 + uint32(uint16(d[1]))
|
||||||
fmt.Fprintf(w, "0x%.8X: 0x%.4X,\n", key, i)
|
binary.BigEndian.PutUint32(buf[:4], key)
|
||||||
|
binary.BigEndian.PutUint32(buf[4:], uint32(i))
|
||||||
|
fmt.Fprintf(w, "\t\t%q + // 0x%.8X: 0x%.8X\n", string(buf[:]), key, uint32(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "}\n\n")
|
// hack so we don't have to special case the trailing plus sign
|
||||||
|
fmt.Fprintf(w, ` ""`)
|
||||||
|
fmt.Fprintln(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w, "// Total size of tables: %dKB (%d bytes)\n", (size+512)/1024, size)
|
fmt.Fprintf(w, "// Total size of tables: %dKB (%d bytes)\n", (size+512)/1024, size)
|
||||||
|
|
@ -857,7 +867,7 @@ func verifyComputed() {
|
||||||
// DerivedNormalizationProps.txt has form:
|
// DerivedNormalizationProps.txt has form:
|
||||||
// 00C0..00C5 ; NFD_QC; N # ...
|
// 00C0..00C5 ; NFD_QC; N # ...
|
||||||
// 0374 ; NFD_QC; N # ...
|
// 0374 ; NFD_QC; N # ...
|
||||||
// See http://unicode.org/reports/tr44/ for full explanation
|
// See https://unicode.org/reports/tr44/ for full explanation
|
||||||
func testDerived() {
|
func testDerived() {
|
||||||
f := gen.OpenUCDFile("DerivedNormalizationProps.txt")
|
f := gen.OpenUCDFile("DerivedNormalizationProps.txt")
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
|
||||||
4
vendor/golang.org/x/text/unicode/norm/normalize.go
generated
vendored
4
vendor/golang.org/x/text/unicode/norm/normalize.go
generated
vendored
|
|
@ -29,8 +29,8 @@ import (
|
||||||
// proceed independently on both sides:
|
// proceed independently on both sides:
|
||||||
// f(x) == append(f(x[0:n]), f(x[n:])...)
|
// f(x) == append(f(x[0:n]), f(x[n:])...)
|
||||||
//
|
//
|
||||||
// References: http://unicode.org/reports/tr15/ and
|
// References: https://unicode.org/reports/tr15/ and
|
||||||
// http://unicode.org/notes/tn5/.
|
// https://unicode.org/notes/tn5/.
|
||||||
type Form int
|
type Form int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
4
vendor/golang.org/x/text/unicode/norm/readwriter.go
generated
vendored
4
vendor/golang.org/x/text/unicode/norm/readwriter.go
generated
vendored
|
|
@ -60,8 +60,8 @@ func (w *normWriter) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer returns a new writer that implements Write(b)
|
// Writer returns a new writer that implements Write(b)
|
||||||
// by writing f(b) to w. The returned writer may use an
|
// by writing f(b) to w. The returned writer may use an
|
||||||
// an internal buffer to maintain state across Write calls.
|
// internal buffer to maintain state across Write calls.
|
||||||
// Calling its Close method writes any buffered data to w.
|
// Calling its Close method writes any buffered data to w.
|
||||||
func (f Form) Writer(w io.Writer) io.WriteCloser {
|
func (f Form) Writer(w io.Writer) io.WriteCloser {
|
||||||
wr := &normWriter{rb: reorderBuffer{}, w: w}
|
wr := &normWriter{rb: reorderBuffer{}, w: w}
|
||||||
|
|
|
||||||
1892
vendor/golang.org/x/text/unicode/norm/tables10.0.0.go
generated
vendored
1892
vendor/golang.org/x/text/unicode/norm/tables10.0.0.go
generated
vendored
File diff suppressed because it is too large
Load diff
7693
vendor/golang.org/x/text/unicode/norm/tables11.0.0.go
generated
vendored
Normal file
7693
vendor/golang.org/x/text/unicode/norm/tables11.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1890
vendor/golang.org/x/text/unicode/norm/tables9.0.0.go
generated
vendored
1890
vendor/golang.org/x/text/unicode/norm/tables9.0.0.go
generated
vendored
File diff suppressed because it is too large
Load diff
10
vendor/golang.org/x/text/unicode/norm/transform.go
generated
vendored
10
vendor/golang.org/x/text/unicode/norm/transform.go
generated
vendored
|
|
@ -18,7 +18,6 @@ func (Form) Reset() {}
|
||||||
// Users should either catch ErrShortDst and allow dst to grow or have dst be at
|
// Users should either catch ErrShortDst and allow dst to grow or have dst be at
|
||||||
// least of size MaxTransformChunkSize to be guaranteed of progress.
|
// least of size MaxTransformChunkSize to be guaranteed of progress.
|
||||||
func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
n := 0
|
|
||||||
// Cap the maximum number of src bytes to check.
|
// Cap the maximum number of src bytes to check.
|
||||||
b := src
|
b := src
|
||||||
eof := atEOF
|
eof := atEOF
|
||||||
|
|
@ -27,13 +26,14 @@ func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
||||||
eof = false
|
eof = false
|
||||||
b = b[:ns]
|
b = b[:ns]
|
||||||
}
|
}
|
||||||
i, ok := formTable[f].quickSpan(inputBytes(b), n, len(b), eof)
|
i, ok := formTable[f].quickSpan(inputBytes(b), 0, len(b), eof)
|
||||||
n += copy(dst[n:], b[n:i])
|
n := copy(dst, b[:i])
|
||||||
if !ok {
|
if !ok {
|
||||||
nDst, nSrc, err = f.transform(dst[n:], src[n:], atEOF)
|
nDst, nSrc, err = f.transform(dst[n:], src[n:], atEOF)
|
||||||
return nDst + n, nSrc + n, err
|
return nDst + n, nSrc + n, err
|
||||||
}
|
}
|
||||||
if n < len(src) && !atEOF {
|
|
||||||
|
if err == nil && n < len(src) && !atEOF {
|
||||||
err = transform.ErrShortSrc
|
err = transform.ErrShortSrc
|
||||||
}
|
}
|
||||||
return n, n, err
|
return n, n, err
|
||||||
|
|
@ -79,7 +79,7 @@ func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
||||||
nSrc += n
|
nSrc += n
|
||||||
nDst += n
|
nDst += n
|
||||||
if ok {
|
if ok {
|
||||||
if n < rb.nsrc && !atEOF {
|
if err == nil && n < rb.nsrc && !atEOF {
|
||||||
err = transform.ErrShortSrc
|
err = transform.ErrShortSrc
|
||||||
}
|
}
|
||||||
return nDst, nSrc, err
|
return nDst, nSrc, err
|
||||||
|
|
|
||||||
18
vendor/gopkg.in/yaml.v2/.travis.yml
generated
vendored
18
vendor/gopkg.in/yaml.v2/.travis.yml
generated
vendored
|
|
@ -1,12 +1,16 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.4
|
- "1.4.x"
|
||||||
- 1.5
|
- "1.5.x"
|
||||||
- 1.6
|
- "1.6.x"
|
||||||
- 1.7
|
- "1.7.x"
|
||||||
- 1.8
|
- "1.8.x"
|
||||||
- 1.9
|
- "1.9.x"
|
||||||
- tip
|
- "1.10.x"
|
||||||
|
- "1.11.x"
|
||||||
|
- "1.12.x"
|
||||||
|
- "1.13.x"
|
||||||
|
- "tip"
|
||||||
|
|
||||||
go_import_path: gopkg.in/yaml.v2
|
go_import_path: gopkg.in/yaml.v2
|
||||||
|
|
|
||||||
48
vendor/gopkg.in/yaml.v2/decode.go
generated
vendored
48
vendor/gopkg.in/yaml.v2/decode.go
generated
vendored
|
|
@ -229,6 +229,10 @@ type decoder struct {
|
||||||
mapType reflect.Type
|
mapType reflect.Type
|
||||||
terrors []string
|
terrors []string
|
||||||
strict bool
|
strict bool
|
||||||
|
|
||||||
|
decodeCount int
|
||||||
|
aliasCount int
|
||||||
|
aliasDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -314,7 +318,43 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm
|
||||||
return out, false, false
|
return out, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 400,000 decode operations is ~500kb of dense object declarations, or
|
||||||
|
// ~5kb of dense object declarations with 10000% alias expansion
|
||||||
|
alias_ratio_range_low = 400000
|
||||||
|
|
||||||
|
// 4,000,000 decode operations is ~5MB of dense object declarations, or
|
||||||
|
// ~4.5MB of dense object declarations with 10% alias expansion
|
||||||
|
alias_ratio_range_high = 4000000
|
||||||
|
|
||||||
|
// alias_ratio_range is the range over which we scale allowed alias ratios
|
||||||
|
alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low)
|
||||||
|
)
|
||||||
|
|
||||||
|
func allowedAliasRatio(decodeCount int) float64 {
|
||||||
|
switch {
|
||||||
|
case decodeCount <= alias_ratio_range_low:
|
||||||
|
// allow 99% to come from alias expansion for small-to-medium documents
|
||||||
|
return 0.99
|
||||||
|
case decodeCount >= alias_ratio_range_high:
|
||||||
|
// allow 10% to come from alias expansion for very large documents
|
||||||
|
return 0.10
|
||||||
|
default:
|
||||||
|
// scale smoothly from 99% down to 10% over the range.
|
||||||
|
// this maps to 396,000 - 400,000 allowed alias-driven decodes over the range.
|
||||||
|
// 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps).
|
||||||
|
return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
||||||
|
d.decodeCount++
|
||||||
|
if d.aliasDepth > 0 {
|
||||||
|
d.aliasCount++
|
||||||
|
}
|
||||||
|
if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) {
|
||||||
|
failf("document contains excessive aliasing")
|
||||||
|
}
|
||||||
switch n.kind {
|
switch n.kind {
|
||||||
case documentNode:
|
case documentNode:
|
||||||
return d.document(n, out)
|
return d.document(n, out)
|
||||||
|
|
@ -353,7 +393,9 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
||||||
failf("anchor '%s' value contains itself", n.value)
|
failf("anchor '%s' value contains itself", n.value)
|
||||||
}
|
}
|
||||||
d.aliases[n] = true
|
d.aliases[n] = true
|
||||||
|
d.aliasDepth++
|
||||||
good = d.unmarshal(n.alias, out)
|
good = d.unmarshal(n.alias, out)
|
||||||
|
d.aliasDepth--
|
||||||
delete(d.aliases, n)
|
delete(d.aliases, n)
|
||||||
return good
|
return good
|
||||||
}
|
}
|
||||||
|
|
@ -746,8 +788,7 @@ func (d *decoder) merge(n *node, out reflect.Value) {
|
||||||
case mappingNode:
|
case mappingNode:
|
||||||
d.unmarshal(n, out)
|
d.unmarshal(n, out)
|
||||||
case aliasNode:
|
case aliasNode:
|
||||||
an, ok := d.doc.anchors[n.value]
|
if n.alias != nil && n.alias.kind != mappingNode {
|
||||||
if ok && an.kind != mappingNode {
|
|
||||||
failWantMap()
|
failWantMap()
|
||||||
}
|
}
|
||||||
d.unmarshal(n, out)
|
d.unmarshal(n, out)
|
||||||
|
|
@ -756,8 +797,7 @@ func (d *decoder) merge(n *node, out reflect.Value) {
|
||||||
for i := len(n.children) - 1; i >= 0; i-- {
|
for i := len(n.children) - 1; i >= 0; i-- {
|
||||||
ni := n.children[i]
|
ni := n.children[i]
|
||||||
if ni.kind == aliasNode {
|
if ni.kind == aliasNode {
|
||||||
an, ok := d.doc.anchors[ni.value]
|
if ni.alias != nil && ni.alias.kind != mappingNode {
|
||||||
if ok && an.kind != mappingNode {
|
|
||||||
failWantMap()
|
failWantMap()
|
||||||
}
|
}
|
||||||
} else if ni.kind != mappingNode {
|
} else if ni.kind != mappingNode {
|
||||||
|
|
|
||||||
2
vendor/gopkg.in/yaml.v2/resolve.go
generated
vendored
2
vendor/gopkg.in/yaml.v2/resolve.go
generated
vendored
|
|
@ -81,7 +81,7 @@ func resolvableTag(tag string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`)
|
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
|
||||||
|
|
||||||
func resolve(tag string, in string) (rtag string, out interface{}) {
|
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||||
if !resolvableTag(tag) {
|
if !resolvableTag(tag) {
|
||||||
|
|
|
||||||
94
vendor/gopkg.in/yaml.v2/scannerc.go
generated
vendored
94
vendor/gopkg.in/yaml.v2/scannerc.go
generated
vendored
|
|
@ -634,13 +634,14 @@ func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
|
||||||
need_more_tokens = true
|
need_more_tokens = true
|
||||||
} else {
|
} else {
|
||||||
// Check if any potential simple key may occupy the head position.
|
// Check if any potential simple key may occupy the head position.
|
||||||
if !yaml_parser_stale_simple_keys(parser) {
|
for i := len(parser.simple_keys) - 1; i >= 0; i-- {
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range parser.simple_keys {
|
|
||||||
simple_key := &parser.simple_keys[i]
|
simple_key := &parser.simple_keys[i]
|
||||||
if simple_key.possible && simple_key.token_number == parser.tokens_parsed {
|
if simple_key.token_number < parser.tokens_parsed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok {
|
||||||
|
return false
|
||||||
|
} else if valid && simple_key.token_number == parser.tokens_parsed {
|
||||||
need_more_tokens = true
|
need_more_tokens = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -678,11 +679,6 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove obsolete potential simple keys.
|
|
||||||
if !yaml_parser_stale_simple_keys(parser) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the indentation level against the current column.
|
// Check the indentation level against the current column.
|
||||||
if !yaml_parser_unroll_indent(parser, parser.mark.column) {
|
if !yaml_parser_unroll_indent(parser, parser.mark.column) {
|
||||||
return false
|
return false
|
||||||
|
|
@ -837,29 +833,30 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
|
||||||
"found character that cannot start any token")
|
"found character that cannot start any token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the list of potential simple keys and remove the positions that
|
func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) {
|
||||||
// cannot contain simple keys anymore.
|
if !simple_key.possible {
|
||||||
func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool {
|
return false, true
|
||||||
// Check for a potential simple key for each flow level.
|
|
||||||
for i := range parser.simple_keys {
|
|
||||||
simple_key := &parser.simple_keys[i]
|
|
||||||
|
|
||||||
// The specification requires that a simple key
|
|
||||||
//
|
|
||||||
// - is limited to a single line,
|
|
||||||
// - is shorter than 1024 characters.
|
|
||||||
if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) {
|
|
||||||
|
|
||||||
// Check if the potential simple key to be removed is required.
|
|
||||||
if simple_key.required {
|
|
||||||
return yaml_parser_set_scanner_error(parser,
|
|
||||||
"while scanning a simple key", simple_key.mark,
|
|
||||||
"could not find expected ':'")
|
|
||||||
}
|
|
||||||
simple_key.possible = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
// The 1.2 specification says:
|
||||||
|
//
|
||||||
|
// "If the ? indicator is omitted, parsing needs to see past the
|
||||||
|
// implicit key to recognize it as such. To limit the amount of
|
||||||
|
// lookahead required, the “:” indicator must appear at most 1024
|
||||||
|
// Unicode characters beyond the start of the key. In addition, the key
|
||||||
|
// is restricted to a single line."
|
||||||
|
//
|
||||||
|
if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index {
|
||||||
|
// Check if the potential simple key to be removed is required.
|
||||||
|
if simple_key.required {
|
||||||
|
return false, yaml_parser_set_scanner_error(parser,
|
||||||
|
"while scanning a simple key", simple_key.mark,
|
||||||
|
"could not find expected ':'")
|
||||||
|
}
|
||||||
|
simple_key.possible = false
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
return true, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a simple key may start at the current position and add it if
|
// Check if a simple key may start at the current position and add it if
|
||||||
|
|
@ -879,8 +876,8 @@ func yaml_parser_save_simple_key(parser *yaml_parser_t) bool {
|
||||||
possible: true,
|
possible: true,
|
||||||
required: required,
|
required: required,
|
||||||
token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head),
|
token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head),
|
||||||
|
mark: parser.mark,
|
||||||
}
|
}
|
||||||
simple_key.mark = parser.mark
|
|
||||||
|
|
||||||
if !yaml_parser_remove_simple_key(parser) {
|
if !yaml_parser_remove_simple_key(parser) {
|
||||||
return false
|
return false
|
||||||
|
|
@ -906,13 +903,26 @@ func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// max_flow_level limits the flow_level
|
||||||
|
const max_flow_level = 10000
|
||||||
|
|
||||||
// Increase the flow level and resize the simple key list if needed.
|
// Increase the flow level and resize the simple key list if needed.
|
||||||
func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool {
|
func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool {
|
||||||
// Reset the simple key on the next level.
|
// Reset the simple key on the next level.
|
||||||
parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{})
|
parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{
|
||||||
|
possible: false,
|
||||||
|
required: false,
|
||||||
|
token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head),
|
||||||
|
mark: parser.mark,
|
||||||
|
})
|
||||||
|
|
||||||
// Increase the flow level.
|
// Increase the flow level.
|
||||||
parser.flow_level++
|
parser.flow_level++
|
||||||
|
if parser.flow_level > max_flow_level {
|
||||||
|
return yaml_parser_set_scanner_error(parser,
|
||||||
|
"while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark,
|
||||||
|
fmt.Sprintf("exceeded max depth of %d", max_flow_level))
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -925,6 +935,9 @@ func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// max_indents limits the indents stack size
|
||||||
|
const max_indents = 10000
|
||||||
|
|
||||||
// Push the current indentation level to the stack and set the new level
|
// Push the current indentation level to the stack and set the new level
|
||||||
// the current column is greater than the indentation level. In this case,
|
// the current column is greater than the indentation level. In this case,
|
||||||
// append or insert the specified token into the token queue.
|
// append or insert the specified token into the token queue.
|
||||||
|
|
@ -939,6 +952,11 @@ func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml
|
||||||
// indentation level.
|
// indentation level.
|
||||||
parser.indents = append(parser.indents, parser.indent)
|
parser.indents = append(parser.indents, parser.indent)
|
||||||
parser.indent = column
|
parser.indent = column
|
||||||
|
if len(parser.indents) > max_indents {
|
||||||
|
return yaml_parser_set_scanner_error(parser,
|
||||||
|
"while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark,
|
||||||
|
fmt.Sprintf("exceeded max depth of %d", max_indents))
|
||||||
|
}
|
||||||
|
|
||||||
// Create a token and insert it into the queue.
|
// Create a token and insert it into the queue.
|
||||||
token := yaml_token_t{
|
token := yaml_token_t{
|
||||||
|
|
@ -1270,7 +1288,11 @@ func yaml_parser_fetch_value(parser *yaml_parser_t) bool {
|
||||||
simple_key := &parser.simple_keys[len(parser.simple_keys)-1]
|
simple_key := &parser.simple_keys[len(parser.simple_keys)-1]
|
||||||
|
|
||||||
// Have we found a simple key?
|
// Have we found a simple key?
|
||||||
if simple_key.possible {
|
if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok {
|
||||||
|
return false
|
||||||
|
|
||||||
|
} else if valid {
|
||||||
|
|
||||||
// Create the KEY token and insert it into the queue.
|
// Create the KEY token and insert it into the queue.
|
||||||
token := yaml_token_t{
|
token := yaml_token_t{
|
||||||
typ: yaml_KEY_TOKEN,
|
typ: yaml_KEY_TOKEN,
|
||||||
|
|
|
||||||
2
vendor/gopkg.in/yaml.v2/yaml.go
generated
vendored
2
vendor/gopkg.in/yaml.v2/yaml.go
generated
vendored
|
|
@ -89,7 +89,7 @@ func UnmarshalStrict(in []byte, out interface{}) (err error) {
|
||||||
return unmarshal(in, out, true)
|
return unmarshal(in, out, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Decorder reads and decodes YAML values from an input stream.
|
// A Decoder reads and decodes YAML values from an input stream.
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
strict bool
|
strict bool
|
||||||
parser *parser
|
parser *parser
|
||||||
|
|
|
||||||
18
vendor/modules.txt
vendored
18
vendor/modules.txt
vendored
|
|
@ -96,6 +96,20 @@ github.com/google/go-cmp/cmp/internal/function
|
||||||
github.com/google/go-cmp/cmp/internal/value
|
github.com/google/go-cmp/cmp/internal/value
|
||||||
# github.com/google/uuid v1.1.1
|
# github.com/google/uuid v1.1.1
|
||||||
github.com/google/uuid
|
github.com/google/uuid
|
||||||
|
# github.com/gophercloud/gophercloud v0.11.0
|
||||||
|
github.com/gophercloud/gophercloud
|
||||||
|
github.com/gophercloud/gophercloud/openstack
|
||||||
|
github.com/gophercloud/gophercloud/openstack/compute/v2/servers
|
||||||
|
github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata
|
||||||
|
github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v2/tokens
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v3/tokens
|
||||||
|
github.com/gophercloud/gophercloud/openstack/utils
|
||||||
|
github.com/gophercloud/gophercloud/pagination
|
||||||
|
github.com/gophercloud/gophercloud/internal
|
||||||
|
github.com/gophercloud/gophercloud/openstack/identity/v2/tenants
|
||||||
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
||||||
github.com/jmespath/go-jmespath
|
github.com/jmespath/go-jmespath
|
||||||
# github.com/julienschmidt/httprouter v1.2.0
|
# github.com/julienschmidt/httprouter v1.2.0
|
||||||
|
|
@ -122,10 +136,10 @@ golang.org/x/net/idna
|
||||||
golang.org/x/sys/unix
|
golang.org/x/sys/unix
|
||||||
golang.org/x/sys/windows
|
golang.org/x/sys/windows
|
||||||
golang.org/x/sys/windows/registry
|
golang.org/x/sys/windows/registry
|
||||||
# golang.org/x/text v0.3.0
|
# golang.org/x/text v0.3.2
|
||||||
golang.org/x/text/secure/bidirule
|
golang.org/x/text/secure/bidirule
|
||||||
golang.org/x/text/unicode/bidi
|
golang.org/x/text/unicode/bidi
|
||||||
golang.org/x/text/unicode/norm
|
golang.org/x/text/unicode/norm
|
||||||
golang.org/x/text/transform
|
golang.org/x/text/transform
|
||||||
# gopkg.in/yaml.v2 v2.2.2
|
# gopkg.in/yaml.v2 v2.2.7
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue