vendor: github.com/terraform-providers/terraform-provider-openstack@v1.15.0

This commit is contained in:
Radek Simko 2019-02-08 10:59:06 +00:00
parent 651ee113ac
commit a67e6e19b1
No known key found for this signature in database
GPG Key ID: 1F1C84FE689A88D7
447 changed files with 44600 additions and 5799 deletions

5
go.mod
View File

@ -45,7 +45,8 @@ require (
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
github.com/google/go-cmp v0.2.0 github.com/google/go-cmp v0.2.0
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e // indirect github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e // indirect
github.com/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72 github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f // indirect github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f // indirect
github.com/gorilla/websocket v1.4.0 // indirect github.com/gorilla/websocket v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
@ -118,7 +119,7 @@ require (
github.com/soheilhy/cmux v0.1.4 // indirect github.com/soheilhy/cmux v0.1.4 // indirect
github.com/spf13/afero v1.0.2 github.com/spf13/afero v1.0.2
github.com/terraform-providers/terraform-provider-aws v1.52.0 github.com/terraform-providers/terraform-provider-aws v1.52.0
github.com/terraform-providers/terraform-provider-openstack v0.0.0-20170616075611-4080a521c6ea github.com/terraform-providers/terraform-provider-openstack v1.15.0
github.com/terraform-providers/terraform-provider-template v1.0.0 // indirect github.com/terraform-providers/terraform-provider-template v1.0.0 // indirect
github.com/terraform-providers/terraform-provider-tls v1.2.0 // indirect github.com/terraform-providers/terraform-provider-tls v1.2.0 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect

10
go.sum
View File

@ -103,8 +103,10 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e h1:CYRpN206UTHUinz3VJoLaBdy1gEGeJNsqT0mvswDcMw= github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e h1:CYRpN206UTHUinz3VJoLaBdy1gEGeJNsqT0mvswDcMw=
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72 h1:I0ssFkBxJw27fhEVIBVjGQVMqKj5HyzfvfIhdr5Tx2E= github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968 h1:Pu+HW4kcQozw0QyrTTgLE+3RXNqFhQNNzhbnoLFL83c=
github.com/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01 h1:OgCNGSnEalfkRpn//WGJHhpo7fkP+LhTpvEITZ7CkK4=
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f h1:JJ2EP5vV3LAD2U1CxQtD7PTOO15Y96kXmKDz7TjxGHs= github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f h1:JJ2EP5vV3LAD2U1CxQtD7PTOO15Y96kXmKDz7TjxGHs=
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@ -301,8 +303,8 @@ github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5Effv
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
github.com/terraform-providers/terraform-provider-aws v1.52.0 h1:hfFaKOUtL/ud9Y4PFgFT7F8Ss61lMIK1P+ndPEhPA7s= github.com/terraform-providers/terraform-provider-aws v1.52.0 h1:hfFaKOUtL/ud9Y4PFgFT7F8Ss61lMIK1P+ndPEhPA7s=
github.com/terraform-providers/terraform-provider-aws v1.52.0/go.mod h1:uvqaeKnm2ydZ2LuKuW1NDNBu6heC/7IDGXWm36/6oKs= github.com/terraform-providers/terraform-provider-aws v1.52.0/go.mod h1:uvqaeKnm2ydZ2LuKuW1NDNBu6heC/7IDGXWm36/6oKs=
github.com/terraform-providers/terraform-provider-openstack v0.0.0-20170616075611-4080a521c6ea h1:IfuzHOI3XwwYZS2Xw8SQbxOtGXlIUrKtXtuDCTNxmsQ= github.com/terraform-providers/terraform-provider-openstack v1.15.0 h1:adpjqej+F8BAX9dHmuPF47sUIkgifeqBu6p7iCsyj0Y=
github.com/terraform-providers/terraform-provider-openstack v0.0.0-20170616075611-4080a521c6ea/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew= github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew=
github.com/terraform-providers/terraform-provider-template v1.0.0 h1:g2pyFaAJu369iAb7qGWmVwtQ15/35lRAfW91Je8wLjE= github.com/terraform-providers/terraform-provider-template v1.0.0 h1:g2pyFaAJu369iAb7qGWmVwtQ15/35lRAfW91Je8wLjE=
github.com/terraform-providers/terraform-provider-template v1.0.0/go.mod h1:/J+B8me5DCMa0rEBH5ic2aKPjhtpWNeScmxFJWxB1EU= github.com/terraform-providers/terraform-provider-template v1.0.0/go.mod h1:/J+B8me5DCMa0rEBH5ic2aKPjhtpWNeScmxFJWxB1EU=
github.com/terraform-providers/terraform-provider-tls v1.2.0 h1:wcD0InKzNh8fanUYQwim62WCd4toeD9WJnPw/RjBI4o= github.com/terraform-providers/terraform-provider-tls v1.2.0 h1:wcD0InKzNh8fanUYQwim62WCd4toeD9WJnPw/RjBI4o=

View File

@ -1 +1,3 @@
**/*.swp **/*.swp
.idea
.vscode

View File

@ -7,13 +7,16 @@ install:
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/goimports - go get golang.org/x/tools/cmd/goimports
go: go:
- 1.7 - "1.10"
- tip - "tip"
env: env:
global: global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ=" - secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
before_script:
- go vet ./...
script: script:
- ./script/coverage - ./script/coverage
- ./script/unittest
- ./script/format - ./script/format
after_success: after_success:
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out

98
vendor/github.com/gophercloud/gophercloud/.zuul.yaml generated vendored Normal file
View File

@ -0,0 +1,98 @@
- 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
- 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-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-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
- job:
name: gophercloud-acceptance-test-mitaka
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on mitaka branch
vars:
global_env:
OS_BRANCH: stable/mitaka
nodeset: ubuntu-trusty
- project:
name: gophercloud/gophercloud
check:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test
recheck-mitaka:
jobs:
- gophercloud-acceptance-test-mitaka
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
periodic:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test

View File

@ -1,148 +0,0 @@
# Tips
## Implementing default logging and re-authentication attempts
You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
like the following and setting it as the provider client's HTTP Client (via the
`gophercloud.ProviderClient.HTTPClient` field):
```go
//...
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default Gophercloud RoundTripper to allow for logging.
type LogRoundTripper struct {
rt http.RoundTripper
numReauthAttempts int
}
// newHTTPClient return a custom HTTP client that allows for logging relevant
// information before and after the HTTP request.
func newHTTPClient() http.Client {
return http.Client{
Transport: &LogRoundTripper{
rt: http.DefaultTransport,
},
}
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
glog.Infof("Request URL: %s\n", request.URL)
response, err := lrt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if lrt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
lrt.numReauthAttempts++
}
glog.Debugf("Response Status: %s\n", response.Status)
return response, nil
}
endpoint := "https://127.0.0.1/auth"
pc := openstack.NewClient(endpoint)
pc.HTTPClient = newHTTPClient()
//...
```
## Implementing custom objects
OpenStack request/response objects may differ among variable names or types.
### Custom request objects
To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
example, to pass in
```go
type MyCreateServerOpts struct {
Name string
Size int
}
```
to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
```go
func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
return map[string]interface{}{
"name": o.Name,
"size": o.Size,
}, nil
}
```
create an instance of your custom options object, and pass it to `servers.Create`:
```go
// ...
myOpts := MyCreateServerOpts{
Name: "s1",
Size: "100",
}
server, err := servers.Create(computeClient, myOpts).Extract()
// ...
```
### Custom response objects
Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
combined to create a custom object:
```go
// ...
type MyVolume struct {
volumes.Volume
tenantattr.VolumeExt
}
var v struct {
MyVolume `json:"volume"`
}
err := volumes.Get(client, volID).ExtractInto(&v)
// ...
```
## Overriding default `UnmarshalJSON` method
For some response objects, a field may be a custom type or may be allowed to take on
different types. In these cases, overriding the default `UnmarshalJSON` method may be
necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
method on the type:
```go
// ...
type MyVolume struct {
ID string `json: "id"`
TimeCreated time.Time `json: "-"`
}
func (r *MyVolume) UnmarshalJSON(b []byte) error {
type tmp MyVolume
var s struct {
tmp
TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Volume(s.tmp)
r.TimeCreated = time.Time(s.CreatedAt)
return err
}
// ...
```

View File

@ -1,32 +0,0 @@
# Compute
## Floating IPs
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
## Security Groups
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
## Servers
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
```golang
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
res := servers.Reboot(client, server.ID, rebootOpts)
```
# Identity
## V3
### Tokens
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
`time.Time`

View File

@ -127,7 +127,7 @@ new resource in the `server` variable (a
## Advanced Usage ## Advanced Usage
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works. Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works.
## Backwards-Compatibility Guarantees ## Backwards-Compatibility Guarantees
@ -140,4 +140,20 @@ See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback ## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues). 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.

View File

@ -1,74 +0,0 @@
## On Pull Requests
- Before you start a PR there needs to be a Github issue and a discussion about it
on that issue with a core contributor, even if it's just a 'SGTM'.
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
- A PR's description must contain link(s) to the line(s) in the OpenStack
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
are not good enough. The link(s) should be to a non-`master` branch. For example,
a pull request implementing the creation of a Neutron v2 subnet might put the
following link in the description:
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
From that link, a reviewer (or user) can verify the fields in the request/response
objects in the PR.
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with
one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM]
prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the
[Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will
let reviewers know it is ready to review.
- A PR should be small. Even if you intend on implementing an entire
service, a PR should only be one route of that service
(e.g. create server or get server, but not both).
- Unless explicitly asked, do not squash commits in the middle of a review; only
append. It makes it difficult for the reviewer to see what's changed from one
review to the next.
## On Code
- In re design: follow as closely as is reasonable the code already in the library.
Most operations (e.g. create, delete) admit the same design.
- Unit tests and acceptance (integration) tests must be written to cover each PR.
Tests for operations with several options (e.g. list, create) should include all
the options in the tests. This will allow users to verify an operation on their
own infrastructure and see an example of usage.
- If in doubt, ask in-line on the PR.
### File Structure
- The following should be used in most cases:
- `requests.go`: contains all the functions that make HTTP requests and the
types associated with the HTTP request (parameters for URL, body, etc)
- `results.go`: contains all the response objects and their methods
- `urls.go`: contains the endpoints to which the requests are made
### Naming
- For methods on a type in `response.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a
`pagination.Pager`, should be named returns of the name `r`.
- Functions in `requests.go` that accept request bodies should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Map`
(eg `ToPortCreateMap`).
- Functions in `requests.go` that accept query strings should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Query`
(eg `ToServerListQuery`).

View File

@ -9,25 +9,45 @@ ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and Its fields are the union of those recognized by each identity implementation and
provider. 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 { type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with // IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by // 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 // all of the identity services, it will often be populated by a provider-level
// function. // 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:"-"` IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's // 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 // control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed. // UserID or a combination of Username and DomainID or DomainName are needed.
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
UserID string `json:"id,omitempty"` UserID string `json:"-"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username // At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional. // with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"` DomainID string `json:"-"`
DomainName string `json:"name,omitempty"` DomainName string `json:"name,omitempty"`
// The TenantID and TenantName fields are optional for the Identity V2 API. // The TenantID and TenantName fields are optional for the Identity V2 API.
@ -39,7 +59,7 @@ type AuthOptions struct {
// If DomainID or DomainName are provided, they will also apply to TenantName. // If DomainID or DomainName are provided, they will also apply to TenantName.
// It is not currently possible to authenticate with Username and a Domain // 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 // and scope to a Project in a different Domain by using TenantName. To
// accomplish that, the ProjectID will need to be provided to the TenantID // accomplish that, the ProjectID will need to be provided as the TenantID
// option. // option.
TenantID string `json:"tenantId,omitempty"` TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"` TenantName string `json:"tenantName,omitempty"`
@ -50,15 +70,34 @@ type AuthOptions struct {
// false, it will not cache these settings, but re-authentication will not be // false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false. // possible. This setting defaults to false.
// //
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked. // NOTE: The reauth function will try to re-authenticate endlessly if left
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client // unchecked. The way to limit the number of attempts is to provide a custom
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries. // HTTP client to the provider client and provide a transport that implements
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 // 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:"-"` AllowReauth bool `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an // TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID. // authentication token ID.
TokenID string `json:"-"` 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
} }
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
@ -109,7 +148,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
type userReq struct { type userReq struct {
ID *string `json:"id,omitempty"` ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Password string `json:"password"` Password string `json:"password,omitempty"`
Domain *domainReq `json:"domain,omitempty"` Domain *domainReq `json:"domain,omitempty"`
} }
@ -121,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
ID string `json:"id"` 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 identityReq struct { type identityReq struct {
Methods []string `json:"methods"` Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"` Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"` Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
} }
type authReq struct { type authReq struct {
@ -161,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
req.Auth.Identity.Token = &tokenReq{ req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID, 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 { } else {
// If no password or token ID are available, authentication can't continue. // If no password or token ID or ApplicationCredential are available, authentication can't continue.
return nil, ErrMissingPassword{} return nil, ErrMissingPassword{}
} }
} else { } else {
@ -241,82 +348,85 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
} }
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
// For backwards compatibility.
var scope struct { // If AuthOptions.Scope was not set, try to determine it.
ProjectID string // This works well for common scenarios.
ProjectName string if opts.Scope == nil {
DomainID string opts.Scope = new(AuthScope)
DomainName string if opts.TenantID != "" {
} opts.Scope.ProjectID = opts.TenantID
} else {
if opts.TenantID != "" { if opts.TenantName != "" {
scope.ProjectID = opts.TenantID opts.Scope.ProjectName = opts.TenantName
} else { opts.Scope.DomainID = opts.DomainID
if opts.TenantName != "" { opts.Scope.DomainName = opts.DomainName
scope.ProjectName = opts.TenantName }
scope.DomainID = opts.DomainID
scope.DomainName = opts.DomainName
} }
} }
if scope.ProjectName != "" { if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied. // ProjectID may not be supplied.
if scope.DomainID == "" && scope.DomainName == "" { if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
return nil, ErrScopeDomainIDOrDomainName{} return nil, ErrScopeDomainIDOrDomainName{}
} }
if scope.ProjectID != "" { if opts.Scope.ProjectID != "" {
return nil, ErrScopeProjectIDOrProjectName{} return nil, ErrScopeProjectIDOrProjectName{}
} }
if scope.DomainID != "" { if opts.Scope.DomainID != "" {
// ProjectName + DomainID // ProjectName + DomainID
return map[string]interface{}{ return map[string]interface{}{
"project": map[string]interface{}{ "project": map[string]interface{}{
"name": &scope.ProjectName, "name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"id": &scope.DomainID}, "domain": map[string]interface{}{"id": &opts.Scope.DomainID},
}, },
}, nil }, nil
} }
if scope.DomainName != "" { if opts.Scope.DomainName != "" {
// ProjectName + DomainName // ProjectName + DomainName
return map[string]interface{}{ return map[string]interface{}{
"project": map[string]interface{}{ "project": map[string]interface{}{
"name": &scope.ProjectName, "name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"name": &scope.DomainName}, "domain": map[string]interface{}{"name": &opts.Scope.DomainName},
}, },
}, nil }, nil
} }
} else if scope.ProjectID != "" { } else if opts.Scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if scope.DomainID != "" { if opts.Scope.DomainID != "" {
return nil, ErrScopeProjectIDAlone{} return nil, ErrScopeProjectIDAlone{}
} }
if scope.DomainName != "" { if opts.Scope.DomainName != "" {
return nil, ErrScopeProjectIDAlone{} return nil, ErrScopeProjectIDAlone{}
} }
// ProjectID // ProjectID
return map[string]interface{}{ return map[string]interface{}{
"project": map[string]interface{}{ "project": map[string]interface{}{
"id": &scope.ProjectID, "id": &opts.Scope.ProjectID,
}, },
}, nil }, nil
} else if scope.DomainID != "" { } else if opts.Scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if scope.DomainName != "" { if opts.Scope.DomainName != "" {
return nil, ErrScopeDomainIDOrDomainName{} return nil, ErrScopeDomainIDOrDomainName{}
} }
// DomainID // DomainID
return map[string]interface{}{ return map[string]interface{}{
"domain": map[string]interface{}{ "domain": map[string]interface{}{
"id": &scope.DomainID, "id": &opts.Scope.DomainID,
},
}, nil
} else if opts.Scope.DomainName != "" {
// DomainName
return map[string]interface{}{
"domain": map[string]interface{}{
"name": &opts.Scope.DomainName,
}, },
}, nil }, nil
} else if scope.DomainName != "" {
return nil, ErrScopeDomainName{}
} }
return nil, nil return nil, nil

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

View File

@ -3,11 +3,17 @@ Package gophercloud provides a multi-vendor interface to OpenStack-compatible
clouds. The library has a three-level hierarchy: providers, services, and clouds. The library has a three-level hierarchy: providers, services, and
resources. resources.
Provider structs represent the service providers that offer and manage a Authenticating with Providers
collection of services. The IdentityEndpoint is typically refered to as
"auth_url" in information provided by the cloud operator. Additionally, Provider structs represent the cloud providers that offer and manage a
the cloud may refer to TenantID or TenantName as project_id and project_name. collection of services. You will generally want to create one Provider
These are defined like so: client per OpenStack cloud.
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{ opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://openstack.example.com:5000/v2.0", IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
@ -18,6 +24,16 @@ These are defined like so:
provider, err := openstack.AuthenticatedClient(opts) 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 Service structs are specific to a provider and handle all of the logic and
operations for a particular OpenStack service. Examples of services include: operations for a particular OpenStack service. Examples of services include:
Compute, Object Storage, Block Storage. In order to define one, you need to Compute, Object Storage, Block Storage. In order to define one, you need to
@ -25,7 +41,9 @@ pass in the parent provider, like so:
opts := gophercloud.EndpointOpts{Region: "RegionOne"} opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts) client, err := openstack.NewComputeV2(provider, opts)
Resources
Resource structs are the domain models that services make use of in order Resource structs are the domain models that services make use of in order
to work with and represent the state of API resources: to work with and represent the state of API resources:
@ -62,6 +80,12 @@ of results:
return true, nil 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 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 throughout the provider and service packages. Of particular note for end users
are the AuthOptions and EndpointOpts structs. are the AuthOptions and EndpointOpts structs.

View File

@ -27,7 +27,7 @@ const (
// unambiguously identify one, and only one, endpoint within the catalog. // unambiguously identify one, and only one, endpoint within the catalog.
// //
// Usually, these are passed to service client factory functions in a provider // Usually, these are passed to service client factory functions in a provider
// package, like "rackspace.NewComputeV2()". // package, like "openstack.NewComputeV2()".
type EndpointOpts struct { type EndpointOpts struct {
// Type [required] is the service type for the client (e.g., "compute", // Type [required] is the service type for the client (e.g., "compute",
// "object-store"). Generally, this will be supplied by the service client // "object-store"). Generally, this will be supplied by the service client

View File

@ -1,6 +1,9 @@
package gophercloud package gophercloud
import "fmt" import (
"fmt"
"strings"
)
// BaseError is an error type that all other error types embed. // BaseError is an error type that all other error types embed.
type BaseError struct { type BaseError struct {
@ -43,6 +46,33 @@ func (e ErrInvalidInput) Error() string {
return e.choseErrString() 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 // ErrUnexpectedResponseCode is returned by the Request method when a response code other than
// those listed in OkCodes is encountered. // those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct { type ErrUnexpectedResponseCode struct {
@ -72,6 +102,11 @@ type ErrDefault401 struct {
ErrUnexpectedResponseCode 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. // ErrDefault404 is the default error type returned on a 404 HTTP response code.
type ErrDefault404 struct { type ErrDefault404 struct {
ErrUnexpectedResponseCode ErrUnexpectedResponseCode
@ -103,11 +138,22 @@ type ErrDefault503 struct {
} }
func (e ErrDefault400) Error() string { func (e ErrDefault400) Error() string {
return "Invalid request due to incorrect syntax or missing required parameters." 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 { func (e ErrDefault401) Error() string {
return "Authentication failed" 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 { func (e ErrDefault404) Error() string {
return "Resource not found" return "Resource not found"
} }
@ -141,6 +187,12 @@ type Err401er interface {
Error401(ErrUnexpectedResponseCode) error 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 // Err404er is the interface resource error types implement to override the error message
// from a 404 error. // from a 404 error.
type Err404er interface { type Err404er interface {
@ -393,16 +445,16 @@ func (e ErrScopeProjectIDAlone) Error() string {
return "ProjectID must be supplied alone in a Scope" return "ProjectID must be supplied alone in a Scope"
} }
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
type ErrScopeDomainName struct{ BaseError }
func (e ErrScopeDomainName) Error() string {
return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
}
// ErrScopeEmpty indicates that no credentials were provided in a Scope. // ErrScopeEmpty indicates that no credentials were provided in a Scope.
type ErrScopeEmpty struct{ BaseError } type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string { func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope" 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"
}

View File

@ -0,0 +1 @@
package internal

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

View File

@ -8,10 +8,27 @@ import (
var nilOptions = gophercloud.AuthOptions{} 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, AuthOptionsFromEnv fills out an identity.AuthOptions structure with the
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must settings found on the various OpenStack OS_* environment variables.
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME.
Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings,
or an error will result. OS_TENANT_ID, OS_TENANT_NAME, OS_PROJECT_ID, and
OS_PROJECT_NAME are optional.
OS_TENANT_ID and OS_TENANT_NAME are mutually exclusive to OS_PROJECT_ID and
OS_PROJECT_NAME. If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will
still be referred as "tenant" in Gophercloud.
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) { func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
authURL := os.Getenv("OS_AUTH_URL") authURL := os.Getenv("OS_AUTH_URL")
username := os.Getenv("OS_USERNAME") username := os.Getenv("OS_USERNAME")
@ -21,31 +38,76 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
tenantName := os.Getenv("OS_TENANT_NAME") tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID") domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME") 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 == "" { if authURL == "" {
err := gophercloud.ErrMissingInput{Argument: "authURL"} err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_AUTH_URL",
}
return nilOptions, err return nilOptions, err
} }
if username == "" && userID == "" { if userID == "" && username == "" {
err := gophercloud.ErrMissingInput{Argument: "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 == "" && applicationCredentialID == "" && applicationCredentialName == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_PASSWORD",
}
return nilOptions, err return nilOptions, err
} }
if password == "" { if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" {
err := gophercloud.ErrMissingInput{Argument: "password"} err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET",
}
return nilOptions, err 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{ ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL, IdentityEndpoint: authURL,
UserID: userID, UserID: userID,
Username: username, Username: username,
Password: password, Password: password,
TenantID: tenantID, TenantID: tenantID,
TenantName: tenantName, TenantName: tenantName,
DomainID: domainID, DomainID: domainID,
DomainName: domainName, DomainName: domainName,
ApplicationCredentialID: applicationCredentialID,
ApplicationCredentialName: applicationCredentialName,
ApplicationCredentialSecret: applicationCredentialSecret,
} }
return ao, nil return ao, nil

View File

@ -1,5 +1,86 @@
// Package volumeactions provides information and interaction with volumes in the /*
// OpenStack Block Storage service. A volume is a detachable block storage Package volumeactions provides information and interaction with volumes in the
// device, akin to a USB hard drive. It can only be attached to one instance at OpenStack Block Storage service. A volume is a detachable block storage
// a time. device, akin to a USB hard drive.
Example of Attaching a Volume to an Instance
attachOpts := volumeactions.AttachOpts{
MountPoint: "/mnt",
Mode: "rw",
InstanceUUID: server.ID,
}
err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr()
if err != nil {
panic(err)
}
detachOpts := volumeactions.DetachOpts{
AttachmentID: volume.Attachments[0].AttachmentID,
}
err = volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr()
if err != nil {
panic(err)
}
Example of Creating an Image from a Volume
uploadImageOpts := volumeactions.UploadImageOpts{
ImageName: "my_vol",
Force: true,
}
volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", volumeImage)
Example of Extending a Volume's Size
extendOpts := volumeactions.ExtendSizeOpts{
NewSize: 100,
}
err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr()
if err != nil {
panic(err)
}
Example of Initializing a Volume Connection
connectOpts := &volumeactions.InitializeConnectionOpts{
IP: "127.0.0.1",
Host: "stack",
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
Multipath: gophercloud.Disabled,
Platform: "x86_64",
OSType: "linux2",
}
connectionInfo, err := volumeactions.InitializeConnection(client, volume.ID, connectOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", connectionInfo["data"])
terminateOpts := &volumeactions.InitializeConnectionOpts{
IP: "127.0.0.1",
Host: "stack",
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
Multipath: gophercloud.Disabled,
Platform: "x86_64",
OSType: "linux2",
}
err = volumeactions.TerminateConnection(client, volume.ID, terminateOpts).ExtractErr()
if err != nil {
panic(err)
}
*/
package volumeactions package volumeactions

View File

@ -13,7 +13,7 @@ type AttachOptsBuilder interface {
// AttachMode describes the attachment mode for volumes. // AttachMode describes the attachment mode for volumes.
type AttachMode string type AttachMode string
// These constants determine how a volume is attached // These constants determine how a volume is attached.
const ( const (
ReadOnly AttachMode = "ro" ReadOnly AttachMode = "ro"
ReadWrite AttachMode = "rw" ReadWrite AttachMode = "rw"
@ -21,13 +21,16 @@ const (
// AttachOpts contains options for attaching a Volume. // AttachOpts contains options for attaching a Volume.
type AttachOpts struct { type AttachOpts struct {
// The mountpoint of this volume // The mountpoint of this volume.
MountPoint string `json:"mountpoint,omitempty"` MountPoint string `json:"mountpoint,omitempty"`
// The nova instance ID, can't set simultaneously with HostName
// The nova instance ID, can't set simultaneously with HostName.
InstanceUUID string `json:"instance_uuid,omitempty"` InstanceUUID string `json:"instance_uuid,omitempty"`
// The hostname of baremetal host, can't set simultaneously with InstanceUUID
// The hostname of baremetal host, can't set simultaneously with InstanceUUID.
HostName string `json:"host_name,omitempty"` HostName string `json:"host_name,omitempty"`
// Mount mode of this volume
// Mount mode of this volume.
Mode AttachMode `json:"mode,omitempty"` Mode AttachMode `json:"mode,omitempty"`
} }
@ -44,16 +47,16 @@ func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(attachURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
return return
} }
// BeginDetach will mark the volume as detaching // BeginDetach will mark the volume as detaching.
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) { func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})} b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})}
_, r.Err = client.Post(beginDetachingURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
return return
@ -65,7 +68,9 @@ type DetachOptsBuilder interface {
ToVolumeDetachMap() (map[string]interface{}, error) ToVolumeDetachMap() (map[string]interface{}, error)
} }
// DetachOpts contains options for detaching a Volume.
type DetachOpts struct { type DetachOpts struct {
// AttachmentID is the ID of the attachment between a volume and instance.
AttachmentID string `json:"attachment_id,omitempty"` AttachmentID string `json:"attachment_id,omitempty"`
} }
@ -75,32 +80,32 @@ func (opts DetachOpts) ToVolumeDetachMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-detach") return gophercloud.BuildRequestBody(opts, "os-detach")
} }
// Detach will detach a volume based on volume id. // Detach will detach a volume based on volume ID.
func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder) (r DetachResult) { func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder) (r DetachResult) {
b, err := opts.ToVolumeDetachMap() b, err := opts.ToVolumeDetachMap()
if err != nil { if err != nil {
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(detachURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
return return
} }
// Reserve will reserve a volume based on volume id. // Reserve will reserve a volume based on volume ID.
func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) { func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) {
b := map[string]interface{}{"os-reserve": make(map[string]interface{})} b := map[string]interface{}{"os-reserve": make(map[string]interface{})}
_, r.Err = client.Post(reserveURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202}, OkCodes: []int{200, 201, 202},
}) })
return return
} }
// Unreserve will unreserve a volume based on volume id. // Unreserve will unreserve a volume based on volume ID.
func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) { func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) {
b := map[string]interface{}{"os-unreserve": make(map[string]interface{})} b := map[string]interface{}{"os-unreserve": make(map[string]interface{})}
_, r.Err = client.Post(unreserveURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202}, OkCodes: []int{200, 201, 202},
}) })
return return
@ -113,6 +118,8 @@ type InitializeConnectionOptsBuilder interface {
} }
// InitializeConnectionOpts hosts options for InitializeConnection. // InitializeConnectionOpts hosts options for InitializeConnection.
// The fields are specific to the storage driver in use and the destination
// attachment.
type InitializeConnectionOpts struct { type InitializeConnectionOpts struct {
IP string `json:"ip,omitempty"` IP string `json:"ip,omitempty"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
@ -131,14 +138,14 @@ func (opts InitializeConnectionOpts) ToVolumeInitializeConnectionMap() (map[stri
return map[string]interface{}{"os-initialize_connection": b}, err return map[string]interface{}{"os-initialize_connection": b}, err
} }
// InitializeConnection initializes iscsi connection. // InitializeConnection initializes an iSCSI connection by volume ID.
func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) { func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) {
b, err := opts.ToVolumeInitializeConnectionMap() b, err := opts.ToVolumeInitializeConnectionMap()
if err != nil { if err != nil {
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(initializeConnectionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202}, OkCodes: []int{200, 201, 202},
}) })
return return
@ -169,14 +176,14 @@ func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string
return map[string]interface{}{"os-terminate_connection": b}, err return map[string]interface{}{"os-terminate_connection": b}, err
} }
// TerminateConnection terminates iscsi connection. // TerminateConnection terminates an iSCSI connection by volume ID.
func TerminateConnection(client *gophercloud.ServiceClient, id string, opts TerminateConnectionOptsBuilder) (r TerminateConnectionResult) { func TerminateConnection(client *gophercloud.ServiceClient, id string, opts TerminateConnectionOptsBuilder) (r TerminateConnectionResult) {
b, err := opts.ToVolumeTerminateConnectionMap() b, err := opts.ToVolumeTerminateConnectionMap()
if err != nil { if err != nil {
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(teminateConnectionURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
return return
@ -188,10 +195,10 @@ type ExtendSizeOptsBuilder interface {
ToVolumeExtendSizeMap() (map[string]interface{}, error) ToVolumeExtendSizeMap() (map[string]interface{}, error)
} }
// ExtendSizeOpts contain options for extending the size of an existing Volume. This object is passed // ExtendSizeOpts contains options for extending the size of an existing Volume.
// to the volumes.ExtendSize function. // This object is passed to the volumes.ExtendSize function.
type ExtendSizeOpts struct { type ExtendSizeOpts struct {
// NewSize is the new size of the volume, in GB // NewSize is the new size of the volume, in GB.
NewSize int `json:"new_size" required:"true"` NewSize int `json:"new_size" required:"true"`
} }
@ -209,7 +216,7 @@ func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOpt
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(extendSizeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
return return
@ -225,11 +232,14 @@ type UploadImageOptsBuilder interface {
type UploadImageOpts struct { type UploadImageOpts struct {
// Container format, may be bare, ofv, ova, etc. // Container format, may be bare, ofv, ova, etc.
ContainerFormat string `json:"container_format,omitempty"` ContainerFormat string `json:"container_format,omitempty"`
// Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc.
DiskFormat string `json:"disk_format,omitempty"` DiskFormat string `json:"disk_format,omitempty"`
// The name of image that will be stored in glance
// The name of image that will be stored in glance.
ImageName string `json:"image_name,omitempty"` ImageName string `json:"image_name,omitempty"`
// Force image creation, usable if volume attached to instance
// Force image creation, usable if volume attached to instance.
Force bool `json:"force,omitempty"` Force bool `json:"force,omitempty"`
} }
@ -239,15 +249,21 @@ func (opts UploadImageOpts) ToVolumeUploadImageMap() (map[string]interface{}, er
return gophercloud.BuildRequestBody(opts, "os-volume_upload_image") return gophercloud.BuildRequestBody(opts, "os-volume_upload_image")
} }
// UploadImage will upload image base on the values in UploadImageOptsBuilder // UploadImage will upload an image based on the values in UploadImageOptsBuilder.
func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageOptsBuilder) (r UploadImageResult) { func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageOptsBuilder) (r UploadImageResult) {
b, err := opts.ToVolumeUploadImageMap() b, err := opts.ToVolumeUploadImageMap()
if err != nil { if err != nil {
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(uploadURL(client, id), b, nil, &gophercloud.RequestOpts{ _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
return return
} }
// ForceDelete will delete the volume regardless of state.
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-force_delete": ""}, nil, nil)
return
}

View File

@ -1,48 +1,68 @@
package volumeactions package volumeactions
import "github.com/gophercloud/gophercloud" import (
"encoding/json"
"time"
// AttachResult contains the response body and error from a Get request. "github.com/gophercloud/gophercloud"
)
// AttachResult contains the response body and error from an Attach request.
type AttachResult struct { type AttachResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// BeginDetachingResult contains the response body and error from a Get request. // BeginDetachingResult contains the response body and error from a BeginDetach
// request.
type BeginDetachingResult struct { type BeginDetachingResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// DetachResult contains the response body and error from a Get request. // DetachResult contains the response body and error from a Detach request.
type DetachResult struct { type DetachResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// UploadImageResult contains the response body and error from a UploadImage request. // UploadImageResult contains the response body and error from an UploadImage
// request.
type UploadImageResult struct { type UploadImageResult struct {
gophercloud.ErrResult gophercloud.Result
} }
// ReserveResult contains the response body and error from a Get request. // ReserveResult contains the response body and error from a Reserve request.
type ReserveResult struct { type ReserveResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// UnreserveResult contains the response body and error from a Get request. // UnreserveResult contains the response body and error from an Unreserve
// request.
type UnreserveResult struct { type UnreserveResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// TerminateConnectionResult contains the response body and error from a Get request. // TerminateConnectionResult contains the response body and error from a
// TerminateConnection request.
type TerminateConnectionResult struct { type TerminateConnectionResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
type commonResult struct { // InitializeConnectionResult contains the response body and error from an
// InitializeConnection request.
type InitializeConnectionResult struct {
gophercloud.Result gophercloud.Result
} }
// Extract will get the Volume object out of the commonResult object. // ExtendSizeResult contains the response body and error from an ExtendSize request.
func (r commonResult) Extract() (map[string]interface{}, error) { type ExtendSizeResult struct {
gophercloud.ErrResult
}
// Extract will get the connection information out of the
// InitializeConnectionResult object.
//
// This will be a generic map[string]interface{} and the results will be
// dependent on the type of connection made.
func (r InitializeConnectionResult) Extract() (map[string]interface{}, error) {
var s struct { var s struct {
ConnectionInfo map[string]interface{} `json:"connection_info"` ConnectionInfo map[string]interface{} `json:"connection_info"`
} }
@ -50,12 +70,122 @@ func (r commonResult) Extract() (map[string]interface{}, error) {
return s.ConnectionInfo, err return s.ConnectionInfo, err
} }
// InitializeConnectionResult contains the response body and error from a Get request. // ImageVolumeType contains volume type information obtained from UploadImage
type InitializeConnectionResult struct { // action.
commonResult type ImageVolumeType struct {
// The ID of a volume type.
ID string `json:"id"`
// Human-readable display name for the volume type.
Name string `json:"name"`
// Human-readable description for the volume type.
Description string `json:"display_description"`
// Flag for public access.
IsPublic bool `json:"is_public"`
// Extra specifications for volume type.
ExtraSpecs map[string]interface{} `json:"extra_specs"`
// ID of quality of service specs.
QosSpecsID string `json:"qos_specs_id"`
// Flag for deletion status of volume type.
Deleted bool `json:"deleted"`
// The date when volume type was deleted.
DeletedAt time.Time `json:"-"`
// The date when volume type was created.
CreatedAt time.Time `json:"-"`
// The date when this volume was last updated.
UpdatedAt time.Time `json:"-"`
} }
// ExtendSizeResult contains the response body and error from an ExtendSize request. func (r *ImageVolumeType) UnmarshalJSON(b []byte) error {
type ExtendSizeResult struct { type tmp ImageVolumeType
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
DeletedAt gophercloud.JSONRFC3339MilliNoZ `json:"deleted_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = ImageVolumeType(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
r.DeletedAt = time.Time(s.DeletedAt)
return err
}
// VolumeImage contains information about volume uploaded to an image service.
type VolumeImage struct {
// The ID of a volume an image is created from.
VolumeID string `json:"id"`
// Container format, may be bare, ofv, ova, etc.
ContainerFormat string `json:"container_format"`
// Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc.
DiskFormat string `json:"disk_format"`
// Human-readable description for the volume.
Description string `json:"display_description"`
// The ID of the created image.
ImageID string `json:"image_id"`
// Human-readable display name for the image.
ImageName string `json:"image_name"`
// Size of the volume in GB.
Size int `json:"size"`
// Current status of the volume.
Status string `json:"status"`
// The date when this volume was last updated.
UpdatedAt time.Time `json:"-"`
// Volume type object of used volume.
VolumeType ImageVolumeType `json:"volume_type"`
}
func (r *VolumeImage) UnmarshalJSON(b []byte) error {
type tmp VolumeImage
var s struct {
tmp
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = VolumeImage(s.tmp)
r.UpdatedAt = time.Time(s.UpdatedAt)
return err
}
// Extract will get an object with info about the uploaded image out of the
// UploadImageResult object.
func (r UploadImageResult) Extract() (VolumeImage, error) {
var s struct {
VolumeImage VolumeImage `json:"os-volume_upload_image"`
}
err := r.ExtractInto(&s)
return s.VolumeImage, err
}
// ForceDeleteResult contains the response body and error from a ForceDelete request.
type ForceDeleteResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }

View File

@ -2,38 +2,6 @@ package volumeactions
import "github.com/gophercloud/gophercloud" import "github.com/gophercloud/gophercloud"
func attachURL(c *gophercloud.ServiceClient, id string) string { func actionURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("volumes", id, "action") return c.ServiceURL("volumes", id, "action")
} }
func beginDetachingURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func detachURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func uploadURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func reserveURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func unreserveURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func initializeConnectionURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func teminateConnectionURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func extendSizeURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}

View File

@ -110,8 +110,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see // to the volumes.Update function. For more information about the parameters, see
// the Volume object. // the Volume object.
type UpdateOpts struct { type UpdateOpts struct {
Name string `json:"display_name,omitempty"` Name *string `json:"display_name,omitempty"`
Description string `json:"display_description,omitempty"` Description *string `json:"display_description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
} }
@ -139,7 +139,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0 count := 0
id := "" id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -1,6 +1,9 @@
package volumes package volumes
import ( import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
@ -18,7 +21,7 @@ type Volume struct {
// Indicates whether this is a bootable volume. // Indicates whether this is a bootable volume.
Bootable string `json:"bootable"` Bootable string `json:"bootable"`
// The date when this volume was created. // The date when this volume was created.
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` CreatedAt time.Time `json:"-"`
// Human-readable description for the volume. // Human-readable description for the volume.
Description string `json:"display_description"` Description string `json:"display_description"`
// The type of volume to create, either SATA or SSD. // The type of volume to create, either SATA or SSD.
@ -35,6 +38,23 @@ type Volume struct {
Size int `json:"size"` Size int `json:"size"`
} }
func (r *Volume) UnmarshalJSON(b []byte) error {
type tmp Volume
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Volume(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
return err
}
// CreateResult contains the response body and error from a Create request. // CreateResult contains the response body and error from a Create request.
type CreateResult struct { type CreateResult struct {
commonResult commonResult

View File

@ -0,0 +1,5 @@
// Package snapshots provides information and interaction with snapshots in the
// OpenStack Block Storage service. A snapshot is a point in time copy of the
// data contained in an external storage volume, and can be controlled
// programmatically.
package snapshots

View File

@ -0,0 +1,175 @@
package snapshots
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToSnapshotCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Snapshot. This object is passed to
// the snapshots.Create function. For more information about these parameters,
// see the Snapshot object.
type CreateOpts struct {
VolumeID string `json:"volume_id" required:"true"`
Force bool `json:"force,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToSnapshotCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "snapshot")
}
// Create will create a new Snapshot based on the values in CreateOpts. To
// extract the Snapshot object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSnapshotCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// Delete will delete the existing Snapshot with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// Get retrieves the Snapshot with the provided ID. To extract the Snapshot
// object from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToSnapshotListQuery() (string, error)
}
// ListOpts hold options for listing Snapshots. It is passed to the
// snapshots.List function.
type ListOpts struct {
// AllTenants will retrieve snapshots of all tenants/projects.
AllTenants bool `q:"all_tenants"`
// Name will filter by the specified snapshot name.
Name string `q:"name"`
// Status will filter by the specified status.
Status string `q:"status"`
// TenantID will filter by a specific tenant/project ID.
// Setting AllTenants is required to use this.
TenantID string `q:"project_id"`
// VolumeID will filter by a specified volume ID.
VolumeID string `q:"volume_id"`
}
// ToSnapshotListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToSnapshotListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns Snapshots optionally limited by the conditions provided in
// ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToSnapshotListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return SnapshotPage{pagination.SinglePageBase(r)}
})
}
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateMetadataOptsBuilder interface {
ToSnapshotUpdateMetadataMap() (map[string]interface{}, error)
}
// UpdateMetadataOpts contain options for updating an existing Snapshot. This
// object is passed to the snapshots.Update function. For more information
// about the parameters, see the Snapshot object.
type UpdateMetadataOpts struct {
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of
// an UpdateMetadataOpts.
func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// UpdateMetadata will update the Snapshot with provided information. To
// extract the updated Snapshot from the response, call the ExtractMetadata
// method on the UpdateMetadataResult.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
b, err := opts.ToSnapshotUpdateMetadataMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateMetadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a snapshot's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractSnapshots(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"}
}
}

View File

@ -0,0 +1,120 @@
package snapshots
import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Snapshot contains all the information associated with a Cinder Snapshot.
type Snapshot struct {
// Unique identifier.
ID string `json:"id"`
// Date created.
CreatedAt time.Time `json:"-"`
// Date updated.
UpdatedAt time.Time `json:"-"`
// Display name.
Name string `json:"name"`
// Display description.
Description string `json:"description"`
// ID of the Volume from which this Snapshot was created.
VolumeID string `json:"volume_id"`
// Currect status of the Snapshot.
Status string `json:"status"`
// Size of the Snapshot, in GB.
Size int `json:"size"`
// User-defined key-value pairs.
Metadata map[string]string `json:"metadata"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// SnapshotPage is a pagination.Pager that is returned from a call to the List function.
type SnapshotPage struct {
pagination.SinglePageBase
}
func (r *Snapshot) UnmarshalJSON(b []byte) error {
type tmp Snapshot
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Snapshot(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
return err
}
// IsEmpty returns true if a SnapshotPage contains no Snapshots.
func (r SnapshotPage) IsEmpty() (bool, error) {
volumes, err := ExtractSnapshots(r)
return len(volumes) == 0, err
}
// ExtractSnapshots extracts and returns Snapshots. It is used while iterating over a snapshots.List call.
func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) {
var s struct {
Snapshots []Snapshot `json:"snapshots"`
}
err := (r.(SnapshotPage)).ExtractInto(&s)
return s.Snapshots, err
}
// UpdateMetadataResult contains the response body and error from an UpdateMetadata request.
type UpdateMetadataResult struct {
commonResult
}
// ExtractMetadata returns the metadata from a response from snapshots.UpdateMetadata.
func (r UpdateMetadataResult) ExtractMetadata() (map[string]interface{}, error) {
if r.Err != nil {
return nil, r.Err
}
m := r.Body.(map[string]interface{})["metadata"]
return m.(map[string]interface{}), nil
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Snapshot object out of the commonResult object.
func (r commonResult) Extract() (*Snapshot, error) {
var s struct {
Snapshot *Snapshot `json:"snapshot"`
}
err := r.ExtractInto(&s)
return s.Snapshot, err
}

View File

@ -0,0 +1,27 @@
package snapshots
import "github.com/gophercloud/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("snapshots")
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("snapshots", id)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}
func listURL(c *gophercloud.ServiceClient) string {
return createURL(c)
}
func metadataURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("snapshots", id, "metadata")
}
func updateMetadataURL(c *gophercloud.ServiceClient, id string) string {
return metadataURL(c, id)
}

View File

@ -0,0 +1,22 @@
package snapshots
import (
"github.com/gophercloud/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -61,9 +61,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return return
} }
// DeleteOptsBuilder allows extensions to add additional parameters to the
// Delete request.
type DeleteOptsBuilder interface {
ToVolumeDeleteQuery() (string, error)
}
// DeleteOpts contains options for deleting a Volume. This object is passed to
// the volumes.Delete function.
type DeleteOpts struct {
// Delete all snapshots of this volume as well.
Cascade bool `q:"cascade"`
}
// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// Delete will delete the existing Volume with the provided ID. // Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) url := deleteURL(client, id)
if opts != nil {
query, err := opts.ToVolumeDeleteQuery()
if err != nil {
r.Err = err
return
}
url += query
}
_, r.Err = client.Delete(url, nil)
return return
} }
@ -83,14 +111,34 @@ type ListOptsBuilder interface {
// ListOpts holds options for listing Volumes. It is passed to the volumes.List // ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function. // function.
type ListOpts struct { type ListOpts struct {
// admin-only option. Set it to true to see all tenant volumes. // AllTenants will retrieve volumes of all tenants/projects.
AllTenants bool `q:"all_tenants"` AllTenants bool `q:"all_tenants"`
// List only volumes that contain Metadata.
// Metadata will filter results based on specified metadata.
Metadata map[string]string `q:"metadata"` Metadata map[string]string `q:"metadata"`
// List only volumes that have Name as the display name.
// Name will filter by the specified volume name.
Name string `q:"name"` Name string `q:"name"`
// List only volumes that have a status of Status.
// Status will filter by the specified status.
Status string `q:"status"` Status string `q:"status"`
// TenantID will filter by a specific tenant/project ID.
// Setting AllTenants is required for this.
TenantID string `q:"project_id"`
// Comma-separated list of sort keys and optional sort directions in the
// form of <key>[:<direction>].
Sort string `q:"sort"`
// Requests a page size of items.
Limit int `q:"limit"`
// Used in conjunction with limit to return a slice of items.
Offset int `q:"offset"`
// The ID of the last-seen item.
Marker string `q:"marker"`
} }
// ToVolumeListQuery formats a ListOpts into a query string. // ToVolumeListQuery formats a ListOpts into a query string.
@ -111,7 +159,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa
} }
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return VolumePage{pagination.SinglePageBase(r)} return VolumePage{pagination.LinkedPageBase{PageResult: r}}
}) })
} }
@ -125,8 +173,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see // to the volumes.Update function. For more information about the parameters, see
// the Volume object. // the Volume object.
type UpdateOpts struct { type UpdateOpts struct {
Name string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Description string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
} }
@ -154,7 +202,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0 count := 0
id := "" id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -98,7 +98,7 @@ func (r *Volume) UnmarshalJSON(b []byte) error {
// VolumePage is a pagination.pager that is returned from a call to the List function. // VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct { type VolumePage struct {
pagination.SinglePageBase pagination.LinkedPageBase
} }
// IsEmpty returns true if a ListResult contains no Volumes. // IsEmpty returns true if a ListResult contains no Volumes.
@ -107,6 +107,19 @@ func (r VolumePage) IsEmpty() (bool, error) {
return len(volumes) == 0, err return len(volumes) == 0, err
} }
// NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (r VolumePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"volumes_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. // ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) { func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s []Volume var s []Volume

View File

@ -0,0 +1,5 @@
// Package snapshots provides information and interaction with snapshots in the
// OpenStack Block Storage service. A snapshot is a point in time copy of the
// data contained in an external storage volume, and can be controlled
// programmatically.
package snapshots

View File

@ -0,0 +1,186 @@
package snapshots
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToSnapshotCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Snapshot. This object is passed to
// the snapshots.Create function. For more information about these parameters,
// see the Snapshot object.
type CreateOpts struct {
VolumeID string `json:"volume_id" required:"true"`
Force bool `json:"force,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToSnapshotCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "snapshot")
}
// Create will create a new Snapshot based on the values in CreateOpts. To
// extract the Snapshot object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSnapshotCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// Delete will delete the existing Snapshot with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// Get retrieves the Snapshot with the provided ID. To extract the Snapshot
// object from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToSnapshotListQuery() (string, error)
}
type ListOpts struct {
// AllTenants will retrieve snapshots of all tenants/projects.
AllTenants bool `q:"all_tenants"`
// Name will filter by the specified snapshot name.
Name string `q:"name"`
// Status will filter by the specified status.
Status string `q:"status"`
// TenantID will filter by a specific tenant/project ID.
// Setting AllTenants is required to use this.
TenantID string `q:"project_id"`
// VolumeID will filter by a specified volume ID.
VolumeID string `q:"volume_id"`
// Comma-separated list of sort keys and optional sort directions in the
// form of <key>[:<direction>].
Sort string `q:"sort"`
// Requests a page size of items.
Limit int `q:"limit"`
// Used in conjunction with limit to return a slice of items.
Offset int `q:"offset"`
// The ID of the last-seen item.
Marker string `q:"marker"`
}
// ToSnapshotListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToSnapshotListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns Snapshots optionally limited by the conditions provided in
// ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToSnapshotListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return SnapshotPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateMetadataOptsBuilder interface {
ToSnapshotUpdateMetadataMap() (map[string]interface{}, error)
}
// UpdateMetadataOpts contain options for updating an existing Snapshot. This
// object is passed to the snapshots.Update function. For more information
// about the parameters, see the Snapshot object.
type UpdateMetadataOpts struct {
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of
// an UpdateMetadataOpts.
func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// UpdateMetadata will update the Snapshot with provided information. To
// extract the updated Snapshot from the response, call the ExtractMetadata
// method on the UpdateMetadataResult.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
b, err := opts.ToSnapshotUpdateMetadataMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateMetadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a snapshot's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractSnapshots(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"}
}
}

View File

@ -0,0 +1,132 @@
package snapshots
import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Snapshot contains all the information associated with a Cinder Snapshot.
type Snapshot struct {
// Unique identifier.
ID string `json:"id"`
// Date created.
CreatedAt time.Time `json:"-"`
// Date updated.
UpdatedAt time.Time `json:"-"`
// Display name.
Name string `json:"name"`
// Display description.
Description string `json:"description"`
// ID of the Volume from which this Snapshot was created.
VolumeID string `json:"volume_id"`
// Currect status of the Snapshot.
Status string `json:"status"`
// Size of the Snapshot, in GB.
Size int `json:"size"`
// User-defined key-value pairs.
Metadata map[string]string `json:"metadata"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// SnapshotPage is a pagination.Pager that is returned from a call to the List function.
type SnapshotPage struct {
pagination.LinkedPageBase
}
// UnmarshalJSON converts our JSON API response into our snapshot struct
func (r *Snapshot) UnmarshalJSON(b []byte) error {
type tmp Snapshot
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Snapshot(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
return err
}
// IsEmpty returns true if a SnapshotPage contains no Snapshots.
func (r SnapshotPage) IsEmpty() (bool, error) {
volumes, err := ExtractSnapshots(r)
return len(volumes) == 0, err
}
func (page SnapshotPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"snapshots_links"`
}
err := page.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractSnapshots extracts and returns Snapshots. It is used while iterating over a snapshots.List call.
func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) {
var s struct {
Snapshots []Snapshot `json:"snapshots"`
}
err := (r.(SnapshotPage)).ExtractInto(&s)
return s.Snapshots, err
}
// UpdateMetadataResult contains the response body and error from an UpdateMetadata request.
type UpdateMetadataResult struct {
commonResult
}
// ExtractMetadata returns the metadata from a response from snapshots.UpdateMetadata.
func (r UpdateMetadataResult) ExtractMetadata() (map[string]interface{}, error) {
if r.Err != nil {
return nil, r.Err
}
m := r.Body.(map[string]interface{})["metadata"]
return m.(map[string]interface{}), nil
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Snapshot object out of the commonResult object.
func (r commonResult) Extract() (*Snapshot, error) {
var s struct {
Snapshot *Snapshot `json:"snapshot"`
}
err := r.ExtractInto(&s)
return s.Snapshot, err
}

View File

@ -0,0 +1,27 @@
package snapshots
import "github.com/gophercloud/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("snapshots")
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("snapshots", id)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}
func listURL(c *gophercloud.ServiceClient) string {
return createURL(c)
}
func metadataURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("snapshots", id, "metadata")
}
func updateMetadataURL(c *gophercloud.ServiceClient, id string) string {
return metadataURL(c, id)
}

View File

@ -0,0 +1,22 @@
package snapshots
import (
"github.com/gophercloud/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,5 @@
// Package volumes provides information and interaction with volumes in the
// OpenStack Block Storage service. A volume is a detachable block storage
// device, akin to a USB hard drive. It can only be attached to one instance at
// a time.
package volumes

View File

@ -0,0 +1,237 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
// The size of the volume, in GB
Size int `json:"size" required:"true"`
// The availability zone
AvailabilityZone string `json:"availability_zone,omitempty"`
// ConsistencyGroupID is the ID of a consistency group
ConsistencyGroupID string `json:"consistencygroup_id,omitempty"`
// The volume description
Description string `json:"description,omitempty"`
// One or more metadata key and value pairs to associate with the volume
Metadata map[string]string `json:"metadata,omitempty"`
// The volume name
Name string `json:"name,omitempty"`
// the ID of the existing volume snapshot
SnapshotID string `json:"snapshot_id,omitempty"`
// SourceReplica is a UUID of an existing volume to replicate with
SourceReplica string `json:"source_replica,omitempty"`
// the ID of the existing volume
SourceVolID string `json:"source_volid,omitempty"`
// The ID of the image from which you want to create the volume.
// Required to create a bootable volume.
ImageID string `json:"imageRef,omitempty"`
// The associated volume type
VolumeType string `json:"volume_type,omitempty"`
// Multiattach denotes if the volume is multi-attach capable.
Multiattach bool `json:"multiattach,omitempty"`
}
// ToVolumeCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Create will create a new Volume based on the values in CreateOpts. To extract
// the Volume object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToVolumeCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// DeleteOptsBuilder allows extensions to add additional parameters to the
// Delete request.
type DeleteOptsBuilder interface {
ToVolumeDeleteQuery() (string, error)
}
// DeleteOpts contains options for deleting a Volume. This object is passed to
// the volumes.Delete function.
type DeleteOpts struct {
// Delete all snapshots of this volume as well.
Cascade bool `q:"cascade"`
}
// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) {
url := deleteURL(client, id)
if opts != nil {
query, err := opts.ToVolumeDeleteQuery()
if err != nil {
r.Err = err
return
}
url += query
}
_, r.Err = client.Delete(url, nil)
return
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToVolumeListQuery() (string, error)
}
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
// AllTenants will retrieve volumes of all tenants/projects.
AllTenants bool `q:"all_tenants"`
// Metadata will filter results based on specified metadata.
Metadata map[string]string `q:"metadata"`
// Name will filter by the specified volume name.
Name string `q:"name"`
// Status will filter by the specified status.
Status string `q:"status"`
// TenantID will filter by a specific tenant/project ID.
// Setting AllTenants is required for this.
TenantID string `q:"project_id"`
// Comma-separated list of sort keys and optional sort directions in the
// form of <key>[:<direction>].
Sort string `q:"sort"`
// Requests a page size of items.
Limit int `q:"limit"`
// Used in conjunction with limit to return a slice of items.
Offset int `q:"offset"`
// The ID of the last-seen item.
Marker string `q:"marker"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToVolumeListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return VolumePage{pagination.LinkedPageBase{PageResult: r}}
})
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToVolumeUpdateMap assembles a request body based on the contents of an
// UpdateOpts.
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToVolumeUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractVolumes(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
}
}

View File

@ -0,0 +1,170 @@
package volumes
import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Attachment represents a Volume Attachment record
type Attachment struct {
AttachedAt time.Time `json:"-"`
AttachmentID string `json:"attachment_id"`
Device string `json:"device"`
HostName string `json:"host_name"`
ID string `json:"id"`
ServerID string `json:"server_id"`
VolumeID string `json:"volume_id"`
}
// UnmarshalJSON is our unmarshalling helper
func (r *Attachment) UnmarshalJSON(b []byte) error {
type tmp Attachment
var s struct {
tmp
AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Attachment(s.tmp)
r.AttachedAt = time.Time(s.AttachedAt)
return err
}
// Volume contains all the information associated with an OpenStack Volume.
type Volume struct {
// Unique identifier for the volume.
ID string `json:"id"`
// Current status of the volume.
Status string `json:"status"`
// Size of the volume in GB.
Size int `json:"size"`
// AvailabilityZone is which availability zone the volume is in.
AvailabilityZone string `json:"availability_zone"`
// The date when this volume was created.
CreatedAt time.Time `json:"-"`
// The date when this volume was last updated
UpdatedAt time.Time `json:"-"`
// Instances onto which the volume is attached.
Attachments []Attachment `json:"attachments"`
// Human-readable display name for the volume.
Name string `json:"name"`
// Human-readable description for the volume.
Description string `json:"description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `json:"volume_type"`
// The ID of the snapshot from which the volume was created
SnapshotID string `json:"snapshot_id"`
// The ID of another block storage volume from which the current volume was created
SourceVolID string `json:"source_volid"`
// Arbitrary key-value pairs defined by the user.
Metadata map[string]string `json:"metadata"`
// UserID is the id of the user who created the volume.
UserID string `json:"user_id"`
// Indicates whether this is a bootable volume.
Bootable string `json:"bootable"`
// Encrypted denotes if the volume is encrypted.
Encrypted bool `json:"encrypted"`
// ReplicationStatus is the status of replication.
ReplicationStatus string `json:"replication_status"`
// ConsistencyGroupID is the consistency group ID.
ConsistencyGroupID string `json:"consistencygroup_id"`
// Multiattach denotes if the volume is multi-attach capable.
Multiattach bool `json:"multiattach"`
}
// UnmarshalJSON another unmarshalling function
func (r *Volume) UnmarshalJSON(b []byte) error {
type tmp Volume
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Volume(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
return err
}
// VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct {
pagination.LinkedPageBase
}
// IsEmpty returns true if a ListResult contains no Volumes.
func (r VolumePage) IsEmpty() (bool, error) {
volumes, err := ExtractVolumes(r)
return len(volumes) == 0, err
}
func (page VolumePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"volumes_links"`
}
err := page.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s []Volume
err := ExtractVolumesInto(r, &s)
return s, err
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) {
var s Volume
err := r.ExtractInto(&s)
return &s, err
}
// ExtractInto converts our response data into a volume struct
func (r commonResult) ExtractInto(v interface{}) error {
return r.Result.ExtractIntoStructPtr(v, "volume")
}
// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes
func ExtractVolumesInto(r pagination.Page, v interface{}) error {
return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes")
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// UpdateResult contains the response body and error from an Update request.
type UpdateResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

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

View File

@ -0,0 +1,22 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -2,7 +2,6 @@ package openstack
import ( import (
"fmt" "fmt"
"net/url"
"reflect" "reflect"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
@ -12,43 +11,66 @@ import (
) )
const ( const (
v20 = "v2.0" // v2 represents Keystone v2.
v30 = "v3.0" // 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. NewClient prepares an unauthenticated ProviderClient instance.
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly, Most users will probably prefer using the AuthenticatedClient function
// for example. 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) { func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
u, err := url.Parse(endpoint) base, err := utils.BaseEndpoint(endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
hadPath := u.Path != ""
u.Path, u.RawQuery, u.Fragment = "", "", ""
base := u.String()
endpoint = gophercloud.NormalizeURL(endpoint) endpoint = gophercloud.NormalizeURL(endpoint)
base = gophercloud.NormalizeURL(base) base = gophercloud.NormalizeURL(base)
if hadPath { p := new(gophercloud.ProviderClient)
return &gophercloud.ProviderClient{ p.IdentityBase = base
IdentityBase: base, p.IdentityEndpoint = endpoint
IdentityEndpoint: endpoint, p.UseTokenLock()
}, nil
}
return &gophercloud.ProviderClient{ return p, nil
IdentityBase: base,
IdentityEndpoint: "",
}, nil
} }
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and /*
// returns a Client instance that's ready to operate. AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses specified by the options, acquires a token, and returns a Provider Client
// the most recent identity service available to proceed. 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(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
*/
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
client, err := NewClient(options.IdentityEndpoint) client, err := NewClient(options.IdentityEndpoint)
if err != nil { if err != nil {
@ -62,11 +84,12 @@ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider
return client, nil return client, nil
} }
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint. // Authenticate or re-authenticate against the most recent identity service
// supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error { func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{ versions := []*utils.Version{
{ID: v20, Priority: 20, Suffix: "/v2.0/"}, {ID: v2, Priority: 20, Suffix: "/v2.0/"},
{ID: v30, Priority: 30, Suffix: "/v3/"}, {ID: v3, Priority: 30, Suffix: "/v3/"},
} }
chosen, endpoint, err := utils.ChooseVersion(client, versions) chosen, endpoint, err := utils.ChooseVersion(client, versions)
@ -75,9 +98,9 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp
} }
switch chosen.ID { switch chosen.ID {
case v20: case v2:
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{}) return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
case v30: case v3:
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{}) return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
default: default:
// The switch statement must be out of date from the versions list. // The switch statement must be out of date from the versions list.
@ -112,7 +135,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
result := tokens2.Create(v2Client, v2Opts) result := tokens2.Create(v2Client, v2Opts)
token, err := result.ExtractToken() err = client.SetTokenAndAuthResult(result)
if err != nil { if err != nil {
return err return err
} }
@ -123,12 +146,24 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
} }
if options.AllowReauth { 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 { client.ReauthFunc = func() error {
client.TokenID = "" err := v2auth(&tac, endpoint, tao, eo)
return v2auth(client, endpoint, options, eo) if err != nil {
return err
}
client.CopyTokenFrom(&tac)
return nil
} }
} }
client.TokenID = token.ID
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V2EndpointURL(catalog, opts) return V2EndpointURL(catalog, opts)
} }
@ -154,7 +189,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
result := tokens3.Create(v3Client, opts) result := tokens3.Create(v3Client, opts)
token, err := result.ExtractToken() err = client.SetTokenAndAuthResult(result)
if err != nil { if err != nil {
return err return err
} }
@ -164,12 +199,34 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return err return err
} }
client.TokenID = token.ID
if opts.CanReauth() { 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
default:
tao = opts
}
client.ReauthFunc = func() error { client.ReauthFunc = func() error {
client.TokenID = "" err := v3auth(&tac, endpoint, tao, eo)
return v3auth(client, endpoint, opts, eo) if err != nil {
return err
}
client.CopyTokenFrom(&tac)
return nil
} }
} }
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
@ -179,12 +236,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return nil return nil
} }
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service. // 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) { func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v2.0/" endpoint := client.IdentityBase + "v2.0/"
clientType := "identity"
var err error var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity") eo.ApplyDefaults(clientType)
endpoint, err = client.EndpointLocator(eo) endpoint, err = client.EndpointLocator(eo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -194,143 +253,174 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
return &gophercloud.ServiceClient{ return &gophercloud.ServiceClient{
ProviderClient: client, ProviderClient: client,
Endpoint: endpoint, Endpoint: endpoint,
Type: clientType,
}, nil }, nil
} }
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service. // 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) { func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v3/" endpoint := client.IdentityBase + "v3/"
clientType := "identity"
var err error var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity") eo.ApplyDefaults(clientType)
endpoint, err = client.EndpointLocator(eo) endpoint, err = client.EndpointLocator(eo)
if err != nil { if err != nil {
return nil, err 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{ return &gophercloud.ServiceClient{
ProviderClient: client, ProviderClient: client,
Endpoint: endpoint, Endpoint: endpoint,
Type: clientType,
}, nil }, nil
} }
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package. 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
}
// 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) { func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("object-store") return initClientOpts(client, eo, "object-store")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package. // NewComputeV2 creates a ServiceClient that may be used with the v2 compute
// package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("compute") return initClientOpts(client, eo, "compute")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package. // NewNetworkV2 creates a ServiceClient that may be used with the v2 network
// package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("network") sc, err := initClientOpts(client, eo, "network")
url, err := client.EndpointLocator(eo) sc.ResourceBase = sc.Endpoint + "v2.0/"
if err != nil { return sc, err
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2.0/",
}, nil
} }
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service. // 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) { func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volume") return initClientOpts(client, eo, "volume")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service. // 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) { func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volumev2") return initClientOpts(client, eo, "volumev2")
url, err := client.EndpointLocator(eo) }
if err != nil {
return nil, err // 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 &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil return initClientOpts(client, eo, "volumev3")
} }
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. // 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) { func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("sharev2") return initClientOpts(client, eo, "sharev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service. // CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("cdn") return initClientOpts(client, eo, "cdn")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service. // 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) { func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("orchestration") return initClientOpts(client, eo, "orchestration")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. // 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) { func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("database") return initClientOpts(client, eo, "database")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS service. // 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) { func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("dns") sc, err := initClientOpts(client, eo, "dns")
url, err := client.EndpointLocator(eo) sc.ResourceBase = sc.Endpoint + "v2/"
if err != nil { return sc, err
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2/"}, nil
} }
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service. // 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) { func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("image") sc, err := initClientOpts(client, eo, "image")
url, err := client.EndpointLocator(eo) sc.ResourceBase = sc.Endpoint + "v2/"
if err != nil { return sc, err
return nil, err }
}
return &gophercloud.ServiceClient{ProviderClient: client, // NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
Endpoint: url, // load balancer service.
ResourceBase: url + "v2/"}, nil func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "load-balancer")
sc.ResourceBase = sc.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")
} }

View File

@ -0,0 +1,52 @@
/*
Package attachinterfaces provides the ability to retrieve and manage network
interfaces through Nova.
Example of Listing a Server's Interfaces
serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f"
allPages, err := attachinterfaces.List(computeClient, serverID).AllPages()
if err != nil {
panic(err)
}
allInterfaces, err := attachinterfaces.ExtractInterfaces(allPages)
if err != nil {
panic(err)
}
for _, interface := range allInterfaces {
fmt.Printf("%+v\n", interface)
}
Example to Get a Server's Interface
portID = "0dde1598-b374-474e-986f-5b8dd1df1d4e"
serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f"
interface, err := attachinterfaces.Get(computeClient, serverID, portID).Extract()
if err != nil {
panic(err)
}
Example to Create a new Interface attachment on the Server
networkID := "8a5fe506-7e9f-4091-899b-96336909d93c"
serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f"
attachOpts := attachinterfaces.CreateOpts{
NetworkID: networkID,
}
interface, err := attachinterfaces.Create(computeClient, serverID, attachOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete an Interface attachment from the Server
portID = "0dde1598-b374-474e-986f-5b8dd1df1d4e"
serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f"
err := attachinterfaces.Delete(computeClient, serverID, portID).ExtractErr()
if err != nil {
panic(err)
}
*/
package attachinterfaces

View File

@ -0,0 +1,72 @@
package attachinterfaces
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// List makes a request against the nova API to list the server's interfaces.
func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
return pagination.NewPager(client, listInterfaceURL(client, serverID), func(r pagination.PageResult) pagination.Page {
return InterfacePage{pagination.SinglePageBase(r)}
})
}
// Get requests details on a single interface attachment by the server and port IDs.
func Get(client *gophercloud.ServiceClient, serverID, portID string) (r GetResult) {
_, r.Err = client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToAttachInterfacesCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies parameters of a new interface attachment.
type CreateOpts struct {
// PortID is the ID of the port for which you want to create an interface.
// The NetworkID and PortID parameters are mutually exclusive.
// If you do not specify the PortID parameter, the OpenStack Networking API
// v2.0 allocates a port and creates an interface for it on the network.
PortID string `json:"port_id,omitempty"`
// NetworkID is the ID of the network for which you want to create an interface.
// The NetworkID and PortID parameters are mutually exclusive.
// If you do not specify the NetworkID parameter, the OpenStack Networking
// API v2.0 uses the network information cache that is associated with the instance.
NetworkID string `json:"net_id,omitempty"`
// Slice of FixedIPs. If you request a specific FixedIP address without a
// NetworkID, the request returns a Bad Request (400) response code.
// Note: this uses the FixedIP struct, but only the IPAddress field can be used.
FixedIPs []FixedIP `json:"fixed_ips,omitempty"`
}
// ToAttachInterfacesCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToAttachInterfacesCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "interfaceAttachment")
}
// Create requests the creation of a new interface attachment on the server.
func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToAttachInterfacesCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Delete makes a request against the nova API to detach a single interface from the server.
// It needs server and port IDs to make a such request.
func Delete(client *gophercloud.ServiceClient, serverID, portID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteInterfaceURL(client, serverID, portID), nil)
return
}

View File

@ -0,0 +1,80 @@
package attachinterfaces
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type attachInterfaceResult struct {
gophercloud.Result
}
// Extract interprets any attachInterfaceResult as an Interface, if possible.
func (r attachInterfaceResult) Extract() (*Interface, error) {
var s struct {
Interface *Interface `json:"interfaceAttachment"`
}
err := r.ExtractInto(&s)
return s.Interface, err
}
// GetResult is the response from a Get operation. Call its Extract
// method to interpret it as an Interface.
type GetResult struct {
attachInterfaceResult
}
// CreateResult is the response from a Create operation. Call its Extract
// method to interpret it as an Interface.
type CreateResult struct {
attachInterfaceResult
}
// 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
}
// FixedIP represents a Fixed IP Address.
// This struct is also used when creating an attachment,
// but it is not possible to specify a SubnetID.
type FixedIP struct {
SubnetID string `json:"subnet_id,omitempty"`
IPAddress string `json:"ip_address"`
}
// Interface represents a network interface on a server.
type Interface struct {
PortState string `json:"port_state"`
FixedIPs []FixedIP `json:"fixed_ips"`
PortID string `json:"port_id"`
NetID string `json:"net_id"`
MACAddr string `json:"mac_addr"`
}
// InterfacePage 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 ExtractInterfaces call.
type InterfacePage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if an InterfacePage contains no interfaces.
func (r InterfacePage) IsEmpty() (bool, error) {
interfaces, err := ExtractInterfaces(r)
return len(interfaces) == 0, err
}
// ExtractInterfaces interprets the results of a single page from a List() call,
// producing a slice of Interface structs.
func ExtractInterfaces(r pagination.Page) ([]Interface, error) {
var s struct {
Interfaces []Interface `json:"interfaceAttachments"`
}
err := (r.(InterfacePage)).ExtractInto(&s)
return s.Interfaces, err
}

View File

@ -0,0 +1,18 @@
package attachinterfaces
import "github.com/gophercloud/gophercloud"
func listInterfaceURL(client *gophercloud.ServiceClient, serverID string) string {
return client.ServiceURL("servers", serverID, "os-interface")
}
func getInterfaceURL(client *gophercloud.ServiceClient, serverID, portID string) string {
return client.ServiceURL("servers", serverID, "os-interface", portID)
}
func createInterfaceURL(client *gophercloud.ServiceClient, serverID string) string {
return client.ServiceURL("servers", serverID, "os-interface")
}
func deleteInterfaceURL(client *gophercloud.ServiceClient, serverID, portID string) string {
return client.ServiceURL("servers", serverID, "os-interface", portID)
}

View File

@ -0,0 +1,61 @@
/*
Package availabilityzones provides the ability to get lists and detailed
availability zone information and to extend a server result with
availability zone information.
Example of Extend server result with Availability Zone Information:
type ServerWithAZ struct {
servers.Server
availabilityzones.ServerAvailabilityZoneExt
}
var allServers []ServerWithAZ
allPages, err := servers.List(client, nil).AllPages()
if err != nil {
panic("Unable to retrieve servers: %s", err)
}
err = servers.ExtractServersInto(allPages, &allServers)
if err != nil {
panic("Unable to extract servers: %s", err)
}
for _, server := range allServers {
fmt.Println(server.AvailabilityZone)
}
Example of Get Availability Zone Information
allPages, err := availabilityzones.List(computeClient).AllPages()
if err != nil {
panic(err)
}
availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages)
if err != nil {
panic(err)
}
for _, zoneInfo := range availabilityZoneInfo {
fmt.Printf("%+v\n", zoneInfo)
}
Example of Get Detailed Availability Zone Information
allPages, err := availabilityzones.ListDetail(computeClient).AllPages()
if err != nil {
panic(err)
}
availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages)
if err != nil {
panic(err)
}
for _, zoneInfo := range availabilityZoneInfo {
fmt.Printf("%+v\n", zoneInfo)
}
*/
package availabilityzones

View File

@ -0,0 +1,20 @@
package availabilityzones
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// List will return the existing availability zones.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
return AvailabilityZonePage{pagination.SinglePageBase(r)}
})
}
// ListDetail will return the existing availability zones with detailed information.
func ListDetail(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listDetailURL(client), func(r pagination.PageResult) pagination.Page {
return AvailabilityZonePage{pagination.SinglePageBase(r)}
})
}

View File

@ -1,12 +1,76 @@
package availabilityzones package availabilityzones
// ServerExt is an extension to the base Server object import (
type ServerExt struct { "encoding/json"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ServerAvailabilityZoneExt is an extension to the base Server object.
type ServerAvailabilityZoneExt struct {
// AvailabilityZone is the availabilty zone the server is in. // AvailabilityZone is the availabilty zone the server is in.
AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
} }
// ServiceState represents the state of a service in an AvailabilityZone.
type ServiceState struct {
Active bool `json:"active"`
Available bool `json:"available"`
UpdatedAt time.Time `json:"-"`
}
// UnmarshalJSON to override default // UnmarshalJSON to override default
func (r *ServerExt) UnmarshalJSON(b []byte) error { func (r *ServiceState) UnmarshalJSON(b []byte) error {
type tmp ServiceState
var s struct {
tmp
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = ServiceState(s.tmp)
r.UpdatedAt = time.Time(s.UpdatedAt)
return nil return nil
} }
// Services is a map of services contained in an AvailabilityZone.
type Services map[string]ServiceState
// Hosts is map of hosts/nodes contained in an AvailabilityZone.
// Each host can have multiple services.
type Hosts map[string]Services
// ZoneState represents the current state of the availability zone.
type ZoneState struct {
// Returns true if the availability zone is available
Available bool `json:"available"`
}
// AvailabilityZone contains all the information associated with an OpenStack
// AvailabilityZone.
type AvailabilityZone struct {
Hosts Hosts `json:"hosts"`
// The availability zone name
ZoneName string `json:"zoneName"`
ZoneState ZoneState `json:"zoneState"`
}
type AvailabilityZonePage struct {
pagination.SinglePageBase
}
// ExtractAvailabilityZones returns a slice of AvailabilityZones contained in a
// single page of results.
func ExtractAvailabilityZones(r pagination.Page) ([]AvailabilityZone, error) {
var s struct {
AvailabilityZoneInfo []AvailabilityZone `json:"availabilityZoneInfo"`
}
err := (r.(AvailabilityZonePage)).ExtractInto(&s)
return s.AvailabilityZoneInfo, err
}

View File

@ -0,0 +1,11 @@
package availabilityzones
import "github.com/gophercloud/gophercloud"
func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-availability-zone")
}
func listDetailURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-availability-zone", "detail")
}

View File

@ -0,0 +1,152 @@
/*
Package bootfromvolume extends a server create request with the ability to
specify block device options. This can be used to boot a server from a block
storage volume as well as specify multiple ephemeral disks upon creation.
It is recommended to refer to the Block Device Mapping documentation to see
all possible ways to configure a server's block devices at creation time:
https://docs.openstack.org/nova/latest/user/block-device-mapping.html
Note that this package implements `block_device_mapping_v2`.
Example of Creating a Server From an Image
This example will boot a server from an image and use a standard ephemeral
disk as the server's root disk. This is virtually no different than creating
a server without using block device mappings.
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
BootIndex: 0,
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationLocal,
SourceType: bootfromvolume.SourceImage,
UUID: "image-uuid",
},
}
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
FlavorRef: "flavor-uuid",
ImageRef: "image-uuid",
}
createOpts := bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
BlockDevice: blockDevices,
}
server, err := bootfromvolume.Create(client, createOpts).Extract()
if err != nil {
panic(err)
}
Example of Creating a Server From a New Volume
This example will create a block storage volume based on the given Image. The
server will use this volume as its root disk.
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceImage,
UUID: "image-uuid",
VolumeSize: 2,
},
}
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
FlavorRef: "flavor-uuid",
}
createOpts := bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
BlockDevice: blockDevices,
}
server, err := bootfromvolume.Create(client, createOpts).Extract()
if err != nil {
panic(err)
}
Example of Creating a Server From an Existing Volume
This example will create a server with an existing volume as its root disk.
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceVolume,
UUID: "volume-uuid",
},
}
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
FlavorRef: "flavor-uuid",
}
createOpts := bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
BlockDevice: blockDevices,
}
server, err := bootfromvolume.Create(client, createOpts).Extract()
if err != nil {
panic(err)
}
Example of Creating a Server with Multiple Ephemeral Disks
This example will create a server with multiple ephemeral disks. The first
block device will be based off of an existing Image. Each additional
ephemeral disks must have an index of -1.
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
BootIndex: 0,
DestinationType: bootfromvolume.DestinationLocal,
DeleteOnTermination: true,
SourceType: bootfromvolume.SourceImage,
UUID: "image-uuid",
VolumeSize: 5,
},
bootfromvolume.BlockDevice{
BootIndex: -1,
DestinationType: bootfromvolume.DestinationLocal,
DeleteOnTermination: true,
GuestFormat: "ext4",
SourceType: bootfromvolume.SourceBlank,
VolumeSize: 1,
},
bootfromvolume.BlockDevice{
BootIndex: -1,
DestinationType: bootfromvolume.DestinationLocal,
DeleteOnTermination: true,
GuestFormat: "ext4",
SourceType: bootfromvolume.SourceBlank,
VolumeSize: 1,
},
}
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
FlavorRef: "flavor-uuid",
ImageRef: "image-uuid",
}
createOpts := bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
BlockDevice: blockDevices,
}
server, err := bootfromvolume.Create(client, createOpts).Extract()
if err != nil {
panic(err)
}
*/
package bootfromvolume

View File

@ -67,6 +67,14 @@ type BlockDevice struct {
// VolumeSize is the size of the volume to create (in gigabytes). This can be // VolumeSize is the size of the volume to create (in gigabytes). This can be
// omitted for existing volumes. // omitted for existing volumes.
VolumeSize int `json:"volume_size,omitempty"` VolumeSize int `json:"volume_size,omitempty"`
// DeviceType specifies the device type of the block devices.
// Examples of this are disk, cdrom, floppy, lun, etc.
DeviceType string `json:"device_type,omitempty"`
// DiskBus is the bus type of the block devices.
// Examples of this are ide, usb, virtio, scsi, etc.
DiskBus string `json:"disk_bus,omitempty"`
} }
// CreateOptsExt is a structure that extends the server `CreateOpts` structure // CreateOptsExt is a structure that extends the server `CreateOpts` structure

View File

@ -5,6 +5,8 @@ import (
) )
// CreateResult temporarily contains the response from a Create call. // CreateResult temporarily contains the response from a Create call.
// It embeds the standard servers.CreateResults type and so can be used the
// same way as a standard server request result.
type CreateResult struct { type CreateResult struct {
os.CreateResult os.CreateResult
} }

View File

@ -1,3 +1,68 @@
// Package floatingips provides the ability to manage floating ips through /*
// nova-network Package floatingips provides the ability to manage floating ips through the
Nova API.
This API has been deprecated and will be removed from a future release of the
Nova API service.
For environements that support this extension, this package can be used
regardless of if either Neutron or nova-network is used as the cloud's network
service.
Example to List Floating IPs
allPages, err := floatingips.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allFloatingIPs, err := floatingips.ExtractFloatingIPs(allPages)
if err != nil {
panic(err)
}
for _, fip := range allFloatingIPs {
fmt.Printf("%+v\n", fip)
}
Example to Create a Floating IP
createOpts := floatingips.CreateOpts{
Pool: "nova",
}
fip, err := floatingips.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Floating IP
err := floatingips.Delete(computeClient, "floatingip-id").ExtractErr()
if err != nil {
panic(err)
}
Example to Associate a Floating IP With a Server
associateOpts := floatingips.AssociateOpts{
FloatingIP: "10.10.10.2",
}
err := floatingips.AssociateInstance(computeClient, "server-id", associateOpts).ExtractErr()
if err != nil {
panic(err)
}
Example to Disassociate a Floating IP From a Server
disassociateOpts := floatingips.DisassociateOpts{
FloatingIP: "10.10.10.2",
}
err := floatingips.DisassociateInstance(computeClient, "server-id", disassociateOpts).ExtractErr()
if err != nil {
panic(err)
}
*/
package floatingips package floatingips

View File

@ -12,15 +12,15 @@ func List(client *gophercloud.ServiceClient) pagination.Pager {
}) })
} }
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the // CreateOptsBuilder allows extensions to add additional parameters to the
// CreateOpts struct in this package does. // Create request.
type CreateOptsBuilder interface { type CreateOptsBuilder interface {
ToFloatingIPCreateMap() (map[string]interface{}, error) ToFloatingIPCreateMap() (map[string]interface{}, error)
} }
// CreateOpts specifies a Floating IP allocation request // CreateOpts specifies a Floating IP allocation request.
type CreateOpts struct { type CreateOpts struct {
// Pool is the pool of floating IPs to allocate one from // Pool is the pool of Floating IPs to allocate one from.
Pool string `json:"pool" required:"true"` Pool string `json:"pool" required:"true"`
} }
@ -29,7 +29,7 @@ func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "") return gophercloud.BuildRequestBody(opts, "")
} }
// Create requests the creation of a new floating IP // Create requests the creation of a new Floating IP.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToFloatingIPCreateMap() b, err := opts.ToFloatingIPCreateMap()
if err != nil { if err != nil {
@ -42,29 +42,30 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return return
} }
// Get returns data about a previously created FloatingIP. // Get returns data about a previously created Floating IP.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) _, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return return
} }
// Delete requests the deletion of a previous allocated FloatingIP. // Delete requests the deletion of a previous allocated Floating IP.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) _, r.Err = client.Delete(deleteURL(client, id), nil)
return return
} }
// AssociateOptsBuilder is the interface types must satfisfy to be used as // AssociateOptsBuilder allows extensions to add additional parameters to the
// Associate options // Associate request.
type AssociateOptsBuilder interface { type AssociateOptsBuilder interface {
ToFloatingIPAssociateMap() (map[string]interface{}, error) ToFloatingIPAssociateMap() (map[string]interface{}, error)
} }
// AssociateOpts specifies the required information to associate a floating IP with an instance // AssociateOpts specifies the required information to associate a Floating IP with an instance
type AssociateOpts struct { type AssociateOpts struct {
// FloatingIP is the floating IP to associate with an instance // FloatingIP is the Floating IP to associate with an instance.
FloatingIP string `json:"address" required:"true"` FloatingIP string `json:"address" required:"true"`
// FixedIP is an optional fixed IP address of the server
// FixedIP is an optional fixed IP address of the server.
FixedIP string `json:"fixed_address,omitempty"` FixedIP string `json:"fixed_address,omitempty"`
} }
@ -73,7 +74,7 @@ func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, er
return gophercloud.BuildRequestBody(opts, "addFloatingIp") return gophercloud.BuildRequestBody(opts, "addFloatingIp")
} }
// AssociateInstance pairs an allocated floating IP with an instance. // AssociateInstance pairs an allocated Floating IP with a server.
func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) { func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) {
b, err := opts.ToFloatingIPAssociateMap() b, err := opts.ToFloatingIPAssociateMap()
if err != nil { if err != nil {
@ -84,23 +85,24 @@ func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts
return return
} }
// DisassociateOptsBuilder is the interface types must satfisfy to be used as // DisassociateOptsBuilder allows extensions to add additional parameters to
// Disassociate options // the Disassociate request.
type DisassociateOptsBuilder interface { type DisassociateOptsBuilder interface {
ToFloatingIPDisassociateMap() (map[string]interface{}, error) ToFloatingIPDisassociateMap() (map[string]interface{}, error)
} }
// DisassociateOpts specifies the required information to disassociate a floating IP with an instance // DisassociateOpts specifies the required information to disassociate a
// Floating IP with a server.
type DisassociateOpts struct { type DisassociateOpts struct {
FloatingIP string `json:"address" required:"true"` FloatingIP string `json:"address" required:"true"`
} }
// ToFloatingIPDisassociateMap constructs a request body from AssociateOpts. // ToFloatingIPDisassociateMap constructs a request body from DisassociateOpts.
func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) { func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "removeFloatingIp") return gophercloud.BuildRequestBody(opts, "removeFloatingIp")
} }
// DisassociateInstance decouples an allocated floating IP from an instance // DisassociateInstance decouples an allocated Floating IP from an instance
func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, opts DisassociateOptsBuilder) (r DisassociateResult) { func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, opts DisassociateOptsBuilder) (r DisassociateResult) {
b, err := opts.ToFloatingIPDisassociateMap() b, err := opts.ToFloatingIPDisassociateMap()
if err != nil { if err != nil {

View File

@ -8,21 +8,21 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// A FloatingIP is an IP that can be associated with an instance // A FloatingIP is an IP that can be associated with a server.
type FloatingIP struct { type FloatingIP struct {
// ID is a unique ID of the Floating IP // ID is a unique ID of the Floating IP
ID string `json:"-"` ID string `json:"-"`
// FixedIP is the IP of the instance related to the Floating IP // FixedIP is a specific IP on the server to pair the Floating IP with.
FixedIP string `json:"fixed_ip,omitempty"` FixedIP string `json:"fixed_ip,omitempty"`
// InstanceID is the ID of the instance that is using the Floating IP // InstanceID is the ID of the server that is using the Floating IP.
InstanceID string `json:"instance_id"` InstanceID string `json:"instance_id"`
// IP is the actual Floating IP // IP is the actual Floating IP.
IP string `json:"ip"` IP string `json:"ip"`
// Pool is the pool of floating IPs that this floating IP belongs to // Pool is the pool of Floating IPs that this Floating IP belongs to.
Pool string `json:"pool"` Pool string `json:"pool"`
} }
@ -49,8 +49,7 @@ func (r *FloatingIP) UnmarshalJSON(b []byte) error {
return err return err
} }
// FloatingIPPage stores a single, only page of FloatingIPs // FloatingIPPage stores a single page of FloatingIPs from a List call.
// results from a List call.
type FloatingIPPage struct { type FloatingIPPage struct {
pagination.SinglePageBase pagination.SinglePageBase
} }
@ -61,8 +60,7 @@ func (page FloatingIPPage) IsEmpty() (bool, error) {
return len(va) == 0, err return len(va) == 0, err
} }
// ExtractFloatingIPs interprets a page of results as a slice of // ExtractFloatingIPs interprets a page of results as a slice of FloatingIPs.
// FloatingIPs.
func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
var s struct { var s struct {
FloatingIPs []FloatingIP `json:"floating_ips"` FloatingIPs []FloatingIP `json:"floating_ips"`
@ -86,32 +84,32 @@ func (r FloatingIPResult) Extract() (*FloatingIP, error) {
return s.FloatingIP, err return s.FloatingIP, err
} }
// CreateResult is the response from a Create operation. Call its Extract method to interpret it // CreateResult is the response from a Create operation. Call its Extract method
// as a FloatingIP. // to interpret it as a FloatingIP.
type CreateResult struct { type CreateResult struct {
FloatingIPResult FloatingIPResult
} }
// GetResult is the response from a Get operation. Call its Extract method to interpret it // GetResult is the response from a Get operation. Call its Extract method to
// as a FloatingIP. // interpret it as a FloatingIP.
type GetResult struct { type GetResult struct {
FloatingIPResult FloatingIPResult
} }
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if // DeleteResult is the response from a Delete operation. Call its ExtractErr
// the call succeeded or failed. // method to determine if the call succeeded or failed.
type DeleteResult struct { type DeleteResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// AssociateResult is the response from a Delete operation. Call its Extract method to determine if // AssociateResult is the response from a Delete operation. Call its ExtractErr
// the call succeeded or failed. // method to determine if the call succeeded or failed.
type AssociateResult struct { type AssociateResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// DisassociateResult is the response from a Delete operation. Call its Extract method to determine if // DisassociateResult is the response from a Delete operation. Call its
// the call succeeded or failed. // ExtractErr method to determine if the call succeeded or failed.
type DisassociateResult struct { type DisassociateResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }

View File

@ -1,3 +1,71 @@
// Package keypairs provides information and interaction with the Keypairs /*
// extension for the OpenStack Compute service. Package keypairs provides the ability to manage key pairs as well as create
servers with a specified key pair.
Example to List Key Pairs
allPages, err := keypairs.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allKeyPairs, err := keypairs.ExtractKeyPairs(allPages)
if err != nil {
panic(err)
}
for _, kp := range allKeyPairs {
fmt.Printf("%+v\n", kp)
}
Example to Create a Key Pair
createOpts := keypairs.CreateOpts{
Name: "keypair-name",
}
keypair, err := keypairs.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", keypair)
Example to Import a Key Pair
createOpts := keypairs.CreateOpts{
Name: "keypair-name",
PublicKey: "public-key",
}
keypair, err := keypairs.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Key Pair
err := keypairs.Delete(computeClient, "keypair-name").ExtractErr()
if err != nil {
panic(err)
}
Example to Create a Server With a Key Pair
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
ImageRef: "image-uuid",
FlavorRef: "flavor-uuid",
}
createOpts := keypairs.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
KeyName: "keypair-name",
}
server, err := servers.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
*/
package keypairs package keypairs

View File

@ -9,11 +9,12 @@ import (
// CreateOptsExt adds a KeyPair option to the base CreateOpts. // CreateOptsExt adds a KeyPair option to the base CreateOpts.
type CreateOptsExt struct { type CreateOptsExt struct {
servers.CreateOptsBuilder servers.CreateOptsBuilder
// KeyName is the name of the key pair.
KeyName string `json:"key_name,omitempty"` KeyName string `json:"key_name,omitempty"`
} }
// ToServerCreateMap adds the key_name and, optionally, key_data options to // ToServerCreateMap adds the key_name to the base server creation options.
// the base server creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap() base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil { if err != nil {
@ -37,18 +38,19 @@ func List(client *gophercloud.ServiceClient) pagination.Pager {
}) })
} }
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the // CreateOptsBuilder allows extensions to add additional parameters to the
// CreateOpts struct in this package does. // Create request.
type CreateOptsBuilder interface { type CreateOptsBuilder interface {
ToKeyPairCreateMap() (map[string]interface{}, error) ToKeyPairCreateMap() (map[string]interface{}, error)
} }
// CreateOpts specifies keypair creation or import parameters. // CreateOpts specifies KeyPair creation or import parameters.
type CreateOpts struct { type CreateOpts struct {
// Name is a friendly name to refer to this KeyPair in other services. // Name is a friendly name to refer to this KeyPair in other services.
Name string `json:"name" required:"true"` Name string `json:"name" required:"true"`
// PublicKey [optional] is a pregenerated OpenSSH-formatted public key. If provided, this key
// will be imported and no new key will be created. // PublicKey [optional] is a pregenerated OpenSSH-formatted public key.
// If provided, this key will be imported and no new key will be created.
PublicKey string `json:"public_key,omitempty"` PublicKey string `json:"public_key,omitempty"`
} }
@ -57,8 +59,8 @@ func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "keypair") return gophercloud.BuildRequestBody(opts, "keypair")
} }
// Create requests the creation of a new keypair on the server, or to import a pre-existing // Create requests the creation of a new KeyPair on the server, or to import a
// keypair. // pre-existing keypair.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToKeyPairCreateMap() b, err := opts.ToKeyPairCreateMap()
if err != nil { if err != nil {

View File

@ -5,29 +5,33 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// KeyPair is an SSH key known to the OpenStack Cloud that is available to be injected into // KeyPair is an SSH key known to the OpenStack Cloud that is available to be
// servers. // injected into servers.
type KeyPair struct { type KeyPair struct {
// Name is used to refer to this keypair from other services within this region. // Name is used to refer to this keypair from other services within this
// region.
Name string `json:"name"` Name string `json:"name"`
// Fingerprint is a short sequence of bytes that can be used to authenticate or validate a longer // Fingerprint is a short sequence of bytes that can be used to authenticate
// public key. // or validate a longer public key.
Fingerprint string `json:"fingerprint"` Fingerprint string `json:"fingerprint"`
// PublicKey is the public key from this pair, in OpenSSH format. "ssh-rsa AAAAB3Nz..." // PublicKey is the public key from this pair, in OpenSSH format.
// "ssh-rsa AAAAB3Nz..."
PublicKey string `json:"public_key"` PublicKey string `json:"public_key"`
// PrivateKey is the private key from this pair, in PEM format. // PrivateKey is the private key from this pair, in PEM format.
// "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..." It is only present if this keypair was just // "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..."
// returned from a Create call // It is only present if this KeyPair was just returned from a Create call.
PrivateKey string `json:"private_key"` PrivateKey string `json:"private_key"`
// UserID is the user who owns this keypair. // UserID is the user who owns this KeyPair.
UserID string `json:"user_id"` UserID string `json:"user_id"`
} }
// KeyPairPage stores a single, only page of KeyPair results from a List call. // KeyPairPage stores a single page of all KeyPair results from a List call.
// Use the ExtractKeyPairs function to convert the results to a slice of
// KeyPairs.
type KeyPairPage struct { type KeyPairPage struct {
pagination.SinglePageBase pagination.SinglePageBase
} }
@ -58,7 +62,8 @@ type keyPairResult struct {
gophercloud.Result gophercloud.Result
} }
// Extract is a method that attempts to interpret any KeyPair resource response as a KeyPair struct. // Extract is a method that attempts to interpret any KeyPair resource response
// as a KeyPair struct.
func (r keyPairResult) Extract() (*KeyPair, error) { func (r keyPairResult) Extract() (*KeyPair, error) {
var s struct { var s struct {
KeyPair *KeyPair `json:"keypair"` KeyPair *KeyPair `json:"keypair"`
@ -67,20 +72,20 @@ func (r keyPairResult) Extract() (*KeyPair, error) {
return s.KeyPair, err return s.KeyPair, err
} }
// CreateResult is the response from a Create operation. Call its Extract method to interpret it // CreateResult is the response from a Create operation. Call its Extract method
// as a KeyPair. // to interpret it as a KeyPair.
type CreateResult struct { type CreateResult struct {
keyPairResult keyPairResult
} }
// GetResult is the response from a Get operation. Call its Extract method to interpret it // GetResult is the response from a Get operation. Call its Extract method to
// as a KeyPair. // interpret it as a KeyPair.
type GetResult struct { type GetResult struct {
keyPairResult keyPairResult
} }
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if // DeleteResult is the response from a Delete operation. Call its ExtractErr
// the call succeeded or failed. // method to determine if the call succeeded or failed.
type DeleteResult struct { type DeleteResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }

View File

@ -1,3 +1,76 @@
// Package schedulerhints enables instances to provide the OpenStack scheduler /*
// hints about where they should be placed in the cloud. Package schedulerhints extends the server create request with the ability to
specify additional parameters which determine where the server will be
created in the OpenStack cloud.
Example to Add a Server to a Server Group
schedulerHints := schedulerhints.SchedulerHints{
Group: "servergroup-uuid",
}
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
ImageRef: "image-uuid",
FlavorRef: "flavor-uuid",
}
createOpts := schedulerhints.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
SchedulerHints: schedulerHints,
}
server, err := servers.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Place Server B on a Different Host than Server A
schedulerHints := schedulerhints.SchedulerHints{
DifferentHost: []string{
"server-a-uuid",
}
}
serverCreateOpts := servers.CreateOpts{
Name: "server_b",
ImageRef: "image-uuid",
FlavorRef: "flavor-uuid",
}
createOpts := schedulerhints.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
SchedulerHints: schedulerHints,
}
server, err := servers.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Place Server B on the Same Host as Server A
schedulerHints := schedulerhints.SchedulerHints{
SameHost: []string{
"server-a-uuid",
}
}
serverCreateOpts := servers.CreateOpts{
Name: "server_b",
ImageRef: "image-uuid",
FlavorRef: "flavor-uuid",
}
createOpts := schedulerhints.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
SchedulerHints: schedulerHints,
}
server, err := servers.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
*/
package schedulerhints package schedulerhints

View File

@ -10,23 +10,29 @@ import (
) )
// SchedulerHints represents a set of scheduling hints that are passed to the // SchedulerHints represents a set of scheduling hints that are passed to the
// OpenStack scheduler // OpenStack scheduler.
type SchedulerHints struct { type SchedulerHints struct {
// Group specifies a Server Group to place the instance in. // Group specifies a Server Group to place the instance in.
Group string Group string
// DifferentHost will place the instance on a compute node that does not // DifferentHost will place the instance on a compute node that does not
// host the given instances. // host the given instances.
DifferentHost []string DifferentHost []string
// SameHost will place the instance on a compute node that hosts the given // SameHost will place the instance on a compute node that hosts the given
// instances. // instances.
SameHost []string SameHost []string
// Query is a conditional statement that results in compute nodes able to // Query is a conditional statement that results in compute nodes able to
// host the instance. // host the instance.
Query []interface{} Query []interface{}
// TargetCell specifies a cell name where the instance will be placed. // TargetCell specifies a cell name where the instance will be placed.
TargetCell string `json:"target_cell,omitempty"` TargetCell string `json:"target_cell,omitempty"`
// BuildNearHostIP specifies a subnet of compute nodes to host the instance. // BuildNearHostIP specifies a subnet of compute nodes to host the instance.
BuildNearHostIP string BuildNearHostIP string
// AdditionalProperies are arbitrary key/values that are not validated by nova. // AdditionalProperies are arbitrary key/values that are not validated by nova.
AdditionalProperties map[string]interface{} AdditionalProperties map[string]interface{}
} }
@ -79,8 +85,9 @@ func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interfa
sh["same_host"] = opts.SameHost sh["same_host"] = opts.SameHost
} }
/* Query can be something simple like: /*
[">=", "$free_ram_mb", 1024] Query can be something simple like:
[">=", "$free_ram_mb", 1024]
Or more complex like: Or more complex like:
['and', ['and',
@ -130,6 +137,7 @@ func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interfa
// CreateOptsExt adds a SchedulerHints option to the base CreateOpts. // CreateOptsExt adds a SchedulerHints option to the base CreateOpts.
type CreateOptsExt struct { type CreateOptsExt struct {
servers.CreateOptsBuilder servers.CreateOptsBuilder
// SchedulerHints provides a set of hints to the scheduler. // SchedulerHints provides a set of hints to the scheduler.
SchedulerHints CreateOptsBuilder SchedulerHints CreateOptsBuilder
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +1,26 @@
// Package tenantnetworks provides the ability for tenants to see information about the networks they have access to /*
Package tenantnetworks provides the ability for tenants to see information
about the networks they have access to.
This is a deprecated API and will be removed from the Nova API service in a
future version.
This API works in both Neutron and nova-network based OpenStack clouds.
Example to List Networks Available to a Tenant
allPages, err := tenantnetworks.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allNetworks, err := tenantnetworks.ExtractNetworks(allPages)
if err != nil {
panic(err)
}
for _, network := range allNetworks {
fmt.Printf("%+v\n", network)
}
*/
package tenantnetworks package tenantnetworks

View File

@ -5,7 +5,7 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// List returns a Pager that allows you to iterate over a collection of Network. // List returns a Pager that allows you to iterate over a collection of Networks.
func List(client *gophercloud.ServiceClient) pagination.Pager { func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
return NetworkPage{pagination.SinglePageBase(r)} return NetworkPage{pagination.SinglePageBase(r)}

View File

@ -5,7 +5,7 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// A Network represents a nova-network that an instance communicates on // A Network represents a network that a server communicates on.
type Network struct { type Network struct {
// CIDR is the IPv4 subnet. // CIDR is the IPv4 subnet.
CIDR string `json:"cidr"` CIDR string `json:"cidr"`
@ -17,8 +17,7 @@ type Network struct {
Name string `json:"label"` Name string `json:"label"`
} }
// NetworkPage stores a single, only page of Networks // NetworkPage stores a single page of all Networks results from a List call.
// results from a List call.
type NetworkPage struct { type NetworkPage struct {
pagination.SinglePageBase pagination.SinglePageBase
} }
@ -29,7 +28,7 @@ func (page NetworkPage) IsEmpty() (bool, error) {
return len(va) == 0, err return len(va) == 0, err
} }
// ExtractNetworks interprets a page of results as a slice of Networks // ExtractNetworks interprets a page of results as a slice of Network.
func ExtractNetworks(r pagination.Page) ([]Network, error) { func ExtractNetworks(r pagination.Page) ([]Network, error) {
var s struct { var s struct {
Networks []Network `json:"networks"` Networks []Network `json:"networks"`
@ -42,8 +41,8 @@ type NetworkResult struct {
gophercloud.Result gophercloud.Result
} }
// Extract is a method that attempts to interpret any Network resource // Extract is a method that attempts to interpret any Network resource response
// response as a Network struct. // as a Network struct.
func (r NetworkResult) Extract() (*Network, error) { func (r NetworkResult) Extract() (*Network, error) {
var s struct { var s struct {
Network *Network `json:"network"` Network *Network `json:"network"`
@ -52,8 +51,8 @@ func (r NetworkResult) Extract() (*Network, error) {
return s.Network, err return s.Network, err
} }
// GetResult is the response from a Get operation. Call its Extract method to interpret it // GetResult is the response from a Get operation. Call its Extract method to
// as a Network. // interpret it as a Network.
type GetResult struct { type GetResult struct {
NetworkResult NetworkResult
} }

View File

@ -1,3 +1,30 @@
// Package volumeattach provides the ability to attach and detach volumes /*
// to instances Package volumeattach provides the ability to attach and detach volumes
from servers.
Example to Attach a Volume
serverID := "7ac8686c-de71-4acb-9600-ec18b1a1ed6d"
volumeID := "87463836-f0e2-4029-abf6-20c8892a3103"
createOpts := volumeattach.CreateOpts{
Device: "/dev/vdc",
VolumeID: volumeID,
}
result, err := volumeattach.Create(computeClient, serverID, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Detach a Volume
serverID := "7ac8686c-de71-4acb-9600-ec18b1a1ed6d"
attachmentID := "ed081613-1c9b-4231-aa5e-ebfd4d87f983"
err := volumeattach.Delete(computeClient, serverID, attachmentID).ExtractErr()
if err != nil {
panic(err)
}
*/
package volumeattach package volumeattach

View File

@ -5,24 +5,26 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// List returns a Pager that allows you to iterate over a collection of VolumeAttachments. // List returns a Pager that allows you to iterate over a collection of
// VolumeAttachments.
func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager { func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page { return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page {
return VolumeAttachmentPage{pagination.SinglePageBase(r)} return VolumeAttachmentPage{pagination.SinglePageBase(r)}
}) })
} }
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the // CreateOptsBuilder allows extensions to add parameters to the Create request.
// CreateOpts struct in this package does.
type CreateOptsBuilder interface { type CreateOptsBuilder interface {
ToVolumeAttachmentCreateMap() (map[string]interface{}, error) ToVolumeAttachmentCreateMap() (map[string]interface{}, error)
} }
// CreateOpts specifies volume attachment creation or import parameters. // CreateOpts specifies volume attachment creation or import parameters.
type CreateOpts struct { type CreateOpts struct {
// Device is the device that the volume will attach to the instance as. Omit for "auto" // Device is the device that the volume will attach to the instance as.
// Omit for "auto".
Device string `json:"device,omitempty"` Device string `json:"device,omitempty"`
// VolumeID is the ID of the volume to attach to the instance
// VolumeID is the ID of the volume to attach to the instance.
VolumeID string `json:"volumeId" required:"true"` VolumeID string `json:"volumeId" required:"true"`
} }
@ -31,7 +33,7 @@ func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, er
return gophercloud.BuildRequestBody(opts, "volumeAttachment") return gophercloud.BuildRequestBody(opts, "volumeAttachment")
} }
// Create requests the creation of a new volume attachment on the server // Create requests the creation of a new volume attachment on the server.
func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToVolumeAttachmentCreateMap() b, err := opts.ToVolumeAttachmentCreateMap()
if err != nil { if err != nil {
@ -50,7 +52,8 @@ func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r Ge
return return
} }
// Delete requests the deletion of a previous stored VolumeAttachment from the server. // Delete requests the deletion of a previous stored VolumeAttachment from
// the server.
func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil) _, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil)
return return

View File

@ -5,35 +5,36 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// VolumeAttachment controls the attachment of a volume to an instance. // VolumeAttachment contains attachment information between a volume
// and server.
type VolumeAttachment struct { type VolumeAttachment struct {
// ID is a unique id of the attachment // ID is a unique id of the attachment.
ID string `json:"id"` ID string `json:"id"`
// Device is what device the volume is attached as // Device is what device the volume is attached as.
Device string `json:"device"` Device string `json:"device"`
// VolumeID is the ID of the attached volume // VolumeID is the ID of the attached volume.
VolumeID string `json:"volumeId"` VolumeID string `json:"volumeId"`
// ServerID is the ID of the instance that has the volume attached // ServerID is the ID of the instance that has the volume attached.
ServerID string `json:"serverId"` ServerID string `json:"serverId"`
} }
// VolumeAttachmentPage stores a single, only page of VolumeAttachments // VolumeAttachmentPage stores a single page all of VolumeAttachment
// results from a List call. // results from a List call.
type VolumeAttachmentPage struct { type VolumeAttachmentPage struct {
pagination.SinglePageBase pagination.SinglePageBase
} }
// IsEmpty determines whether or not a VolumeAttachmentsPage is empty. // IsEmpty determines whether or not a VolumeAttachmentPage is empty.
func (page VolumeAttachmentPage) IsEmpty() (bool, error) { func (page VolumeAttachmentPage) IsEmpty() (bool, error) {
va, err := ExtractVolumeAttachments(page) va, err := ExtractVolumeAttachments(page)
return len(va) == 0, err return len(va) == 0, err
} }
// ExtractVolumeAttachments interprets a page of results as a slice of // ExtractVolumeAttachments interprets a page of results as a slice of
// VolumeAttachments. // VolumeAttachment.
func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) { func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) {
var s struct { var s struct {
VolumeAttachments []VolumeAttachment `json:"volumeAttachments"` VolumeAttachments []VolumeAttachment `json:"volumeAttachments"`
@ -57,20 +58,20 @@ func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) {
return s.VolumeAttachment, err return s.VolumeAttachment, err
} }
// CreateResult is the response from a Create operation. Call its Extract method to interpret it // CreateResult is the response from a Create operation. Call its Extract method
// as a VolumeAttachment. // to interpret it as a VolumeAttachment.
type CreateResult struct { type CreateResult struct {
VolumeAttachmentResult VolumeAttachmentResult
} }
// GetResult is the response from a Get operation. Call its Extract method to interpret it // GetResult is the response from a Get operation. Call its Extract method to
// as a VolumeAttachment. // interpret it as a VolumeAttachment.
type GetResult struct { type GetResult struct {
VolumeAttachmentResult VolumeAttachmentResult
} }
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if // DeleteResult is the response from a Delete operation. Call its ExtractErr
// the call succeeded or failed. // method to determine if the call succeeded or failed.
type DeleteResult struct { type DeleteResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }

View File

@ -1,7 +1,137 @@
// Package flavors provides information and interaction with the flavor API /*
// resource in the OpenStack Compute service. Package flavors provides information and interaction with the flavor API
// in the OpenStack Compute service.
// A flavor is an available hardware configuration for a server. Each flavor
// has a unique combination of disk space, memory capacity and priority for CPU A flavor is an available hardware configuration for a server. Each flavor
// time. has a unique combination of disk space, memory capacity and priority for CPU
time.
Example to List Flavors
listOpts := flavors.ListOpts{
AccessType: flavors.PublicAccess,
}
allPages, err := flavors.ListDetail(computeClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allFlavors, err := flavors.ExtractFlavors(allPages)
if err != nil {
panic(err)
}
for _, flavor := range allFlavors {
fmt.Printf("%+v\n", flavor)
}
Example to Create a Flavor
createOpts := flavors.CreateOpts{
ID: "1",
Name: "m1.tiny",
Disk: gophercloud.IntToPointer(1),
RAM: 512,
VCPUs: 1,
RxTxFactor: 1.0,
}
flavor, err := flavors.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to List Flavor Access
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
allPages, err := flavors.ListAccesses(computeClient, flavorID).AllPages()
if err != nil {
panic(err)
}
allAccesses, err := flavors.ExtractAccesses(allPages)
if err != nil {
panic(err)
}
for _, access := range allAccesses {
fmt.Printf("%+v", access)
}
Example to Grant Access to a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
accessOpts := flavors.AddAccessOpts{
Tenant: "15153a0979884b59b0592248ef947921",
}
accessList, err := flavors.AddAccess(computeClient, flavor.ID, accessOpts).Extract()
if err != nil {
panic(err)
}
Example to Remove/Revoke Access to a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
accessOpts := flavors.RemoveAccessOpts{
Tenant: "15153a0979884b59b0592248ef947921",
}
accessList, err := flavors.RemoveAccess(computeClient, flavor.ID, accessOpts).Extract()
if err != nil {
panic(err)
}
Example to Create Extra Specs for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
createOpts := flavors.ExtraSpecsOpts{
"hw:cpu_policy": "CPU-POLICY",
"hw:cpu_thread_policy": "CPU-THREAD-POLICY",
}
createdExtraSpecs, err := flavors.CreateExtraSpecs(computeClient, flavorID, createOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", createdExtraSpecs)
Example to Get Extra Specs for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
extraSpecs, err := flavors.ListExtraSpecs(computeClient, flavorID).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", extraSpecs)
Example to Update Extra Specs for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
updateOpts := flavors.ExtraSpecsOpts{
"hw:cpu_thread_policy": "CPU-THREAD-POLICY-UPDATED",
}
updatedExtraSpec, err := flavors.UpdateExtraSpec(computeClient, flavorID, updateOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", updatedExtraSpec)
Example to Delete an Extra Spec for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
err := flavors.DeleteExtraSpec(computeClient, flavorID, "hw:cpu_thread_policy").ExtractErr()
if err != nil {
panic(err)
}
*/
package flavors package flavors

View File

@ -11,24 +11,66 @@ type ListOptsBuilder interface {
ToFlavorListQuery() (string, error) ToFlavorListQuery() (string, error)
} }
// ListOpts helps control the results returned by the List() function. /*
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20. AccessType maps to OpenStack's Flavor.is_public field. Although the is_public
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call. field is boolean, the request options are ternary, which is why AccessType is
type ListOpts struct { a string. The following values are allowed:
// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided. The AccessType arguement is optional, and if it is not supplied, OpenStack
returns the PublicAccess flavors.
*/
type AccessType string
const (
// PublicAccess returns public flavors and private flavors associated with
// that project.
PublicAccess AccessType = "true"
// PrivateAccess (admin only) returns private flavors, across all projects.
PrivateAccess AccessType = "false"
// AllAccess (admin only) returns public and private flavors across all
// projects.
AllAccess AccessType = "None"
)
/*
ListOpts filters the results returned by the List() function.
For example, a flavor with a minDisk field of 10 will not be returned if you
specify MinDisk set to 20.
Typically, software will use the last ID of the previous call to List to set
the Marker for the current call.
*/
type ListOpts struct {
// ChangesSince, if provided, instructs List to return only those things which
// have changed since the timestamp provided.
ChangesSince string `q:"changes-since"` ChangesSince string `q:"changes-since"`
// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria. // MinDisk and MinRAM, if provided, elides flavors which do not meet your
// criteria.
MinDisk int `q:"minDisk"` MinDisk int `q:"minDisk"`
MinRAM int `q:"minRam"` MinRAM int `q:"minRam"`
// SortDir allows to select sort direction.
// It can be "asc" or "desc" (default).
SortDir string `q:"sort_dir"`
// SortKey allows to sort by one of the flavors attributes.
// Default is flavorid.
SortKey string `q:"sort_key"`
// Marker and Limit control paging. // Marker and Limit control paging.
// Marker instructs List where to start listing from. // Marker instructs List where to start listing from.
Marker string `q:"marker"` Marker string `q:"marker"`
// Limit instructs List to refrain from sending excessively large lists of flavors. // Limit instructs List to refrain from sending excessively large lists of
// flavors.
Limit int `q:"limit"` Limit int `q:"limit"`
// AccessType, if provided, instructs List which set of flavors to return.
// If IsPublic not provided, flavors for the current project are returned.
AccessType AccessType `q:"is_public"`
} }
// ToFlavorListQuery formats a ListOpts into a query string. // ToFlavorListQuery formats a ListOpts into a query string.
@ -38,8 +80,8 @@ func (opts ListOpts) ToFlavorListQuery() (string, error) {
} }
// ListDetail instructs OpenStack to provide a list of flavors. // ListDetail instructs OpenStack to provide a list of flavors.
// You may provide criteria by which List curtails its results for easier processing. // You may provide criteria by which List curtails its results for easier
// See ListOpts for more details. // processing.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client) url := listURL(client)
if opts != nil { if opts != nil {
@ -58,31 +100,42 @@ type CreateOptsBuilder interface {
ToFlavorCreateMap() (map[string]interface{}, error) ToFlavorCreateMap() (map[string]interface{}, error)
} }
// CreateOpts is passed to Create to create a flavor // CreateOpts specifies parameters used for creating a flavor.
// Source:
// https://github.com/openstack/nova/blob/stable/newton/nova/api/openstack/compute/schemas/flavor_manage.py#L20
type CreateOpts struct { type CreateOpts struct {
// Name is the name of the flavor.
Name string `json:"name" required:"true"` Name string `json:"name" required:"true"`
// memory size, in MBs
RAM int `json:"ram" required:"true"` // RAM is the memory of the flavor, measured in MB.
RAM int `json:"ram" required:"true"`
// VCPUs is the number of vcpus for the flavor.
VCPUs int `json:"vcpus" required:"true"` VCPUs int `json:"vcpus" required:"true"`
// disk size, in GBs
Disk *int `json:"disk" required:"true"` // Disk the amount of root disk space, measured in GB.
ID string `json:"id,omitempty"` Disk *int `json:"disk" required:"true"`
// non-zero, positive
Swap *int `json:"swap,omitempty"` // ID is a unique ID for the flavor.
ID string `json:"id,omitempty"`
// Swap is the amount of swap space for the flavor, measured in MB.
Swap *int `json:"swap,omitempty"`
// RxTxFactor alters the network bandwidth of a flavor.
RxTxFactor float64 `json:"rxtx_factor,omitempty"` RxTxFactor float64 `json:"rxtx_factor,omitempty"`
IsPublic *bool `json:"os-flavor-access:is_public,omitempty"`
// ephemeral disk size, in GBs, non-zero, positive // IsPublic flags a flavor as being available to all projects or not.
IsPublic *bool `json:"os-flavor-access:is_public,omitempty"`
// Ephemeral is the amount of ephemeral disk space, measured in GB.
Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"`
} }
// ToFlavorCreateMap satisfies the CreateOptsBuilder interface // ToFlavorCreateMap constructs a request body from CreateOpts.
func (opts *CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { func (opts CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "flavor") return gophercloud.BuildRequestBody(opts, "flavor")
} }
// Create a flavor // Create requests the creation of a new flavor.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToFlavorCreateMap() b, err := opts.ToFlavorCreateMap()
if err != nil { if err != nil {
@ -95,14 +148,177 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return return
} }
// Get instructs OpenStack to provide details on a single flavor, identified by its ID. // Get retrieves details of a single flavor. Use ExtractFlavor to convert its
// Use ExtractFlavor to convert its result into a Flavor. // result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) _, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return return
} }
// IDFromName is a convienience function that returns a flavor's ID given its name. // Delete deletes the specified flavor ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// ListAccesses retrieves the tenants which have access to a flavor.
func ListAccesses(client *gophercloud.ServiceClient, id string) pagination.Pager {
url := accessURL(client, id)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return AccessPage{pagination.SinglePageBase(r)}
})
}
// AddAccessOptsBuilder allows extensions to add additional parameters to the
// AddAccess requests.
type AddAccessOptsBuilder interface {
ToFlavorAddAccessMap() (map[string]interface{}, error)
}
// AddAccessOpts represents options for adding access to a flavor.
type AddAccessOpts struct {
// Tenant is the project/tenant ID to grant access.
Tenant string `json:"tenant"`
}
// ToFlavorAddAccessMap constructs a request body from AddAccessOpts.
func (opts AddAccessOpts) ToFlavorAddAccessMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "addTenantAccess")
}
// AddAccess grants a tenant/project access to a flavor.
func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsBuilder) (r AddAccessResult) {
b, err := opts.ToFlavorAddAccessMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// RemoveAccessOptsBuilder allows extensions to add additional parameters to the
// RemoveAccess requests.
type RemoveAccessOptsBuilder interface {
ToFlavorRemoveAccessMap() (map[string]interface{}, error)
}
// RemoveAccessOpts represents options for removing access to a flavor.
type RemoveAccessOpts struct {
// Tenant is the project/tenant ID to grant access.
Tenant string `json:"tenant"`
}
// ToFlavorRemoveAccessMap constructs a request body from RemoveAccessOpts.
func (opts RemoveAccessOpts) ToFlavorRemoveAccessMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "removeTenantAccess")
}
// RemoveAccess removes/revokes a tenant/project access to a flavor.
func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAccessOptsBuilder) (r RemoveAccessResult) {
b, err := opts.ToFlavorRemoveAccessMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ExtraSpecs requests all the extra-specs for the given flavor ID.
func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) {
_, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
return
}
func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) {
_, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil)
return
}
// CreateExtraSpecsOptsBuilder allows extensions to add additional parameters to the
// CreateExtraSpecs requests.
type CreateExtraSpecsOptsBuilder interface {
ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error)
}
// ExtraSpecsOpts is a map that contains key-value pairs.
type ExtraSpecsOpts map[string]string
// ToFlavorExtraSpecsCreateMap assembles a body for a Create request based on
// the contents of ExtraSpecsOpts.
func (opts ExtraSpecsOpts) ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) {
return map[string]interface{}{"extra_specs": opts}, nil
}
// CreateExtraSpecs will create or update the extra-specs key-value pairs for
// the specified Flavor.
func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts CreateExtraSpecsOptsBuilder) (r CreateExtraSpecsResult) {
b, err := opts.ToFlavorExtraSpecsCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// UpdateExtraSpecOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateExtraSpecOptsBuilder interface {
ToFlavorExtraSpecUpdateMap() (map[string]string, string, error)
}
// ToFlavorExtraSpecUpdateMap assembles a body for an Update request based on
// the contents of a ExtraSpecOpts.
func (opts ExtraSpecsOpts) ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) {
if len(opts) != 1 {
err := gophercloud.ErrInvalidInput{}
err.Argument = "flavors.ExtraSpecOpts"
err.Info = "Must have 1 and only one key-value pair"
return nil, "", err
}
var key string
for k := range opts {
key = k
}
return opts, key, nil
}
// UpdateExtraSpec will updates the value of the specified flavor's extra spec
// for the key in opts.
func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts UpdateExtraSpecOptsBuilder) (r UpdateExtraSpecResult) {
b, key, err := opts.ToFlavorExtraSpecUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// DeleteExtraSpec will delete the key-value pair with the given key for the given
// flavor ID.
func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) {
_, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a flavor's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0 count := 0
id := "" id := ""

View File

@ -12,16 +12,26 @@ type commonResult struct {
gophercloud.Result gophercloud.Result
} }
// CreateResult is the response of a Get operations. Call its Extract method to
// interpret it as a Flavor.
type CreateResult struct { type CreateResult struct {
commonResult commonResult
} }
// GetResult temporarily holds the response from a Get call. // GetResult is the response of a Get operations. Call its Extract method to
// interpret it as a Flavor.
type GetResult struct { type GetResult struct {
commonResult commonResult
} }
// Extract provides access to the individual Flavor returned by the Get and Create functions. // DeleteResult is the result from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// Extract provides access to the individual Flavor returned by the Get and
// Create functions.
func (r commonResult) Extract() (*Flavor, error) { func (r commonResult) Extract() (*Flavor, error) {
var s struct { var s struct {
Flavor *Flavor `json:"flavor"` Flavor *Flavor `json:"flavor"`
@ -30,22 +40,35 @@ func (r commonResult) Extract() (*Flavor, error) {
return s.Flavor, err return s.Flavor, err
} }
// Flavor records represent (virtual) hardware configurations for server resources in a region. // Flavor represent (virtual) hardware configurations for server resources
// in a region.
type Flavor struct { type Flavor struct {
// The Id field contains the flavor's unique identifier. // ID is the flavor's unique ID.
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
ID string `json:"id"` ID string `json:"id"`
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
// Disk is the amount of root disk, measured in GB.
Disk int `json:"disk"` Disk int `json:"disk"`
RAM int `json:"ram"`
// The Name field provides a human-readable moniker for the flavor. // RAM is the amount of memory, measured in MB.
Name string `json:"name"` RAM int `json:"ram"`
// Name is the name of the flavor.
Name string `json:"name"`
// RxTxFactor describes bandwidth alterations of the flavor.
RxTxFactor float64 `json:"rxtx_factor"` RxTxFactor float64 `json:"rxtx_factor"`
// Swap indicates how much space is reserved for swap.
// If not provided, this field will be set to 0. // Swap is the amount of swap space, measured in MB.
Swap int `json:"swap"` Swap int `json:"-"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor. // VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `json:"vcpus"` VCPUs int `json:"vcpus"`
// IsPublic indicates whether the flavor is public.
IsPublic bool `json:"os-flavor-access:is_public"`
// Ephemeral is the amount of ephemeral disk space, measured in GB.
Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"`
} }
func (r *Flavor) UnmarshalJSON(b []byte) error { func (r *Flavor) UnmarshalJSON(b []byte) error {
@ -80,18 +103,19 @@ func (r *Flavor) UnmarshalJSON(b []byte) error {
return nil return nil
} }
// FlavorPage contains a single page of the response from a List call. // FlavorPage contains a single page of all flavors from a ListDetails call.
type FlavorPage struct { type FlavorPage struct {
pagination.LinkedPageBase pagination.LinkedPageBase
} }
// IsEmpty determines if a page contains any results. // IsEmpty determines if a FlavorPage contains any results.
func (page FlavorPage) IsEmpty() (bool, error) { func (page FlavorPage) IsEmpty() (bool, error) {
flavors, err := ExtractFlavors(page) flavors, err := ExtractFlavors(page)
return len(flavors) == 0, err return len(flavors) == 0, err
} }
// NextPageURL uses the response's embedded link reference to navigate to the next page of results. // NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (page FlavorPage) NextPageURL() (string, error) { func (page FlavorPage) NextPageURL() (string, error) {
var s struct { var s struct {
Links []gophercloud.Link `json:"flavors_links"` Links []gophercloud.Link `json:"flavors_links"`
@ -103,7 +127,8 @@ func (page FlavorPage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links) return gophercloud.ExtractNextURL(s.Links)
} }
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation. // ExtractFlavors provides access to the list of flavors in a page acquired
// from the ListDetail operation.
func ExtractFlavors(r pagination.Page) ([]Flavor, error) { func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
var s struct { var s struct {
Flavors []Flavor `json:"flavors"` Flavors []Flavor `json:"flavors"`
@ -111,3 +136,117 @@ func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
err := (r.(FlavorPage)).ExtractInto(&s) err := (r.(FlavorPage)).ExtractInto(&s)
return s.Flavors, err return s.Flavors, err
} }
// AccessPage contains a single page of all FlavorAccess entries for a flavor.
type AccessPage struct {
pagination.SinglePageBase
}
// IsEmpty indicates whether an AccessPage is empty.
func (page AccessPage) IsEmpty() (bool, error) {
v, err := ExtractAccesses(page)
return len(v) == 0, err
}
// ExtractAccesses interprets a page of results as a slice of FlavorAccess.
func ExtractAccesses(r pagination.Page) ([]FlavorAccess, error) {
var s struct {
FlavorAccesses []FlavorAccess `json:"flavor_access"`
}
err := (r.(AccessPage)).ExtractInto(&s)
return s.FlavorAccesses, err
}
type accessResult struct {
gophercloud.Result
}
// AddAccessResult is the response of an AddAccess operation. Call its
// Extract method to interpret it as a slice of FlavorAccess.
type AddAccessResult struct {
accessResult
}
// RemoveAccessResult is the response of a RemoveAccess operation. Call its
// Extract method to interpret it as a slice of FlavorAccess.
type RemoveAccessResult struct {
accessResult
}
// Extract provides access to the result of an access create or delete.
// The result will be all accesses that the flavor has.
func (r accessResult) Extract() ([]FlavorAccess, error) {
var s struct {
FlavorAccesses []FlavorAccess `json:"flavor_access"`
}
err := r.ExtractInto(&s)
return s.FlavorAccesses, err
}
// FlavorAccess represents an ACL of tenant access to a specific Flavor.
type FlavorAccess struct {
// FlavorID is the unique ID of the flavor.
FlavorID string `json:"flavor_id"`
// TenantID is the unique ID of the tenant.
TenantID string `json:"tenant_id"`
}
// Extract interprets any extraSpecsResult as ExtraSpecs, if possible.
func (r extraSpecsResult) Extract() (map[string]string, error) {
var s struct {
ExtraSpecs map[string]string `json:"extra_specs"`
}
err := r.ExtractInto(&s)
return s.ExtraSpecs, err
}
// extraSpecsResult 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 extraSpecsResult struct {
gophercloud.Result
}
// ListExtraSpecsResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type ListExtraSpecsResult struct {
extraSpecsResult
}
// CreateExtraSpecResult contains the result of a Create operation. Call its
// Extract method to interpret it as a map[string]interface.
type CreateExtraSpecsResult struct {
extraSpecsResult
}
// extraSpecResult contains the result of a call for individual a single
// key-value pair.
type extraSpecResult struct {
gophercloud.Result
}
// GetExtraSpecResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type GetExtraSpecResult struct {
extraSpecResult
}
// UpdateExtraSpecResult contains the result of an Update operation. Call its
// Extract method to interpret it as a map[string]interface.
type UpdateExtraSpecResult struct {
extraSpecResult
}
// DeleteExtraSpecResult contains the result of a Delete operation. Call its
// ExtractErr method to determine if the call succeeded or failed.
type DeleteExtraSpecResult struct {
gophercloud.ErrResult
}
// Extract interprets any extraSpecResult as an ExtraSpec, if possible.
func (r extraSpecResult) Extract() (map[string]string, error) {
var s map[string]string
err := r.ExtractInto(&s)
return s, err
}

View File

@ -15,3 +15,35 @@ func listURL(client *gophercloud.ServiceClient) string {
func createURL(client *gophercloud.ServiceClient) string { func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("flavors") return client.ServiceURL("flavors")
} }
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id)
}
func accessURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "os-flavor-access")
}
func accessActionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "action")
}
func extraSpecsListURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "os-extra_specs")
}
func extraSpecsGetURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("flavors", id, "os-extra_specs", key)
}
func extraSpecsCreateURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "os-extra_specs")
}
func extraSpecUpdateURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("flavors", id, "os-extra_specs", key)
}
func extraSpecDeleteURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("flavors", id, "os-extra_specs", key)
}

View File

@ -1,7 +1,32 @@
// Package images provides information and interaction with the image API /*
// resource in the OpenStack Compute service. Package images provides information and interaction with the images through
// the OpenStack Compute service.
// An image is a collection of files used to create or rebuild a server.
// Operators provide a number of pre-built OS images by default. You may also This API is deprecated and will be removed from a future version of the Nova
// create custom images from cloud servers you have launched. API service.
An image is a collection of files used to create or rebuild a server.
Operators provide a number of pre-built OS images by default. You may also
create custom images from cloud servers you have launched.
Example to List Images
listOpts := images.ListOpts{
Limit: 2,
}
allPages, err := images.ListDetail(computeClient, 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)
}
*/
package images package images

View File

@ -6,26 +6,33 @@ import (
) )
// ListOptsBuilder allows extensions to add additional parameters to the // ListOptsBuilder allows extensions to add additional parameters to the
// List request. // ListDetail request.
type ListOptsBuilder interface { type ListOptsBuilder interface {
ToImageListQuery() (string, error) ToImageListQuery() (string, error)
} }
// ListOpts contain options for limiting the number of Images returned from a call to ListDetail. // ListOpts contain options filtering Images returned from a call to ListDetail.
type ListOpts struct { type ListOpts struct {
// When the image last changed status (in date-time format). // ChangesSince filters Images based on the last changed status (in date-time
// format).
ChangesSince string `q:"changes-since"` ChangesSince string `q:"changes-since"`
// The number of Images to return.
// Limit limits the number of Images to return.
Limit int `q:"limit"` Limit int `q:"limit"`
// UUID of the Image at which to set a marker.
// Mark is an Image UUID at which to set a marker.
Marker string `q:"marker"` Marker string `q:"marker"`
// The name of the Image.
// Name is the name of the Image.
Name string `q:"name"` Name string `q:"name"`
// The name of the Server (in URL format).
// Server is the name of the Server (in URL format).
Server string `q:"server"` Server string `q:"server"`
// The current status of the Image.
// Status is the current status of the Image.
Status string `q:"status"` Status string `q:"status"`
// The value of the type of image (e.g. BASE, SERVER, ALL)
// Type is the type of image (e.g. BASE, SERVER, ALL).
Type string `q:"type"` Type string `q:"type"`
} }
@ -50,8 +57,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat
}) })
} }
// Get acquires additional detail about a specific image by ID. // Get returns data about a specific image by its ID.
// Use ExtractImage() to interpret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) _, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return return
@ -63,7 +69,8 @@ func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
return return
} }
// IDFromName is a convienience function that returns an image's ID given its name. // IDFromName is a convienience function that returns an image's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0 count := 0
id := "" id := ""

View File

@ -5,12 +5,14 @@ import (
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// GetResult temporarily stores a Get response. // GetResult is the response from a Get operation. Call its Extract method to
// interpret it as an Image.
type GetResult struct { type GetResult struct {
gophercloud.Result gophercloud.Result
} }
// DeleteResult represents the result of an image.Delete operation. // DeleteResult is the result from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct { type DeleteResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
@ -24,44 +26,53 @@ func (r GetResult) Extract() (*Image, error) {
return s.Image, err return s.Image, err
} }
// Image is used for JSON (un)marshalling. // Image represents an Image returned by the Compute API.
// It provides a description of an OS image.
type Image struct { type Image struct {
// ID contains the image's unique identifier. // ID is the unique ID of an image.
ID string ID string
// Created is the date when the image was created.
Created string Created string
// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image. // MinDisk is the minimum amount of disk a flavor must have to be able
// to create a server based on the image, measured in GB.
MinDisk int MinDisk int
MinRAM int
// MinRAM is the minimum amount of RAM a flavor must have to be able
// to create a server based on the image, measured in MB.
MinRAM int
// Name provides a human-readable moniker for the OS image. // Name provides a human-readable moniker for the OS image.
Name string Name string
// The Progress and Status fields indicate image-creation status. // The Progress and Status fields indicate image-creation status.
// Any usable image will have 100% progress.
Progress int Progress int
Status string
// Status is the current status of the image.
Status string
// Update is the date when the image was updated.
Updated string Updated string
// Metadata provides free-form key/value pairs that further describe the
// image.
Metadata map[string]interface{} Metadata map[string]interface{}
} }
// ImagePage contains a single page of results from a List operation. // ImagePage contains a single page of all Images returne from a ListDetail
// Use ExtractImages to convert it into a slice of usable structs. // operation. Use ExtractImages to convert it into a slice of usable structs.
type ImagePage struct { type ImagePage struct {
pagination.LinkedPageBase pagination.LinkedPageBase
} }
// IsEmpty returns true if a page contains no Image results. // IsEmpty returns true if an ImagePage contains no Image results.
func (page ImagePage) IsEmpty() (bool, error) { func (page ImagePage) IsEmpty() (bool, error) {
images, err := ExtractImages(page) images, err := ExtractImages(page)
return len(images) == 0, err return len(images) == 0, err
} }
// NextPageURL uses the response's embedded link reference to navigate to the next page of results. // NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (page ImagePage) NextPageURL() (string, error) { func (page ImagePage) NextPageURL() (string, error) {
var s struct { var s struct {
Links []gophercloud.Link `json:"images_links"` Links []gophercloud.Link `json:"images_links"`
@ -73,7 +84,8 @@ func (page ImagePage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links) return gophercloud.ExtractNextURL(s.Links)
} }
// ExtractImages converts a page of List results into a slice of usable Image structs. // ExtractImages converts a page of List results into a slice of usable Image
// structs.
func ExtractImages(r pagination.Page) ([]Image, error) { func ExtractImages(r pagination.Page) ([]Image, error) {
var s struct { var s struct {
Images []Image `json:"images"` Images []Image `json:"images"`

View File

@ -1,6 +1,115 @@
// Package servers provides information and interaction with the server API /*
// resource in the OpenStack Compute service. 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. 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 package servers

View File

@ -0,0 +1,11 @@
package servers
// ExtractTags will extract the tags of a server.
// This requires the client to be set to microversion 2.26 or later.
func (r serverResult) ExtractTags() ([]string, error) {
var s struct {
Tags []string `json:"tags"`
}
err := r.ExtractInto(&s)
return s.Tags, err
}

View File

@ -21,13 +21,13 @@ type ListOptsBuilder interface {
// the server attributes you want to see returned. Marker and Limit are used // the server attributes you want to see returned. Marker and Limit are used
// for pagination. // for pagination.
type ListOpts struct { type ListOpts struct {
// A time/date stamp for when the server last changed status. // ChangesSince is a time/date stamp for when the server last changed status.
ChangesSince string `q:"changes-since"` ChangesSince string `q:"changes-since"`
// Name of the image in URL format. // Image is the name of the image in URL format.
Image string `q:"image"` Image string `q:"image"`
// Name of the flavor in URL format. // Flavor is the name of the flavor in URL format.
Flavor string `q:"flavor"` Flavor string `q:"flavor"`
// Name of the server as a string; can be queried with regular expressions. // Name of the server as a string; can be queried with regular expressions.
@ -36,20 +36,25 @@ type ListOpts struct {
// underlying database server implemented for Compute. // underlying database server implemented for Compute.
Name string `q:"name"` Name string `q:"name"`
// Value of the status of the server so that you can filter on "ACTIVE" for example. // Status is the value of the status of the server so that you can filter on
// "ACTIVE" for example.
Status string `q:"status"` Status string `q:"status"`
// Name of the host as a string. // Host is the name of the host as a string.
Host string `q:"host"` Host string `q:"host"`
// UUID of the server at which you want to set a marker. // Marker is a UUID of the server at which you want to set a marker.
Marker string `q:"marker"` Marker string `q:"marker"`
// Integer value for the limit of values to return. // Limit is an integer value for the limit of values to return.
Limit int `q:"limit"` Limit int `q:"limit"`
// Bool to show all tenants // AllTenants is a bool to show all tenants.
AllTenants bool `q:"all_tenants"` AllTenants bool `q:"all_tenants"`
// TenantID lists servers for a particular tenant.
// Setting "AllTenants = true" is required.
TenantID string `q:"tenant_id"`
} }
// ToServerListQuery formats a ListOpts into a query string. // ToServerListQuery formats a ListOpts into a query string.
@ -73,15 +78,16 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa
}) })
} }
// CreateOptsBuilder describes struct types that can be accepted by the Create call. // CreateOptsBuilder allows extensions to add additional parameters to the
// The CreateOpts struct in this package does. // Create request.
type CreateOptsBuilder interface { type CreateOptsBuilder interface {
ToServerCreateMap() (map[string]interface{}, error) ToServerCreateMap() (map[string]interface{}, error)
} }
// Network is used within CreateOpts to control a new server's network attachments. // Network is used within CreateOpts to control a new server's network
// attachments.
type Network struct { type Network struct {
// UUID of a nova-network to attach to the newly provisioned server. // UUID of a network to attach to the newly provisioned server.
// Required unless Port is provided. // Required unless Port is provided.
UUID string UUID string
@ -89,19 +95,21 @@ type Network struct {
// Required unless UUID is provided. // Required unless UUID is provided.
Port string Port string
// FixedIP [optional] specifies a fixed IPv4 address to be used on this network. // FixedIP specifies a fixed IPv4 address to be used on this network.
FixedIP string FixedIP string
} }
// Personality is an array of files that are injected into the server at launch. // Personality is an array of files that are injected into the server at launch.
type Personality []*File type Personality []*File
// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. // File is used within CreateOpts and RebuildOpts to inject a file into the
// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested, // server at launch.
// json.Marshal will call File's MarshalJSON method. // 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 { type File struct {
// Path of the file // Path of the file.
Path string Path string
// Contents of the file. Maximum content size is 255 bytes. // Contents of the file. Maximum content size is 255 bytes.
Contents []byte Contents []byte
} }
@ -123,13 +131,13 @@ type CreateOpts struct {
// Name is the name to assign to the newly launched server. // Name is the name to assign to the newly launched server.
Name string `json:"name" required:"true"` Name string `json:"name" required:"true"`
// ImageRef [optional; required if ImageName is not provided] is the ID or full // ImageRef [optional; required if ImageName is not provided] is the ID or
// URL to the image that contains the server's OS and initial state. // full URL to the image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension. // Also optional if using the boot-from-volume extension.
ImageRef string `json:"imageRef"` ImageRef string `json:"imageRef"`
// ImageName [optional; required if ImageRef is not provided] is the name of the // ImageName [optional; required if ImageRef is not provided] is the name of
// image that contains the server's OS and initial state. // the image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension. // Also optional if using the boot-from-volume extension.
ImageName string `json:"-"` ImageName string `json:"-"`
@ -141,7 +149,8 @@ type CreateOpts struct {
// the flavor that describes the server's specs. // the flavor that describes the server's specs.
FlavorName string `json:"-"` FlavorName string `json:"-"`
// SecurityGroups lists the names of the security groups to which this server should belong. // SecurityGroups lists the names of the security groups to which this server
// should belong.
SecurityGroups []string `json:"-"` SecurityGroups []string `json:"-"`
// UserData contains configuration information or scripts to use upon launch. // UserData contains configuration information or scripts to use upon launch.
@ -152,10 +161,12 @@ type CreateOpts struct {
AvailabilityZone string `json:"availability_zone,omitempty"` AvailabilityZone string `json:"availability_zone,omitempty"`
// Networks dictates how this server will be attached to available networks. // 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. // By default, the server will be attached to all isolated networks for the
// tenant.
Networks []Network `json:"-"` Networks []Network `json:"-"`
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the server. // Metadata contains key-value pairs (up to 255 bytes each) to attach to the
// server.
Metadata map[string]string `json:"metadata,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
// Personality includes files to inject into the server at launch. // Personality includes files to inject into the server at launch.
@ -166,7 +177,7 @@ type CreateOpts struct {
ConfigDrive *bool `json:"config_drive,omitempty"` ConfigDrive *bool `json:"config_drive,omitempty"`
// AdminPass sets the root user password. If not set, a randomly-generated // AdminPass sets the root user password. If not set, a randomly-generated
// password will be created and returned in the rponse. // password will be created and returned in the response.
AdminPass string `json:"adminPass,omitempty"` AdminPass string `json:"adminPass,omitempty"`
// AccessIPv4 specifies an IPv4 address for the instance. // AccessIPv4 specifies an IPv4 address for the instance.
@ -178,9 +189,14 @@ type CreateOpts struct {
// ServiceClient will allow calls to be made to retrieve an image or // ServiceClient will allow calls to be made to retrieve an image or
// flavor ID by name. // flavor ID by name.
ServiceClient *gophercloud.ServiceClient `json:"-"` 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. // ToServerCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
sc := opts.ServiceClient sc := opts.ServiceClient
opts.ServiceClient = nil opts.ServiceClient = nil
@ -274,13 +290,14 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return return
} }
// Delete requests that a server previously provisioned be removed from your account. // Delete requests that a server previously provisioned be removed from your
// account.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) _, r.Err = client.Delete(deleteURL(client, id), nil)
return return
} }
// ForceDelete forces the deletion of a server // ForceDelete forces the deletion of a server.
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
return return
@ -294,12 +311,14 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
return return
} }
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request. // UpdateOptsBuilder allows extensions to add additional attributes to the
// Update request.
type UpdateOptsBuilder interface { type UpdateOptsBuilder interface {
ToServerUpdateMap() (map[string]interface{}, error) ToServerUpdateMap() (map[string]interface{}, error)
} }
// UpdateOpts specifies the base attributes that may be updated on an existing server. // UpdateOpts specifies the base attributes that may be updated on an existing
// server.
type UpdateOpts struct { type UpdateOpts struct {
// Name changes the displayed name of the server. // Name changes the displayed name of the server.
// The server host name will *not* change. // The server host name will *not* change.
@ -331,7 +350,8 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
return return
} }
// ChangeAdminPassword alters the administrator or root password for a specified server. // ChangeAdminPassword alters the administrator or root password for a specified
// server.
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) { func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) {
b := map[string]interface{}{ b := map[string]interface{}{
"changePassword": map[string]string{ "changePassword": map[string]string{
@ -354,33 +374,38 @@ const (
PowerCycle = HardReboot PowerCycle = HardReboot
) )
// RebootOptsBuilder is an interface that options must satisfy in order to be // RebootOptsBuilder allows extensions to add additional parameters to the
// used when rebooting a server instance // reboot request.
type RebootOptsBuilder interface { type RebootOptsBuilder interface {
ToServerRebootMap() (map[string]interface{}, error) ToServerRebootMap() (map[string]interface{}, error)
} }
// RebootOpts satisfies the RebootOptsBuilder interface // RebootOpts provides options to the reboot request.
type RebootOpts struct { type RebootOpts struct {
// Type is the type of reboot to perform on the server.
Type RebootMethod `json:"type" required:"true"` Type RebootMethod `json:"type" required:"true"`
} }
// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder // ToServerRebootMap builds a body for the reboot request.
// interface func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "reboot") return gophercloud.BuildRequestBody(opts, "reboot")
} }
// Reboot requests that a given server reboot. /*
// Two methods exist for rebooting a server: Reboot requests that a given server reboot.
//
// HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM, Two methods exist for rebooting a server:
// terminating it at the hypervisor level.
// It's done. Caput. Full stop. HardReboot (aka PowerCycle) starts the server instance by physically cutting
// Then, after a brief while, power is rtored or the VM instance rtarted. power to the machine, or if a VM, terminating it at the hypervisor level.
// It's done. Caput. Full stop.
// SoftReboot (aka OSReboot) simply tells the OS to rtart under its own procedur. Then, after a brief while, power is rtored or the VM instance restarted.
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine.
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) { func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
b, err := opts.ToServerRebootMap() b, err := opts.ToServerRebootMap()
if err != nil { if err != nil {
@ -391,31 +416,43 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder
return return
} }
// RebuildOptsBuilder is an interface that allows extensions to override the // RebuildOptsBuilder allows extensions to provide additional parameters to the
// default behaviour of rebuild options // rebuild request.
type RebuildOptsBuilder interface { type RebuildOptsBuilder interface {
ToServerRebuildMap() (map[string]interface{}, error) ToServerRebuildMap() (map[string]interface{}, error)
} }
// RebuildOpts represents the configuration options used in a server rebuild // RebuildOpts represents the configuration options used in a server rebuild
// operation // operation.
type RebuildOpts struct { type RebuildOpts struct {
// The server's admin password // AdminPass is the server's admin password
AdminPass string `json:"adminPass,omitempty"` AdminPass string `json:"adminPass,omitempty"`
// The ID of the image you want your server to be provisioned on
ImageID string `json:"imageRef"` // ImageID is the ID of the image you want your server to be provisioned on.
ImageID string `json:"imageRef"`
// ImageName is readable name of an image.
ImageName string `json:"-"` ImageName string `json:"-"`
// Name to set the server to // Name to set the server to
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// AccessIPv4 [optional] provides a new IPv4 address for the instance. // AccessIPv4 [optional] provides a new IPv4 address for the instance.
AccessIPv4 string `json:"accessIPv4,omitempty"` AccessIPv4 string `json:"accessIPv4,omitempty"`
// AccessIPv6 [optional] provides a new IPv6 address for the instance. // AccessIPv6 [optional] provides a new IPv6 address for the instance.
AccessIPv6 string `json:"accessIPv6,omitempty"` AccessIPv6 string `json:"accessIPv6,omitempty"`
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
// Metadata [optional] contains key-value pairs (up to 255 bytes each)
// to attach to the server.
Metadata map[string]string `json:"metadata,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
// Personality [optional] includes files to inject into the server at launch. // Personality [optional] includes files to inject into the server at launch.
// Rebuild will base64-encode file contents for you. // Rebuild will base64-encode file contents for you.
Personality Personality `json:"personality,omitempty"` 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:"-"` ServiceClient *gophercloud.ServiceClient `json:"-"`
} }
@ -458,31 +495,34 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild
return return
} }
// ResizeOptsBuilder is an interface that allows extensions to override the default structure of // ResizeOptsBuilder allows extensions to add additional parameters to the
// a Resize request. // resize request.
type ResizeOptsBuilder interface { type ResizeOptsBuilder interface {
ToServerResizeMap() (map[string]interface{}, error) ToServerResizeMap() (map[string]interface{}, error)
} }
// ResizeOpts represents the configuration options used to control a Resize operation. // ResizeOpts represents the configuration options used to control a Resize
// operation.
type ResizeOpts struct { type ResizeOpts struct {
// FlavorRef is the ID of the flavor you wish your server to become. // FlavorRef is the ID of the flavor you wish your server to become.
FlavorRef string `json:"flavorRef" required:"true"` FlavorRef string `json:"flavorRef" required:"true"`
} }
// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the // ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON
// Resize request. // request body for the Resize request.
func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) { func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "resize") return gophercloud.BuildRequestBody(opts, "resize")
} }
// Resize instructs the provider to change the flavor of the server. // Resize instructs the provider to change the flavor of the server.
//
// Note that this implies rebuilding it. // Note that this implies rebuilding it.
//
// Unfortunately, one cannot pass rebuild parameters to the resize function. // Unfortunately, one cannot pass rebuild parameters to the resize function.
// When the resize completes, the server will be in RESIZE_VERIFY state. // 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. // While in this state, you can explore the use of the new server's
// If you like it, call ConfirmResize() to commit the resize permanently. // configuration. If you like it, call ConfirmResize() to commit the resize
// Otherwise, call RevertResize() to restore the old configuration. // permanently. Otherwise, call RevertResize() to restore the old configuration.
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
b, err := opts.ToServerResizeMap() b, err := opts.ToServerResizeMap()
if err != nil { if err != nil {
@ -509,41 +549,8 @@ func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult)
return return
} }
// RescueOptsBuilder is an interface that allows extensions to override the // ResetMetadataOptsBuilder allows extensions to add additional parameters to
// default structure of a Rescue request. // the Reset request.
type RescueOptsBuilder interface {
ToServerRescueMap() (map[string]interface{}, error)
}
// RescueOpts represents the configuration options used to control a Rescue
// option.
type RescueOpts struct {
// AdminPass is the desired administrative password for the instance in
// RESCUE mode. If it's left blank, the server will generate a password.
AdminPass string `json:"adminPass,omitempty"`
}
// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
// request body for the Rescue request.
func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "rescue")
}
// Rescue instructs the provider to place the server into RESCUE mode.
func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) {
b, err := opts.ToServerRescueMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
// Reset request.
type ResetMetadataOptsBuilder interface { type ResetMetadataOptsBuilder interface {
ToMetadataResetMap() (map[string]interface{}, error) ToMetadataResetMap() (map[string]interface{}, error)
} }
@ -551,20 +558,23 @@ type ResetMetadataOptsBuilder interface {
// MetadataOpts is a map that contains key-value pairs. // MetadataOpts is a map that contains key-value pairs.
type MetadataOpts map[string]string type MetadataOpts map[string]string
// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts. // ToMetadataResetMap assembles a body for a Reset request based on the contents
// of a MetadataOpts.
func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) { func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil return map[string]interface{}{"metadata": opts}, nil
} }
// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts. // ToMetadataUpdateMap assembles a body for an Update request based on the
// contents of a MetadataOpts.
func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) { func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil return map[string]interface{}{"metadata": opts}, nil
} }
// ResetMetadata will create multiple new key-value pairs for the given server ID. // ResetMetadata will create multiple new key-value pairs for the given server
// Note: Using this operation will erase any already-existing metadata and create // ID.
// the new metadata provided. To keep any already-existing metadata, use the // Note: Using this operation will erase any already-existing metadata and
// UpdateMetadatas or UpdateMetadata function. // 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) { func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) {
b, err := opts.ToMetadataResetMap() b, err := opts.ToMetadataResetMap()
if err != nil { if err != nil {
@ -583,15 +593,15 @@ func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult
return return
} }
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the // UpdateMetadataOptsBuilder allows extensions to add additional parameters to
// Create request. // the Create request.
type UpdateMetadataOptsBuilder interface { type UpdateMetadataOptsBuilder interface {
ToMetadataUpdateMap() (map[string]interface{}, error) ToMetadataUpdateMap() (map[string]interface{}, error)
} }
// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID. // UpdateMetadata updates (or creates) all the metadata specified by opts for
// This operation does not affect already-existing metadata that is not specified // the given server ID. This operation does not affect already-existing metadata
// by opts. // that is not specified by opts.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
b, err := opts.ToMetadataUpdateMap() b, err := opts.ToMetadataUpdateMap()
if err != nil { if err != nil {
@ -613,7 +623,8 @@ type MetadatumOptsBuilder interface {
// MetadatumOpts is a map of length one that contains a key-value pair. // MetadatumOpts is a map of length one that contains a key-value pair.
type MetadatumOpts map[string]string type MetadatumOpts map[string]string
// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts. // ToMetadatumCreateMap assembles a body for a Create request based on the
// contents of a MetadataumOpts.
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) { func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
if len(opts) != 1 { if len(opts) != 1 {
err := gophercloud.ErrInvalidInput{} err := gophercloud.ErrInvalidInput{}
@ -629,7 +640,8 @@ func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string
return metadatum, key, nil return metadatum, key, nil
} }
// CreateMetadatum will create or update the key-value pair with the given key for the given server ID. // 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) { func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) {
b, key, err := opts.ToMetadatumCreateMap() b, key, err := opts.ToMetadatumCreateMap()
if err != nil { if err != nil {
@ -642,53 +654,60 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu
return return
} }
// Metadatum requests the key-value pair with the given key for the given server ID. // 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) { func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil)
return return
} }
// DeleteMetadatum will delete the key-value pair with the given key for the given server ID. // 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) { func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
_, r.Err = client.Delete(metadatumURL(client, id, key), nil) _, r.Err = client.Delete(metadatumURL(client, id, key), nil)
return return
} }
// ListAddresses makes a request against the API to list the servers IP addresses. // ListAddresses makes a request against the API to list the servers IP
// addresses.
func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager { func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page { return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
return AddressPage{pagination.SinglePageBase(r)} return AddressPage{pagination.SinglePageBase(r)}
}) })
} }
// ListAddressesByNetwork makes a request against the API to list the servers IP addresses // ListAddressesByNetwork makes a request against the API to list the servers IP
// for the given network. // addresses for the given network.
func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager { 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 pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
return NetworkAddressPage{pagination.SinglePageBase(r)} return NetworkAddressPage{pagination.SinglePageBase(r)}
}) })
} }
// CreateImageOptsBuilder is the interface types must satisfy in order to be // CreateImageOptsBuilder allows extensions to add additional parameters to the
// used as CreateImage options // CreateImage request.
type CreateImageOptsBuilder interface { type CreateImageOptsBuilder interface {
ToServerCreateImageMap() (map[string]interface{}, error) ToServerCreateImageMap() (map[string]interface{}, error)
} }
// CreateImageOpts satisfies the CreateImageOptsBuilder // CreateImageOpts provides options to pass to the CreateImage request.
type CreateImageOpts struct { type CreateImageOpts struct {
// Name of the image/snapshot // Name of the image/snapshot.
Name string `json:"name" required:"true"` Name string `json:"name" required:"true"`
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image.
// Metadata contains key-value pairs (up to 255 bytes each) to attach to
// the created image.
Metadata map[string]string `json:"metadata,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
} }
// ToServerCreateImageMap formats a CreateImageOpts structure into a request body. // ToServerCreateImageMap formats a CreateImageOpts structure into a request
// body.
func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) { func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "createImage") return gophercloud.BuildRequestBody(opts, "createImage")
} }
// CreateImage makes a request against the nova API to schedule an image to be created of the server // 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) { func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) {
b, err := opts.ToServerCreateImageMap() b, err := opts.ToServerCreateImageMap()
if err != nil { if err != nil {
@ -703,11 +722,17 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO
return return
} }
// IDFromName is a convienience function that returns a server's ID given its name. // IDFromName is a convienience function that returns a server's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0 count := 0
id := "" id := ""
allPages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
allPages, err := List(client, listOpts).AllPages()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -734,8 +759,40 @@ func IDFromName(client *gophercloud.ServiceClient, name string) (string, error)
} }
} }
// GetPassword makes a request against the nova API to get the encrypted administrative password. // GetPassword makes a request against the nova API to get the encrypted
// administrative password.
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) { func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
_, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) _, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil)
return 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
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}

View File

@ -32,54 +32,73 @@ func ExtractServersInto(r pagination.Page, v interface{}) error {
return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers")
} }
// CreateResult temporarily contains the response from a Create call. // CreateResult is the response from a Create operation. Call its Extract
// method to interpret it as a Server.
type CreateResult struct { type CreateResult struct {
serverResult serverResult
} }
// GetResult temporarily contains the response from a Get call. // GetResult is the response from a Get operation. Call its Extract
// method to interpret it as a Server.
type GetResult struct { type GetResult struct {
serverResult serverResult
} }
// UpdateResult temporarily contains the response from an Update call. // UpdateResult is the response from an Update operation. Call its Extract
// method to interpret it as a Server.
type UpdateResult struct { type UpdateResult struct {
serverResult serverResult
} }
// DeleteResult temporarily contains the response from a Delete call. // DeleteResult is the response from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct { type DeleteResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// RebuildResult temporarily contains the response from a Rebuild call. // RebuildResult is the response from a Rebuild operation. Call its Extract
// method to interpret it as a Server.
type RebuildResult struct { type RebuildResult struct {
serverResult serverResult
} }
// ActionResult represents the result of server action operations, like reboot // 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 { type ActionResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
// RescueResult represents the result of a server rescue operation // CreateImageResult is the response from a CreateImage operation. Call its
type RescueResult struct { // ExtractImageID method to retrieve the ID of the newly created image.
ActionResult
}
// CreateImageResult represents the result of an image creation operation
type CreateImageResult struct { type CreateImageResult struct {
gophercloud.Result 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. // GetPasswordResult represent the result of a get os-server-password operation.
// Call its ExtractPassword method to retrieve the password.
type GetPasswordResult struct { type GetPasswordResult struct {
gophercloud.Result gophercloud.Result
} }
// ExtractPassword gets the encrypted password. // ExtractPassword gets the encrypted password.
// If privateKey != nil the password is decrypted with the private key. // If privateKey != nil the password is decrypted with the private key.
// If privateKey == nil the encrypted password is returned and can be decrypted with: // If privateKey == nil the encrypted password is returned and can be decrypted
// with:
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key> // echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
var s struct { var s struct {
@ -107,7 +126,7 @@ func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (stri
return string(password), nil return string(password), nil
} }
// ExtractImageID gets the ID of the newly created server image from the header // ExtractImageID gets the ID of the newly created server image from the header.
func (r CreateImageResult) ExtractImageID() (string, error) { func (r CreateImageResult) ExtractImageID() (string, error) {
if r.Err != nil { if r.Err != nil {
return "", r.Err return "", r.Err
@ -124,54 +143,84 @@ func (r CreateImageResult) ExtractImageID() (string, error) {
return imageID, nil return imageID, nil
} }
// Extract interprets any RescueResult as an AdminPass, if possible. // Server represents a server/instance in the OpenStack cloud.
func (r RescueResult) Extract() (string, error) {
var s struct {
AdminPass string `json:"adminPass"`
}
err := r.ExtractInto(&s)
return s.AdminPass, err
}
// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
type Server struct { type Server struct {
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant. // ID uniquely identifies this server amongst all other servers,
// including those not accessible to the current tenant.
ID string `json:"id"` ID string `json:"id"`
// TenantID identifies the tenant owning this server resource. // TenantID identifies the tenant owning this server resource.
TenantID string `json:"tenant_id"` TenantID string `json:"tenant_id"`
// UserID uniquely identifies the user account owning the tenant. // UserID uniquely identifies the user account owning the tenant.
UserID string `json:"user_id"` UserID string `json:"user_id"`
// Name contains the human-readable name for the server. // Name contains the human-readable name for the server.
Name string `json:"name"` 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 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"` Updated time.Time `json:"updated"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
HostID string `json:"hostid"`
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE. // 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"` Status string `json:"status"`
// Progress ranges from 0..100. // Progress ranges from 0..100.
// A request made against the server completes only once Progress reaches 100. // A request made against the server completes only once Progress reaches 100.
Progress int `json:"progress"` Progress int `json:"progress"`
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server,
// suitable for remote access for administration.
AccessIPv4 string `json:"accessIPv4"` AccessIPv4 string `json:"accessIPv4"`
AccessIPv6 string `json:"accessIPv6"` AccessIPv6 string `json:"accessIPv6"`
// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
// Image refers to a JSON object, which itself indicates the OS image used to
// deploy the server.
Image map[string]interface{} `json:"-"` Image map[string]interface{} `json:"-"`
// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
// Flavor refers to a JSON object, which itself indicates the hardware
// configuration of the deployed server.
Flavor map[string]interface{} `json:"flavor"` Flavor map[string]interface{} `json:"flavor"`
// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
// Addresses includes a list of all IP addresses assigned to the server,
// keyed by pool.
Addresses map[string]interface{} `json:"addresses"` Addresses map[string]interface{} `json:"addresses"`
// Metadata includes a list of all user-specified key-value pairs attached to the server.
// Metadata includes a list of all user-specified key-value pairs attached
// to the server.
Metadata map[string]string `json:"metadata"` 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 includes HTTP references to the itself, useful for passing along to
// other APIs that might want a server reference.
Links []interface{} `json:"links"` Links []interface{} `json:"links"`
// KeyName indicates which public key was injected into the server on launch. // KeyName indicates which public key was injected into the server on launch.
KeyName string `json:"key_name"` 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.
// 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. // Note that this is the ONLY time this field will be valid.
AdminPass string `json:"adminPass"` AdminPass string `json:"adminPass"`
// SecurityGroups includes the security groups that this instance has applied to it
// SecurityGroups includes the security groups that this instance has applied
// to it.
SecurityGroups []map[string]interface{} `json:"security_groups"` SecurityGroups []map[string]interface{} `json:"security_groups"`
// Fault contains failure information about a server.
Fault Fault `json:"fault"`
}
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 { func (r *Server) UnmarshalJSON(b []byte) error {
@ -200,9 +249,10 @@ func (r *Server) UnmarshalJSON(b []byte) error {
return err return err
} }
// ServerPage abstracts the raw results of making a List() request against the API. // ServerPage abstracts the raw results of making a List() request against
// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the // the API. As OpenStack extensions may freely alter the response bodies of
// data provided through the ExtractServers call. // structures returned to the client, you may only safely access the data
// provided through the ExtractServers call.
type ServerPage struct { type ServerPage struct {
pagination.LinkedPageBase pagination.LinkedPageBase
} }
@ -213,7 +263,8 @@ func (r ServerPage) IsEmpty() (bool, error) {
return len(s) == 0, err return len(s) == 0, err
} }
// NextPageURL uses the response's embedded link reference to navigate to the next page of results. // NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (r ServerPage) NextPageURL() (string, error) { func (r ServerPage) NextPageURL() (string, error) {
var s struct { var s struct {
Links []gophercloud.Link `json:"servers_links"` Links []gophercloud.Link `json:"servers_links"`
@ -225,49 +276,59 @@ func (r ServerPage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links) return gophercloud.ExtractNextURL(s.Links)
} }
// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities. // 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) { func ExtractServers(r pagination.Page) ([]Server, error) {
var s []Server var s []Server
err := ExtractServersInto(r, &s) err := ExtractServersInto(r, &s)
return s, err return s, err
} }
// MetadataResult contains the result of a call for (potentially) multiple key-value pairs. // 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 { type MetadataResult struct {
gophercloud.Result gophercloud.Result
} }
// GetMetadataResult temporarily contains the response from a metadata Get call. // GetMetadataResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type GetMetadataResult struct { type GetMetadataResult struct {
MetadataResult MetadataResult
} }
// ResetMetadataResult temporarily contains the response from a metadata Reset call. // ResetMetadataResult contains the result of a Reset operation. Call its
// Extract method to interpret it as a map[string]interface.
type ResetMetadataResult struct { type ResetMetadataResult struct {
MetadataResult MetadataResult
} }
// UpdateMetadataResult temporarily contains the response from a metadata Update call. // UpdateMetadataResult contains the result of an Update operation. Call its
// Extract method to interpret it as a map[string]interface.
type UpdateMetadataResult struct { type UpdateMetadataResult struct {
MetadataResult MetadataResult
} }
// MetadatumResult contains the result of a call for individual a single key-value pair. // MetadatumResult contains the result of a call for individual a single
// key-value pair.
type MetadatumResult struct { type MetadatumResult struct {
gophercloud.Result gophercloud.Result
} }
// GetMetadatumResult temporarily contains the response from a metadatum Get call. // GetMetadatumResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type GetMetadatumResult struct { type GetMetadatumResult struct {
MetadatumResult MetadatumResult
} }
// CreateMetadatumResult temporarily contains the response from a metadatum Create call. // CreateMetadatumResult contains the result of a Create operation. Call its
// Extract method to interpret it as a map[string]interface.
type CreateMetadatumResult struct { type CreateMetadatumResult struct {
MetadatumResult MetadatumResult
} }
// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call. // DeleteMetadatumResult contains the result of a Delete operation. Call its
// ExtractErr method to determine if the call succeeded or failed.
type DeleteMetadatumResult struct { type DeleteMetadatumResult struct {
gophercloud.ErrResult gophercloud.ErrResult
} }
@ -296,9 +357,10 @@ type Address struct {
Address string `json:"addr"` Address string `json:"addr"`
} }
// AddressPage abstracts the raw results of making a ListAddresses() request against the API. // AddressPage abstracts the raw results of making a ListAddresses() request
// As OpenStack extensions may freely alter the response bodies of structures returned // against the API. As OpenStack extensions may freely alter the response bodies
// to the client, you may only safely access the data provided through the ExtractAddresses call. // of structures returned to the client, you may only safely access the data
// provided through the ExtractAddresses call.
type AddressPage struct { type AddressPage struct {
pagination.SinglePageBase pagination.SinglePageBase
} }
@ -309,8 +371,8 @@ func (r AddressPage) IsEmpty() (bool, error) {
return len(addresses) == 0, err return len(addresses) == 0, err
} }
// ExtractAddresses interprets the results of a single page from a ListAddresses() call, // ExtractAddresses interprets the results of a single page from a
// producing a map of addresses. // ListAddresses() call, producing a map of addresses.
func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
var s struct { var s struct {
Addresses map[string][]Address `json:"addresses"` Addresses map[string][]Address `json:"addresses"`
@ -319,9 +381,11 @@ func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
return s.Addresses, err return s.Addresses, err
} }
// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API. // NetworkAddressPage abstracts the raw results of making a
// As OpenStack extensions may freely alter the response bodies of structures returned // ListAddressesByNetwork() request against the API.
// to the client, you may only safely access the data provided through the ExtractAddresses call. // 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 { type NetworkAddressPage struct {
pagination.SinglePageBase pagination.SinglePageBase
} }
@ -332,8 +396,8 @@ func (r NetworkAddressPage) IsEmpty() (bool, error) {
return len(addresses) == 0, err return len(addresses) == 0, err
} }
// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call, // ExtractNetworkAddresses interprets the results of a single page from a
// producing a slice of addresses. // ListAddressesByNetwork() call, producing a slice of addresses.
func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
var s map[string][]Address var s map[string][]Address
err := (r.(NetworkAddressPage)).ExtractInto(&s) err := (r.(NetworkAddressPage)).ExtractInto(&s)

View File

@ -2,8 +2,9 @@ package servers
import "github.com/gophercloud/gophercloud" import "github.com/gophercloud/gophercloud"
// WaitForStatus will continually poll a server until it successfully transitions to a specified // WaitForStatus will continually poll a server until it successfully
// status. It will do this for at most the number of seconds specified. // 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 { func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) { return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract() current, err := Get(c, id).Extract()

View File

@ -0,0 +1,101 @@
/*
Package clusters contains functionality for working with Magnum Cluster resources.
Example to Create a Cluster
masterCount := 1
nodeCount := 1
createTimeout := 30
opts := clusters.CreateOpts{
ClusterTemplateID: "0562d357-8641-4759-8fed-8173f02c9633",
CreateTimeout: &createTimeout,
DiscoveryURL: "",
FlavorID: "m1.small",
KeyPair: "my_keypair",
Labels: map[string]string{},
MasterCount: &masterCount,
MasterFlavorID: "m1.small",
Name: "k8s",
NodeCount: &nodeCount,
}
cluster, err := clusters.Create(serviceClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Get a Cluster
clusterName := "cluster123"
cluster, err := clusters.Get(serviceClient, clusterName).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", cluster)
Example to List Clusters
listOpts := clusters.ListOpts{
Limit: 20,
}
allPages, err := clusters.List(serviceClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allClusters, err := clusters.ExtractClusters(allPages)
if err != nil {
panic(err)
}
for _, cluster := range allClusters {
fmt.Printf("%+v\n", cluster)
}
Example to List Clusters with detailed information
allPagesDetail, err := clusters.ListDetail(serviceClient, clusters.ListOpts{}).AllPages()
if err != nil {
panic(err)
}
allClustersDetail, err := clusters.ExtractClusters(allPagesDetail)
if err != nil {
panic(err)
}
for _, clusterDetail := range allClustersDetail {
fmt.Printf("%+v\n", clusterDetail)
}
Example to Update a Cluster
updateOpts := []clusters.UpdateOptsBuilder{
clusters.UpdateOpts{
Op: clusters.ReplaceOp,
Path: "/master_lb_enabled",
Value: "True",
},
clusters.UpdateOpts{
Op: clusters.ReplaceOp,
Path: "/registry_enabled",
Value: "True",
},
}
clusterUUID, err := clusters.Update(serviceClient, clusterUUID, updateOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%s\n", clusterUUID)
Example to Delete a Cluster
clusterUUID := "dc6d336e3fc4c0a951b5698cd1236ee"
err := clusters.Delete(serviceClient, clusterUUID).ExtractErr()
if err != nil {
panic(err)
}
*/
package clusters

View File

@ -0,0 +1,177 @@
package clusters
import (
"net/http"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder Builder.
type CreateOptsBuilder interface {
ToClusterCreateMap() (map[string]interface{}, error)
}
// CreateOpts params
type CreateOpts struct {
ClusterTemplateID string `json:"cluster_template_id" required:"true"`
CreateTimeout *int `json:"create_timeout"`
DiscoveryURL string `json:"discovery_url,omitempty"`
DockerVolumeSize *int `json:"docker_volume_size,omitempty"`
FlavorID string `json:"flavor_id,omitempty"`
Keypair string `json:"keypair,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
MasterCount *int `json:"master_count,omitempty"`
MasterFlavorID string `json:"master_flavor_id,omitempty"`
Name string `json:"name"`
NodeCount *int `json:"node_count,omitempty"`
}
// ToClusterCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// Create requests the creation of a new cluster.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToClusterCreateMap()
if err != nil {
r.Err = err
return
}
var result *http.Response
result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
if r.Err == nil {
r.Header = result.Header
}
return
}
// Get retrieves a specific clusters based on its unique ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
var result *http.Response
result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
if r.Err == nil {
r.Header = result.Header
}
return
}
// Delete deletes the specified cluster ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
var result *http.Response
result, r.Err = client.Delete(deleteURL(client, id), nil)
r.Header = result.Header
return
}
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToClustersListQuery() (string, error)
}
// ListOpts allows the sorting of paginated collections through
// the API. SortKey allows you to sort by a particular cluster attribute.
// SortDir sets the direction, and is either `asc' or `desc'.
// Marker and Limit are used for pagination.
type ListOpts struct {
Marker string `q:"marker"`
Limit int `q:"limit"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// ToClustersListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToClustersListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
// clusters. It accepts a ListOptsBuilder, which allows you to sort
// the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(c)
if opts != nil {
query, err := opts.ToClustersListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return ClusterPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// ListDetail returns a Pager which allows you to iterate over a collection of
// clusters with detailed information.
// It accepts a ListOptsBuilder, which allows you to sort the returned
// collection for greater efficiency.
func ListDetail(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listDetailURL(c)
if opts != nil {
query, err := opts.ToClustersListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return ClusterPage{pagination.LinkedPageBase{PageResult: r}}
})
}
type UpdateOp string
const (
AddOp UpdateOp = "add"
RemoveOp UpdateOp = "remove"
ReplaceOp UpdateOp = "replace"
)
type UpdateOpts struct {
Op UpdateOp `json:"op" required:"true"`
Path string `json:"path" required:"true"`
Value string `json:"value,omitempty"`
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToClustersUpdateMap() (map[string]interface{}, error)
}
// ToClusterUpdateMap assembles a request body based on the contents of
// UpdateOpts.
func (opts UpdateOpts) ToClustersUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// Update implements cluster updated request.
func Update(client *gophercloud.ServiceClient, id string, opts []UpdateOptsBuilder) (r UpdateResult) {
var o []map[string]interface{}
for _, opt := range opts {
b, err := opt.ToClustersUpdateMap()
if err != nil {
r.Err = err
return r
}
o = append(o, b)
}
var result *http.Response
result, r.Err = client.Patch(updateURL(client, id), o, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
if r.Err == nil {
r.Header = result.Header
}
return
}

View File

@ -0,0 +1,114 @@
package clusters
import (
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type commonResult struct {
gophercloud.Result
}
// CreateResult is the response of a Create operations.
type CreateResult struct {
commonResult
}
// DeleteResult is the result from a Delete operation. Call its Extract or ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// Extract is a function that accepts a result and extracts a cluster resource.
func (r commonResult) Extract() (*Cluster, error) {
var s *Cluster
err := r.ExtractInto(&s)
return s, err
}
// UpdateResult is the response of a Update operations.
type UpdateResult struct {
commonResult
}
func (r CreateResult) Extract() (string, error) {
var s struct {
UUID string
}
err := r.ExtractInto(&s)
return s.UUID, err
}
func (r UpdateResult) Extract() (string, error) {
var s struct {
UUID string
}
err := r.ExtractInto(&s)
return s.UUID, err
}
type Cluster struct {
APIAddress string `json:"api_address"`
COEVersion string `json:"coe_version"`
ClusterTemplateID string `json:"cluster_template_id"`
ContainerVersion string `json:"container_version"`
CreateTimeout int `json:"create_timeout"`
CreatedAt time.Time `json:"created_at"`
DiscoveryURL string `json:"discovery_url"`
DockerVolumeSize int `json:"docker_volume_size"`
Faults map[string]string `json:"faults"`
FlavorID string `json:"flavor_id"`
KeyPair string `json:"keypair"`
Labels map[string]string `json:"labels"`
Links []gophercloud.Link `json:"links"`
MasterFlavorID string `json:"master_flavor_id"`
MasterAddresses []string `json:"master_addresses"`
MasterCount int `json:"master_count"`
Name string `json:"name"`
NodeAddresses []string `json:"node_addresses"`
NodeCount int `json:"node_count"`
ProjectID string `json:"project_id"`
StackID string `json:"stack_id"`
Status string `json:"status"`
StatusReason string `json:"status_reason"`
UUID string `json:"uuid"`
UpdatedAt time.Time `json:"updated_at"`
UserID string `json:"user_id"`
}
type ClusterPage struct {
pagination.LinkedPageBase
}
func (r ClusterPage) NextPageURL() (string, error) {
var s struct {
Next string `json:"next"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Next, nil
}
// IsEmpty checks whether a ClusterPage struct is empty.
func (r ClusterPage) IsEmpty() (bool, error) {
is, err := ExtractClusters(r)
return len(is) == 0, err
}
func ExtractClusters(r pagination.Page) ([]Cluster, error) {
var s struct {
Clusters []Cluster `json:"clusters"`
}
err := (r.(ClusterPage)).ExtractInto(&s)
return s.Clusters, err
}

View File

@ -0,0 +1,39 @@
package clusters
import (
"github.com/gophercloud/gophercloud"
)
var apiName = "clusters"
func commonURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL(apiName)
}
func idURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL(apiName, id)
}
func createURL(client *gophercloud.ServiceClient) string {
return commonURL(client)
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return idURL(client, id)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("clusters", id)
}
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("clusters")
}
func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("clusters", "detail")
}
func updateURL(client *gophercloud.ServiceClient, id string) string {
return idURL(client, id)
}

View File

@ -0,0 +1,90 @@
// Package clustertemplates contains functionality for working with Magnum Cluster Templates
// resources.
/*
Package clustertemplates provides information and interaction with the cluster-templates through
the OpenStack Container Infra service.
Example to Create Cluster Template
boolFalse := false
boolTrue := true
createOpts := clustertemplates.CreateOpts{
Name: "test-cluster-template",
Labels: map[string]string{},
FixedSubnet: "",
MasterFlavorID: "",
NoProxy: "10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost",
HTTPSProxy: "http://10.164.177.169:8080",
TLSDisabled: &boolFalse,
KeyPairID: "kp",
Public: &boolFalse,
HTTPProxy: "http://10.164.177.169:8080",
ServerType: "vm",
ExternalNetworkID: "public",
ImageID: "fedora-atomic-latest",
VolumeDriver: "cinder",
RegistryEnabled: &boolFalse,
DockerStorageDriver: "devicemapper",
NetworkDriver: "flannel",
FixedNetwork: "",
COE: "kubernetes",
FlavorID: "m1.small",
MasterLBEnabled: &boolTrue,
DNSNameServer: "8.8.8.8",
}
clustertemplate, err := clustertemplates.Create(serviceClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete Cluster Template
clusterTemplateID := "dc6d336e3fc4c0a951b5698cd1236ee"
err := clustertemplates.Delete(serviceClient, clusterTemplateID).ExtractErr()
if err != nil {
panic(err)
}
Example to List Clusters Templates
listOpts := clustertemplates.ListOpts{
Limit: 20,
}
allPages, err := clustertemplates.List(serviceClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allClusterTemplates, err := clusters.ExtractClusterTemplates(allPages)
if err != nil {
panic(err)
}
for _, clusterTemplate := range allClusterTemplates {
fmt.Printf("%+v\n", clusterTemplate)
}
Example to Update Cluster Template
updateOpts := []clustertemplates.UpdateOptsBuilder{
clustertemplates.UpdateOpts{
Op: clustertemplates.ReplaceOp,
Path: "/master_lb_enabled",
Value: "True",
},
clustertemplates.UpdateOpts{
Op: clustertemplates.ReplaceOp,
Path: "/registry_enabled",
Value: "True",
},
}
clustertemplate, err := clustertemplates.Update(serviceClient, updateOpts).Extract()
if err != nil {
panic(err)
}
*/
package clustertemplates

View File

@ -0,0 +1,178 @@
package clustertemplates
import (
"net/http"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder Builder.
type CreateOptsBuilder interface {
ToClusterCreateMap() (map[string]interface{}, error)
}
// CreateOpts params
type CreateOpts struct {
APIServerPort *int `json:"apiserver_port,omitempty"`
COE string `json:"coe" required:"true"`
DNSNameServer string `json:"dns_nameserver,omitempty"`
DockerStorageDriver string `json:"docker_storage_driver,omitempty"`
DockerVolumeSize *int `json:"docker_volume_size,omitempty"`
ExternalNetworkID string `json:"external_network_id,omitempty"`
FixedNetwork string `json:"fixed_network,omitempty"`
FixedSubnet string `json:"fixed_subnet,omitempty"`
FlavorID string `json:"flavor_id,omitempty"`
FloatingIPEnabled *bool `json:"floating_ip_enabled,omitempty"`
HTTPProxy string `json:"http_proxy,omitempty"`
HTTPSProxy string `json:"https_proxy,omitempty"`
ImageID string `json:"image_id" required:"true"`
InsecureRegistry string `json:"insecure_registry,omitempty"`
KeyPairID string `json:"keypair_id,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
MasterFlavorID string `json:"master_flavor_id,omitempty"`
MasterLBEnabled *bool `json:"master_lb_enabled,omitempty"`
Name string `json:"name,omitempty"`
NetworkDriver string `json:"network_driver,omitempty"`
NoProxy string `json:"no_proxy,omitempty"`
Public *bool `json:"public,omitempty"`
RegistryEnabled *bool `json:"registry_enabled,omitempty"`
ServerType string `json:"server_type,omitempty"`
TLSDisabled *bool `json:"tls_disabled,omitempty"`
VolumeDriver string `json:"volume_driver,omitempty"`
}
// ToClusterCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// Create requests the creation of a new cluster.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToClusterCreateMap()
if err != nil {
r.Err = err
return
}
var result *http.Response
result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201},
})
if r.Err == nil {
r.Header = result.Header
}
return
}
// Delete deletes the specified cluster ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
var result *http.Response
result, r.Err = client.Delete(deleteURL(client, id), nil)
r.Header = result.Header
return
}
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToClusterTemplateListQuery() (string, error)
}
// ListOpts allows the sorting of paginated collections through
// the API. SortKey allows you to sort by a particular cluster templates attribute.
// SortDir sets the direction, and is either `asc' or `desc'.
// Marker and Limit are used for pagination.
type ListOpts struct {
Marker string `q:"marker"`
Limit int `q:"limit"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// ToClusterTemplateListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToClusterTemplateListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
// cluster-templates. It accepts a ListOptsBuilder, which allows you to sort
// the returned collection for greater efficiency.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToClusterTemplateListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return ClusterTemplatePage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get retrieves a specific cluster-template based on its unique ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
var result *http.Response
result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
if r.Err == nil {
r.Header = result.Header
}
return
}
type UpdateOp string
const (
AddOp UpdateOp = "add"
RemoveOp UpdateOp = "remove"
ReplaceOp UpdateOp = "replace"
)
type UpdateOpts struct {
Op UpdateOp `json:"op" required:"true"`
Path string `json:"path" required:"true"`
Value string `json:"value,omitempty"`
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToClusterTemplateUpdateMap() (map[string]interface{}, error)
}
// ToClusterUpdateMap assembles a request body based on the contents of
// UpdateOpts.
func (opts UpdateOpts) ToClusterTemplateUpdateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
return b, nil
}
// Update implements cluster updated request.
func Update(client *gophercloud.ServiceClient, id string, opts []UpdateOptsBuilder) (r UpdateResult) {
var o []map[string]interface{}
for _, opt := range opts {
b, err := opt.ToClusterTemplateUpdateMap()
if err != nil {
r.Err = err
return r
}
o = append(o, b)
}
var result *http.Response
result, r.Err = client.Patch(updateURL(client, id), o, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
if r.Err == nil {
r.Header = result.Header
}
return
}

View File

@ -0,0 +1,114 @@
package clustertemplates
import (
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type commonResult struct {
gophercloud.Result
}
// CreateResult is the response of a Create operations.
type CreateResult struct {
commonResult
}
// DeleteResult is the result from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// GetResult is the response of a Get operations.
type GetResult struct {
commonResult
}
// UpdateResult is the response of a Update operations.
type UpdateResult struct {
commonResult
}
// Extract is a function that accepts a result and extracts a cluster-template resource.
func (r commonResult) Extract() (*ClusterTemplate, error) {
var s *ClusterTemplate
err := r.ExtractInto(&s)
return s, err
}
// Represents a template for a Cluster Template
type ClusterTemplate struct {
APIServerPort int `json:"apiserver_port"`
COE string `json:"coe"`
ClusterDistro string `json:"cluster_distro"`
CreatedAt time.Time `json:"created_at"`
DNSNameServer string `json:"dns_nameserver"`
DockerStorageDriver string `json:"docker_storage_driver"`
DockerVolumeSize int `json:"docker_volume_size"`
ExternalNetworkID string `json:"external_network_id"`
FixedNetwork string `json:"fixed_network"`
FixedSubnet string `json:"fixed_subnet"`
FlavorID string `json:"flavor_id"`
FloatingIPEnabled bool `json:"floating_ip_enabled"`
HTTPProxy string `json:"http_proxy"`
HTTPSProxy string `json:"https_proxy"`
ImageID string `json:"image_id"`
InsecureRegistry string `json:"insecure_registry"`
KeyPairID string `json:"keypair_id"`
Labels map[string]string `json:"labels"`
Links []gophercloud.Link `json:"links"`
MasterFlavorID string `json:"master_flavor_id"`
MasterLBEnabled bool `json:"master_lb_enabled"`
Name string `json:"name"`
NetworkDriver string `json:"network_driver"`
NoProxy string `json:"no_proxy"`
ProjectID string `json:"project_id"`
Public bool `json:"public"`
RegistryEnabled bool `json:"registry_enabled"`
ServerType string `json:"server_type"`
TLSDisabled bool `json:"tls_disabled"`
UUID string `json:"uuid"`
UpdatedAt time.Time `json:"updated_at"`
UserID string `json:"user_id"`
VolumeDriver string `json:"volume_driver"`
}
// ClusterTemplatePage is the page returned by a pager when traversing over a
// collection of cluster-templates.
type ClusterTemplatePage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of cluster template has reached
// the end of a page and the pager seeks to traverse over a new one. In order
// to do this, it needs to construct the next page's URL.
func (r ClusterTemplatePage) NextPageURL() (string, error) {
var s struct {
Next string `json:"next"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Next, nil
}
// IsEmpty checks whether a ClusterTemplatePage struct is empty.
func (r ClusterTemplatePage) IsEmpty() (bool, error) {
is, err := ExtractClusterTemplates(r)
return len(is) == 0, err
}
// ExtractClusterTemplates accepts a Page struct, specifically a ClusterTemplatePage struct,
// and extracts the elements into a slice of cluster templates structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractClusterTemplates(r pagination.Page) ([]ClusterTemplate, error) {
var s struct {
ClusterTemplates []ClusterTemplate `json:"clustertemplates"`
}
err := (r.(ClusterTemplatePage)).ExtractInto(&s)
return s.ClusterTemplates, err
}

View File

@ -0,0 +1,35 @@
package clustertemplates
import (
"github.com/gophercloud/gophercloud"
)
var apiName = "clustertemplates"
func commonURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL(apiName)
}
func idURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL(apiName, id)
}
func createURL(client *gophercloud.ServiceClient) string {
return commonURL(client)
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return idURL(client, id)
}
func listURL(client *gophercloud.ServiceClient) string {
return commonURL(client)
}
func getURL(client *gophercloud.ServiceClient, id string) string {
return idURL(client, id)
}
func updateURL(client *gophercloud.ServiceClient, id string) string {
return idURL(client, id)
}

View File

@ -0,0 +1,11 @@
// Package configurations provides information and interaction with the
// configuration API resource in the Rackspace Database service.
//
// A configuration group is a collection of key/value pairs which define how a
// particular database operates. These key/value pairs are specific to each
// datastore type and serve like settings. Some directives are capable of being
// applied dynamically, while other directives require a server restart to take
// effect. The configuration group can be applied to an instance at creation or
// applied to an existing instance to modify the behavior of the running
// datastore on the instance.
package configurations

View File

@ -0,0 +1,167 @@
package configurations
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
"github.com/gophercloud/gophercloud/pagination"
)
// List will list all of the available configurations.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, baseURL(client), func(r pagination.PageResult) pagination.Page {
return ConfigPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder is a top-level interface which renders a JSON map.
type CreateOptsBuilder interface {
ToConfigCreateMap() (map[string]interface{}, error)
}
// DatastoreOpts is the primary options struct for creating and modifying
// how configuration resources are associated with datastores.
type DatastoreOpts struct {
// The type of datastore. Defaults to "MySQL".
Type string `json:"type,omitempty"`
// The specific version of a datastore. Defaults to "5.6".
Version string `json:"version,omitempty"`
}
// CreateOpts is the struct responsible for configuring new configurations.
type CreateOpts struct {
// The configuration group name
Name string `json:"name" required:"true"`
// A map of user-defined configuration settings that will define
// how each associated datastore works. Each key/value pair is specific to a
// datastore type.
Values map[string]interface{} `json:"values" required:"true"`
// Associates the configuration group with a particular datastore.
Datastore *DatastoreOpts `json:"datastore,omitempty"`
// A human-readable explanation for the group.
Description string `json:"description,omitempty"`
}
// ToConfigCreateMap casts a CreateOpts struct into a JSON map.
func (opts CreateOpts) ToConfigCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "configuration")
}
// Create will create a new configuration group.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToConfigCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
return
}
// Get will retrieve the details for a specified configuration group.
func Get(client *gophercloud.ServiceClient, configID string) (r GetResult) {
_, r.Err = client.Get(resourceURL(client, configID), &r.Body, nil)
return
}
// UpdateOptsBuilder is the top-level interface for casting update options into
// JSON maps.
type UpdateOptsBuilder interface {
ToConfigUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the struct responsible for modifying existing configurations.
type UpdateOpts struct {
// The configuration group name
Name string `json:"name,omitempty"`
// A map of user-defined configuration settings that will define
// how each associated datastore works. Each key/value pair is specific to a
// datastore type.
Values map[string]interface{} `json:"values,omitempty"`
// Associates the configuration group with a particular datastore.
Datastore *DatastoreOpts `json:"datastore,omitempty"`
// A human-readable explanation for the group.
Description *string `json:"description,omitempty"`
}
// ToConfigUpdateMap will cast an UpdateOpts struct into a JSON map.
func (opts UpdateOpts) ToConfigUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "configuration")
}
// Update will modify an existing configuration group by performing a merge
// between new and existing values. If the key already exists, the new value
// will overwrite. All other keys will remain unaffected.
func Update(client *gophercloud.ServiceClient, configID string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToConfigUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Patch(resourceURL(client, configID), &b, nil, nil)
return
}
// Replace will modify an existing configuration group by overwriting the
// entire parameter group with the new values provided. Any existing keys not
// included in UpdateOptsBuilder will be deleted.
func Replace(client *gophercloud.ServiceClient, configID string, opts UpdateOptsBuilder) (r ReplaceResult) {
b, err := opts.ToConfigUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(resourceURL(client, configID), &b, nil, nil)
return
}
// Delete will permanently delete a configuration group. Please note that
// config groups cannot be deleted whilst still attached to running instances -
// you must detach and then delete them.
func Delete(client *gophercloud.ServiceClient, configID string) (r DeleteResult) {
_, r.Err = client.Delete(resourceURL(client, configID), nil)
return
}
// ListInstances will list all the instances associated with a particular
// configuration group.
func ListInstances(client *gophercloud.ServiceClient, configID string) pagination.Pager {
return pagination.NewPager(client, instancesURL(client, configID), func(r pagination.PageResult) pagination.Page {
return instances.InstancePage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}}
})
}
// ListDatastoreParams will list all the available and supported parameters
// that can be used for a particular datastore ID and a particular version.
// For example, if you are wondering how you can configure a MySQL 5.6 instance,
// you can use this operation (you will need to retrieve the MySQL datastore ID
// by using the datastores API).
func ListDatastoreParams(client *gophercloud.ServiceClient, datastoreID, versionID string) pagination.Pager {
return pagination.NewPager(client, listDSParamsURL(client, datastoreID, versionID), func(r pagination.PageResult) pagination.Page {
return ParamPage{pagination.SinglePageBase(r)}
})
}
// GetDatastoreParam will retrieve information about a specific configuration
// parameter. For example, you can use this operation to understand more about
// "innodb_file_per_table" configuration param for MySQL datastores. You will
// need the param's ID first, which can be attained by using the ListDatastoreParams
// operation.
func GetDatastoreParam(client *gophercloud.ServiceClient, datastoreID, versionID, paramID string) (r ParamResult) {
_, r.Err = client.Get(getDSParamURL(client, datastoreID, versionID, paramID), &r.Body, nil)
return
}
// ListGlobalParams is similar to ListDatastoreParams but does not require a
// DatastoreID.
func ListGlobalParams(client *gophercloud.ServiceClient, versionID string) pagination.Pager {
return pagination.NewPager(client, listGlobalParamsURL(client, versionID), func(r pagination.PageResult) pagination.Page {
return ParamPage{pagination.SinglePageBase(r)}
})
}
// GetGlobalParam is similar to GetDatastoreParam but does not require a
// DatastoreID.
func GetGlobalParam(client *gophercloud.ServiceClient, versionID, paramID string) (r ParamResult) {
_, r.Err = client.Get(getGlobalParamURL(client, versionID, paramID), &r.Body, nil)
return
}

View File

@ -0,0 +1,141 @@
package configurations
import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Config represents a configuration group API resource.
type Config struct {
Created time.Time `json:"-"`
Updated time.Time `json:"-"`
DatastoreName string `json:"datastore_name"`
DatastoreVersionID string `json:"datastore_version_id"`
DatastoreVersionName string `json:"datastore_version_name"`
Description string
ID string
Name string
Values map[string]interface{}
}
func (r *Config) UnmarshalJSON(b []byte) error {
type tmp Config
var s struct {
tmp
Created gophercloud.JSONRFC3339NoZ `json:"created"`
Updated gophercloud.JSONRFC3339NoZ `json:"updated"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Config(s.tmp)
r.Created = time.Time(s.Created)
r.Updated = time.Time(s.Updated)
return nil
}
// ConfigPage contains a page of Config resources in a paginated collection.
type ConfigPage struct {
pagination.SinglePageBase
}
// IsEmpty indicates whether a ConfigPage is empty.
func (r ConfigPage) IsEmpty() (bool, error) {
is, err := ExtractConfigs(r)
return len(is) == 0, err
}
// ExtractConfigs will retrieve a slice of Config structs from a page.
func ExtractConfigs(r pagination.Page) ([]Config, error) {
var s struct {
Configs []Config `json:"configurations"`
}
err := (r.(ConfigPage)).ExtractInto(&s)
return s.Configs, err
}
type commonResult struct {
gophercloud.Result
}
// Extract will retrieve a Config resource from an operation result.
func (r commonResult) Extract() (*Config, error) {
var s struct {
Config *Config `json:"configuration"`
}
err := r.ExtractInto(&s)
return s.Config, err
}
// GetResult represents the result of a Get operation.
type GetResult struct {
commonResult
}
// CreateResult represents the result of a Create operation.
type CreateResult struct {
commonResult
}
// UpdateResult represents the result of an Update operation.
type UpdateResult struct {
gophercloud.ErrResult
}
// ReplaceResult represents the result of a Replace operation.
type ReplaceResult struct {
gophercloud.ErrResult
}
// DeleteResult represents the result of a Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// Param represents a configuration parameter API resource.
type Param struct {
Max float64
Min float64
Name string
RestartRequired bool `json:"restart_required"`
Type string
}
// ParamPage contains a page of Param resources in a paginated collection.
type ParamPage struct {
pagination.SinglePageBase
}
// IsEmpty indicates whether a ParamPage is empty.
func (r ParamPage) IsEmpty() (bool, error) {
is, err := ExtractParams(r)
return len(is) == 0, err
}
// ExtractParams will retrieve a slice of Param structs from a page.
func ExtractParams(r pagination.Page) ([]Param, error) {
var s struct {
Params []Param `json:"configuration-parameters"`
}
err := (r.(ParamPage)).ExtractInto(&s)
return s.Params, err
}
// ParamResult represents the result of an operation which retrieves details
// about a particular configuration param.
type ParamResult struct {
gophercloud.Result
}
// Extract will retrieve a param from an operation result.
func (r ParamResult) Extract() (*Param, error) {
var s *Param
err := r.ExtractInto(&s)
return s, err
}

View File

@ -0,0 +1,31 @@
package configurations
import "github.com/gophercloud/gophercloud"
func baseURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("configurations")
}
func resourceURL(c *gophercloud.ServiceClient, configID string) string {
return c.ServiceURL("configurations", configID)
}
func instancesURL(c *gophercloud.ServiceClient, configID string) string {
return c.ServiceURL("configurations", configID, "instances")
}
func listDSParamsURL(c *gophercloud.ServiceClient, datastoreID, versionID string) string {
return c.ServiceURL("datastores", datastoreID, "versions", versionID, "parameters")
}
func getDSParamURL(c *gophercloud.ServiceClient, datastoreID, versionID, paramID string) string {
return c.ServiceURL("datastores", datastoreID, "versions", versionID, "parameters", paramID)
}
func listGlobalParamsURL(c *gophercloud.ServiceClient, versionID string) string {
return c.ServiceURL("datastores", "versions", versionID, "parameters")
}
func getGlobalParamURL(c *gophercloud.ServiceClient, versionID, paramID string) string {
return c.ServiceURL("datastores", "versions", versionID, "parameters", paramID)
}

View File

@ -0,0 +1,6 @@
// Package flavors provides information and interaction with the database API
// resource in the OpenStack Database service.
//
// A database, when referred to here, refers to the database engine running on
// an instance.
package databases

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